@@ -28,12 +28,11 @@ type chatUI struct {
28
28
CanvasObject fyne.CanvasObject
29
29
Panel * Panel
30
30
List * widget.List
31
- ItemHeight map [streamcontrol.ChatMessageID ]uint
32
- TotalListHeight uint
33
- MessagesHistoryLocker sync.Mutex
31
+ MessagesHistoryLocker xsync.Mutex
34
32
MessagesHistory []api.ChatMessage
35
33
EnableButtons bool
36
34
ReverseOrder bool
35
+ SuppressNotifications bool
37
36
OnAdd func (context.Context , api.ChatMessage )
38
37
OnRemove func (context.Context , api.ChatMessage )
39
38
@@ -42,6 +41,11 @@ type chatUI struct {
42
41
43
42
CurrentlyPlayingChatMessageSoundCount int32
44
43
44
+ ItemLocker xsync.Mutex
45
+ ItemsByCanvasObject map [fyne.CanvasObject ]* chatItem
46
+ ItemsByMessageID map [streamcontrol.ChatMessageID ]* chatItem
47
+ TotalListHeight uint
48
+
45
49
// TODO: do not store ctx in a struct:
46
50
ctx context.Context
47
51
}
@@ -50,18 +54,21 @@ func newChatUI(
50
54
ctx context.Context ,
51
55
enableButtons bool ,
52
56
reverseOrder bool ,
57
+ suppressNotifications bool ,
53
58
panel * Panel ,
54
59
) (_ret * chatUI , _err error ) {
55
60
logger .Debugf (ctx , "newChatUI" )
56
61
defer func () { logger .Debugf (ctx , "/newChatUI: %v %v" , _ret , _err ) }()
57
62
58
63
ui := & chatUI {
59
- Panel : panel ,
60
- EnableButtons : enableButtons ,
61
- ReverseOrder : reverseOrder ,
62
- CapabilitiesCache : make (map [streamcontrol.PlatformName ]map [streamcontrol.Capability ]struct {}),
63
- ItemHeight : map [streamcontrol.ChatMessageID ]uint {},
64
- ctx : ctx ,
64
+ Panel : panel ,
65
+ EnableButtons : enableButtons ,
66
+ ReverseOrder : reverseOrder ,
67
+ SuppressNotifications : suppressNotifications ,
68
+ CapabilitiesCache : make (map [streamcontrol.PlatformName ]map [streamcontrol.Capability ]struct {}),
69
+ ItemsByCanvasObject : map [fyne.CanvasObject ]* chatItem {},
70
+ ItemsByMessageID : map [streamcontrol.ChatMessageID ]* chatItem {},
71
+ ctx : ctx ,
65
72
}
66
73
if err := ui .init (ctx ); err != nil {
67
74
return nil , err
@@ -169,7 +176,7 @@ func (ui *chatUI) messageReceiverLoop(
169
176
logger .Errorf (ctx , "message channel got closed" )
170
177
return
171
178
}
172
- ui .onReceiveMessage (ctx , msg , false )
179
+ ui .onReceiveMessage (ctx , msg , ui . SuppressNotifications )
173
180
}
174
181
}
175
182
}
@@ -184,12 +191,20 @@ func (ui *chatUI) onReceiveMessage(
184
191
if onAdd := ui .OnAdd ; onAdd != nil {
185
192
defer onAdd (ctx , msg )
186
193
}
187
- ui .MessagesHistoryLocker .Lock ()
188
- defer ui .MessagesHistoryLocker .Unlock ()
194
+ ui .MessagesHistoryLocker .ManualLock (ctx )
195
+ prevLen := len (ui .MessagesHistory )
196
+ defer ui .List .RefreshItem (prevLen )
197
+ defer ui .MessagesHistoryLocker .ManualUnlock (ctx )
189
198
ui .MessagesHistory = append (ui .MessagesHistory , msg )
190
- observability . Go (ctx , func () {
191
- ui .List . Refresh ()
199
+ notificationsEnabled := xsync . DoR1 (ctx , & ui . Panel . configLocker , func () bool {
200
+ return ui .Panel . Config . Chat . NotificationsEnabled ()
192
201
})
202
+ if muteNotifications {
203
+ notificationsEnabled = false
204
+ }
205
+ if ! notificationsEnabled {
206
+ return
207
+ }
193
208
observability .GoSafe (ctx , func () {
194
209
commandTemplate := xsync .DoR1 (ctx , & ui .Panel .configLocker , func () string {
195
210
return ui .Panel .Config .Chat .CommandOnReceiveMessage
@@ -202,15 +217,6 @@ func (ui *chatUI) onReceiveMessage(
202
217
203
218
ui .Panel .execCommand (ctx , commandTemplate , msg )
204
219
})
205
- notificationsEnabled := xsync .DoR1 (ctx , & ui .Panel .configLocker , func () bool {
206
- return ui .Panel .Config .Chat .NotificationsEnabled ()
207
- })
208
- if muteNotifications {
209
- notificationsEnabled = false
210
- }
211
- if ! notificationsEnabled {
212
- return
213
- }
214
220
observability .GoSafe (ctx , func () {
215
221
logger .Debugf (ctx , "SendNotification" )
216
222
defer logger .Debugf (ctx , "/SendNotification" )
@@ -242,30 +248,81 @@ func (ui *chatUI) onReceiveMessage(
242
248
}
243
249
244
250
func (ui * chatUI ) listLength () int {
245
- ui .MessagesHistoryLocker .Lock ()
246
- defer ui .MessagesHistoryLocker .Unlock ()
251
+ ctx := context .TODO ()
252
+ ui .MessagesHistoryLocker .ManualLock (ctx )
253
+ defer ui .MessagesHistoryLocker .ManualUnlock (ctx )
247
254
return len (ui .MessagesHistory )
248
255
}
249
256
257
+ type chatItem struct {
258
+ BanUserButton * widget.Button
259
+ RemoveMessageButton * widget.Button
260
+ TimestampSegment * widget.TextSegment
261
+ UsernameSegment * widget.TextSegment
262
+ MessageSegment * widget.TextSegment
263
+ Text * widget.RichText
264
+ Height uint
265
+
266
+ * fyne.Container
267
+ }
268
+
250
269
func (ui * chatUI ) listCreateItem () fyne.CanvasObject {
251
- banUserButton := widget .NewButtonWithIcon ("" , theme .ErrorIcon (), func () {})
252
- removeMsgButton := widget .NewButtonWithIcon ("" , theme .DeleteIcon (), func () {})
253
- label := widget .NewLabel ("<...loading...>" )
254
- label .Wrapping = fyne .TextWrapWord
270
+ item := & chatItem {
271
+ Container : & fyne.Container {},
272
+ TimestampSegment : & widget.TextSegment {
273
+ Style : widget.RichTextStyle {Inline : true , TextStyle : fyne.TextStyle {Bold : true }},
274
+ },
275
+ UsernameSegment : & widget.TextSegment {
276
+ Style : widget.RichTextStyle {
277
+ Inline : true ,
278
+ SizeName : theme .SizeNameSubHeadingText ,
279
+ TextStyle : fyne.TextStyle {Bold : true },
280
+ },
281
+ },
282
+ MessageSegment : & widget.TextSegment {
283
+ Style : widget.RichTextStyle {
284
+ Inline : true ,
285
+ SizeName : theme .SizeNameSubHeadingText ,
286
+ TextStyle : fyne.TextStyle {Bold : true },
287
+ },
288
+ },
289
+ }
290
+
255
291
var leftPanel fyne.CanvasObject
256
292
if ui .EnableButtons {
293
+ item .BanUserButton = widget .NewButtonWithIcon ("" , theme .ErrorIcon (), func () {})
294
+ item .RemoveMessageButton = widget .NewButtonWithIcon ("" , theme .DeleteIcon (), func () {})
257
295
leftPanel = container .NewHBox (
258
- banUserButton ,
259
- removeMsgButton ,
296
+ item . BanUserButton ,
297
+ item . RemoveMessageButton ,
260
298
)
261
299
}
262
- return container .NewBorder (
300
+ item .Text = widget .NewRichText (
301
+ item .TimestampSegment ,
302
+ & widget.TextSegment {
303
+ Text : " " ,
304
+ Style : widget.RichTextStyle {Inline : true },
305
+ },
306
+ item .UsernameSegment ,
307
+ & widget.TextSegment {
308
+ Text : " " ,
309
+ Style : widget.RichTextStyle {Inline : true },
310
+ },
311
+ item .MessageSegment ,
312
+ )
313
+ item .Text .Wrapping = fyne .TextWrapWord
314
+ item .Container = container .NewBorder (
263
315
nil ,
264
316
nil ,
265
317
leftPanel ,
266
318
nil ,
267
- label ,
319
+ item . Text ,
268
320
)
321
+ ctx := context .TODO ()
322
+ ui .ItemLocker .Do (ctx , func () {
323
+ ui .ItemsByCanvasObject [item .Container ] = item
324
+ })
325
+ return item .Container
269
326
}
270
327
271
328
func (ui * chatUI ) getPlatformCapabilities (
@@ -299,8 +356,8 @@ func (ui *chatUI) listUpdateItem(
299
356
obj fyne.CanvasObject ,
300
357
) {
301
358
ctx := context .TODO ()
302
- ui .MessagesHistoryLocker .Lock ( )
303
- defer ui .MessagesHistoryLocker .Unlock ( )
359
+ ui .MessagesHistoryLocker .ManualLock ( ctx )
360
+ defer ui .MessagesHistoryLocker .ManualUnlock ( ctx )
304
361
var entryID int
305
362
if ui .ReverseOrder {
306
363
entryID = len (ui .MessagesHistory ) - 1 - rowID
@@ -319,12 +376,11 @@ func (ui *chatUI) listUpdateItem(
319
376
platCaps = map [streamcontrol.Capability ]struct {}{}
320
377
}
321
378
322
- containerPtr := obj .( * fyne. Container )
323
- objs := containerPtr . Objects
324
- label := objs [ 0 ].( * widget. Label )
379
+ item := xsync . DoR1 ( ctx , & ui . ItemLocker , func () * chatItem {
380
+ return ui . ItemsByCanvasObject [ obj ]
381
+ } )
325
382
if ui .EnableButtons {
326
- subContainer := objs [1 ].(* fyne.Container )
327
- banUserButton := subContainer .Objects [0 ].(* widget.Button )
383
+ banUserButton := item .BanUserButton
328
384
banUserButton .OnTapped = func () {
329
385
w := dialog .NewConfirm (
330
386
"Banning an user" ,
@@ -344,7 +400,7 @@ func (ui *chatUI) listUpdateItem(
344
400
} else {
345
401
banUserButton .Enable ()
346
402
}
347
- removeMsgButton := subContainer . Objects [ 1 ].( * widget. Button )
403
+ removeMsgButton := item . RemoveMessageButton
348
404
removeMsgButton .OnTapped = func () {
349
405
w := dialog .NewConfirm (
350
406
"Removing a message" ,
@@ -366,20 +422,22 @@ func (ui *chatUI) listUpdateItem(
366
422
removeMsgButton .Enable ()
367
423
}
368
424
}
369
- label . SetText ( fmt . Sprintf (
370
- "%s: %s: %s: %s" ,
371
- msg . CreatedAt . Format ( "15:04:05" ),
372
- msg .Platform ,
373
- msg .Username ,
374
- msg . Message ,
375
- ) )
376
-
377
- requiredHeight := containerPtr .MinSize ().Height
425
+ item . TimestampSegment . Text = msg . CreatedAt . Format ( "15:04:05" )
426
+ item . TimestampSegment . Style . ColorName = colorForPlatform ( msg . Platform )
427
+ item . UsernameSegment . Text = msg . Username
428
+ item . UsernameSegment . Style . ColorName = colorForUsername ( msg .Username )
429
+ item . MessageSegment . Text = msg .Message
430
+ item . Text . Refresh ()
431
+ logger . Tracef ( ctx , "%d: updated message is: '%s'" , rowID , msg . Message )
432
+
433
+ requiredHeight := item . Container .MinSize ().Height
378
434
logger .Tracef (ctx , "%d: requiredHeight == %f" , rowID , requiredHeight )
379
435
380
- prevHeight := ui .ItemHeight [msg .MessageID ]
381
- ui .TotalListHeight += uint (requiredHeight ) - prevHeight
382
- ui .ItemHeight [msg .MessageID ] = uint (requiredHeight )
436
+ ui .ItemLocker .Do (ctx , func () {
437
+ ui .ItemsByMessageID [msg .MessageID ] = item
438
+ ui .TotalListHeight += uint (requiredHeight ) - item .Height
439
+ ui .ItemsByMessageID [msg .MessageID ].Height = uint (requiredHeight )
440
+ })
383
441
384
442
// TODO: think of how to get rid of this racy hack:
385
443
observability .Go (ctx , func () { ui .List .SetItemHeight (rowID , requiredHeight ) })
@@ -407,8 +465,8 @@ func (ui *chatUI) onRemoveClicked(
407
465
ctx := context .TODO ()
408
466
logger .Debugf (ctx , "onRemoveClicked(%s)" , itemID )
409
467
defer func () { logger .Debugf (ctx , "/onRemoveClicked(%s)" , itemID ) }()
410
- ui .MessagesHistoryLocker .Lock ( )
411
- defer ui .MessagesHistoryLocker .Unlock ( )
468
+ ui .MessagesHistoryLocker .ManualLock ( ctx )
469
+ defer ui .MessagesHistoryLocker .ManualUnlock ( ctx )
412
470
if itemID < 0 || itemID >= len (ui .MessagesHistory ) {
413
471
return
414
472
}
@@ -417,9 +475,12 @@ func (ui *chatUI) onRemoveClicked(
417
475
defer onRemove (ctx , msg )
418
476
}
419
477
ui .MessagesHistory = append (ui .MessagesHistory [:itemID ], ui .MessagesHistory [itemID + 1 :]... )
420
- prevHeight := ui .ItemHeight [msg .MessageID ]
421
- ui .TotalListHeight -= prevHeight
422
- delete (ui .ItemHeight , msg .MessageID )
478
+ ui .ItemLocker .Do (ctx , func () {
479
+ item := ui .ItemsByMessageID [msg .MessageID ]
480
+ ui .TotalListHeight -= item .Height
481
+ delete (ui .ItemsByMessageID , msg .MessageID )
482
+ delete (ui .ItemsByCanvasObject , item .Container )
483
+ })
423
484
err := ui .Panel .chatMessageRemove (ui .ctx , msg .Platform , msg .MessageID )
424
485
if err != nil {
425
486
ui .Panel .DisplayError (err )
0 commit comments