diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index c0c76c54..85a12324 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -187,8 +187,6 @@ class ChatController extends State { final int _loadHistoryCount = 100; - String inputText = ''; - String pendingText = ''; bool showEmojiPicker = false; @@ -260,7 +258,6 @@ class ChatController extends State { if (!mounted) { return; } - setReadMarker(); if (!scrollController.hasClients) return; if (timeline?.allowNewEvent == false || scrollController.position.pixels > 0 && _scrolledUp == false) { @@ -285,7 +282,6 @@ class ChatController extends State { final draft = prefs.getString('draft_$roomId'); if (draft != null && draft.isNotEmpty) { sendController.text = draft; - setState(() => inputText = draft); } } @@ -472,7 +468,7 @@ class ChatController extends State { ); setState(() { - inputText = pendingText; + sendController.text = pendingText; replyEvent = null; editEvent = null; pendingText = ''; @@ -938,7 +934,7 @@ class ChatController extends State { ); }); await loadTimelineFuture; - setReadMarker(eventId: timeline!.events.first.eventId); + setReadMarker(); } scrollController.jumpTo(0); } @@ -1040,7 +1036,7 @@ class ChatController extends State { setState(() { pendingText = sendController.text; editEvent = selectedEvents.first; - inputText = sendController.text = + sendController.text = editEvent!.getDisplayEvent(timeline!).calcLocalizedBodyFallback( MatrixLocals(L10n.of(context)!), withSenderNamePrefix: false, @@ -1187,7 +1183,6 @@ class ChatController extends State { final prefs = await SharedPreferences.getInstance(); await prefs.setString('draft_$roomId', text); }); - setReadMarker(); if (text.endsWith(' ') && Matrix.of(context).hasComplexBundles) { final clients = currentRoomBundle; for (final client in clients) { @@ -1196,7 +1191,6 @@ class ChatController extends State { text.toLowerCase() == '${prefix.toLowerCase()} ') { setSendingClient(client); setState(() { - inputText = ''; sendController.text = ''; }); return; @@ -1222,9 +1216,15 @@ class ChatController extends State { ); } } - setState(() => inputText = text); + if (_inputTextIsEmpty != text.isEmpty) { + setState(() { + _inputTextIsEmpty = text.isEmpty; + }); + } } + bool _inputTextIsEmpty = true; + bool get isArchived => {Membership.leave, Membership.ban}.contains(room.membership); @@ -1291,7 +1291,7 @@ class ChatController extends State { void cancelReplyEventAction() => setState(() { if (editEvent != null) { - inputText = sendController.text = pendingText; + sendController.text = pendingText; pendingText = ''; } replyEvent = null; diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart index 6f5fe428..8e0f5982 100644 --- a/lib/pages/chat/chat_input_row.dart +++ b/lib/pages/chat/chat_input_row.dart @@ -105,7 +105,7 @@ class ChatInputRow extends StatelessWidget { duration: FluffyThemes.animationDuration, curve: FluffyThemes.animationCurve, height: 56, - width: controller.inputText.isEmpty ? 56 : 0, + width: controller.sendController.text.isEmpty ? 56 : 0, alignment: Alignment.center, clipBehavior: Clip.hardEdge, decoration: const BoxDecoration(), @@ -268,7 +268,7 @@ class ChatInputRow extends StatelessWidget { ), ), if (PlatformInfos.platformCanRecord && - controller.inputText.isEmpty) + controller.sendController.text.isEmpty) Container( height: 56, alignment: Alignment.center, @@ -278,7 +278,8 @@ class ChatInputRow extends StatelessWidget { onPressed: controller.voiceMessageAction, ), ), - if (!PlatformInfos.isMobile || controller.inputText.isNotEmpty) + if (!PlatformInfos.isMobile || + controller.sendController.text.isNotEmpty) Container( height: 56, alignment: Alignment.center, diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index c8ef099a..84ec246d 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -149,222 +149,228 @@ class ChatView extends StatelessWidget { child: GestureDetector( onTapDown: (_) => controller.setReadMarker(), behavior: HitTestBehavior.opaque, - child: StreamBuilder( - stream: controller.room.onUpdate.stream - .rateLimit(const Duration(seconds: 1)), - builder: (context, snapshot) => FutureBuilder( - future: controller.loadTimelineFuture, - builder: (BuildContext context, snapshot) { - return Scaffold( - appBar: AppBar( - actionsIconTheme: IconThemeData( - color: controller.selectedEvents.isEmpty - ? null - : Theme.of(context).colorScheme.primary, + child: MouseRegion( + onEnter: (_) => controller.setReadMarker(), + child: StreamBuilder( + stream: controller.room.onUpdate.stream + .rateLimit(const Duration(seconds: 1)), + builder: (context, snapshot) => FutureBuilder( + future: controller.loadTimelineFuture, + builder: (BuildContext context, snapshot) { + return Scaffold( + appBar: AppBar( + actionsIconTheme: IconThemeData( + color: controller.selectedEvents.isEmpty + ? null + : Theme.of(context).colorScheme.primary, + ), + leading: controller.selectMode + ? IconButton( + icon: const Icon(Icons.close), + onPressed: controller.clearSelectedEvents, + tooltip: L10n.of(context)!.close, + color: Theme.of(context).colorScheme.primary, + ) + : UnreadRoomsBadge( + filter: (r) => r.id != controller.roomId, + badgePosition: BadgePosition.topEnd(end: 8, top: 4), + child: const Center(child: BackButton()), + ), + titleSpacing: 0, + title: ChatAppBarTitle(controller), + actions: _appBarActions(context), ), - leading: controller.selectMode - ? IconButton( - icon: const Icon(Icons.close), - onPressed: controller.clearSelectedEvents, - tooltip: L10n.of(context)!.close, - color: Theme.of(context).colorScheme.primary, + floatingActionButton: controller.showScrollDownButton && + controller.selectedEvents.isEmpty + ? Padding( + padding: const EdgeInsets.only(bottom: 56.0), + child: FloatingActionButton( + onPressed: controller.scrollDown, + heroTag: null, + mini: true, + child: const Icon(Icons.arrow_downward_outlined), + ), ) - : UnreadRoomsBadge( - filter: (r) => r.id != controller.roomId, - badgePosition: BadgePosition.topEnd(end: 8, top: 4), - child: const Center(child: BackButton()), - ), - titleSpacing: 0, - title: ChatAppBarTitle(controller), - actions: _appBarActions(context), - ), - floatingActionButton: controller.showScrollDownButton && - controller.selectedEvents.isEmpty - ? Padding( - padding: const EdgeInsets.only(bottom: 56.0), - child: FloatingActionButton( - onPressed: controller.scrollDown, - heroTag: null, - mini: true, - child: const Icon(Icons.arrow_downward_outlined), - ), - ) - : null, - body: DropTarget( - onDragDone: controller.onDragDone, - onDragEntered: controller.onDragEntered, - onDragExited: controller.onDragExited, - child: Stack( - children: [ - if (Matrix.of(context).wallpaper != null) - Image.file( - Matrix.of(context).wallpaper!, - width: double.infinity, - height: double.infinity, - fit: BoxFit.cover, - filterQuality: FilterQuality.medium, - ), - SafeArea( - child: Column( - children: [ - TombstoneDisplay(controller), - if (scrollUpBannerEventId != null) - Material( - color: Theme.of(context) - .colorScheme - .surfaceVariant, - shape: Border( - bottom: BorderSide( - width: 1, - color: Theme.of(context).dividerColor, + : null, + body: DropTarget( + onDragDone: controller.onDragDone, + onDragEntered: controller.onDragEntered, + onDragExited: controller.onDragExited, + child: Stack( + children: [ + if (Matrix.of(context).wallpaper != null) + Image.file( + Matrix.of(context).wallpaper!, + width: double.infinity, + height: double.infinity, + fit: BoxFit.cover, + filterQuality: FilterQuality.medium, + ), + SafeArea( + child: Column( + children: [ + TombstoneDisplay(controller), + if (scrollUpBannerEventId != null) + Material( + color: Theme.of(context) + .colorScheme + .surfaceVariant, + shape: Border( + bottom: BorderSide( + width: 1, + color: Theme.of(context).dividerColor, + ), + ), + child: ListTile( + leading: IconButton( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + icon: const Icon(Icons.close), + tooltip: L10n.of(context)!.close, + onPressed: () { + controller + .discardScrollUpBannerEventId(); + controller.setReadMarker(); + }, + ), + title: Text( + L10n.of(context)!.jumpToLastReadMessage, + ), + contentPadding: + const EdgeInsets.only(left: 8), + trailing: TextButton( + onPressed: () { + controller.scrollToEventId( + scrollUpBannerEventId, + ); + controller + .discardScrollUpBannerEventId(); + }, + child: Text(L10n.of(context)!.jump), + ), ), ), - child: ListTile( - leading: IconButton( - color: Theme.of(context) - .colorScheme - .onSurfaceVariant, - icon: const Icon(Icons.close), - tooltip: L10n.of(context)!.close, - onPressed: () { - controller.discardScrollUpBannerEventId(); - controller.setReadMarker(); + PinnedEvents(controller), + Expanded( + child: GestureDetector( + onTap: controller.clearSingleSelectedEvent, + child: Builder( + builder: (context) { + if (controller.timeline == null) { + return const Center( + child: CircularProgressIndicator + .adaptive( + strokeWidth: 2, + ), + ); + } + return ChatEventList( + controller: controller, + ); }, ), - title: Text( - L10n.of(context)!.jumpToLastReadMessage, - ), - contentPadding: - const EdgeInsets.only(left: 8), - trailing: TextButton( - onPressed: () { - controller.scrollToEventId( - scrollUpBannerEventId, - ); - controller.discardScrollUpBannerEventId(); - }, - child: Text(L10n.of(context)!.jump), - ), ), ), - PinnedEvents(controller), - Expanded( - child: GestureDetector( - onTap: controller.clearSingleSelectedEvent, - child: Builder( - builder: (context) { - if (controller.timeline == null) { - return const Center( - child: - CircularProgressIndicator.adaptive( - strokeWidth: 2, - ), - ); - } - - return ChatEventList( - controller: controller, - ); - }, - ), - ), - ), - if (controller.room.canSendDefaultMessages && - controller.room.membership == Membership.join) - Container( - margin: EdgeInsets.only( - bottom: bottomSheetPadding, - left: bottomSheetPadding, - right: bottomSheetPadding, - ), - constraints: const BoxConstraints( - maxWidth: FluffyThemes.columnWidth * 2.5, - ), - alignment: Alignment.center, - child: Material( - borderRadius: const BorderRadius.only( - bottomLeft: - Radius.circular(AppConfig.borderRadius), - bottomRight: - Radius.circular(AppConfig.borderRadius), + if (controller.room.canSendDefaultMessages && + controller.room.membership == Membership.join) + Container( + margin: EdgeInsets.only( + bottom: bottomSheetPadding, + left: bottomSheetPadding, + right: bottomSheetPadding, ), - elevation: 4, - shadowColor: Colors.black.withAlpha(64), - clipBehavior: Clip.hardEdge, - color: Theme.of(context).brightness == - Brightness.light - ? Colors.white - : Colors.black, - child: controller.room.isAbandonedDMRoom == - true - ? Row( - mainAxisAlignment: - MainAxisAlignment.spaceEvenly, - children: [ - TextButton.icon( - style: TextButton.styleFrom( - padding: - const EdgeInsets.all(16), - foregroundColor: - Theme.of(context) - .colorScheme - .error, + constraints: const BoxConstraints( + maxWidth: FluffyThemes.columnWidth * 2.5, + ), + alignment: Alignment.center, + child: Material( + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular( + AppConfig.borderRadius, + ), + bottomRight: Radius.circular( + AppConfig.borderRadius, + ), + ), + elevation: 4, + shadowColor: Colors.black.withAlpha(64), + clipBehavior: Clip.hardEdge, + color: Theme.of(context).brightness == + Brightness.light + ? Colors.white + : Colors.black, + child: controller.room.isAbandonedDMRoom == + true + ? Row( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + children: [ + TextButton.icon( + style: TextButton.styleFrom( + padding: + const EdgeInsets.all(16), + foregroundColor: + Theme.of(context) + .colorScheme + .error, + ), + icon: const Icon( + Icons.archive_outlined, + ), + onPressed: controller.leaveChat, + label: Text( + L10n.of(context)!.leave, + ), ), - icon: const Icon( - Icons.archive_outlined, + TextButton.icon( + style: TextButton.styleFrom( + padding: + const EdgeInsets.all(16), + ), + icon: const Icon( + Icons.forum_outlined, + ), + onPressed: + controller.recreateChat, + label: Text( + L10n.of(context)!.reopenChat, + ), ), - onPressed: controller.leaveChat, - label: Text( - L10n.of(context)!.leave, - ), - ), - TextButton.icon( - style: TextButton.styleFrom( - padding: - const EdgeInsets.all(16), - ), - icon: const Icon( - Icons.forum_outlined, - ), - onPressed: - controller.recreateChat, - label: Text( - L10n.of(context)!.reopenChat, - ), - ), - ], - ) - : Column( - mainAxisSize: MainAxisSize.min, - children: [ - const ConnectionStatusHeader(), - ReactionsPicker(controller), - ReplyDisplay(controller), - ChatInputRow(controller), - ChatEmojiPicker(controller), - ], - ), + ], + ) + : Column( + mainAxisSize: MainAxisSize.min, + children: [ + const ConnectionStatusHeader(), + ReactionsPicker(controller), + ReplyDisplay(controller), + ChatInputRow(controller), + ChatEmojiPicker(controller), + ], + ), + ), ), - ), - ], - ), - ), - if (controller.dragging) - Container( - color: Theme.of(context) - .scaffoldBackgroundColor - .withOpacity(0.9), - alignment: Alignment.center, - child: const Icon( - Icons.upload_outlined, - size: 100, + ], ), ), - ], + if (controller.dragging) + Container( + color: Theme.of(context) + .scaffoldBackgroundColor + .withOpacity(0.9), + alignment: Alignment.center, + child: const Icon( + Icons.upload_outlined, + size: 100, + ), + ), + ], + ), ), - ), - ); - }, + ); + }, + ), ), ), ),