diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index fcebc5a6d..c00458d3d 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -65,6 +65,7 @@ import 'package:tmail_ui_user/features/composer/presentation/extensions/get_sent import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_message_failure_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/list_identities_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/list_shared_media_file_extension.dart'; +import 'package:tmail_ui_user/features/composer/presentation/extensions/update_screen_display_mode_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/mixin/drag_drog_file_mixin.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/create_email_request.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/draggable_email_address.dart'; @@ -1616,6 +1617,7 @@ class ComposerController extends BaseController richTextWebController!.editorController.setText(textCurrent); } screenDisplayMode.value = displayMode; + updateDisplayModeForComposerQueue(displayMode); await Future.delayed( const Duration(milliseconds: 300), diff --git a/lib/features/composer/presentation/composer_overlay_view.dart b/lib/features/composer/presentation/composer_overlay_view.dart index ea070bf17..8cb857268 100644 --- a/lib/features/composer/presentation/composer_overlay_view.dart +++ b/lib/features/composer/presentation/composer_overlay_view.dart @@ -1,13 +1,9 @@ -import 'dart:math'; - -import 'package:collection/collection.dart'; import 'package:core/presentation/resources/image_paths.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/composer/presentation/extensions/update_screen_display_mode_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/manager/composer_manager.dart'; -import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart'; -import 'package:tmail_ui_user/features/composer/presentation/utils/composer_utils.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/expand_composer_button.dart'; class ComposerOverlayView extends StatefulWidget { @@ -18,93 +14,52 @@ class ComposerOverlayView extends StatefulWidget { } class _ComposerOverlayViewState extends State { + late final ComposerManager _composerManager; + late final ImagePaths _imagePaths; - final ComposerManager _composerManager = Get.find(); - final ImagePaths _imagePaths = Get.find(); + @override + void initState() { + super.initState(); + _composerManager = Get.find(); + _imagePaths = Get.find(); + } @override Widget build(BuildContext context) { return Obx(() { final composers = _composerManager.composers; - if (composers.isEmpty) return const SizedBox.shrink(); + final composerIdsQueue = _composerManager.composerIdsQueue; + if (composers.isEmpty || composerIdsQueue.isEmpty) return const SizedBox.shrink(); - if (composers.length == 1) { - return _composerManager.getComposerView(_composerManager.singleComposerId); + final fullScreenComposerView = _composerManager.firstFullScreenComposerView; + if (fullScreenComposerView != null) return fullScreenComposerView; + + if (composerIdsQueue.length == 1) { + return _composerManager.getComposerView(composerIdsQueue.first); } + final countComposerHidden = _composerManager.countComposerHidden; + final visibleComposers = composerIdsQueue + .map(_composerManager.getComposerView) + .where((view) => !view.controller.isHiddenScreen) + .toList(); + return Align( alignment: AlignmentDirectional.bottomEnd, child: Padding( - padding: const EdgeInsetsDirectional.symmetric( - horizontal: 24, - vertical: 16, - ), - child: LayoutBuilder( - builder: (_, constraints) { - final availableWidth = constraints.maxWidth - ComposerUtils.composerExpandMoreButtonMaxWidth; - - final composerIds = _composerManager.composerIdsQueue.toList(); - final composerViews = composerIds - .map((id) => _composerManager.getComposerView(id)) - .toList(); - - final countNormalComposer = composerViews.where( - (view) => view.controller.screenDisplayMode.value == ScreenDisplayMode.normal, - ).length; - - final maxNormalComposer = ComposerUtils.calculateNormalWidgets(availableWidth); - final countDisplayNormalComposer = min( - countNormalComposer, - maxNormalComposer, - ); - - final countDisplayComposer = countDisplayNormalComposer > 0 - ? ComposerUtils.calculateNormalAndMinimizeWidgets(availableWidth, countDisplayNormalComposer) - : ComposerUtils.calculateMinimizeWidgets(availableWidth); - - if (countDisplayComposer < 1) return const SizedBox.shrink(); - - Iterable composerIdsDisplayed = []; - if (composerIds.length > countDisplayComposer) { - composerIdsDisplayed = composerIds - .reversed - .take(countDisplayComposer) - .toList() - .reversed; - } else { - composerIdsDisplayed = composerIds; - } - - final countMinimizeComposer = - composerIdsDisplayed.length - countDisplayNormalComposer; - - int countComposerHidden = composerIds.length - countDisplayComposer; - - return Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - if (countComposerHidden > 0) - ExpandComposerButton( - imagePaths: _imagePaths, - countComposerHidden: countComposerHidden, - onToggleDisplayComposerAction: () {}, - ), - ...composerIdsDisplayed.mapIndexed((index, id) { - final composerView = _composerManager.getComposerView(id); - final isMinimized = countDisplayNormalComposer < 1 || - (countMinimizeComposer > 0 && index < countMinimizeComposer); - - composerView.controller.screenDisplayMode.value = isMinimized - ? ScreenDisplayMode.minimize - : ScreenDisplayMode.normal; - - return composerView; - }).toList(), - ], - ); - } + padding: const EdgeInsetsDirectional.symmetric(horizontal: 24, vertical: 16), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + if (countComposerHidden > 0) + ExpandComposerButton( + imagePaths: _imagePaths, + countComposerHidden: countComposerHidden, + onToggleDisplayComposerAction: () {}, + ), + ...visibleComposers, + ], ), ), ); diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart index e016b3173..fa690cd41 100644 --- a/lib/features/composer/presentation/composer_view_web.dart +++ b/lib/features/composer/presentation/composer_view_web.dart @@ -639,6 +639,7 @@ class ComposerView extends GetWidget { ]), ); }, + composerManager: controller.mailboxDashBoardController.composerManager, responsiveUtils: controller.responsiveUtils, displayMode: controller.screenDisplayMode.value, emailSubject: controller.subjectEmail.value ?? '', diff --git a/lib/features/composer/presentation/extensions/update_screen_display_mode_extension.dart b/lib/features/composer/presentation/extensions/update_screen_display_mode_extension.dart new file mode 100644 index 000000000..23703c20c --- /dev/null +++ b/lib/features/composer/presentation/extensions/update_screen_display_mode_extension.dart @@ -0,0 +1,82 @@ + +import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart'; +import 'package:tmail_ui_user/features/composer/presentation/utils/composer_utils.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; + +extension UpdateScreenDisplayModeExtension on ComposerController { + + bool get isNormalScreen => + screenDisplayMode.value == ScreenDisplayMode.normal; + + bool get isMinimizeScreen => + screenDisplayMode.value == ScreenDisplayMode.minimize; + + bool get isHiddenScreen => + screenDisplayMode.value == ScreenDisplayMode.hidden; + + bool get isFullScreen => + screenDisplayMode.value == ScreenDisplayMode.fullScreen; + + double get composerWidth { + switch (screenDisplayMode.value) { + case ScreenDisplayMode.normal: + return ComposerUtils.normalWidth; + case ScreenDisplayMode.minimize: + return ComposerUtils.minimizeWidth; + case ScreenDisplayMode.hidden: + case ScreenDisplayMode.fullScreen: + return 0; + } + } + + void setScreenDisplayMode(ScreenDisplayMode displayMode) => + screenDisplayMode.value = displayMode; + + bool setToMinimizeIfNormal() { + if (isNormalScreen) { + screenDisplayMode.value = ScreenDisplayMode.minimize; + return true; + } + return false; + } + + void updateDisplayModeForComposerQueue(ScreenDisplayMode displayMode) { + if (composerId == null || currentContext == null) return; + + final composerManager = mailboxDashBoardController.composerManager; + final screenWidth = responsiveUtils.getSizeScreenWidth(currentContext!); + + if (!composerManager.isExceedsScreenSize( + screenWidth: screenWidth, + composerIdsQueue: composerManager.composerIdsQueue, + )) { + return; + } + + if (displayMode == ScreenDisplayMode.normal) { + final recordComposerIds = composerManager.findSurroundingComposerIds(composerId!); + final nextComposer = recordComposerIds.nextTargetId; + final prevComposer = recordComposerIds.previousTargetId; + + if (nextComposer != null && composerManager.getComposerView(nextComposer).controller.setToMinimizeIfNormal()) { + return; + } + if (prevComposer != null && composerManager.getComposerView(prevComposer).controller.setToMinimizeIfNormal()) { + return; + } + + final firstNormalComposerId = composerManager.findFirstNormalComposerIdInQueue(); + if (firstNormalComposerId != null && firstNormalComposerId != composerId) { + composerManager + .getComposerView(firstNormalComposerId) + .controller + .setScreenDisplayMode(ScreenDisplayMode.minimize); + } + } else if (displayMode == ScreenDisplayMode.fullScreen) { + + } + + composerManager.syncComposerQueue(screenWidth: screenWidth); + } +} \ No newline at end of file diff --git a/lib/features/composer/presentation/manager/composer_manager.dart b/lib/features/composer/presentation/manager/composer_manager.dart index f0841692c..3c717d4a8 100644 --- a/lib/features/composer/presentation/manager/composer_manager.dart +++ b/lib/features/composer/presentation/manager/composer_manager.dart @@ -1,39 +1,48 @@ import 'dart:collection'; - -import 'package:core/utils/app_logger.dart'; +import 'package:collection/collection.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_bindings.dart'; +import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_view_web.dart'; +import 'package:tmail_ui_user/features/composer/presentation/extensions/update_screen_display_mode_extension.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart'; import 'package:tmail_ui_user/features/composer/presentation/utils/composer_utils.dart'; import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class ComposerManager extends GetxController { final RxMap composers = {}.obs; final Queue composerIdsQueue = Queue(); + final ResponsiveUtils _responsiveUtils = Get.find(); + void addComposer(ComposerArguments composerArguments) { - final String id = DateTime.now().millisecondsSinceEpoch.toString(); - log('ComposerManager::addComposer:ID = $id'); - ComposerBindings( - composerId: id, - composerArguments: composerArguments, - ).dependencies(); + final id = DateTime.now().millisecondsSinceEpoch.toString(); + ComposerBindings(composerId: id, composerArguments: composerArguments).dependencies(); + + composers[id] = ComposerView(key: Key(id), composerId: id); composerIdsQueue.add(id); - composers[id] = ComposerView( - key: Key(id), - composerId: id, - ); - log('ComposerManager::addComposer:Success'); + + _syncQueueIfNeeded(); } void removeComposer(String id) { - log('ComposerManager::removeComposer:ID = $id'); - if (composers.containsKey(id)) { - composerIdsQueue.remove(id); - composers.remove(id); - ComposerBindings(composerId: id).dispose(); - log('ComposerManager::removeComposer:Success'); + if (!composers.containsKey(id)) return; + + composerIdsQueue.remove(id); + composers.remove(id); + ComposerBindings(composerId: id).dispose(); + + _syncQueueIfNeeded(); + } + + void _syncQueueIfNeeded() { + if (currentContext != null && composerIdsQueue.isNotEmpty) { + syncComposerQueue( + screenWidth: _responsiveUtils.getSizeScreenWidth(currentContext!), + ); } } @@ -41,14 +50,103 @@ class ComposerManager extends GetxController { String? previousTargetId, String? targetId, String? nextTargetId, - }) findSurroundingComposerIds(String targetId) => - ComposerUtils.findSurroundingElements(composerIdsQueue, targetId); + }) findSurroundingComposerIds(String targetId) { + var ids = composerIdsQueue.toList(), + index = ids.indexOf(targetId); + + return index == -1 + ? (previousTargetId: null, targetId: null, nextTargetId: null) + : ( + previousTargetId: index > 0 ? ids[index - 1] : null, + targetId: ids[index], + nextTargetId: index < ids.length - 1 ? ids[index + 1] : null + ); + } + + String? findFirstNormalComposerIdInQueue() { + return composerIdsQueue + .toList() + .reversed + .firstWhereOrNull((id) => getComposerView(id).controller.isNormalScreen); + } + + bool isExceedsScreenSize({ + required Queue composerIdsQueue, + required double screenWidth, + }) { + final availableWidth = screenWidth + - ComposerUtils.composerExpandMoreButtonMaxWidth + - ComposerUtils.horizontalPadding * 2; + + double totalWidth = composerIdsQueue.fold( + 0, + (sum, id) => sum + getComposerView(id).controller.composerWidth, + ); + + return totalWidth > availableWidth; + } + + void syncComposerQueue({required double screenWidth}) { + if (composerIdsQueue.isEmpty) return; + + final availableWidth = screenWidth + - ComposerUtils.composerExpandMoreButtonMaxWidth + - ComposerUtils.horizontalPadding * 2; + + double totalWidth = 0; + final composerControllers = {}; + + for (var id in composerIdsQueue) { + final controller = getComposerView(id).controller; + final composerWidth = controller.composerWidth; + composerControllers[id] = controller; + totalWidth += composerWidth; + } + + for (var id in composerIdsQueue) { + final controller = composerControllers[id]!; + + if (controller.isNormalScreen) { + controller.setScreenDisplayMode(ScreenDisplayMode.minimize); + totalWidth -= (ComposerUtils.normalWidth - ComposerUtils.minimizeWidth); + } else if (controller.isMinimizeScreen) { + controller.setScreenDisplayMode(ScreenDisplayMode.hidden); + totalWidth -= ComposerUtils.minimizeWidth; + } + + if (totalWidth <= availableWidth) break; + } + + for (var id in composerIdsQueue) { + final controller = composerControllers[id]!; + final composerWidth = controller.composerWidth; + + if (totalWidth + (ComposerUtils.normalWidth - composerWidth) <= availableWidth) { + controller.setScreenDisplayMode(ScreenDisplayMode.normal); + totalWidth += (ComposerUtils.normalWidth - composerWidth); + } else if (totalWidth + (ComposerUtils.minimizeWidth - composerWidth) <= availableWidth) { + controller.setScreenDisplayMode(ScreenDisplayMode.minimize); + totalWidth += (ComposerUtils.minimizeWidth - composerWidth); + } + } + + composers.refresh(); + } bool get hasComposer => composerIdsQueue.isNotEmpty; - List get composerIds => composerIdsQueue.toList(); + int get countComposerHidden => composers.values + .where((composerView) => composerView.controller.isHiddenScreen) + .length; - String get singleComposerId => composerIdsQueue.first; + ComposerView? get firstFullScreenComposerView => composers.values + .firstWhereOrNull((composerView) => composerView.controller.isFullScreen); ComposerView getComposerView(String id) => composers[id]!; + + @override + void onClose() { + composerIdsQueue.clear(); + super.onClose(); + } } \ No newline at end of file diff --git a/lib/features/composer/presentation/model/screen_display_mode.dart b/lib/features/composer/presentation/model/screen_display_mode.dart index f480c1697..96238c9fb 100644 --- a/lib/features/composer/presentation/model/screen_display_mode.dart +++ b/lib/features/composer/presentation/model/screen_display_mode.dart @@ -2,18 +2,6 @@ enum ScreenDisplayMode { fullScreen, minimize, - normal; - - factory ScreenDisplayMode.fromJson(String value) { - switch (value) { - case 'fullScreen': - return ScreenDisplayMode.fullScreen; - case 'minimize': - return ScreenDisplayMode.minimize; - default: - return ScreenDisplayMode.normal; - } - } - - String toJson() => name; + normal, + hidden; } \ No newline at end of file diff --git a/lib/features/composer/presentation/utils/composer_utils.dart b/lib/features/composer/presentation/utils/composer_utils.dart index a5c26bb50..a9e59f10a 100644 --- a/lib/features/composer/presentation/utils/composer_utils.dart +++ b/lib/features/composer/presentation/utils/composer_utils.dart @@ -1,38 +1,9 @@ -import 'dart:collection'; - class ComposerUtils { static const double normalWidth = 725; static const double normalHeight = 525; static const double minimizeWidth = 400; static const double minimizeHeight = 52; static const double composerExpandMoreButtonMaxWidth = 130; - - static int calculateNormalAndMinimizeWidgets(double width, int countNormalWidgets) { - double remainingWidth = width - (countNormalWidgets * normalWidth); - if (remainingWidth < 0) return countNormalWidgets; - int countWidgets = countNormalWidgets + (remainingWidth / minimizeWidth).floor(); - return countWidgets; - } - - static int calculateNormalWidgets(double width) => (width / normalWidth).floor(); - - static int calculateMinimizeWidgets(double width) => (width / minimizeWidth).floor(); - - static ({ - String? previousTargetId, - String? targetId, - String? nextTargetId, - }) findSurroundingElements(Queue queue, String targetId) { - var ids = queue.toList(), - index = ids.indexOf(targetId); - - return index == -1 - ? (previousTargetId: null, targetId: null, nextTargetId: null) - : ( - previousTargetId: index > 0 ? ids[index - 1] : null, - targetId: ids[index], - nextTargetId: index < ids.length - 1 ? ids[index + 1] : null - ); - } + static const double horizontalPadding = 24; } \ No newline at end of file diff --git a/lib/features/composer/presentation/view/web/desktop_responsive_container_view.dart b/lib/features/composer/presentation/view/web/desktop_responsive_container_view.dart index c3ecf9806..e3657870b 100644 --- a/lib/features/composer/presentation/view/web/desktop_responsive_container_view.dart +++ b/lib/features/composer/presentation/view/web/desktop_responsive_container_view.dart @@ -1,6 +1,5 @@ import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:tmail_ui_user/features/composer/presentation/manager/composer_manager.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart'; @@ -10,17 +9,17 @@ import 'package:tmail_ui_user/features/composer/presentation/widgets/web/minimiz class DesktopResponsiveContainerView extends StatelessWidget { final ResponsiveUtils responsiveUtils; + final ComposerManager composerManager; final ScreenDisplayMode displayMode; final String emailSubject; final VoidCallback onCloseViewAction; final OnChangeDisplayModeAction onChangeDisplayModeAction; final Widget Function(BuildContext context, BoxConstraints constraints) childBuilder; - final ComposerManager _composerManager = Get.find(); - - DesktopResponsiveContainerView({ + const DesktopResponsiveContainerView({ super.key, required this.childBuilder, + required this.composerManager, required this.responsiveUtils, required this.displayMode, required this.emailSubject, @@ -31,71 +30,47 @@ class DesktopResponsiveContainerView extends StatelessWidget { @override Widget build(BuildContext context) { if (displayMode == ScreenDisplayMode.minimize) { - if (_composerManager.composerIds.length == 1) { - return PositionedDirectional( + final composerWidget = PointerInterceptor( + child: MinimizeComposerWidget( + emailSubject: emailSubject, + onCloseViewAction: onCloseViewAction, + onChangeDisplayModeAction: onChangeDisplayModeAction, + ), + ); + + return composerManager.composerIdsQueue.length == 1 + ? PositionedDirectional( end: DesktopResponsiveContainerViewStyle.margin, bottom: DesktopResponsiveContainerViewStyle.margin, - child: PointerInterceptor( - child: MinimizeComposerWidget( - emailSubject: emailSubject, - onCloseViewAction: onCloseViewAction, - onChangeDisplayModeAction: onChangeDisplayModeAction, - ), - ) - ); - } else { - return PointerInterceptor( - child: MinimizeComposerWidget( - emailSubject: emailSubject, - onCloseViewAction: onCloseViewAction, - onChangeDisplayModeAction: onChangeDisplayModeAction, - ), - ); - } - + child: composerWidget, + ) + : composerWidget; } else if (displayMode == ScreenDisplayMode.normal) { - if (_composerManager.composerIds.length == 1) { - return PositionedDirectional( + final composerWidget = Card( + elevation: DesktopResponsiveContainerViewStyle.elevation, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(DesktopResponsiveContainerViewStyle.radius)), + ), + clipBehavior: Clip.antiAlias, + child: Container( + color: DesktopResponsiveContainerViewStyle.backgroundColor, + width: DesktopResponsiveContainerViewStyle.normalScreenMaxWidth, + height: DesktopResponsiveContainerViewStyle.normalScreenMaxHeight, + child: LayoutBuilder(builder: (context, constraints) => + PointerInterceptor( + child: childBuilder.call(context, constraints), + ) + ), + ), + ); + + return composerManager.composerIdsQueue.length == 1 + ? PositionedDirectional( end: DesktopResponsiveContainerViewStyle.margin, bottom: DesktopResponsiveContainerViewStyle.margin, - child: Card( - elevation: DesktopResponsiveContainerViewStyle.elevation, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(DesktopResponsiveContainerViewStyle.radius)) - ), - clipBehavior: Clip.antiAlias, - child: Container( - color: DesktopResponsiveContainerViewStyle.backgroundColor, - width: DesktopResponsiveContainerViewStyle.normalScreenMaxWidth, - height: DesktopResponsiveContainerViewStyle.normalScreenMaxHeight, - child: LayoutBuilder(builder: (context, constraints) => - PointerInterceptor( - child: childBuilder.call(context, constraints) - ) - ) - ), - ) - ); - } else { - return Card( - elevation: DesktopResponsiveContainerViewStyle.elevation, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(DesktopResponsiveContainerViewStyle.radius)) - ), - clipBehavior: Clip.antiAlias, - child: Container( - color: DesktopResponsiveContainerViewStyle.backgroundColor, - width: DesktopResponsiveContainerViewStyle.normalScreenMaxWidth, - height: DesktopResponsiveContainerViewStyle.normalScreenMaxHeight, - child: LayoutBuilder(builder: (context, constraints) => - PointerInterceptor( - child: childBuilder.call(context, constraints) - ) - ) - ), - ); - } - + child: composerWidget, + ) + : composerWidget; } else if (displayMode == ScreenDisplayMode.fullScreen) { final maxWidth = responsiveUtils.getSizeScreenWidth(context) * 0.85; final maxHeight = responsiveUtils.getSizeScreenHeight(context) * 0.9;