From 2acf49a12b7c58e1d859654582fafa93248aaf00 Mon Sep 17 00:00:00 2001 From: Krille Date: Sat, 25 Mar 2023 15:06:12 +0100 Subject: [PATCH] refactor: Not nullable room in ChatPage --- lib/config/routes.dart | 10 +-- lib/pages/chat/chat.dart | 120 +++++++++++++++---------- lib/pages/chat/chat_app_bar_title.dart | 3 - lib/pages/chat/chat_input_row.dart | 4 +- lib/pages/chat/chat_view.dart | 37 +++----- lib/pages/chat/pinned_events.dart | 13 ++- lib/pages/chat/reactions_picker.dart | 2 +- lib/pages/chat/seen_by_row.dart | 2 +- lib/pages/chat/tombstone_display.dart | 4 +- lib/pages/chat/typing_indicators.dart | 2 +- 10 files changed, 102 insertions(+), 95 deletions(-) diff --git a/lib/config/routes.dart b/lib/config/routes.dart index a98036bc..92fd66cb 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -72,7 +72,7 @@ class AppRoutes { ), VWidget( path: ':roomid', - widget: const Chat(), + widget: const ChatPage(), stackedRoutes: [ VWidget( path: 'encryption', @@ -100,7 +100,7 @@ class AppRoutes { stackedRoutes: [ VWidget( path: ':roomid', - widget: const Chat(), + widget: const ChatPage(), buildTransition: _dynamicTransition, ), ], @@ -174,14 +174,14 @@ class AppRoutes { VNester( path: ':roomid', widgetBuilder: (child) => SideViewLayout( - mainView: const Chat(), + mainView: const ChatPage(), sideView: child, ), buildTransition: _fadeTransition, nestedRoutes: [ VWidget( path: '', - widget: const Chat(), + widget: const ChatPage(), buildTransition: _fadeTransition, ), VWidget( @@ -245,7 +245,7 @@ class AppRoutes { ), VWidget( path: ':roomid', - widget: const Chat(), + widget: const ChatPage(), buildTransition: _dynamicTransition, ), ], diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 86c0ae54..8d9c17ed 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -35,17 +35,48 @@ import 'send_file_dialog.dart'; import 'send_location_dialog.dart'; import 'sticker_picker_dialog.dart'; -class Chat extends StatefulWidget { +class ChatPage extends StatelessWidget { final Widget? sideView; - const Chat({Key? key, this.sideView}) : super(key: key); + const ChatPage({Key? key, this.sideView}) : super(key: key); + + @override + Widget build(BuildContext context) { + final roomId = context.vRouter.pathParameters['roomid']; + final room = + roomId == null ? null : Matrix.of(context).client.getRoomById(roomId); + if (room == null) { + return Scaffold( + appBar: AppBar(title: Text(L10n.of(context)!.oopsSomethingWentWrong)), + body: Center( + child: Padding( + padding: const EdgeInsets.all(16), + child: + Text(L10n.of(context)!.youAreNoLongerParticipatingInThisChat), + ), + ), + ); + } + return ChatPageWithRoom(sideView: sideView, room: room); + } +} + +class ChatPageWithRoom extends StatefulWidget { + final Widget? sideView; + final Room room; + + const ChatPageWithRoom({ + Key? key, + required this.sideView, + required this.room, + }) : super(key: key); @override ChatController createState() => ChatController(); } -class ChatController extends State { - Room? room; +class ChatController extends State { + Room get room => widget.room; late Client sendingClient; @@ -53,7 +84,7 @@ class ChatController extends State { String? readMarkerEventId; - String? get roomId => context.vRouter.pathParameters['roomid']; + String get roomId => widget.room.id; final AutoScrollController scrollController = AutoScrollController(); @@ -95,7 +126,7 @@ class ChatController extends State { useRootNavigator: false, builder: (c) => SendFileDialog( files: matrixFiles, - room: room!, + room: room, ), ); } @@ -134,13 +165,13 @@ class ChatController extends State { bool get lastReadEventVisible => timeline == null || - room!.fullyRead.isEmpty || - timeline!.events.any((event) => event.eventId == room!.fullyRead); + room.fullyRead.isEmpty || + timeline!.events.any((event) => event.eventId == room.fullyRead); void recreateChat() async { final room = this.room; - final userId = room?.directChatMatrixID; - if (room == null || userId == null) { + final userId = room.directChatMatrixID; + if (userId == null) { throw Exception( 'Try to recreate a room with is not a DM room. This should not be possible from the UI!', ); @@ -162,12 +193,6 @@ class ChatController extends State { } void leaveChat() async { - final room = this.room; - if (room == null) { - throw Exception( - 'Leave room button clicked while room is null. This should not be possible from the UI!', - ); - } final success = await showFutureLoadingDialog( context: context, future: room.leave, @@ -262,12 +287,12 @@ class ChatController extends State { if (timeline == null) { await Matrix.of(context).client.roomsLoading; await Matrix.of(context).client.accountDataLoading; - timeline = await room!.getTimeline( + timeline = await room.getTimeline( onUpdate: updateView, eventContextId: eventContextId, ); if (timeline!.events.isNotEmpty) { - if (room!.markedUnread) room!.markUnread(false); + if (room.markedUnread) room.markUnread(false); setReadMarker(); } @@ -293,8 +318,8 @@ class ChatController extends State { void setReadMarker({String? eventId}) { if (_setReadMarkerFuture != null) return; if (lastReadEventVisible && - !room!.hasNewMessages && - room!.notificationCount == 0) { + !room.hasNewMessages && + room.notificationCount == 0) { return; } if (!Matrix.of(context).webHasFocus) return; @@ -312,7 +337,7 @@ class ChatController extends State { _setReadMarkerFuture = timeline.setReadMarker(eventId).then((_) { _setReadMarkerFuture = null; }); - room!.client.updateIosBadge(); + room.client.updateIosBadge(); } @override @@ -331,7 +356,7 @@ class ChatController extends State { // no need to have the setting typing to false be blocking typingCoolDown?.cancel(); typingCoolDown = null; - room!.setTyping(false); + room.setTyping(false); currentlyTyping = false; } // then set the new sending client @@ -351,7 +376,7 @@ class ChatController extends State { final commandMatch = RegExp(r'^\/(\w+)').firstMatch(sendController.text); if (commandMatch != null && - !room!.client.commands.keys.contains(commandMatch[1]!.toLowerCase())) { + !room.client.commands.keys.contains(commandMatch[1]!.toLowerCase())) { final l10n = L10n.of(context)!; final dialogResult = await showOkCancelAlertDialog( context: context, @@ -366,7 +391,7 @@ class ChatController extends State { } // ignore: unawaited_futures - room!.sendTextEvent( + room.sendTextEvent( sendController.text, inReplyTo: replyEvent, editEventId: editEvent?.eventId, @@ -403,7 +428,7 @@ class ChatController extends State { ).detectFileType, ) .toList(), - room: room!, + room: room, ), ); } @@ -428,7 +453,7 @@ class ChatController extends State { ).detectFileType, ) .toList(), - room: room!, + room: room, ), ); } @@ -449,7 +474,7 @@ class ChatController extends State { name: file.path, ) ], - room: room!, + room: room, ), ); } @@ -470,7 +495,7 @@ class ChatController extends State { name: file.path, ) ], - room: room!, + room: room, ), ); } @@ -478,7 +503,7 @@ class ChatController extends State { void sendStickerAction() async { final sticker = await showAdaptiveBottomSheet( context: context, - builder: (c) => StickerPickerDialog(room: room!), + builder: (c) => StickerPickerDialog(room: room), ); if (sticker == null) return; final eventContent = { @@ -487,7 +512,7 @@ class ChatController extends State { 'url': sticker.url.toString(), }; // send the sticker - await room!.sendEvent( + await room.sendEvent( eventContent, type: EventTypes.Sticker, ); @@ -521,7 +546,7 @@ class ChatController extends State { bytes: audioFile.readAsBytesSync(), name: audioFile.path, ); - await room!.sendFileEvent( + await room.sendFileEvent( file, inReplyTo: replyEvent, extraContent: { @@ -571,7 +596,7 @@ class ChatController extends State { await showDialog( context: context, useRootNavigator: false, - builder: (c) => SendLocationDialog(room: room!), + builder: (c) => SendLocationDialog(room: room), ); } @@ -677,7 +702,7 @@ class ChatController extends State { if (client == null) { return; } - final room = client.getRoomById(roomId!)!; + final room = client.getRoomById(roomId)!; await Event.fromJson(event.toJson(), room).redactEvent(); } } else { @@ -694,7 +719,7 @@ class ChatController extends State { List get currentRoomBundle { final clients = Matrix.of(context).currentBundle!; - clients.removeWhere((c) => c!.getRoomById(roomId!) == null); + clients.removeWhere((c) => c!.getRoomById(roomId) == null); return clients; } @@ -808,7 +833,7 @@ class ChatController extends State { void forgetRoom() async { final result = await showFutureLoadingDialog( context: context, - future: room!.forget, + future: room.forget, ); if (result.error != null) return; VRouter.of(context).to('/archive'); @@ -857,7 +882,7 @@ class ChatController extends State { final events = List.from(selectedEvents); setState(() => selectedEvents.clear()); for (final event in events) { - await room!.sendReaction( + await room.sendReaction( event.eventId, emoji!, ); @@ -904,7 +929,7 @@ class ChatController extends State { useRootNavigator: false, context: context, title: L10n.of(context)!.goToTheNewRoom, - message: room! + message: room .getState(EventTypes.RoomTombstone)! .parsedTombstoneContent .body, @@ -915,8 +940,8 @@ class ChatController extends State { } final result = await showFutureLoadingDialog( context: context, - future: () => room!.client.joinRoom( - room! + future: () => room.client.joinRoom( + room .getState(EventTypes.RoomTombstone)! .parsedTombstoneContent .replacementRoom, @@ -924,7 +949,7 @@ class ChatController extends State { ); await showFutureLoadingDialog( context: context, - future: room!.leave, + future: room.leave, ); if (result.error == null) { VRouter.of(context).toSegments(['rooms', result.result!]); @@ -1001,18 +1026,16 @@ class ChatController extends State { cancelLabel: L10n.of(context)!.cancel, ); if (response == OkCancelResult.ok) { - final events = room!.pinnedEventIds + final events = room.pinnedEventIds ..removeWhere((oldEvent) => oldEvent == eventId); showFutureLoadingDialog( context: context, - future: () => room!.setPinnedEvents(events), + future: () => room.setPinnedEvents(events), ); } } void pinEvent() { - final room = this.room; - if (room == null) return; final pinnedEventIds = room.pinnedEventIds; final selectedEventIds = selectedEvents.map((e) => e.eventId).toSet(); final unpin = selectedEventIds.length == 1 && @@ -1057,7 +1080,7 @@ class ChatController extends State { typingCoolDown = Timer(const Duration(seconds: 2), () { typingCoolDown = null; currentlyTyping = false; - room!.setTyping(false); + room.setTyping(false); }); typingTimeout ??= Timer(const Duration(seconds: 30), () { typingTimeout = null; @@ -1065,14 +1088,13 @@ class ChatController extends State { }); if (!currentlyTyping) { currentlyTyping = true; - room! - .setTyping(true, timeout: const Duration(seconds: 30).inMilliseconds); + room.setTyping(true, timeout: const Duration(seconds: 30).inMilliseconds); } setState(() => inputText = text); } bool get isArchived => - {Membership.leave, Membership.ban}.contains(room?.membership); + {Membership.leave, Membership.ban}.contains(room.membership); void showEventInfo([Event? event]) => (event ?? selectedEvents.single).showInfoDialog(context); @@ -1120,7 +1142,7 @@ class ChatController extends State { if (success.result != null) { final voipPlugin = Matrix.of(context).voipPlugin; try { - await voipPlugin!.voip.inviteToCall(room!.id, callType); + await voipPlugin!.voip.inviteToCall(room.id, callType); } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(e.toLocalizedString(context))), diff --git a/lib/pages/chat/chat_app_bar_title.dart b/lib/pages/chat/chat_app_bar_title.dart index c0bc1da8..23609f62 100644 --- a/lib/pages/chat/chat_app_bar_title.dart +++ b/lib/pages/chat/chat_app_bar_title.dart @@ -16,9 +16,6 @@ class ChatAppBarTitle extends StatelessWidget { @override Widget build(BuildContext context) { final room = controller.room; - if (room == null) { - return const SizedBox.shrink(); - } if (controller.selectedEvents.isNotEmpty) { return Text(controller.selectedEvents.length.toString()); } diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart index a0d70ae7..2c389d52 100644 --- a/lib/pages/chat/chat_input_row.dart +++ b/lib/pages/chat/chat_input_row.dart @@ -146,7 +146,7 @@ class ChatInputRow extends StatelessWidget { contentPadding: const EdgeInsets.all(0), ), ), - if (controller.room! + if (controller.room .getImagePacks(ImagePackUsage.sticker) .isNotEmpty) PopupMenuItem( @@ -227,7 +227,7 @@ class ChatInputRow extends StatelessWidget { child: Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: InputBar( - room: controller.room!, + room: controller.room, minLines: 1, maxLines: 8, autofocus: !PlatformInfos.isMobile, diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index a8e5ca85..65c0bafd 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -125,37 +125,26 @@ class ChatView extends StatelessWidget { } else { return [ if (Matrix.of(context).voipPlugin != null && - controller.room!.isDirectChat) + controller.room.isDirectChat) IconButton( onPressed: controller.onPhoneButtonTap, icon: const Icon(Icons.call_outlined), tooltip: L10n.of(context)!.placeCall, ), - EncryptionButton(controller.room!), - ChatSettingsPopupMenu(controller.room!, !controller.room!.isDirectChat), + EncryptionButton(controller.room), + ChatSettingsPopupMenu(controller.room, !controller.room.isDirectChat), ]; } } @override Widget build(BuildContext context) { - controller.room = controller.sendingClient.getRoomById(controller.roomId!); - controller.readMarkerEventId ??= controller.room!.fullyRead; - if (controller.room == null) { - return Scaffold( - appBar: AppBar( - title: Text(L10n.of(context)!.oopsSomethingWentWrong), - ), - body: Center( - child: Text(L10n.of(context)!.youAreNoLongerParticipatingInThisChat), - ), - ); - } + controller.readMarkerEventId ??= controller.room.fullyRead; - if (controller.room!.membership == Membership.invite) { + if (controller.room.membership == Membership.invite) { showFutureLoadingDialog( context: context, - future: () => controller.room!.join(), + future: () => controller.room.join(), ); } final bottomSheetPadding = FluffyThemes.isColumnMode(context) ? 16.0 : 8.0; @@ -175,7 +164,7 @@ class ChatView extends StatelessWidget { onTapDown: (_) => controller.setReadMarker(), behavior: HitTestBehavior.opaque, child: StreamBuilder( - stream: controller.room!.onUpdate.stream + stream: controller.room.onUpdate.stream .rateLimit(const Duration(seconds: 1)), builder: (context, snapshot) => FutureBuilder( future: controller.getTimeline(), @@ -196,7 +185,7 @@ class ChatView extends StatelessWidget { color: Theme.of(context).colorScheme.primary, ) : UnreadRoomsBadge( - filter: (r) => r.id != controller.roomId!, + filter: (r) => r.id != controller.roomId, badgePosition: BadgePosition.topEnd(end: 8, top: 4), child: const Center(child: BackButton()), ), @@ -269,8 +258,8 @@ class ChatView extends StatelessWidget { ), ), ), - if (controller.room!.canSendDefaultMessages && - controller.room!.membership == Membership.join) + if (controller.room.canSendDefaultMessages && + controller.room.membership == Membership.join) Container( margin: EdgeInsets.only( bottom: bottomSheetPadding, @@ -295,7 +284,7 @@ class ChatView extends StatelessWidget { Brightness.light ? Colors.white : Colors.black, - child: controller.room?.isAbandonedDMRoom == + child: controller.room.isAbandonedDMRoom == true ? Row( mainAxisAlignment: @@ -359,14 +348,14 @@ class ChatView extends StatelessWidget { child: FloatingActionButton.extended( icon: const Icon(Icons.arrow_upward_outlined), onPressed: () => controller - .scrollToEventId(controller.room!.fullyRead), + .scrollToEventId(controller.room.fullyRead), label: Row( mainAxisSize: MainAxisSize.min, children: [ Text(L10n.of(context)!.jumpToLastReadMessage), IconButton( onPressed: () => controller.setReadMarker( - eventId: controller.room!.fullyRead, + eventId: controller.room.fullyRead, ), icon: const Icon(Icons.close), ), diff --git a/lib/pages/chat/pinned_events.dart b/lib/pages/chat/pinned_events.dart index 210bcb76..35ec5d2d 100644 --- a/lib/pages/chat/pinned_events.dart +++ b/lib/pages/chat/pinned_events.dart @@ -46,14 +46,14 @@ class PinnedEvents extends StatelessWidget { @override Widget build(BuildContext context) { - final pinnedEventIds = controller.room!.pinnedEventIds; + final pinnedEventIds = controller.room.pinnedEventIds; if (pinnedEventIds.isEmpty) { return const SizedBox.shrink(); } final completers = pinnedEventIds.map>((e) { final completer = Completer(); - controller.room! + controller.room .getEventById(e) .then((value) => completer.complete(value)); return completer; @@ -86,11 +86,10 @@ class PinnedEvents extends StatelessWidget { color: Theme.of(context).colorScheme.onSurfaceVariant, icon: const Icon(Icons.push_pin), tooltip: L10n.of(context)!.unpin, - onPressed: controller.room - ?.canSendEvent(EventTypes.RoomPinnedEvents) ?? - false - ? () => controller.unpinEvent(event.eventId) - : null, + onPressed: + controller.room.canSendEvent(EventTypes.RoomPinnedEvents) + ? () => controller.unpinEvent(event.eventId) + : null, ), Expanded( child: Padding( diff --git a/lib/pages/chat/reactions_picker.dart b/lib/pages/chat/reactions_picker.dart index 2fc4e3f9..5ba28d5a 100644 --- a/lib/pages/chat/reactions_picker.dart +++ b/lib/pages/chat/reactions_picker.dart @@ -18,7 +18,7 @@ class ReactionsPicker extends StatelessWidget { if (controller.showEmojiPicker) return const SizedBox.shrink(); final display = controller.editEvent == null && controller.replyEvent == null && - controller.room!.canSendDefaultMessages && + controller.room.canSendDefaultMessages && controller.selectedEvents.isNotEmpty; return AnimatedContainer( duration: FluffyThemes.animationDuration, diff --git a/lib/pages/chat/seen_by_row.dart b/lib/pages/chat/seen_by_row.dart index 6875bddb..47bc4f9c 100644 --- a/lib/pages/chat/seen_by_row.dart +++ b/lib/pages/chat/seen_by_row.dart @@ -12,7 +12,7 @@ class SeenByRow extends StatelessWidget { @override Widget build(BuildContext context) { - final seenByUsers = controller.room!.getSeenByUsers(controller.timeline!); + final seenByUsers = controller.room.getSeenByUsers(controller.timeline!); const maxAvatars = 7; return Container( width: double.infinity, diff --git a/lib/pages/chat/tombstone_display.dart b/lib/pages/chat/tombstone_display.dart index 7604ea96..50609cfd 100644 --- a/lib/pages/chat/tombstone_display.dart +++ b/lib/pages/chat/tombstone_display.dart @@ -11,7 +11,7 @@ class TombstoneDisplay extends StatelessWidget { @override Widget build(BuildContext context) { - if (controller.room!.getState(EventTypes.RoomTombstone) == null) { + if (controller.room.getState(EventTypes.RoomTombstone) == null) { return const SizedBox.shrink(); } return SizedBox( @@ -26,7 +26,7 @@ class TombstoneDisplay extends StatelessWidget { child: const Icon(Icons.upgrade_outlined), ), title: Text( - controller.room! + controller.room .getState(EventTypes.RoomTombstone)! .parsedTombstoneContent .body, diff --git a/lib/pages/chat/typing_indicators.dart b/lib/pages/chat/typing_indicators.dart index 12bd11ad..3bb3f907 100644 --- a/lib/pages/chat/typing_indicators.dart +++ b/lib/pages/chat/typing_indicators.dart @@ -12,7 +12,7 @@ class TypingIndicators extends StatelessWidget { @override Widget build(BuildContext context) { - final typingUsers = controller.room!.typingUsers + final typingUsers = controller.room.typingUsers ..removeWhere((u) => u.stateKey == Matrix.of(context).client.userID); const topPadding = 20.0; const bottomPadding = 4.0;