@ -70,6 +70,11 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
bool _isLoadingOlder = false ;
bool _isLoadingOlder = false ;
bool _communitiesLoaded = false ;
bool _communitiesLoaded = false ;
/ / / The channel on screen . Starts as the pushed one , then changes in place
/ / / when another is picked from the nav panel , so the panel stays docked and
/ / / only the conversation swaps .
late Channel _currentChannel = widget . channel ;
MeshCoreConnector ? _connector ;
MeshCoreConnector ? _connector ;
DateTime ? _lastChannelSendAt ;
DateTime ? _lastChannelSendAt ;
bool _channelSkipNextBottomSnap = false ;
bool _channelSkipNextBottomSnap = false ;
@ -87,38 +92,45 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
_scrollController . showJumpToBottom . addListener ( _clearDividerAtBottom ) ;
_scrollController . showJumpToBottom . addListener ( _clearDividerAtBottom ) ;
SchedulerBinding . instance . addPostFrameCallback ( ( _ ) {
SchedulerBinding . instance . addPostFrameCallback ( ( _ ) {
if ( ! mounted ) return ;
if ( ! mounted ) return ;
final connector = context . read < MeshCoreConnector > ( ) ;
final settings = context . read < AppSettingsService > ( ) . settings ;
final idx = widget . channel . index ;
final unread = widget . initialUnreadCount ;
final messages = connector . getChannelMessages ( widget . channel ) ;
_loadCommunities ( ) ;
_loadCommunities ( ) ;
ChannelMessage ? anchor ;
_activateChannel ( widget . initialUnreadCount ) ;
if ( unread > 0 ) {
anchor = _findOldestUnreadChannelAnchor ( messages , unread ) ;
}
setState ( ( ) {
if ( anchor ! = null ) _unreadDividerMessageId = anchor . messageId ;
} ) ;
connector . setActiveChannel ( idx ) ;
_connector = connector ;
if ( anchor ! = null & & settings . jumpToOldestUnread ) {
_channelSkipNextBottomSnap = true ;
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
if ( ! mounted ) return ;
_scrollController . jumpToEstimatedOffset (
unreadCount: unread ,
totalMessages: messages . length ,
onJumped: ( ) {
if ( ! mounted ) return ;
_scrollToMessage ( anchor ! . messageId ) ;
} ,
) ;
} ) ;
}
} ) ;
} ) ;
}
}
/ / / Bind the screen to [ _currentChannel ] : mark it active , place the unread
/ / / divider , and optionally jump to the oldest unread . Runs on first build and
/ / / again each time the channel is switched from the nav panel .
void _activateChannel ( int unread ) {
final connector = context . read < MeshCoreConnector > ( ) ;
final settings = context . read < AppSettingsService > ( ) . settings ;
final messages = connector . getChannelMessages ( _currentChannel ) ;
ChannelMessage ? anchor ;
if ( unread > 0 ) {
anchor = _findOldestUnreadChannelAnchor ( messages , unread ) ;
}
setState ( ( ) {
_unreadDividerMessageId = anchor ? . messageId ;
} ) ;
connector . setActiveChannel ( _currentChannel . index ) ;
_connector = connector ;
if ( anchor ! = null & & settings . jumpToOldestUnread ) {
_channelSkipNextBottomSnap = true ;
WidgetsBinding . instance . addPostFrameCallback ( ( _ ) {
if ( ! mounted ) return ;
_scrollController . jumpToEstimatedOffset (
unreadCount: unread ,
totalMessages: messages . length ,
onJumped: ( ) {
if ( ! mounted ) return ;
_scrollToMessage ( anchor ! . messageId ) ;
} ,
) ;
} ) ;
}
}
/ / TODO: Reload communities when returning from another screen
/ / TODO: Reload communities when returning from another screen
Future < void > _loadCommunities ( ) async {
Future < void > _loadCommunities ( ) async {
final connector = context . read < MeshCoreConnector > ( ) ;
final connector = context . read < MeshCoreConnector > ( ) ;
@ -166,7 +178,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
setState ( ( ) = > _isLoadingOlder = true ) ;
setState ( ( ) = > _isLoadingOlder = true ) ;
final connector = context . read < MeshCoreConnector > ( ) ;
final connector = context . read < MeshCoreConnector > ( ) ;
await connector . loadOlderChannelMessages ( widget. c hannel. index ) ;
await connector . loadOlderChannelMessages ( _currentC hannel. index ) ;
if ( mounted ) {
if ( mounted ) {
setState ( ( ) = > _isLoadingOlder = false ) ;
setState ( ( ) = > _isLoadingOlder = false ) ;
@ -273,24 +285,31 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
) ;
) ;
}
}
/ / / Switch to another channel from the drawer without backing out to the
/ / / Switch to another channel picked from the nav panel .
/ / / channel list . pushReplacement keeps the back stack one deep , so system
/ / /
/ / / back still returns to the channel list rather than the previous channel .
/ / / Swaps the conversation in place rather than pushing a route , so a pinned
/ / / panel stays docked and only the chat area changes . The back stack is
/ / / untouched , so system back still returns to the channel list .
void _switchChannel ( Channel channel ) {
void _switchChannel ( Channel channel ) {
if ( channel . index = = widget . channel . index ) {
final scaffold = Scaffold . maybeOf ( context ) ;
if ( scaffold ? . isDrawerOpen ? ? false ) {
Navigator . pop ( context ) ;
Navigator . pop ( context ) ;
return ;
}
}
if ( channel . index = = _currentChannel . index ) return ;
final connector = context . read < MeshCoreConnector > ( ) ;
final connector = context . read < MeshCoreConnector > ( ) ;
final unread = connector . getUnreadCountForChannelIndex ( channel . index ) ;
final unread = connector . getUnreadCountForChannelIndex ( channel . index ) ;
connector . markChannelRead ( channel . index ) ;
connector . markChannelRead ( channel . index ) ;
Navigator . pushReplacement (
context ,
/ / Drop per - channel view state before rebinding .
MaterialPageRoute (
_replyingToMessage = null ;
builder: ( context ) = >
_messageKeys . clear ( ) ;
ChannelChatScreen ( channel: channel , initialUnreadCount: unread ) ,
_unreadDividerMessageId = null ;
) ,
_channelSkipNextBottomSnap = false ;
) ;
_textController . clear ( ) ;
setState ( ( ) = > _currentChannel = channel ) ;
_activateChannel ( unread ) ;
}
}
@ override
@ override
@ -300,7 +319,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
/ / pinnable here , which is the wide - screen point - keeping the channel
/ / pinnable here , which is the wide - screen point - keeping the channel
/ / list and its unread counts visible while reading a channel .
/ / list and its unread counts visible while reading a channel .
drawerContent: ChannelDrawerList (
drawerContent: ChannelDrawerList (
currentChannelIndex: widget. c hannel. index ,
currentChannelIndex: _currentC hannel. index ,
onChannelSelected: _switchChannel ,
onChannelSelected: _switchChannel ,
) ,
) ,
appBarBuilder: ( context , pinned ) = > AppBar (
appBarBuilder: ( context , pinned ) = > AppBar (
@ -319,25 +338,25 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
) ,
) ,
title: Row (
title: Row (
children: [
children: [
_channelIcon ( widget. c hannel) ,
_channelIcon ( _currentC hannel) ,
const SizedBox ( width: 8 ) ,
const SizedBox ( width: 8 ) ,
Expanded (
Expanded (
child: Column (
child: Column (
crossAxisAlignment: CrossAxisAlignment . start ,
crossAxisAlignment: CrossAxisAlignment . start ,
children: [
children: [
Text (
Text (
widget. c hannel. name . isEmpty
_currentC hannel. name . isEmpty
? context . l10n . channels_channelIndex (
? context . l10n . channels_channelIndex (
widget. c hannel. index ,
_currentC hannel. index ,
)
)
: widget. c hannel. name ,
: _currentC hannel. name ,
style: const TextStyle ( fontSize: 16 ) ,
style: const TextStyle ( fontSize: 16 ) ,
) ,
) ,
Consumer < MeshCoreConnector > (
Consumer < MeshCoreConnector > (
builder: ( context , connector , _ ) {
builder: ( context , connector , _ ) {
final unreadCount = connector
final unreadCount = connector
. getUnreadCountForChannelIndex ( widget. c hannel. index ) ;
. getUnreadCountForChannelIndex ( _currentC hannel. index ) ;
final privacy = widget. c hannel. isPublicChannel
final privacy = _currentC hannel. isPublicChannel
? context . l10n . channels_public
? context . l10n . channels_public
: context . l10n . channels_private ;
: context . l10n . channels_private ;
return Text (
return Text (
@ -361,7 +380,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
onSelected: ( value ) {
onSelected: ( value ) {
if ( value = = ' clearChat ' ) {
if ( value = = ' clearChat ' ) {
context . read < MeshCoreConnector > ( ) . clearMessagesForChannel (
context . read < MeshCoreConnector > ( ) . clearMessagesForChannel (
widget. c hannel. index ,
_currentC hannel. index ,
) ;
) ;
}
}
} ,
} ,
@ -390,7 +409,9 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
Expanded (
Expanded (
child: Consumer < MeshCoreConnector > (
child: Consumer < MeshCoreConnector > (
builder: ( context , connector , child ) {
builder: ( context , connector , child ) {
final messages = connector . getChannelMessages ( widget . channel ) ;
final messages = connector . getChannelMessages (
_currentChannel ,
) ;
final blockService = context . watch < BlockService > ( ) ;
final blockService = context . watch < BlockService > ( ) ;
final visibleMessages = messages
final visibleMessages = messages
. where (
. where (
@ -405,7 +426,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
mainAxisAlignment: MainAxisAlignment . center ,
mainAxisAlignment: MainAxisAlignment . center ,
children: [
children: [
Icon (
Icon (
widget. c hannel. isPublicChannel
_currentC hannel. isPublicChannel
? Icons . public
? Icons . public
: Icons . tag ,
: Icons . tag ,
size: 64 ,
size: 64 ,
@ -536,14 +557,14 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
void _markAsUnread ( ChannelMessage message ) {
void _markAsUnread ( ChannelMessage message ) {
final connector = context . read < MeshCoreConnector > ( ) ;
final connector = context . read < MeshCoreConnector > ( ) ;
final messages = connector . getChannelMessages ( widget. c hannel) ;
final messages = connector . getChannelMessages ( _currentC hannel) ;
var count = 0 ;
var count = 0 ;
var found = false ;
var found = false ;
for ( final m in messages ) {
for ( final m in messages ) {
if ( m . messageId = = message . messageId ) found = true ;
if ( m . messageId = = message . messageId ) found = true ;
if ( found & & ! m . isOutgoing ) count + + ;
if ( found & & ! m . isOutgoing ) count + + ;
}
}
connector . setChannelUnreadCount ( widget. c hannel. index , count ) ;
connector . setChannelUnreadCount ( _currentC hannel. index , count ) ;
}
}
/ / / Block the sender of a channel post . Resolves the claimed name to known
/ / / Block the sender of a channel post . Resolves the claimed name to known
@ -890,7 +911,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
? colorScheme . onPrimaryContainer
? colorScheme . onPrimaryContainer
: colorScheme . onSurface ;
: colorScheme . onSurface ;
final metaColor = textColor . withValues ( alpha: 0.7 ) ;
final metaColor = textColor . withValues ( alpha: 0.7 ) ;
final channelColor = widget. c hannel. isPublicChannel
final channelColor = _currentC hannel. isPublicChannel
? Colors . orange
? Colors . orange
: Colors . blue ;
: Colors . blue ;
@ -905,7 +926,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
final selfName = context . read < MeshCoreConnector > ( ) . selfName ? ? ' Me ' ;
final selfName = context . read < MeshCoreConnector > ( ) . selfName ? ? ' Me ' ;
final fromName = isOutgoing ? selfName : senderName ;
final fromName = isOutgoing ? selfName : senderName ;
final key = buildSharedMarkerKey (
final key = buildSharedMarkerKey (
sourceId: ' channel: ${ widget. c hannel. index } ' ,
sourceId: ' channel: ${ _currentC hannel. index } ' ,
label: poi . label ,
label: poi . label ,
fromName: fromName ,
fromName: fromName ,
flags: poi . flags ,
flags: poi . flags ,
@ -1166,13 +1187,13 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
onSubmitted: ( _ ) = > _sendMessage ( ) ,
onSubmitted: ( _ ) = > _sendMessage ( ) ,
encoder:
encoder:
( connector . isChannelSmazEnabled (
( connector . isChannelSmazEnabled (
widget. c hannel. index ,
_currentC hannel. index ,
) | |
) | |
connector . isChannelCyr2LatEnabled (
connector . isChannelCyr2LatEnabled (
widget. c hannel. index ,
_currentC hannel. index ,
) )
) )
? ( text ) = > connector . prepareChannelOutboundText (
? ( text ) = > connector . prepareChannelOutboundText (
widget. c hannel. index ,
_currentC hannel. index ,
text ,
text ,
)
)
: null ,
: null ,
@ -1214,7 +1235,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
/ / Recent senders in this channel , keyed to their most recent timestamp .
/ / Recent senders in this channel , keyed to their most recent timestamp .
final recentTime = < String , DateTime > { } ;
final recentTime = < String , DateTime > { } ;
for ( final message in connector . getChannelMessages ( widget. c hannel) ) {
for ( final message in connector . getChannelMessages ( _currentC hannel) ) {
if ( message . isOutgoing ) continue ;
if ( message . isOutgoing ) continue ;
final name = message . senderName . trim ( ) ;
final name = message . senderName . trim ( ) ;
if ( name . isEmpty | | name = = ' Unknown ' ) continue ;
if ( name . isEmpty | | name = = ' Unknown ' ) continue ;
@ -1304,7 +1325,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
final maxBytes = maxChannelMessageBytes ( connector . selfName ) ;
final maxBytes = maxChannelMessageBytes ( connector . selfName ) ;
final outboundText = connector . prepareChannelOutboundText (
final outboundText = connector . prepareChannelOutboundText (
widget. c hannel. index ,
_currentC hannel. index ,
messageText ,
messageText ,
) ;
) ;
if ( utf8 . encode ( outboundText ) . length > maxBytes ) {
if ( utf8 . encode ( outboundText ) . length > maxBytes ) {
@ -1319,7 +1340,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
/ / but we getting messages doubles in chat screen ( source text and transformed ) .
/ / but we getting messages doubles in chat screen ( source text and transformed ) .
/ / To prevent , we ' ll perform transform of source before pass to main sender logic.
/ / To prevent , we ' ll perform transform of source before pass to main sender logic.
/ / We can pass whole text , senderName will be kept intact
/ / We can pass whole text , senderName will be kept intact
if ( connector . isChannelCyr2LatEnabled ( widget. c hannel. index ) ) {
if ( connector . isChannelCyr2LatEnabled ( _currentC hannel. index ) ) {
messageText = Cyr2Lat . encode ( messageText ) ;
messageText = Cyr2Lat . encode ( messageText ) ;
}
}
/ / end transform
/ / end transform
@ -1328,7 +1349,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
_cancelReply ( ) ;
_cancelReply ( ) ;
_textFieldFocusNode . requestFocus ( ) ;
_textFieldFocusNode . requestFocus ( ) ;
connector . sendChannelMessage (
connector . sendChannelMessage (
widget. c hannel,
_currentC hannel,
messageText ,
messageText ,
originalText: originalText ,
originalText: originalText ,
translatedLanguageCode: translatedLanguageCode ,
translatedLanguageCode: translatedLanguageCode ,
@ -1349,7 +1370,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
var path = _formatPathPrefixes ( message . pathBytes , w ) ;
var path = _formatPathPrefixes ( message . pathBytes , w ) ;
var truncated = false ;
var truncated = false ;
String outbound ( ) = > connector . prepareChannelOutboundText (
String outbound ( ) = > connector . prepareChannelOutboundText (
widget. c hannel. index ,
_currentC hannel. index ,
' $ prefix $ path ${ truncated ? ' … ' : ' ' } ' ,
' $ prefix $ path ${ truncated ? ' … ' : ' ' } ' ,
) ;
) ;
while ( path . isNotEmpty & & utf8 . encode ( outbound ( ) ) . length > maxBytes ) {
while ( path . isNotEmpty & & utf8 . encode ( outbound ( ) ) . length > maxBytes ) {
@ -1358,10 +1379,10 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
}
}
var text = ' $ prefix $ path ${ truncated ? ' … ' : ' ' } ' ;
var text = ' $ prefix $ path ${ truncated ? ' … ' : ' ' } ' ;
if ( connector . isChannelCyr2LatEnabled ( widget. c hannel. index ) ) {
if ( connector . isChannelCyr2LatEnabled ( _currentC hannel. index ) ) {
text = Cyr2Lat . encode ( text ) ;
text = Cyr2Lat . encode ( text ) ;
}
}
connector . sendChannelMessage ( widget. c hannel, text ) ;
connector . sendChannelMessage ( _currentC hannel, text ) ;
}
}
void _ensureDateFormats ( BuildContext context ) {
void _ensureDateFormats ( BuildContext context ) {
@ -1583,7 +1604,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
Navigator . pop ( sheetContext ) ;
Navigator . pop ( sheetContext ) ;
unawaited (
unawaited (
context . read < MeshCoreConnector > ( ) . translateChannelMessage (
context . read < MeshCoreConnector > ( ) . translateChannelMessage (
widget. c hannel. index ,
_currentC hannel. index ,
message ,
message ,
manualTranslation: true ,
manualTranslation: true ,
) ,
) ,
@ -1650,7 +1671,7 @@ class _ChannelChatScreenState extends State<ChannelChatScreen> {
message . text ,
message . text ,
) ;
) ;
final reactionText = ReactionHelper . encodeReaction ( hash , emojiIndex ) ;
final reactionText = ReactionHelper . encodeReaction ( hash , emojiIndex ) ;
connector . sendChannelMessage ( widget. c hannel, reactionText ) ;
connector . sendChannelMessage ( _currentC hannel, reactionText ) ;
}
}
void _copyMessageText ( String text ) {
void _copyMessageText ( String text ) {