diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 6eb061a6..b932749a 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -94,6 +94,13 @@ class AppRoutes { VWidget( path: '/archive', widget: const Archive(), + stackedRoutes: [ + VWidget( + path: ':roomid', + widget: const Chat(), + buildTransition: _dynamicTransition, + ), + ], ), VWidget( path: '/newprivatechat', @@ -220,13 +227,25 @@ class AppRoutes { ), ], ), - VWidget( + VNester( path: '/archive', - widget: const TwoColumnLayout( - mainView: Archive(), - sideView: EmptyPage(), + widgetBuilder: (child) => TwoColumnLayout( + mainView: const Archive(), + sideView: child, ), buildTransition: _fadeTransition, + nestedRoutes: [ + VWidget( + path: '', + widget: const EmptyPage(), + buildTransition: _dynamicTransition, + ), + VWidget( + path: ':roomid', + widget: const Chat(), + buildTransition: _dynamicTransition, + ), + ], ), ], ), diff --git a/lib/pages/archive/archive.dart b/lib/pages/archive/archive.dart index 4872c294..be1a975c 100644 --- a/lib/pages/archive/archive.dart +++ b/lib/pages/archive/archive.dart @@ -21,11 +21,9 @@ class ArchiveController extends State { Future> getArchive(BuildContext context) async { final archive = this.archive; if (archive != null) return archive; - return await Matrix.of(context).client.loadArchive(); + return this.archive = await Matrix.of(context).client.loadArchive(); } - void forgetAction(int i) => setState(() => archive?.removeAt(i)); - void forgetAllAction() async { final archive = this.archive; if (archive == null) return; diff --git a/lib/pages/archive/archive_view.dart b/lib/pages/archive/archive_view.dart index ea429547..c19f7459 100644 --- a/lib/pages/archive/archive_view.dart +++ b/lib/pages/archive/archive_view.dart @@ -21,10 +21,14 @@ class ArchiveView extends StatelessWidget { leading: const BackButton(), title: Text(L10n.of(context)!.archive), actions: [ - if (snapshot.hasData && archive != null && archive!.isNotEmpty) - TextButton( - onPressed: controller.forgetAllAction, - child: Text(L10n.of(context)!.clearArchive), + if (snapshot.data?.isNotEmpty ?? false) + Padding( + padding: const EdgeInsets.all(8.0), + child: TextButton.icon( + onPressed: controller.forgetAllAction, + label: Text(L10n.of(context)!.clearArchive), + icon: const Icon(Icons.cleaning_services_outlined), + ), ) ], ), @@ -50,7 +54,6 @@ class ArchiveView extends StatelessWidget { itemCount: archive!.length, itemBuilder: (BuildContext context, int i) => ChatListItem( archive![i], - onForget: controller.forgetAction, ), ); } diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 5bf0adf8..b104218e 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -630,6 +630,7 @@ class ChatController extends State { } bool get canRedactSelectedEvents { + if (isArchived) return false; final clients = matrix!.currentBundle; for (final event in selectedEvents) { if (event.canRedact == false && @@ -639,7 +640,9 @@ class ChatController extends State { } bool get canEditSelectedEvents { - if (selectedEvents.length != 1 || !selectedEvents.first.status.isSent) { + if (isArchived || + selectedEvents.length != 1 || + !selectedEvents.first.status.isSent) { return false; } return currentRoomBundle @@ -757,6 +760,15 @@ class ChatController extends State { return sendEmojiAction(emoji.emoji); } + void forgetRoom() async { + final result = await showFutureLoadingDialog( + context: context, + future: room!.forget, + ); + if (result.error != null) return; + VRouter.of(context).to('/archive'); + } + void typeEmoji(Emoji? emoji) { if (emoji == null) return; final text = sendController.text; @@ -1008,6 +1020,9 @@ class ChatController extends State { setState(() => inputText = text); } + bool get isArchived => + {Membership.leave, Membership.ban}.contains(room?.membership); + void showEventInfo([Event? event]) => (event ?? selectedEvents.single).showInfoDialog(context); diff --git a/lib/pages/chat/chat_app_bar_title.dart b/lib/pages/chat/chat_app_bar_title.dart index 5905a49b..7dffe860 100644 --- a/lib/pages/chat/chat_app_bar_title.dart +++ b/lib/pages/chat/chat_app_bar_title.dart @@ -37,7 +37,10 @@ class ChatAppBarTitle extends StatelessWidget { '${room.unsafeGetUserFromMemoryOrFallback(directChatMatrixID).mention} ', ), ) - : () => VRouter.of(context).toSegments(['rooms', room.id, 'details']), + : controller.isArchived + ? null + : () => + VRouter.of(context).toSegments(['rooms', room.id, 'details']), child: Row( children: [ Hero( diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index c73f4fe6..6c53f79e 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -108,6 +108,20 @@ class ChatView extends StatelessWidget { ], ), ]; + } else if (controller.isArchived) { + return [ + Padding( + padding: const EdgeInsets.all(8.0), + child: TextButton.icon( + onPressed: controller.forgetRoom, + style: TextButton.styleFrom( + foregroundColor: Theme.of(context).colorScheme.error, + ), + icon: const Icon(Icons.delete_forever_outlined), + label: Text(L10n.of(context)!.delete), + ), + ) + ]; } else { return [ if (Matrix.of(context).voipPlugin != null && diff --git a/lib/pages/chat/pinned_events.dart b/lib/pages/chat/pinned_events.dart index 5f8b8e16..9618ead7 100644 --- a/lib/pages/chat/pinned_events.dart +++ b/lib/pages/chat/pinned_events.dart @@ -21,10 +21,11 @@ class PinnedEvents extends StatelessWidget { BuildContext context, List events) async { final eventId = events.length == 1 ? events.single?.eventId - : await showModalActionSheet( + : await showConfirmationDialog( context: context, + title: L10n.of(context)!.pinMessage, actions: events - .map((event) => SheetAction( + .map((event) => AlertDialogAction( key: event?.eventId ?? '', label: event?.calcLocalizedBodyFallback( MatrixLocals(L10n.of(context)!), diff --git a/lib/pages/chat_list/chat_list_item.dart b/lib/pages/chat_list/chat_list_item.dart index 79f35ea6..fd62cef7 100644 --- a/lib/pages/chat_list/chat_list_item.dart +++ b/lib/pages/chat_list/chat_list_item.dart @@ -21,7 +21,6 @@ class ChatListItem extends StatelessWidget { final Room room; final bool activeChat; final bool selected; - final Function? onForget; final void Function()? onTap; final void Function()? onLongPress; @@ -31,7 +30,6 @@ class ChatListItem extends StatelessWidget { this.selected = false, this.onTap, this.onLongPress, - this.onForget, Key? key, }) : super(key: key); @@ -62,35 +60,7 @@ class ChatListItem extends StatelessWidget { } if (room.membership == Membership.leave) { - final action = await showModalActionSheet( - context: context, - title: L10n.of(context)!.archivedRoom, - message: L10n.of(context)!.thisRoomHasBeenArchived, - actions: [ - SheetAction( - label: L10n.of(context)!.rejoin, - key: ArchivedRoomAction.rejoin, - ), - SheetAction( - label: L10n.of(context)!.delete, - key: ArchivedRoomAction.delete, - isDestructiveAction: true, - ), - ], - ); - if (action != null) { - switch (action) { - case ArchivedRoomAction.delete: - await archiveAction(context); - break; - case ArchivedRoomAction.rejoin: - await showFutureLoadingDialog( - context: context, - future: () => room.join(), - ); - break; - } - } + VRouter.of(context).toSegments(['archive', room.id]); } if (room.membership == Membership.join) { @@ -122,13 +92,10 @@ class ChatListItem extends StatelessWidget { Future archiveAction(BuildContext context) async { { if ([Membership.leave, Membership.ban].contains(room.membership)) { - final success = await showFutureLoadingDialog( + await showFutureLoadingDialog( context: context, future: () => room.forget(), ); - if (success.error == null) { - if (onForget != null) onForget!(); - } return; } final confirmed = await showOkCancelAlertDialog( diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart index fac07a11..c2a6049a 100644 --- a/lib/pages/chat_list/client_chooser_button.dart +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -66,6 +66,16 @@ class ClientChooserButton extends StatelessWidget { ], ), ), + PopupMenuItem( + value: SettingsAction.archive, + child: Row( + children: [ + const Icon(Icons.archive_outlined), + const SizedBox(width: 18), + Text(L10n.of(context)!.archive), + ], + ), + ), PopupMenuItem( value: SettingsAction.settings, child: Row( @@ -259,6 +269,9 @@ class ClientChooserButton extends StatelessWidget { case SettingsAction.settings: VRouter.of(context).to('/settings'); break; + case SettingsAction.archive: + VRouter.of(context).to('/archive'); + break; } } } @@ -336,4 +349,5 @@ enum SettingsAction { newSpace, invite, settings, + archive, } diff --git a/lib/utils/adaptive_bottom_sheet.dart b/lib/utils/adaptive_bottom_sheet.dart index c075b0ee..bfedb095 100644 --- a/lib/utils/adaptive_bottom_sheet.dart +++ b/lib/utils/adaptive_bottom_sheet.dart @@ -8,12 +8,14 @@ Future showAdaptiveBottomSheet({ required BuildContext context, required Widget Function(BuildContext) builder, bool isDismissible = true, + bool isScrollControlled = false, }) => showModalBottomSheet( context: context, builder: builder, useRootNavigator: !PlatformInfos.isMobile, isDismissible: isDismissible, + isScrollControlled: isScrollControlled, constraints: BoxConstraints( maxHeight: MediaQuery.of(context).size.height - 128, maxWidth: FluffyThemes.columnWidth * 1.5, diff --git a/lib/widgets/permission_slider_dialog.dart b/lib/widgets/permission_slider_dialog.dart index d2b1991b..bc7dafa1 100644 --- a/lib/widgets/permission_slider_dialog.dart +++ b/lib/widgets/permission_slider_dialog.dart @@ -29,12 +29,12 @@ extension on PermissionLevel { Future showPermissionChooser(BuildContext context, {int currentLevel = 0}) async { - final permissionLevel = await showModalActionSheet( + final permissionLevel = await showConfirmationDialog( context: context, title: L10n.of(context)!.setPermissionsLevel, actions: PermissionLevel.values .map( - (level) => SheetAction( + (level) => AlertDialogAction( key: level, label: level.toLocalizedString(context), ),