From f3e09da441b5d75ff82855fd386fa03e7876a255 Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Mon, 17 Jul 2023 23:58:41 +0700 Subject: [PATCH 1/4] TW-218: Create `ForwardMessageInteractor` to handle forward message actions --- .../forward/forward_message_state.dart | 38 +++++++++ .../forward/forward_message_interactor.dart | 79 +++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 lib/domain/app_state/forward/forward_message_state.dart create mode 100644 lib/domain/usecase/forward/forward_message_interactor.dart diff --git a/lib/domain/app_state/forward/forward_message_state.dart b/lib/domain/app_state/forward/forward_message_state.dart new file mode 100644 index 0000000000..77029de0a1 --- /dev/null +++ b/lib/domain/app_state/forward/forward_message_state.dart @@ -0,0 +1,38 @@ +import 'package:fluffychat/app_state/failure.dart'; +import 'package:fluffychat/app_state/success.dart'; +import 'package:matrix/matrix.dart'; + +class ForwardMessageLoading extends Success { + @override + List get props => []; +} + +class ForwardMessageSuccess extends Success { + final Room room; + + const ForwardMessageSuccess(this.room); + @override + List get props => [room]; +} + +class ForwardMessageFailed extends Failure { + final dynamic exception; + + const ForwardMessageFailed({required this.exception}); + + @override + List get props => [exception]; +} + +class ForwardMessageIsShareFileState extends Success { + final MatrixFile shareFile; + final Room room; + + const ForwardMessageIsShareFileState({ + required this.shareFile, + required this.room + }); + + @override + List get props => [shareFile, room]; +} diff --git a/lib/domain/usecase/forward/forward_message_interactor.dart b/lib/domain/usecase/forward/forward_message_interactor.dart new file mode 100644 index 0000000000..f043f80527 --- /dev/null +++ b/lib/domain/usecase/forward/forward_message_interactor.dart @@ -0,0 +1,79 @@ + + +import 'package:dartz/dartz.dart'; +import 'package:fluffychat/app_state/failure.dart'; +import 'package:fluffychat/app_state/success.dart'; +import 'package:fluffychat/domain/app_state/forward/forward_message_state.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:matrix/matrix.dart'; + +class ForwardMessageInteractor { + Stream> execute({ + required List rooms, + required List selectedEvents, + required MatrixState matrixState, + }) async* { + try { + yield Right(ForwardMessageLoading()); + + final room = rooms.firstWhere((element) => element.id == selectedEvents.first); + if (room.membership == Membership.join) { + if (matrixState.shareContentList.isEmpty && matrixState.shareContent != null) { + yield* _forwardOneMessageAction(room: room, matrixState: matrixState); + } else { + yield* _forwardMoreMessageAction(room: room, matrixState: matrixState); + } + } + } catch (exception) { + yield Left(ForwardMessageFailed(exception: exception)); + } + } + + Stream> _forwardMessage(Map message, Room room) async* { + final shareFile = message.tryGet('file'); + if (message.tryGet('msgtype') == 'chat.fluffy.shared_file' && shareFile != null) { + yield Right(ForwardMessageIsShareFileState(shareFile: shareFile, room: room)); + } + await room.sendEvent(message); + } + + Stream> _forwardOneMessageAction({ + required Room room, + required MatrixState matrixState, + }) async* { + try { + final message = matrixState.shareContent; + if (message != null) { + yield* _forwardMessage(message, room); + matrixState.shareContent?.clear(); + } + yield Right(ForwardMessageSuccess(room)); + } catch (exception) { + yield Left(ForwardMessageFailed(exception: exception)); + } + } + + Stream> _forwardMoreMessageAction({ + required Room room, + required MatrixState matrixState, + }) async* { + try { + yield Right(ForwardMessageLoading()); + + final messages = matrixState.shareContentList; + if (messages.isNotEmpty) { + for (final message in messages) { + if (message != null) { + yield* _forwardMessage(message, room); + } else { + continue; + } + } + matrixState.shareContentList.clear(); + } + yield Right(ForwardMessageSuccess(room)); + } catch (exception) { + yield Left(ForwardMessageFailed(exception: exception)); + } + } +} \ No newline at end of file From 38ea58ad809e57e80bda1af80a1a6fa461d702a5 Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Mon, 17 Jul 2023 23:59:36 +0700 Subject: [PATCH 2/4] TW-218: Create `ForwardDI` to register `ForwardMessageInteractor` --- lib/config/routes.dart | 66 ++++++++++++++++++++-------------- lib/di/forward/forward_di.dart | 18 ++++++++++ 2 files changed, 58 insertions(+), 26 deletions(-) create mode 100644 lib/di/forward/forward_di.dart diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 579af5ce84..cdbdb73e08 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -1,6 +1,7 @@ import 'package:fluffychat/di/chat/chat_di.dart'; import 'package:fluffychat/di/contact/contact_di.dart'; import 'package:fluffychat/di/create_direct_chat/create_direct_chat_di.dart'; +import 'package:fluffychat/di/forward/forward_di.dart'; import 'package:fluffychat/pages/add_story/add_story.dart'; import 'package:fluffychat/pages/archive/archive.dart'; import 'package:fluffychat/pages/chat/chat.dart'; @@ -13,6 +14,7 @@ import 'package:fluffychat/pages/chat_permissions_settings/chat_permissions_sett import 'package:fluffychat/pages/connect/connect_page.dart'; import 'package:fluffychat/pages/contacts_tab/contacts_tab.dart'; import 'package:fluffychat/pages/device_settings/device_settings.dart'; +import 'package:fluffychat/pages/forward/forward.dart'; import 'package:fluffychat/pages/homeserver_picker/homeserver_picker.dart'; import 'package:fluffychat/pages/invitation_selection/invitation_selection.dart'; import 'package:fluffychat/pages/login/login.dart'; @@ -141,6 +143,12 @@ class AppRoutes { stackedRoutes: _chatDetailsRoutes, buildTransition: rightToLeftTransition, ), + VWidgetWithDependency( + path: 'forward', + widget: const Forward(), + di: ForwardDI(), + buildTransition: rightToLeftTransition, + ) ], ), VWidget( @@ -356,32 +364,38 @@ class AppRoutes { ]; List get _chatDetailsRoutes => [ - VWidget( - path: 'permissions', - widget: const ChatPermissionsSettings(), - buildTransition: _dynamicTransition, - ), - VWidget( - path: 'invite', - widget: const InvitationSelection(), - buildTransition: _dynamicTransition, - ), - VWidget( - path: 'multiple_emotes', - widget: const MultipleEmotesSettings(), - buildTransition: _dynamicTransition, - ), - VWidget( - path: 'emotes', - widget: const EmotesSettings(), - buildTransition: _dynamicTransition, - ), - VWidget( - path: 'emotes/:state_key', - widget: const EmotesSettings(), - buildTransition: _dynamicTransition, - ), - ]; + VWidget( + path: 'permissions', + widget: const ChatPermissionsSettings(), + buildTransition: _dynamicTransition, + ), + VWidget( + path: 'invite', + widget: const InvitationSelection(), + buildTransition: _dynamicTransition, + ), + VWidget( + path: 'multiple_emotes', + widget: const MultipleEmotesSettings(), + buildTransition: _dynamicTransition, + ), + VWidget( + path: 'emotes', + widget: const EmotesSettings(), + buildTransition: _dynamicTransition, + ), + VWidget( + path: 'emotes/:state_key', + widget: const EmotesSettings(), + buildTransition: _dynamicTransition, + ), + VWidgetWithDependency( + path: 'forward', + widget: const Forward(), + di: ForwardDI(), + buildTransition: _fadeTransition, + ) + ]; List get _settingsRoutes => [ VWidget( diff --git a/lib/di/forward/forward_di.dart b/lib/di/forward/forward_di.dart new file mode 100644 index 0000000000..e79749e7cf --- /dev/null +++ b/lib/di/forward/forward_di.dart @@ -0,0 +1,18 @@ +import 'package:fluffychat/di/base_di.dart'; +import 'package:fluffychat/domain/usecase/forward/forward_message_interactor.dart'; +import 'package:get_it/get_it.dart'; +import 'package:matrix/matrix.dart'; + +class ForwardDI extends BaseDI { + @override + String get scopeName => 'Create Forward'; + + @override + void setUp(GetIt get) { + Logs().d('ForwardDI::setUp()'); + + get.registerSingleton(ForwardMessageInteractor()); + + Logs().d('ForwardDI::setUp() - done'); + } +} \ No newline at end of file From 54607a768ef867b45dfc95a24b971b402a2d61a2 Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Tue, 18 Jul 2023 00:00:33 +0700 Subject: [PATCH 3/4] TW-218: Change `Forward()` dialog to `Forward()` VRoute --- lib/pages/chat/chat.dart | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index 04a6a70c87..a10cb8e3f6 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -8,6 +8,9 @@ import 'package:fluffychat/domain/model/download_file/download_file_for_preview_ import 'package:fluffychat/domain/model/preview_file/document_uti.dart'; import 'package:fluffychat/domain/model/preview_file/supported_preview_file_types.dart'; import 'package:fluffychat/domain/usecase/download_file_for_preview_interactor.dart'; +import 'package:fluffychat/domain/usecase/send_image_interactor.dart'; +import 'package:fluffychat/domain/usecase/send_images_interactor.dart'; +import 'package:fluffychat/pages/chat/chat_actions.dart'; import 'package:fluffychat/pages/forward/forward.dart'; import 'package:fluffychat/presentation/mixin/image_picker_mixin.dart'; import 'package:fluffychat/presentation/mixin/send_files_mixin.dart'; @@ -743,12 +746,7 @@ class ChatController extends State with ImagePickerMixin, SendFilesMixin { Logs().d("forwardEventsAction():: shareContentList: ${Matrix.of(context).shareContentList}"); } setState(() => selectedEvents.clear()); - await showDialog( - context: context, - useSafeArea: false, - useRootNavigator: false, - builder: (c) => const Forward(), - ); + VRouter.of(context).toSegments(['rooms', room!.id, 'forward']); } void sendAgainAction() { From 712b0b756f9c3532009a4a86ff61eb8b9873ab06 Mon Sep 17 00:00:00 2001 From: HuyNguyen Date: Tue, 18 Jul 2023 00:01:22 +0700 Subject: [PATCH 4/4] TW-218: Add loading bottom when click on forward message --- lib/config/routes.dart | 3 +- .../forward/forward_message_interactor.dart | 10 +- lib/pages/forward/forward.dart | 110 +++++++++++------- lib/pages/forward/forward_view.dart | 69 +++++++---- lib/widgets/twake_components/twake_fab.dart | 29 +++-- 5 files changed, 139 insertions(+), 82 deletions(-) diff --git a/lib/config/routes.dart b/lib/config/routes.dart index cdbdb73e08..85242ec24e 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -38,6 +38,7 @@ import 'package:fluffychat/widgets/layouts/side_view_layout.dart'; import 'package:fluffychat/widgets/layouts/two_column_layout.dart'; import 'package:fluffychat/widgets/log_view.dart'; import 'package:fluffychat/widgets/vwidget_with_dependencies.dart'; +import 'package:fluffychat/widgets/vwidget_with_dependency.dart'; import 'package:flutter/material.dart'; import 'package:vrouter/vrouter.dart'; @@ -393,7 +394,7 @@ class AppRoutes { path: 'forward', widget: const Forward(), di: ForwardDI(), - buildTransition: _fadeTransition, + buildTransition: _leftToRightTransition, ) ]; diff --git a/lib/domain/usecase/forward/forward_message_interactor.dart b/lib/domain/usecase/forward/forward_message_interactor.dart index f043f80527..c08374ab73 100644 --- a/lib/domain/usecase/forward/forward_message_interactor.dart +++ b/lib/domain/usecase/forward/forward_message_interactor.dart @@ -1,5 +1,3 @@ - - import 'package:dartz/dartz.dart'; import 'package:fluffychat/app_state/failure.dart'; import 'package:fluffychat/app_state/success.dart'; @@ -21,7 +19,7 @@ class ForwardMessageInteractor { if (matrixState.shareContentList.isEmpty && matrixState.shareContent != null) { yield* _forwardOneMessageAction(room: room, matrixState: matrixState); } else { - yield* _forwardMoreMessageAction(room: room, matrixState: matrixState); + yield* _forwardMultipleMessagesAction(room: room, matrixState: matrixState); } } } catch (exception) { @@ -45,7 +43,7 @@ class ForwardMessageInteractor { final message = matrixState.shareContent; if (message != null) { yield* _forwardMessage(message, room); - matrixState.shareContent?.clear(); + message.clear(); } yield Right(ForwardMessageSuccess(room)); } catch (exception) { @@ -53,7 +51,7 @@ class ForwardMessageInteractor { } } - Stream> _forwardMoreMessageAction({ + Stream> _forwardMultipleMessagesAction({ required Room room, required MatrixState matrixState, }) async* { @@ -69,7 +67,7 @@ class ForwardMessageInteractor { continue; } } - matrixState.shareContentList.clear(); + messages.clear(); } yield Right(ForwardMessageSuccess(room)); } catch (exception) { diff --git a/lib/pages/forward/forward.dart b/lib/pages/forward/forward.dart index 61580a0c55..772187d641 100644 --- a/lib/pages/forward/forward.dart +++ b/lib/pages/forward/forward.dart @@ -1,3 +1,10 @@ +import 'dart:async'; +import 'package:dartz/dartz.dart' hide State; +import 'package:fluffychat/app_state/failure.dart'; +import 'package:fluffychat/app_state/success.dart'; +import 'package:fluffychat/di/global/get_it_initializer.dart'; +import 'package:fluffychat/domain/app_state/forward/forward_message_state.dart'; +import 'package:fluffychat/domain/usecase/forward/forward_message_interactor.dart'; import 'package:fluffychat/pages/chat/send_file_dialog.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pages/forward/forward_view.dart'; @@ -20,6 +27,12 @@ class Forward extends StatefulWidget { class ForwardController extends State { + final _forwardMessageInteractor = getIt.get(); + + final forwardMessageNotifier = ValueNotifier?>(null); + + StreamSubscription? forwardMessageInteractorStreamSubscription; + List? rooms; Timeline? timeline; @@ -34,6 +47,18 @@ class ForwardController extends State { String? get activeChat => VRouter.of(context).pathParameters['roomid']; + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + forwardListController.dispose(); + forwardMessageInteractorStreamSubscription?.cancel(); + super.dispose(); + } + void onSelectChat(String id) { if (selectedEvents.contains(id)) { setState( @@ -73,56 +98,53 @@ class ForwardController extends State { .where(getRoomFilterByActiveFilter(_activeFilterAllChats)) .toList(); - - Future _forwardMessage(Map message, Room room) async { - final shareFile = message.tryGet('file'); - if (message.tryGet('msgtype') == 'chat.fluffy.shared_file' && shareFile != null) { - await showDialog( - context: context, - useRootNavigator: false, - builder: (c) => SendFileDialog( - files: [shareFile], - room: room) - ); - } else { - await room.sendEvent(message); - } - } - - void _forwardOneMessageAction(BuildContext context, Room room) async { - final message = Matrix.of(context).shareContent; - if (message != null) { - await _forwardMessage(message, room); - Matrix.of(context).shareContent = null; - } - VRouter.of(context).toSegments(['rooms', room.id]); + void forwardAction(BuildContext context) async { + forwardMessageInteractorStreamSubscription = _forwardMessageInteractor.execute( + rooms: filteredRoomsForAll, + selectedEvents: selectedEvents, + matrixState: Matrix.of(context) + ).listen( + (event) => _handleForwardMessageOnData(context, event), + onDone: _handleForwardMessageOnDone, + onError: _handleForwardMessageOnError, + ); } - void _forwardMoreMessageAction(BuildContext context, Room room) async { - final messages = Matrix.of(context).shareContentList; - if (messages.isNotEmpty) { - for (final message in messages) { - if (message != null) { - await _forwardMessage(message, room); - } else { - continue; + void _handleForwardMessageOnData(BuildContext context, Either event) { + Logs().d('ForwardController::_handleForwardMessageOnData()'); + forwardMessageNotifier.value = event; + event.fold( + (failure) { + Logs().e('ForwardController::_handleForwardMessageOnData() - failure: $failure'); + }, + (success) async { + Logs().d('ForwardController::_handleForwardMessageOnData() - success: $success'); + switch (success.runtimeType) { + case ForwardMessageSuccess: + final dataOnSuccess = success as ForwardMessageSuccess; + VRouter.of(context).toSegments(['rooms', dataOnSuccess.room.id]); + break; + case ForwardMessageIsShareFileState: + final dataOnSuccess = success as ForwardMessageIsShareFileState; + await showDialog( + context: context, + useRootNavigator: false, + builder: (c) => SendFileDialog( + files: [dataOnSuccess.shareFile], + room: dataOnSuccess.room) + ); + break; } } - Matrix.of(context).shareContentList.clear(); - } - VRouter.of(context).toSegments(['rooms', room.id]); + ); } - void forwardAction(BuildContext context) async { - final rooms = filteredRoomsForAll; - final room = rooms.firstWhere((element) => element.id == selectedEvents.first); - if (room.membership == Membership.join) { - if (Matrix.of(context).shareContentList.isEmpty && Matrix.of(context).shareContent != null) { - _forwardOneMessageAction(context, room); - } else { - _forwardMoreMessageAction(context, room); - } - } + void _handleForwardMessageOnDone() { + Logs().d('ForwardController::_handleForwardMessageOnDone()'); + } + + void _handleForwardMessageOnError(dynamic error, StackTrace? stackTrace) { + Logs().e('ForwardController::_handleForwardMessageOnError() - error: $error | stackTrace: $stackTrace'); } @override diff --git a/lib/pages/forward/forward_view.dart b/lib/pages/forward/forward_view.dart index 629f4670c0..03aa61af85 100644 --- a/lib/pages/forward/forward_view.dart +++ b/lib/pages/forward/forward_view.dart @@ -1,8 +1,12 @@ - +import 'package:dartz/dartz.dart' hide State; +import 'package:fluffychat/app_state/failure.dart'; +import 'package:fluffychat/app_state/success.dart'; +import 'package:fluffychat/domain/app_state/forward/forward_message_state.dart'; import 'package:fluffychat/pages/chat/chat_app_bar_title_style.dart'; import 'package:fluffychat/pages/forward/forward.dart'; import 'package:fluffychat/pages/forward/forward_item.dart'; import 'package:fluffychat/pages/forward/forward_view_style.dart'; +import 'package:fluffychat/widgets/twake_components/twake_fab.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:fluffychat/resource/image_paths.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -20,7 +24,7 @@ class ForwardView extends StatefulWidget { } class _ForwardViewState extends State { - bool isShowRecentlyChats = false; + bool isShowRecentlyChats = true; bool isSearchBarShow = false; void _toggleRecentlyChats() { @@ -53,24 +57,49 @@ class _ForwardViewState extends State { Widget _buildBottomBar() { if (widget.controller.selectedEvents.length == 1) { - return SizedBox( - height: ForwardViewStyle.bottomBarHeight, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.only(right: 14), - child: TwakeIconButton( - size: ForwardViewStyle.iconSendSize, - onPressed: () => widget.controller.forwardAction(context), - tooltip: L10n.of(context)!.send, - imagePath: ImagePaths.icSend, - ), + return ValueListenableBuilder?>( + valueListenable: widget.controller.forwardMessageNotifier, + builder: (context, forwardMessageNotifier, child) { + if (forwardMessageNotifier == null) { + return child!; + } else { + return forwardMessageNotifier.fold( + (failure) => child!, + (success) { + if (success is ForwardMessageLoading) { + return SizedBox( + height: ForwardViewStyle.bottomBarHeight, + child: const Align( + alignment: Alignment.centerRight, + child: Padding( + padding: EdgeInsets.only(right: 14), + child: TwakeFloatingActionButton( + customIcon: SizedBox(child: CircularProgressIndicator()) + ), + ), + ), + ); + } else { + return const SizedBox(); + } + } + ); + } + }, + child: SizedBox( + height: ForwardViewStyle.bottomBarHeight, + child: Align( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.only(right: 14), + child: TwakeIconButton( + size: ForwardViewStyle.iconSendSize, + onPressed: () => widget.controller.forwardAction(context), + tooltip: L10n.of(context)!.send, + imagePath: ImagePaths.icSend, ), ), - ], + ), ), ); } else { @@ -132,8 +161,8 @@ class _ForwardViewState extends State { bottom: PreferredSize( preferredSize: const Size(double.infinity, 4), child: Container( - color: Theme.of(context).colorScheme.surfaceTint.withOpacity(0.08), - height: 1)), + color: Theme.of(context).colorScheme.surfaceTint.withOpacity(0.08), + height: 1)), ); } diff --git a/lib/widgets/twake_components/twake_fab.dart b/lib/widgets/twake_components/twake_fab.dart index 44bd5143c1..315fa4ebd8 100644 --- a/lib/widgets/twake_components/twake_fab.dart +++ b/lib/widgets/twake_components/twake_fab.dart @@ -50,21 +50,28 @@ class TwakeFloatingActionButton extends StatelessWidget { onTap: onTap, child: Padding( padding: const EdgeInsets.all(16.0), - child: icon != null - ? Icon( - icon, - size: size, - color: Theme.of(context).colorScheme.onPrimaryContainer) - : customIcon != null - ? SizedBox( - width: 24.0, - height: 24.0, - child: customIcon) - : imagePath != null ? SvgPicture.asset(imagePath!) : null, + child: _buildBottomWidget(context) ), ), ), ); } + Widget _buildBottomWidget(BuildContext context) { + if (icon != null) { + return Icon( + icon, + size: size, + color: Theme.of(context).colorScheme.onPrimaryContainer); + } else if (customIcon != null) { + return SizedBox( + width: 24.0, + height: 24.0, + child: customIcon); + } else if (imagePath != null) { + return SvgPicture.asset(imagePath!); + } else { + return const SizedBox(); + } + } } \ No newline at end of file