TF-3479 Handle switch state display mode of multiple composer (Fullscreen <-> Normal <-> Minimize)

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-02-14 02:03:22 +07:00
committed by Dat H. Pham
parent c603b5e40b
commit e6ad72e42d
8 changed files with 283 additions and 211 deletions
@@ -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),
@@ -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<ComposerOverlayView> {
late final ComposerManager _composerManager;
late final ImagePaths _imagePaths;
final ComposerManager _composerManager = Get.find<ComposerManager>();
final ImagePaths _imagePaths = Get.find<ImagePaths>();
@override
void initState() {
super.initState();
_composerManager = Get.find<ComposerManager>();
_imagePaths = Get.find<ImagePaths>();
}
@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<String> 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,
],
),
),
);
@@ -639,6 +639,7 @@ class ComposerView extends GetWidget<ComposerController> {
]),
);
},
composerManager: controller.mailboxDashBoardController.composerManager,
responsiveUtils: controller.responsiveUtils,
displayMode: controller.screenDisplayMode.value,
emailSubject: controller.subjectEmail.value ?? '',
@@ -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);
}
}
@@ -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<String, ComposerView> composers = <String, ComposerView>{}.obs;
final Queue<String> composerIdsQueue = Queue<String>();
final ResponsiveUtils _responsiveUtils = Get.find<ResponsiveUtils>();
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<String> composerIdsQueue,
required double screenWidth,
}) {
final availableWidth = screenWidth
- ComposerUtils.composerExpandMoreButtonMaxWidth
- ComposerUtils.horizontalPadding * 2;
double totalWidth = composerIdsQueue.fold<double>(
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 = <String, ComposerController>{};
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<String> 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();
}
}
@@ -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;
}
@@ -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<String> 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;
}
@@ -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<ComposerManager>();
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;