@@ -110,6 +110,7 @@ class StreamMessageListView extends StatefulWidget {
110110 this .parentMessage,
111111 this .threadBuilder,
112112 this .onThreadTap,
113+ this .onViewInChannelTap,
113114 this .onEditMessageTap,
114115 this .onReplyTap,
115116 this .swipeToReply = false ,
@@ -216,6 +217,19 @@ class StreamMessageListView extends StatefulWidget {
216217 /// built using [threadBuilder]
217218 final ThreadTapCallback ? onThreadTap;
218219
220+ /// Called when the "View" button on the "Also sent in channel" annotation
221+ /// is tapped inside a thread view.
222+ ///
223+ /// Use this to navigate to the channel screen and scroll to / highlight
224+ /// the given [Message] .
225+ ///
226+ /// When null and the thread was opened via the default [threadBuilder]
227+ /// navigation, the thread screen is automatically popped and the channel
228+ /// list scrolls to the message. Provide this callback to override that
229+ /// behaviour — for example when the thread is opened from a thread list
230+ /// or deep link where popping would not land on the channel screen.
231+ final void Function (Message message)? onViewInChannelTap;
232+
219233 /// {@macro onEditMessageTap}
220234 ///
221235 /// If provided, the inline edit flow is used instead of the edit bottom sheet.
@@ -451,7 +465,7 @@ class StreamMessageListView extends StatefulWidget {
451465
452466class _StreamMessageListViewState extends State <StreamMessageListView > {
453467 ItemScrollController ? _scrollController;
454- void Function (Message )? _onThreadTap;
468+ void Function (Message parentMessage, Message ? threadMessage )? _onThreadTap;
455469 final ValueNotifier <bool > _showScrollToBottom = ValueNotifier (false );
456470 late final ItemPositionsListener _itemPositionListener;
457471 int ? _messageListLength;
@@ -520,22 +534,29 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
520534 unreadCount = streamChannel? .channel.state? .unreadCount ?? 0 ;
521535 _firstUnreadMessage = streamChannel? .getFirstUnreadMessage ();
522536
523- if (widget.highlightInitialMessage) {
524- final initialMessageId = streamChannel? .initialMessageId;
525- if (initialMessageId != null ) {
526- _highlightedMessageId = initialMessageId;
527- _highlightGeneration++ ;
528- }
537+ final highlightMessageId = widget.highlightInitialMessage
538+ ? (streamChannel? .initialMessageId ?? _ThreadHighlightScope .of (context))
539+ : null ;
540+
541+ if (highlightMessageId != null ) {
542+ WidgetsBinding .instance.addPostFrameCallback ((_) {
543+ if (! mounted) return ;
544+ _moveToAndHighlight (
545+ messages: messages,
546+ messageId: highlightMessageId,
547+ initialScrollIndex: widget.initialScrollIndex,
548+ scrollTo: false ,
549+ );
550+ });
551+ } else {
552+ initialIndex = getInitialIndex (
553+ widget.initialScrollIndex,
554+ streamChannel! ,
555+ widget.messageFilter,
556+ );
557+ initialAlignment = _initialAlignment;
529558 }
530559
531- initialIndex = getInitialIndex (
532- widget.initialScrollIndex,
533- streamChannel! ,
534- widget.messageFilter,
535- );
536-
537- initialAlignment = _initialAlignment;
538-
539560 if (_scrollController? .isAttached == true ) {
540561 _scrollController? .jumpTo (
541562 index: initialIndex,
@@ -585,31 +606,50 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
585606 });
586607 }
587608
588- Future <void > _scrollToAndHighlight (
589- String messageId, {
609+ Future <void > _moveToAndHighlight ({
590610 required List <Message > messages,
611+ String ? messageId,
612+ int ? initialScrollIndex,
613+ bool scrollTo = true ,
591614 }) async {
592- final index = messages.indexWhere ((m) => m.id == messageId);
593- if (index >= 0 ) {
594- await _scrollController? .scrollTo (
595- index: index + 2 , // +2 to account for loader and footer
596- duration: const Duration (seconds: 1 ),
597- curve: Curves .easeInOut,
598- alignment: 0.1 ,
615+ if (messageId != null ) {
616+ final index = messages.indexWhere ((m) => m.id == messageId);
617+
618+ if (index >= 0 ) {
619+ if (scrollTo) {
620+ _scrollController? .scrollTo (
621+ index: index + 2 , // +2 to account for loader and footer
622+ duration: const Duration (seconds: 1 ),
623+ curve: Curves .easeInOut,
624+ alignment: 0.1 ,
625+ );
626+ } else {
627+ _scrollController? .jumpTo (
628+ index: index + 2 , // +2 to account for loader and footer
629+ alignment: 0.1 ,
630+ );
631+ }
632+ } else {
633+ await streamChannel! .loadChannelAtMessage (messageId).then ((_) async {
634+ initialIndex = getInitialIndex (
635+ initialScrollIndex,
636+ streamChannel! ,
637+ widget.messageFilter,
638+ messageId: messageId,
639+ );
640+ initialAlignment = 0.1 ;
641+ });
642+ }
643+ } else if (initialScrollIndex != null ) {
644+ _scrollController? .jumpTo (
645+ index: initialScrollIndex,
646+ alignment: initialAlignment,
599647 );
600- } else {
601- await streamChannel! .loadChannelAtMessage (messageId).then ((_) async {
602- initialIndex = getInitialIndex (
603- null ,
604- streamChannel! ,
605- widget.messageFilter,
606- messageId: messageId,
607- );
608- initialAlignment = 0.1 ;
609- });
610648 }
611649
612- _highlightMessage (messageId);
650+ if (messageId != null ) {
651+ _highlightMessage (messageId);
652+ }
613653 }
614654
615655 @override
@@ -1255,6 +1295,9 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
12551295 message: message,
12561296 swipeToReply: widget.swipeToReply,
12571297 onThreadTap: _onThreadTap,
1298+ onViewInChannelTap: _isThreadConversation
1299+ ? widget.onViewInChannelTap ?? (message) => Navigator .of (context).pop (message.id)
1300+ : null ,
12581301 onMessageTap: widget.onMessageTap,
12591302 onMessageLongPress: widget.onMessageLongPress,
12601303 onEditMessageTap: widget.onEditMessageTap,
@@ -1265,8 +1308,8 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
12651308 onUserMentionTap: widget.onUserMentionTap,
12661309 onQuotedMessageTap: switch (widget.onQuotedMessageTap) {
12671310 final onTap? => onTap,
1268- _ => (quotedMessage) => _scrollToAndHighlight (
1269- quotedMessage.id,
1311+ _ => (quotedMessage) => _moveToAndHighlight (
1312+ messageId : quotedMessage.id,
12701313 messages: messages,
12711314 ),
12721315 },
@@ -1391,37 +1434,64 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
13911434 // Case 1: widget.onThreadTap is provided.
13921435 // The created callback will use widget.onThreadTap, passing the result
13931436 // of widget.threadBuilder (if provided) as the second argument.
1394- (final onThreadTap? , final threadBuilder) => (Message message ) {
1437+ (final onThreadTap? , final threadBuilder) => (Message parentMessage, Message ? threadMessage ) {
13951438 onThreadTap (
1396- message ,
1397- threadBuilder? .call (context, message ),
1439+ parentMessage ,
1440+ threadBuilder? .call (context, parentMessage ),
13981441 );
13991442 },
14001443 // Case 2: widget.onThreadTap is null, but widget.threadBuilder is provided.
14011444 // The created callback will perform the default navigation action,
14021445 // using widget.threadBuilder to build the thread page.
1403- (null , final threadBuilder? ) => (Message message) {
1404- final threadPage = StreamChatConfiguration (
1446+ (null , final threadBuilder? ) => (Message parentMessage, Message ? threadMessage) async {
1447+ Widget threadPage = StreamChatConfiguration (
14051448 // This is needed to provide the nearest reaction icons to the
14061449 // StreamMessageReactionsModal.
14071450 data: StreamChatConfiguration .of (context),
14081451 child: StreamChannel (
14091452 channel: streamChannel! .channel,
14101453 child: BetterStreamBuilder <Message >(
1411- initialData: message ,
1454+ initialData: parentMessage ,
14121455 stream: streamChannel! .channel.state? .messagesStream.map (
1413- (it) => it.firstWhere ((m) => m.id == message .id),
1456+ (it) => it.firstWhere ((m) => m.id == parentMessage .id),
14141457 ),
14151458 builder: (_, data) => threadBuilder (context, data),
14161459 ),
14171460 ),
14181461 );
14191462
1420- Navigator .of (context).push (
1463+ if (threadMessage != null ) {
1464+ threadPage = _ThreadHighlightScope (
1465+ messageId: threadMessage.id,
1466+ child: threadPage,
1467+ );
1468+ }
1469+
1470+ final result = await Navigator .of (context).push <String >(
14211471 MaterialPageRoute (builder: (_) => threadPage),
14221472 );
1473+
1474+ if (result != null && mounted) {
1475+ _moveToAndHighlight (messageId: result, messages: messages);
1476+ }
14231477 },
14241478 _ => null ,
14251479 };
14261480 }
14271481}
1482+
1483+ class _ThreadHighlightScope extends InheritedWidget {
1484+ const _ThreadHighlightScope ({
1485+ required this .messageId,
1486+ required super .child,
1487+ });
1488+
1489+ final String messageId;
1490+
1491+ static String ? of (BuildContext context) {
1492+ return context.findAncestorWidgetOfExactType <_ThreadHighlightScope >()? .messageId;
1493+ }
1494+
1495+ @override
1496+ bool updateShouldNotify (_ThreadHighlightScope oldWidget) => messageId != oldWidget.messageId;
1497+ }
0 commit comments