TF-3479 Allow open multiple composer on screen
Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
@@ -262,7 +262,6 @@ class ComposerController extends BaseController
|
||||
if (PlatformInfo.isWeb) {
|
||||
responsiveContainerKey = GlobalKey();
|
||||
richTextWebController = getBinding<RichTextWebController>(tag: composerId);
|
||||
responsiveContainerKey = GlobalKey();
|
||||
menuMoreOptionController = CustomPopupMenuController();
|
||||
} else {
|
||||
richTextMobileTabletController = getBinding<RichTextMobileTabletController>(tag: composerId);
|
||||
@@ -310,7 +309,6 @@ class ComposerController extends BaseController
|
||||
richTextMobileTabletController = null;
|
||||
}
|
||||
_identityContentOnOpenPolicy = SignatureStatus.editedAvailable;
|
||||
responsiveContainerKey = null;
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
|
||||
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/composer_view_web.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 {
|
||||
const ComposerOverlayView({super.key});
|
||||
@@ -14,13 +20,94 @@ class ComposerOverlayView extends StatefulWidget {
|
||||
class _ComposerOverlayViewState extends State<ComposerOverlayView> {
|
||||
|
||||
final ComposerManager _composerManager = Get.find<ComposerManager>();
|
||||
final ImagePaths _imagePaths = Get.find<ImagePaths>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Obx(() {
|
||||
if (!_composerManager.hasComposer) return const SizedBox.shrink();
|
||||
final composers = _composerManager.composers;
|
||||
if (composers.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
return ComposerView(composerId: _composerManager.singleComposerId);
|
||||
if (composers.length == 1) {
|
||||
return _composerManager.getComposerView(_composerManager.singleComposerId);
|
||||
}
|
||||
|
||||
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(),
|
||||
],
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -639,6 +639,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
||||
]),
|
||||
);
|
||||
},
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
displayMode: controller.screenDisplayMode.value,
|
||||
emailSubject: controller.subjectEmail.value ?? '',
|
||||
onCloseViewAction: () => controller.handleClickCloseComposer(context),
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:core/utils/app_logger.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/utils/composer_utils.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
|
||||
|
||||
class ComposerManager extends GetxController {
|
||||
final RxMap<String, ComposerController> composers = <String, ComposerController>{}.obs;
|
||||
final RxMap<String, ComposerView> composers = <String, ComposerView>{}.obs;
|
||||
final Queue<String> composerIdsQueue = Queue<String>();
|
||||
|
||||
void addComposer(ComposerArguments composerArguments) {
|
||||
final String id = DateTime.now().millisecondsSinceEpoch.toString();
|
||||
@@ -14,22 +19,36 @@ class ComposerManager extends GetxController {
|
||||
composerId: id,
|
||||
composerArguments: composerArguments,
|
||||
).dependencies();
|
||||
composers[id] = Get.find<ComposerController>(tag: id);
|
||||
composerIdsQueue.add(id);
|
||||
composers[id] = ComposerView(
|
||||
key: Key(id),
|
||||
composerId: id,
|
||||
);
|
||||
log('ComposerManager::addComposer:Success');
|
||||
}
|
||||
|
||||
void removeComposer(String id) {
|
||||
log('ComposerManager::removeComposer:ID = $id');
|
||||
if (composers.containsKey(id)) {
|
||||
ComposerBindings(composerId: id).dispose();
|
||||
composerIdsQueue.remove(id);
|
||||
composers.remove(id);
|
||||
ComposerBindings(composerId: id).dispose();
|
||||
log('ComposerManager::removeComposer:Success');
|
||||
}
|
||||
}
|
||||
|
||||
bool get hasComposer => composers.isNotEmpty;
|
||||
({
|
||||
String? previousTargetId,
|
||||
String? targetId,
|
||||
String? nextTargetId,
|
||||
}) findSurroundingComposerIds(String targetId) =>
|
||||
ComposerUtils.findSurroundingElements(composerIdsQueue, targetId);
|
||||
|
||||
List<String> get composerIds => composers.keys.toList();
|
||||
bool get hasComposer => composerIdsQueue.isNotEmpty;
|
||||
|
||||
String get singleComposerId => composerIds.first;
|
||||
List<String> get composerIds => composerIdsQueue.toList();
|
||||
|
||||
String get singleComposerId => composerIdsQueue.first;
|
||||
|
||||
ComposerView getComposerView(String id) => composers[id]!;
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/utils/composer_utils.dart';
|
||||
|
||||
class MinimizeComposerWidgetStyle {
|
||||
static const double radius = 24;
|
||||
static const double elevation = 16;
|
||||
static const double width = 500;
|
||||
static const double height = 50;
|
||||
static const double width = ComposerUtils.minimizeWidth;
|
||||
static const double height = ComposerUtils.minimizeHeight;
|
||||
static const double space = 8;
|
||||
static const double iconSize = 20;
|
||||
|
||||
|
||||
+3
@@ -1,10 +1,13 @@
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/utils/composer_utils.dart';
|
||||
|
||||
class DesktopResponsiveContainerViewStyle {
|
||||
static const double radius = 28;
|
||||
static const double margin = 20;
|
||||
static const double elevation = 16;
|
||||
static const double normalScreenMaxWidth = ComposerUtils.normalWidth;
|
||||
static const double normalScreenMaxHeight = ComposerUtils.normalHeight;
|
||||
|
||||
static const Color backgroundColor = Colors.white;
|
||||
static const Color outSideBackgroundColor = Colors.black38;
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
+58
-26
@@ -2,23 +2,26 @@ 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';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/styles/web/desktop_responsive_container_view_style.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/minimize_composer_widget.dart';
|
||||
|
||||
class DesktopResponsiveContainerView extends StatelessWidget {
|
||||
|
||||
final ResponsiveUtils responsiveUtils;
|
||||
final ScreenDisplayMode displayMode;
|
||||
final String emailSubject;
|
||||
final VoidCallback onCloseViewAction;
|
||||
final OnChangeDisplayModeAction onChangeDisplayModeAction;
|
||||
final Widget Function(BuildContext context, BoxConstraints constraints) childBuilder;
|
||||
|
||||
final _responsiveUtils = Get.find<ResponsiveUtils>();
|
||||
final ComposerManager _composerManager = Get.find<ComposerManager>();
|
||||
|
||||
DesktopResponsiveContainerView({
|
||||
super.key,
|
||||
required this.childBuilder,
|
||||
required this.responsiveUtils,
|
||||
required this.displayMode,
|
||||
required this.emailSubject,
|
||||
required this.onCloseViewAction,
|
||||
@@ -28,45 +31,74 @@ class DesktopResponsiveContainerView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (displayMode == ScreenDisplayMode.minimize) {
|
||||
return PositionedDirectional(
|
||||
end: DesktopResponsiveContainerViewStyle.margin,
|
||||
bottom: DesktopResponsiveContainerViewStyle.margin,
|
||||
child: PointerInterceptor(
|
||||
if (_composerManager.composerIds.length == 1) {
|
||||
return 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,
|
||||
),
|
||||
)
|
||||
);
|
||||
} else if (displayMode == ScreenDisplayMode.normal) {
|
||||
final maxWidth = _responsiveUtils.getSizeScreenWidth(context) * 0.5;
|
||||
final maxHeight = _responsiveUtils.getSizeScreenHeight(context) * 0.75;
|
||||
);
|
||||
}
|
||||
|
||||
return PositionedDirectional(
|
||||
end: DesktopResponsiveContainerViewStyle.margin,
|
||||
bottom: DesktopResponsiveContainerViewStyle.margin,
|
||||
child: Card(
|
||||
} else if (displayMode == ScreenDisplayMode.normal) {
|
||||
if (_composerManager.composerIds.length == 1) {
|
||||
return 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))
|
||||
borderRadius: BorderRadius.all(Radius.circular(DesktopResponsiveContainerViewStyle.radius))
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Container(
|
||||
color: DesktopResponsiveContainerViewStyle.backgroundColor,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
child: LayoutBuilder(builder: (context, constraints) =>
|
||||
PointerInterceptor(
|
||||
child: childBuilder.call(context, constraints)
|
||||
color: DesktopResponsiveContainerViewStyle.backgroundColor,
|
||||
width: DesktopResponsiveContainerViewStyle.normalScreenMaxWidth,
|
||||
height: DesktopResponsiveContainerViewStyle.normalScreenMaxHeight,
|
||||
child: LayoutBuilder(builder: (context, constraints) =>
|
||||
PointerInterceptor(
|
||||
child: childBuilder.call(context, constraints)
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
)
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
} else if (displayMode == ScreenDisplayMode.fullScreen) {
|
||||
final maxWidth = _responsiveUtils.getSizeScreenWidth(context) * 0.85;
|
||||
final maxHeight = _responsiveUtils.getSizeScreenHeight(context) * 0.9;
|
||||
final maxWidth = responsiveUtils.getSizeScreenWidth(context) * 0.85;
|
||||
final maxHeight = responsiveUtils.getSizeScreenHeight(context) * 0.9;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: DesktopResponsiveContainerViewStyle.outSideBackgroundColor,
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import 'package:core/presentation/resources/image_paths.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/utils/composer_utils.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
|
||||
class ExpandComposerButton extends StatelessWidget {
|
||||
|
||||
final ImagePaths imagePaths;
|
||||
final int countComposerHidden;
|
||||
final VoidCallback onToggleDisplayComposerAction;
|
||||
|
||||
const ExpandComposerButton({
|
||||
super.key,
|
||||
required this.imagePaths,
|
||||
required this.countComposerHidden,
|
||||
required this.onToggleDisplayComposerAction,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
elevation: 16,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(12))
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
onTap: () => {},
|
||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||
child: Container(
|
||||
color: Colors.white,
|
||||
width: ComposerUtils.composerExpandMoreButtonMaxWidth,
|
||||
height: ComposerUtils.minimizeHeight,
|
||||
padding: const EdgeInsetsDirectional.symmetric(horizontal: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(imagePaths.icDoubleArrowUp),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
'+$countComposerHidden ${AppLocalizations.of(context).more.toLowerCase()}',
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 1,
|
||||
),
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user