TF-3479 Handle Show all & Hide all popup menu composer

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-02-14 10:10:30 +07:00
committed by Dat H. Pham
parent 1bad81dbf2
commit 16cb8c8a37
12 changed files with 341 additions and 125 deletions
@@ -1,6 +1,7 @@
import 'dart:collection';
import 'package:collection/collection.dart';
import 'package:core/presentation/utils/responsive_utils.dart';
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';
@@ -73,23 +74,25 @@ class ComposerManager extends GetxController {
bool isExceedsScreenSize({required double screenWidth}) {
final availableWidth = screenWidth
- ComposerUtils.composerExpandMoreButtonMaxWidth
- ComposerUtils.horizontalPadding * 2;
- ComposerUtils.padding * 2;
double totalWidth = composerIdsQueue.fold<double>(
0,
(sum, id) => sum + getComposerView(id).controller.composerWidth,
);
return totalWidth > availableWidth;
bool isExceedsScreenSize = totalWidth > availableWidth;
log('ComposerManager::isExceedsScreenSize:isExceedsScreenSize = $isExceedsScreenSize');
return isExceedsScreenSize;
}
void syncComposerQueue({required double screenWidth}) {
if (composerIdsQueue.isEmpty) return;
log('ComposerManager::syncComposerQueue:screenWidth = $screenWidth');
final availableWidth = screenWidth
- ComposerUtils.composerExpandMoreButtonMaxWidth
- ComposerUtils.horizontalPadding * 2;
- ComposerUtils.padding * 2;
log('ComposerManager::syncComposerQueue:availableWidth = $availableWidth');
double totalWidth = 0;
final composerControllers = <String, ComposerController>{};
@@ -99,7 +102,7 @@ class ComposerManager extends GetxController {
composerControllers[id] = controller;
totalWidth += composerWidth;
}
log('ComposerManager::syncComposerQueue:totalWidth = $totalWidth');
for (var id in composerIdsQueue) {
final controller = composerControllers[id]!;
@@ -113,6 +116,7 @@ class ComposerManager extends GetxController {
if (totalWidth <= availableWidth) break;
}
log('ComposerManager::syncComposerQueue:totalWidth_after = $totalWidth');
for (var id in composerIdsQueue) {
final controller = composerControllers[id]!;
@@ -126,8 +130,7 @@ class ComposerManager extends GetxController {
totalWidth += (ComposerUtils.minimizeWidth - composerWidth);
}
}
composers.refresh();
log('ComposerManager::syncComposerQueue:totalWidth_finally = $totalWidth');
}
bool get hasComposer => composerIdsQueue.isNotEmpty;
@@ -164,6 +167,20 @@ class ComposerManager extends GetxController {
: null;
}
List<String> get hiddenComposerIds {
return composerIdsQueue
.where((id) => getComposerView(id).controller.isHiddenScreen)
.toList();
}
void showComposerIfHidden(String composerId) {
if (!composers.containsKey(composerId)) return;
composerIdsQueue.remove(composerId);
composerIdsQueue.add(composerId);
final composerView = getComposerView(composerId);
composerView.controller.setScreenDisplayMode(ScreenDisplayMode.normal);
}
ComposerView getComposerView(String id) => composers[id]!;
@override
@@ -0,0 +1,16 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:flutter/material.dart';
class HiddenComposerItemStyle {
static const double radius = 12;
static const double elevation = 16;
static const double width = 320;
static const double height = 52;
static const double space = 8;
static const double iconSize = 24;
static const Color backgroundColor = Colors.white;
static const Color iconColor = AppColor.steelGray200;
static const EdgeInsetsGeometry padding = EdgeInsetsDirectional.symmetric(horizontal: 8);
}
@@ -5,5 +5,6 @@ class ComposerUtils {
static const double minimizeWidth = 400;
static const double minimizeHeight = 52;
static const double composerExpandMoreButtonMaxWidth = 130;
static const double horizontalPadding = 24;
static const double composerExpandMoreButtonMaxHeight = 52;
static const double padding = 16;
}
@@ -1,11 +1,11 @@
import 'package:core/presentation/resources/image_paths.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/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/widgets/expand_composer_button.dart';
import 'package:tmail_ui_user/features/composer/presentation/utils/composer_utils.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/expand_composer_button.dart';
class ComposerOverlayView extends StatefulWidget {
const ComposerOverlayView({super.key});
@@ -16,14 +16,12 @@ class ComposerOverlayView extends StatefulWidget {
class _ComposerOverlayViewState extends State<ComposerOverlayView> {
late final ComposerManager _composerManager;
late final ImagePaths _imagePaths;
late final ResponsiveUtils _responsiveUtils;
@override
void initState() {
super.initState();
_composerManager = Get.find<ComposerManager>();
_imagePaths = Get.find<ImagePaths>();
_responsiveUtils = Get.find<ResponsiveUtils>();
}
@@ -63,16 +61,17 @@ class _ComposerOverlayViewState extends State<ComposerOverlayView> {
return Align(
alignment: AlignmentDirectional.bottomEnd,
child: Padding(
padding: const EdgeInsetsDirectional.symmetric(horizontal: 24, vertical: 16),
padding: const EdgeInsetsDirectional.all(ComposerUtils.padding),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
if (countComposerHidden > 0)
ExpandComposerButton(
imagePaths: _imagePaths,
countComposerHidden: countComposerHidden,
onToggleDisplayComposerAction: () {},
onRemoveHiddenComposerItem: (controller) =>
controller.handleClickCloseComposer(context),
onShowComposerAction: _composerManager.showComposerIfHidden,
),
...visibleComposers,
],
@@ -1,58 +0,0 @@
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,
),
),
]
)
),
),
);
}
}
@@ -0,0 +1,122 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_portal/flutter_portal.dart';
import 'package:flutter_svg/flutter_svg.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/utils/composer_utils.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/hidden_composer_list_view_overlay.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class ExpandComposerButton extends StatefulWidget {
final int countComposerHidden;
final OnRemoveHiddenComposerItem onRemoveHiddenComposerItem;
final OnShowComposerAction onShowComposerAction;
const ExpandComposerButton({
super.key,
required this.countComposerHidden,
required this.onRemoveHiddenComposerItem,
required this.onShowComposerAction,
});
@override
State<ExpandComposerButton> createState() => _ExpandComposerButtonState();
}
class _ExpandComposerButtonState extends State<ExpandComposerButton> {
late final ComposerManager _composerManager;
late final ImagePaths _imagePaths;
late final ResponsiveUtils _responsiveUtils;
bool _visible = false;
@override
void initState() {
super.initState();
_composerManager = Get.find<ComposerManager>();
_imagePaths = Get.find<ImagePaths>();
_responsiveUtils = Get.find<ResponsiveUtils>();
}
@override
Widget build(BuildContext context) {
final expandButton = PointerInterceptor(
child: Card(
elevation: 16,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(12))
),
clipBehavior: Clip.antiAlias,
child: InkWell(
onTap: () => setState(() => _visible = !_visible),
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: Container(
color: Colors.white,
width: ComposerUtils.composerExpandMoreButtonMaxWidth,
height: ComposerUtils.composerExpandMoreButtonMaxHeight,
padding: const EdgeInsetsDirectional.symmetric(horizontal: 8),
child: Row(
children: [
SvgPicture.asset(
_visible
? _imagePaths.icDoubleArrowDown
: _imagePaths.icDoubleArrowUp,
),
const SizedBox(width: 8),
Expanded(
child: Text(
_visible
? AppLocalizations.of(context).hideAll
: '+${widget.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,
),
),
]
)
),
),
),
);
return PortalTarget(
visible: _visible,
portalFollower: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => setState(() => _visible = false),
),
child: PortalTarget(
anchor: const Aligned(
follower: Alignment.bottomLeft,
target: Alignment.topLeft,
offset: Offset(0.0, 0.0),
),
portalFollower: HiddenComposerListViewOverlay(
composerManager: _composerManager,
imagePaths: _imagePaths,
responsiveUtils: _responsiveUtils,
onRemoveHiddenComposerItem: (controller) {
widget.onRemoveHiddenComposerItem(controller);
setState(() => _visible = false);
},
onShowComposerAction: (composerId) {
widget.onShowComposerAction(composerId);
setState(() => _visible = false);
}
),
visible: _visible,
child: expandButton,
)
);
}
}
@@ -0,0 +1,60 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:flutter/material.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/features/composer/presentation/styles/web/hidden_composer_item_style.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/title_composer_widget.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class HiddenComposerItem extends StatelessWidget {
final ImagePaths imagePaths;
final String composerSubject;
final VoidCallback onCloseViewAction;
final VoidCallback onShowComposer;
const HiddenComposerItem({
super.key,
required this.imagePaths,
required this.composerSubject,
required this.onCloseViewAction,
required this.onShowComposer,
});
@override
Widget build(BuildContext context) {
return PointerInterceptor(
child: Card(
elevation: HiddenComposerItemStyle.elevation,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(HiddenComposerItemStyle.radius))
),
clipBehavior: Clip.antiAlias,
child: Container(
color: HiddenComposerItemStyle.backgroundColor,
width: HiddenComposerItemStyle.width,
height: HiddenComposerItemStyle.height,
padding: HiddenComposerItemStyle.padding,
child: InkWell(
onTap: onShowComposer,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
TMailButtonWidget.fromIcon(
icon: imagePaths.icCancel,
backgroundColor: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).saveAndClose,
iconSize: HiddenComposerItemStyle.iconSize,
iconColor: HiddenComposerItemStyle.iconColor,
onTapActionCallback: onCloseViewAction
),
const SizedBox(width: HiddenComposerItemStyle.space),
Expanded(child: TitleComposerWidget(emailSubject: composerSubject)),
]
),
)
),
),
);
}
}
@@ -0,0 +1,58 @@
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:flutter/material.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/manager/composer_manager.dart';
import 'package:tmail_ui_user/features/composer/presentation/styles/web/hidden_composer_item_style.dart';
import 'package:tmail_ui_user/features/composer/presentation/utils/composer_utils.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/hidden_composer_item.dart';
typedef OnRemoveHiddenComposerItem = Function(ComposerController controller);
typedef OnShowComposerAction = Function(String composerId);
class HiddenComposerListViewOverlay extends StatelessWidget {
final ComposerManager composerManager;
final ImagePaths imagePaths;
final ResponsiveUtils responsiveUtils;
final OnRemoveHiddenComposerItem onRemoveHiddenComposerItem;
final OnShowComposerAction onShowComposerAction;
const HiddenComposerListViewOverlay({
super.key,
required this.composerManager,
required this.imagePaths,
required this.responsiveUtils,
required this.onRemoveHiddenComposerItem,
required this.onShowComposerAction,
});
@override
Widget build(BuildContext context) {
final hiddenComposerIds = composerManager.hiddenComposerIds;
final maxHeight = responsiveUtils.getSizeScreenHeight(context)
- ComposerUtils.composerExpandMoreButtonMaxHeight
- ComposerUtils.padding;
return Container(
constraints: BoxConstraints(maxHeight: maxHeight),
width: HiddenComposerItemStyle.width,
child: ListView.builder(
itemCount: hiddenComposerIds.length,
shrinkWrap: true,
itemBuilder: (context, index) {
final composerId = hiddenComposerIds[index];
final controller = composerManager.getComposerView(composerId).controller;
final subjectEmail = controller.subjectEmail.value ?? '';
return HiddenComposerItem(
imagePaths: imagePaths,
composerSubject: subjectEmail,
onCloseViewAction: () => onRemoveHiddenComposerItem(controller),
onShowComposer: () => onShowComposerAction(composerId),
);
}),
);
}
}
@@ -37,52 +37,40 @@ class MinimizeComposerWidget extends StatelessWidget {
width: MinimizeComposerWidgetStyle.width,
height: MinimizeComposerWidgetStyle.height,
padding: MinimizeComposerWidgetStyle.padding,
child: Stack(
child: Row(
children: [
Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: MinimizeComposerWidgetStyle.width / 2),
child: TitleComposerWidget(emailSubject: emailSubject),
),
TMailButtonWidget.fromIcon(
icon: _imagePaths.icCancel,
backgroundColor: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).saveAndClose,
iconSize: MinimizeComposerWidgetStyle.iconSize,
iconColor: MinimizeComposerWidgetStyle.iconColor,
padding: MinimizeComposerWidgetStyle.iconPadding,
onTapActionCallback: onCloseViewAction
),
Align(
alignment: AlignmentDirectional.centerStart,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
TMailButtonWidget.fromIcon(
icon: _imagePaths.icCancel,
backgroundColor: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).saveAndClose,
iconSize: MinimizeComposerWidgetStyle.iconSize,
iconColor: MinimizeComposerWidgetStyle.iconColor,
padding: MinimizeComposerWidgetStyle.iconPadding,
onTapActionCallback: onCloseViewAction
),
const SizedBox(width: MinimizeComposerWidgetStyle.space),
TMailButtonWidget.fromIcon(
icon: _imagePaths.icFullScreen,
backgroundColor: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).fullscreen,
iconSize: MinimizeComposerWidgetStyle.iconSize,
iconColor: MinimizeComposerWidgetStyle.iconColor,
padding: MinimizeComposerWidgetStyle.iconPadding,
onTapActionCallback: () => onChangeDisplayModeAction(ScreenDisplayMode.fullScreen)
),
const SizedBox(width: MinimizeComposerWidgetStyle.space),
TMailButtonWidget.fromIcon(
icon: _imagePaths.icChevronUp,
backgroundColor: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).show,
iconSize: MinimizeComposerWidgetStyle.iconSize,
iconColor: MinimizeComposerWidgetStyle.iconColor,
padding: MinimizeComposerWidgetStyle.iconPadding,
onTapActionCallback: () => onChangeDisplayModeAction(ScreenDisplayMode.normal)
),
]
),
)
],
const SizedBox(width: MinimizeComposerWidgetStyle.space),
TMailButtonWidget.fromIcon(
icon: _imagePaths.icFullScreen,
backgroundColor: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).fullscreen,
iconSize: MinimizeComposerWidgetStyle.iconSize,
iconColor: MinimizeComposerWidgetStyle.iconColor,
padding: MinimizeComposerWidgetStyle.iconPadding,
onTapActionCallback: () => onChangeDisplayModeAction(ScreenDisplayMode.fullScreen)
),
const SizedBox(width: MinimizeComposerWidgetStyle.space),
TMailButtonWidget.fromIcon(
icon: _imagePaths.icChevronUp,
backgroundColor: Colors.transparent,
tooltipMessage: AppLocalizations.of(context).show,
iconSize: MinimizeComposerWidgetStyle.iconSize,
iconColor: MinimizeComposerWidgetStyle.iconColor,
padding: MinimizeComposerWidgetStyle.iconPadding,
onTapActionCallback: () => onChangeDisplayModeAction(ScreenDisplayMode.normal)
),
const SizedBox(width: MinimizeComposerWidgetStyle.space),
Expanded(child: TitleComposerWidget(emailSubject: emailSubject)),
]
)
),
);
@@ -8,7 +8,7 @@ import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:model/extensions/session_extension.dart';
import 'package:tmail_ui_user/features/base/widget/popup_item_no_icon_widget.dart';
import 'package:tmail_ui_user/features/base/widget/scrollbar_list_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_overlay_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/view/web/composer_overlay_view.dart';
import 'package:tmail_ui_user/features/email/presentation/email_view.dart';
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
+12 -6
View File
@@ -2962,12 +2962,6 @@
"placeholders_order": [],
"placeholders": {}
},
"errorWhileFetchingSubaddress": "Error while fetching the subaddress",
"@errorWhileFetchingSubaddress": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"connectedToTheInternet": "Connected to the internet",
"@connectedToTheInternet": {
"type": "text",
@@ -4244,6 +4238,12 @@
"placeholders_order": [],
"placeholders": {}
},
"messageWarningDialogWhenExpiredOIDCTokenAndReconnection": "Your session expired. We need to take you back to the login page in order to refresh it. You might want to save the email you are currently editing before we do so.",
"@messageWarningDialogWhenExpiredOIDCTokenAndReconnection": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"replyToList": "Reply to list",
"@replyToList": {
"type": "text",
@@ -4363,5 +4363,11 @@
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"hideAll": "Hide all",
"@hideAll": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
}
}
@@ -4583,4 +4583,11 @@ class AppLocalizations {
name: 'senderImportantSettingToggleDescription',
);
}
String get hideAll {
return Intl.message(
'Hide all',
name: 'hideAll',
);
}
}