TF-3758 Update mailbox view on mobile web responsive

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-05-30 16:57:07 +07:00
committed by Dat H. Pham
parent 6c7602ebe6
commit 15c0d58c7c
26 changed files with 695 additions and 898 deletions
@@ -413,20 +413,15 @@ class EmailView extends GetWidget<SingleEmailController> {
contentPadding: 0,
useDefaultFont: true,
),
if (controller.mailboxDashBoardController.isAttachmentDraggableAppActive)
if (controller.mailboxDashBoardController.isAttachmentDraggableAppActive ||
controller.mailboxDashBoardController.isLocalFileDraggableAppActive ||
controller.mailboxDashBoardController.isAppGridDialogDisplayed.isTrue)
PointerInterceptor(
child: SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
)
),
if (controller.mailboxDashBoardController.isLocalFileDraggableAppActive)
PointerInterceptor(
child: SizedBox(
width: constraints.maxWidth,
height: constraints.maxHeight,
)
)
],
);
});
@@ -13,6 +13,7 @@ import 'package:jmap_dart_client/jmap/core/capability/capability_properties.dart
import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/core/unsigned_int.dart';
import 'package:model/download_all/download_all_capability.dart';
import 'package:model/mailbox/mailbox_constants.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/home/data/model/session_hive_obj.dart';
import 'package:tmail_ui_user/features/home/domain/converter/session_account_converter.dart';
@@ -115,4 +116,27 @@ extension SessionExtensions on Session {
linagoraDownloadAllCapability,
);
}
bool isSubAddressingSupported(AccountId? accountId) {
try {
if (accountId == null) {
return false;
}
if (!CapabilityIdentifier.jmapTeamMailboxes.isSupported(this, accountId)) {
return false;
}
final capability = getCapabilityProperties(
accountId,
CapabilityIdentifier.jmapTeamMailboxes,
);
final props = capability?.props[0] as Map<String, dynamic>?;
return props?[subaddressingSupported] ?? false;
} catch (e) {
logError('SessionExtensions::isSubAddressingSupported:Exception = $e');
return false;
}
}
}
@@ -1,16 +1,31 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:core/presentation/views/list/tree_view.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:model/mailbox/expand_mode.dart';
import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart';
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/extensions/handle_mailbox_action_extension.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/extensions/open_app_grid_extension.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/extensions/toggle_expand_folders_extension.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_controller.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_categories.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/styles/mailbox_item_widget_styles.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/folder_widget.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/folders_bar_widget.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_app_bar.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_category_widget.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_item_widget.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_loading_bar_widget.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/sending_queue_mailbox_widget.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
abstract class BaseMailboxView extends GetWidget<MailboxController>
with
AppLoaderMixin,
MailboxWidgetMixin {
with AppLoaderMixin {
BaseMailboxView({Key? key}) : super(key: key);
@@ -44,4 +59,327 @@ abstract class BaseMailboxView extends GetWidget<MailboxController>
);
});
}
Widget buildFolders(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Obx(() {
if (controller.personalMailboxIsNotEmpty) {
return buildMailboxCategory(
context,
MailboxCategories.personalFolders,
controller.personalRootNode,
);
} else {
return const SizedBox.shrink();
}
}),
Obx(() {
if (controller.teamMailboxesIsNotEmpty) {
return buildMailboxCategory(
context,
MailboxCategories.teamMailboxes,
controller.teamMailboxesRootNode,
);
} else {
return const SizedBox.shrink();
}
}),
],
);
}
Widget buildMailboxCategory(
BuildContext context,
MailboxCategories categories,
MailboxNode mailboxNode,
) {
final isDesktop = controller.responsiveUtils.isDesktop(context);
switch (categories) {
case MailboxCategories.exchange:
return _buildBodyMailboxCategory(
context: context,
categories: categories,
mailboxNode: mailboxNode,
padding: isDesktop ? null : const EdgeInsets.symmetric(horizontal: 12),
);
default:
return Column(
children: [
Obx(() => MailboxCategoryWidget(
categories: categories,
expandMode: categories.getExpandMode(
controller.mailboxCategoriesExpandMode.value,
),
onToggleMailboxCategories: (categories, itemKey) =>
controller.toggleMailboxCategories(
categories,
controller.mailboxListScrollController,
itemKey,
),
isArrangeLTR: false,
showIcon: true,
padding: isDesktop
? const EdgeInsetsDirectional.only(start: 10)
: const EdgeInsets.symmetric(horizontal: 24),
height: isDesktop ? 36 : 40,
iconSpace: isDesktop
? null
: MailboxItemWidgetStyles.mobileLabelIconSpace,
labelTextStyle: isDesktop ? null : ThemeUtils.textStyleInter500(),
)),
Obx(() {
final categoriesExpandMode = controller
.mailboxCategoriesExpandMode
.value;
bool isExpand = categories.getExpandMode(categoriesExpandMode) ==
ExpandMode.EXPAND;
return AnimatedContainer(
duration: const Duration(milliseconds: 400),
child: isExpand
? _buildBodyMailboxCategory(
context: context,
categories: categories,
mailboxNode: mailboxNode,
padding: isDesktop
? const EdgeInsetsDirectional.only(start: 10)
: const EdgeInsetsDirectional.only(start: 24, end: 12),
)
: const Offstage(),
);
}),
],
);
}
}
Widget _buildBodyMailboxCategory({
required BuildContext context,
required MailboxCategories categories,
required MailboxNode mailboxNode,
EdgeInsetsGeometry? padding,
}) {
final treeView = TreeView(
key: Key('${categories.keyValue}_mailbox_list'),
children: _buildListChildTileWidget(context, mailboxNode),
);
if (padding != null) {
return Padding(padding: padding, child: treeView);
} else {
return treeView;
}
}
List<Widget> _buildListChildTileWidget(
BuildContext context,
MailboxNode parentNode,
) {
if (parentNode.childrenItems == null) {
return [];
}
return parentNode.childrenItems!.map((mailboxNode) {
final mailboxItemWidget = Obx(() => MailboxItemWidget(
mailboxNode: mailboxNode,
mailboxNodeSelected: controller
.mailboxDashBoardController
.selectedMailbox
.value,
onOpenMailboxFolderClick: (mailboxNode) =>
controller.openMailbox(context, mailboxNode.item),
onExpandFolderActionClick: mailboxNode.hasChildren()
? (mailboxNode, itemKey) => controller.toggleMailboxFolder(
mailboxNode,
controller.mailboxListScrollController,
itemKey,
)
: null,
onSelectMailboxFolderClick: controller.selectMailboxNode,
onLongPressMailboxNodeAction: PlatformInfo.isMobile
? (mailboxNode) => controller.handleLongPressMailboxNodeAction(
context,
mailboxNode.item,
)
: null,
onDragItemAccepted: PlatformInfo.isMobile
? null
: controller.handleDragItemAccepted,
onMenuActionClick: PlatformInfo.isMobile
? null
: (position, mailboxNode) {
controller.openMailboxContextMenuAction(
context,
position,
mailboxNode.item,
);
},
onEmptyMailboxActionCallback: PlatformInfo.isMobile
? null
: (mailboxNode) => controller.emptyMailboxAction(
context,
mailboxNode.item,
),
));
if (mailboxNode.hasChildren()) {
return TreeViewChild(
context,
key: const Key('children_tree_mailbox_child'),
isExpanded: mailboxNode.expandMode == ExpandMode.EXPAND,
paddingChild: const EdgeInsetsDirectional.only(start: 12),
parent: mailboxItemWidget,
children: _buildListChildTileWidget(context, mailboxNode),
).build();
} else {
return mailboxItemWidget;
}
}).toList();
}
Widget buildListMailbox(BuildContext context) {
final isDesktop = controller.responsiveUtils.isDesktop(context);
return SingleChildScrollView(
controller: controller.mailboxListScrollController,
key: const PageStorageKey('mailbox_list'),
physics: const AlwaysScrollableScrollPhysics(),
padding: isDesktop
? const EdgeInsetsDirectional.only(end: 16)
: const EdgeInsets.only(bottom: 16, top: 16),
child: Column(children: [
Obx(() => MailboxLoadingBarWidget(viewState: controller.viewState.value)),
Obx(() {
if (controller.defaultMailboxIsNotEmpty) {
return buildMailboxCategory(
context,
MailboxCategories.exchange,
controller.defaultRootNode,
);
} else {
return const SizedBox.shrink();
}
}),
Obx(() {
final sendingEmails = controller
.mailboxDashBoardController
.listSendingEmails;
final isSendingQueueSelected =
controller.mailboxDashBoardController.dashboardRoute.value == DashboardRoutes.sendingQueue;
if (sendingEmails.isNotEmpty && PlatformInfo.isMobile) {
return SendingQueueMailboxWidget(
imagePaths: controller.imagePaths,
responsiveUtils: controller.responsiveUtils,
listSendingEmails: sendingEmails,
onOpenSendingQueueAction: () =>
controller.openSendingQueueViewAction(context),
isSelected: isSendingQueueSelected,
);
} else {
return const SizedBox.shrink();
}
}),
Obx(() {
final sendingEmails = controller
.mailboxDashBoardController
.listSendingEmails;
if (controller.defaultMailboxIsNotEmpty ||
(sendingEmails.isNotEmpty && PlatformInfo.isMobile)) {
return const Padding(
padding: EdgeInsets.only(top: 8),
child: Divider(color: AppColor.folderDivider, height: 1),
);
} else {
return const SizedBox.shrink();
}
}),
Obx(() {
return FoldersBarWidget(
onOpenSearchFolder: () => controller.openSearchViewAction(context),
onAddNewFolder: () => controller.goToCreateNewMailboxView(context),
imagePaths: controller.imagePaths,
responsiveUtils: controller.responsiveUtils,
height: isDesktop ? 48 : 40,
padding: isDesktop
? null
: const EdgeInsetsDirectional.only(start: 24, end: 12),
labelStyle: isDesktop ? null : ThemeUtils.textStyleInter500(),
expandMode: controller.foldersExpandMode.value,
onToggleExpandFolder: controller.toggleExpandFolders,
);
}),
Obx(() => AnimatedContainer(
duration: const Duration(milliseconds: 400),
child: controller.foldersExpandMode.value == ExpandMode.EXPAND
? buildFolders(context)
: const Offstage(),
)),
Obx(() {
final accountId = controller
.mailboxDashBoardController
.accountId
.value;
if (accountId == null) return const SizedBox.shrink();
final contactSupportCapability = controller
.mailboxDashBoardController
.sessionCurrent
?.getContactSupportCapability(accountId);
if (contactSupportCapability?.isAvailable != true) {
return const SizedBox.shrink();
}
final isFoldersExpanded =
controller.foldersExpandMode.value == ExpandMode.EXPAND;
final dividerPadding = EdgeInsets.only(
top: isFoldersExpanded ? 8 : 0,
bottom: 8,
);
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: dividerPadding,
child: const Divider(color: AppColor.folderDivider, height: 1),
),
FolderWidget(
icon: controller.imagePaths.icHelp,
label: AppLocalizations.of(context).support,
onOpenFolderAction: () => controller
.mailboxDashBoardController
.onGetHelpOrReportBug(contactSupportCapability!),
tooltip: AppLocalizations.of(context).getHelpOrReportABug,
padding: isDesktop
? null
: const EdgeInsets.symmetric(horizontal: 12),
itemPadding: const EdgeInsets.symmetric(
horizontal: MailboxItemWidgetStyles.mobileItemPadding,
),
borderRadius: isDesktop
? null
: MailboxItemWidgetStyles.mobileBorderRadius,
height: isDesktop
? null
: MailboxItemWidgetStyles.mobileHeight,
labelTextStyle: isDesktop
? null
: ThemeUtils.textStyleInter500(),
),
],
);
}),
]),
);
}
}
@@ -0,0 +1,110 @@
import 'package:core/utils/app_logger.dart';
import 'package:flutter/material.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_controller.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/utils/mailbox_utils.dart';
extension HandleMailboxActionExtension on MailboxController {
void handleLongPressMailboxNodeAction(
BuildContext context,
PresentationMailbox mailbox,
) {
final deletedMessageVaultSupported =
MailboxUtils.isDeletedMessageVaultSupported(session, accountId);
final isSubAddressingSupported =
session?.isSubAddressingSupported(accountId) ?? false;
final contextMenuActions = listContextMenuItemAction(
mailbox,
mailboxDashBoardController.enableSpamReport,
deletedMessageVaultSupported,
isSubAddressingSupported,
);
if (contextMenuActions.isEmpty) {
return;
}
openContextMenuAction(
context,
contextMenuMailboxActionTiles(
context,
imagePaths,
mailbox,
contextMenuActions,
handleMailboxAction: handleMailboxAction,
),
);
}
void handleDragItemAccepted(
List<PresentationEmail> listEmails,
PresentationMailbox presentationMailbox,
) {
final mailboxPath = findNodePath(presentationMailbox.id)
?? presentationMailbox.name?.name;
log('HandleMailboxActionExtension::handleDragItemAccepted():mailboxPath = $mailboxPath');
final newMailbox = mailboxPath != null
? presentationMailbox.toPresentationMailboxWithMailboxPath(mailboxPath)
: presentationMailbox;
mailboxDashBoardController.dragSelectedMultipleEmailToMailboxAction(
listEmails,
newMailbox,
);
}
void openMailboxContextMenuAction(
BuildContext context,
RelativeRect position,
PresentationMailbox mailbox,
) {
final deletedMessageVaultSupported =
MailboxUtils.isDeletedMessageVaultSupported(session, accountId);
final isSubAddressingSupported =
session?.isSubAddressingSupported(accountId) ?? false;
final contextMenuActions = listContextMenuItemAction(
mailbox,
mailboxDashBoardController.enableSpamReport,
deletedMessageVaultSupported,
isSubAddressingSupported,
);
if (contextMenuActions.isEmpty) {
return;
}
if (responsiveUtils.isScreenWithShortestSide(context)) {
openContextMenuAction(
context,
contextMenuMailboxActionTiles(
context,
imagePaths,
mailbox,
contextMenuActions,
handleMailboxAction: handleMailboxAction,
)
);
} else {
openPopupMenuAction(
context,
position,
popupMenuMailboxActionTiles(
context,
imagePaths,
mailbox,
contextMenuActions,
handleMailboxAction: handleMailboxAction,
),
);
}
}
}
@@ -10,6 +10,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosyst
extension OpenAppGridExtension on MailboxController {
void openAppGrid(List<AppLinagoraEcosystem> linagoraApps) {
log('OpenAppGridExtension::openAppGrid: length of linagoraApps = ${linagoraApps.length}');
mailboxDashBoardController.isAppGridDialogDisplayed.value = true;
Get.dialog(
Center(
child: Dialog(
@@ -55,7 +56,8 @@ extension OpenAppGridExtension on MailboxController {
),
),
barrierColor: AppColor.blackAlpha20,
);
).whenComplete(() =>
mailboxDashBoardController.isAppGridDialogDisplayed.value = false);
}
Future<void> _handleOpenApp(AppLinagoraEcosystem app) async {
@@ -71,6 +71,7 @@ import 'package:tmail_ui_user/features/mailbox/domain/usecases/subscribe_mailbox
import 'package:tmail_ui_user/features/mailbox/domain/usecases/subscribe_multiple_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/action/mailbox_ui_action.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_categories_expand_mode.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart';
@@ -105,7 +106,8 @@ import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart';
class MailboxController extends BaseMailboxController
with MailboxActionHandlerMixin,
ContactSupportMixin,
LauncherApplicationMixin {
LauncherApplicationMixin,
MailboxWidgetMixin {
final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
final isMailboxListScrollable = false.obs;
@@ -1,21 +1,7 @@
import 'package:core/core.dart';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/base/widget/application_version_widget.dart';
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/base_mailbox_view.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/extensions/toggle_expand_folders_extension.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_categories.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/styles/mailbox_item_widget_styles.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/folder_widget.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/folders_bar_widget.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_category_widget.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_item_widget.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_loading_bar_widget.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/sending_queue_mailbox_widget.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart';
import 'package:tmail_ui_user/features/quotas/presentation/quotas_view.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
@@ -28,6 +14,7 @@ class MailboxView extends BaseMailboxView {
return Drawer(
backgroundColor: Colors.white,
shape: InputBorder.none,
shadowColor: AppColor.blackAlpha20,
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -37,25 +24,19 @@ class MailboxView extends BaseMailboxView {
child: RefreshIndicator(
color: AppColor.primaryColor,
onRefresh: controller.refreshAllMailbox,
child: _buildListMailbox(context),
),
),
QuotasView(
labelStyle: Theme.of(context).textTheme.bodySmall?.copyWith(
color: AppColor.steelGray400,
),
labelMaxLines: 1,
padding: const EdgeInsetsDirectional.only(
start: 24,
end: 23,
top: 16,
child: buildListMailbox(context),
),
),
const QuotasView(),
Container(
alignment: AlignmentDirectional.centerStart,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
padding: const EdgeInsetsDirectional.only(
bottom: 16,
start: 24,
end: 24,
),
child: ApplicationVersionWidget(
title: '${AppLocalizations.of(context).version} ',
title: '${AppLocalizations.of(context).version.toLowerCase()} ',
),
),
],
@@ -63,254 +44,4 @@ class MailboxView extends BaseMailboxView {
),
);
}
Widget _buildListMailbox(BuildContext context) {
return SingleChildScrollView(
controller: controller.mailboxListScrollController,
key: const PageStorageKey('mailbox_list'),
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.only(bottom: 16, top: 16),
child: Column(children: [
Obx(() => MailboxLoadingBarWidget(viewState: controller.viewState.value)),
Obx(() {
if (controller.defaultMailboxIsNotEmpty) {
return _buildMailboxCategory(
context,
MailboxCategories.exchange,
controller.defaultRootNode
);
} else {
return const SizedBox.shrink();
}
}),
Obx(() {
if (controller.mailboxDashBoardController.listSendingEmails.isNotEmpty) {
return SendingQueueMailboxWidget(
imagePaths: controller.imagePaths,
responsiveUtils: controller.responsiveUtils,
listSendingEmails: controller.mailboxDashBoardController.listSendingEmails,
onOpenSendingQueueAction: () => controller.openSendingQueueViewAction(context),
isSelected: controller.mailboxDashBoardController.dashboardRoute.value == DashboardRoutes.sendingQueue,
);
} else {
return const SizedBox.shrink();
}
}),
Obx(() {
if (controller.defaultMailboxIsNotEmpty ||
controller.mailboxDashBoardController.listSendingEmails.isNotEmpty) {
return const Padding(
padding: EdgeInsets.only(top: 8),
child: Divider(color: AppColor.folderDivider, height: 1),
);
} else {
return const SizedBox.shrink();
}
}),
Obx(() {
return FoldersBarWidget(
onOpenSearchFolder: () => controller.openSearchViewAction(context),
onAddNewFolder: () => controller.goToCreateNewMailboxView(context),
imagePaths: controller.imagePaths,
responsiveUtils: controller.responsiveUtils,
height: 40,
padding: const EdgeInsetsDirectional.only(start: 24, end: 12),
labelStyle: ThemeUtils.textStyleInter500(),
expandMode: controller.foldersExpandMode.value,
onToggleExpandFolder: controller.toggleExpandFolders,
);
}),
Obx(() => AnimatedContainer(
duration: const Duration(milliseconds: 400),
child: controller.foldersExpandMode.value == ExpandMode.EXPAND
? _buildFolders(context)
: const Offstage(),
)),
Obx(() {
final accountId = controller
.mailboxDashBoardController
.accountId
.value;
if (accountId == null) return const SizedBox.shrink();
final contactSupportCapability = controller
.mailboxDashBoardController
.sessionCurrent
?.getContactSupportCapability(accountId);
if (contactSupportCapability?.isAvailable != true) {
return const SizedBox.shrink();
}
final isFoldersExpanded =
controller.foldersExpandMode.value == ExpandMode.EXPAND;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: isFoldersExpanded
? const EdgeInsets.only(top: 8)
: EdgeInsets.zero,
child: const Divider(color: AppColor.folderDivider, height: 1),
),
FolderWidget(
icon: controller.imagePaths.icHelp,
label: AppLocalizations.of(context).support,
onOpenFolderAction: () => controller
.mailboxDashBoardController
.onGetHelpOrReportBug(contactSupportCapability!),
tooltip: AppLocalizations.of(context).getHelpOrReportABug,
padding: const EdgeInsets.symmetric(horizontal: 12),
itemPadding: const EdgeInsets.symmetric(
horizontal: MailboxItemWidgetStyles.mobileItemPadding,
),
borderRadius: MailboxItemWidgetStyles.mobileBorderRadius,
height: MailboxItemWidgetStyles.mobileHeight,
labelTextStyle: ThemeUtils.textStyleInter500(),
),
],
);
}),
])
);
}
Widget _buildFolders(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Obx(() {
if (controller.personalMailboxIsNotEmpty) {
return _buildMailboxCategory(
context,
MailboxCategories.personalFolders,
controller.personalRootNode,
);
} else {
return const SizedBox.shrink();
}
}),
Obx(() {
if (controller.teamMailboxesIsNotEmpty) {
return _buildMailboxCategory(
context,
MailboxCategories.teamMailboxes,
controller.teamMailboxesRootNode,
);
} else {
return const SizedBox.shrink();
}
}),
],
);
}
Widget _buildBodyMailboxCategory({
required BuildContext context,
required MailboxCategories categories,
required MailboxNode mailboxNode,
EdgeInsetsGeometry? padding,
}) {
final lastNode = mailboxNode.childrenItems?.last;
final treeView = TreeView(
key: Key('${categories.keyValue}_mailbox_list'),
children: _buildListChildTileWidget(
context,
mailboxNode,
lastNode: lastNode,
),
);
if (padding != null) {
return Padding(
padding: padding,
child: treeView,
);
} else {
return treeView;
}
}
Widget _buildMailboxCategory(BuildContext context, MailboxCategories categories, MailboxNode mailboxNode) {
if (categories == MailboxCategories.exchange) {
return _buildBodyMailboxCategory(
context: context,
categories: categories,
mailboxNode: mailboxNode,
padding: const EdgeInsets.symmetric(horizontal: 12),
);
}
return Column(children: [
Obx(() => MailboxCategoryWidget(
categories: categories,
expandMode: categories.getExpandMode(
controller.mailboxCategoriesExpandMode.value,
),
onToggleMailboxCategories: (categories, itemKey) =>
controller.toggleMailboxCategories(
categories,
controller.mailboxListScrollController,
itemKey,
),
isArrangeLTR: false,
showIcon: true,
padding: const EdgeInsets.symmetric(horizontal: 24),
height: 40,
iconSpace: MailboxItemWidgetStyles.mobileLabelIconSpace,
labelTextStyle: ThemeUtils.textStyleInter500(),
)),
AnimatedContainer(
duration: const Duration(milliseconds: 400),
child: categories.getExpandMode(controller.mailboxCategoriesExpandMode.value) == ExpandMode.EXPAND
? _buildBodyMailboxCategory(
context: context,
categories: categories,
mailboxNode: mailboxNode,
padding: const EdgeInsetsDirectional.only(
start: 24,
end: 12,
),
)
: const Offstage())
]);
}
List<Widget> _buildListChildTileWidget(BuildContext context, MailboxNode parentNode, {MailboxNode? lastNode}) {
return parentNode.childrenItems
?.map((mailboxNode) {
if (mailboxNode.hasChildren()) {
return TreeViewChild(
context,
key: const Key('children_tree_mailbox_child'),
isExpanded: mailboxNode.expandMode == ExpandMode.EXPAND,
paddingChild: const EdgeInsetsDirectional.only(start: 12),
parent: Obx(() => MailboxItemWidget(
mailboxNode: mailboxNode,
mailboxNodeSelected: controller.mailboxDashBoardController.selectedMailbox.value,
onLongPressMailboxNodeAction: (mailboxNode) => openMailboxMenuActionOnMobile(context, controller.imagePaths, mailboxNode.item, controller),
onOpenMailboxFolderClick: (mailboxNode) => controller.openMailbox(context, mailboxNode.item),
onExpandFolderActionClick: (mailboxNode, itemKey) =>
controller.toggleMailboxFolder(
mailboxNode,
controller.mailboxListScrollController,
itemKey,
),
onSelectMailboxFolderClick: (mailboxNode) => controller.selectMailboxNode(mailboxNode),
)),
children: _buildListChildTileWidget(context, mailboxNode)
).build();
} else {
return Obx(() => MailboxItemWidget(
mailboxNode: mailboxNode,
mailboxNodeSelected: controller.mailboxDashBoardController.selectedMailbox.value,
onLongPressMailboxNodeAction: (mailboxNode) => openMailboxMenuActionOnMobile(context, controller.imagePaths, mailboxNode.item, controller),
onOpenMailboxFolderClick: (mailboxNode) => controller.openMailbox(context, mailboxNode.item),
onSelectMailboxFolderClick: (mailboxNode) => controller.selectMailboxNode(mailboxNode)
));
}
}).toList() ?? <Widget>[];
}
}
@@ -1,24 +1,13 @@
import 'package:core/core.dart';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/base/widget/application_logo_with_text_widget.dart';
import 'package:tmail_ui_user/features/base/widget/application_version_widget.dart';
import 'package:tmail_ui_user/features/base/widget/scrollbar_list_view.dart';
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/base_mailbox_view.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_categories.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/app_grid_view.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/folder_widget.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/folders_bar_widget.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_category_widget.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_item_widget.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_loading_bar_widget.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/user_information_widget.dart';
import 'package:tmail_ui_user/features/quotas/presentation/quotas_view.dart';
import 'package:tmail_ui_user/features/quotas/presentation/styles/quotas_view_styles.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class MailboxView extends BaseMailboxView {
@@ -27,182 +16,49 @@ class MailboxView extends BaseMailboxView {
@override
Widget build(BuildContext context) {
final isDesktop = controller.responsiveUtils.isDesktop(context);
return Drawer(
elevation: controller.responsiveUtils.isDesktop(context) ? 0 : 16.0,
shape: controller.responsiveUtils.isDesktop(context)
? const RoundedRectangleBorder()
: null,
child: Scaffold(
backgroundColor: controller.responsiveUtils.isWebDesktop(context)
? AppColor.colorBgDesktop
: Colors.white,
body: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
if (!controller.responsiveUtils.isDesktop(context))
ApplicationLogoWidthTextWidget(
margin: const EdgeInsetsDirectional.only(
top: 16,
bottom: 16,
start: 16,
),
),
if (!controller.responsiveUtils.isDesktop(context))
const Divider(
color: AppColor.colorDividerMailbox,
height: 0.5,
thickness: 0.2),
Expanded(child: Container(
padding: EdgeInsetsDirectional.only(start: controller.responsiveUtils.isDesktop(context) ? 16 : 0),
color: controller.responsiveUtils.isDesktop(context)
? AppColor.colorBgDesktop
: Colors.white,
child: Container(
color: controller.responsiveUtils.isDesktop(context)
? AppColor.colorBgDesktop
: Colors.white,
child: _buildListMailbox(context),
),
)),
const Divider(color: AppColor.colorDividerHorizontal),
const QuotasView(
padding: EdgeInsetsDirectional.only(
start: QuotasViewStyles.padding,
top: QuotasViewStyles.padding,
),
backgroundColor: isDesktop ? AppColor.colorBgDesktop : Colors.white,
shape: InputBorder.none,
shadowColor: AppColor.blackAlpha20,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isDesktop) buildMailboxAppBar(),
Expanded(
child: isDesktop
? Padding(
padding: const EdgeInsetsDirectional.only(start: 16),
child: _buildListMailbox(context),
)
: _buildListMailbox(context),
),
const QuotasView(),
Container(
width: double.infinity,
alignment: controller.responsiveUtils.isDesktop(context)
? AlignmentDirectional.centerStart
: AlignmentDirectional.center,
padding: const EdgeInsets.all(16),
color: controller.responsiveUtils.isWebDesktop(context)
? AppColor.colorBgDesktop
: Colors.white,
alignment: isDesktop
? AlignmentDirectional.center
: AlignmentDirectional.centerStart,
padding: const EdgeInsetsDirectional.only(
bottom: 16,
start: 24,
end: 24,
),
child: ApplicationVersionWidget(
title: '${AppLocalizations.of(context).version.toLowerCase()} ',
textStyle: isDesktop
? ThemeUtils.textStyleContentCaption()
: null,
),
),
]),
)
],
),
);
}
Widget _buildListMailbox(BuildContext context) {
final mailboxListWidget = SingleChildScrollView(
controller: controller.mailboxListScrollController,
key: const PageStorageKey('mailbox_list'),
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsetsDirectional.only(end: controller.responsiveUtils.isDesktop(context) ? 16 : 0),
child: Column(children: [
if (!controller.responsiveUtils.isDesktop(context))
Obx(() => UserInformationWidget(
userName: controller.mailboxDashBoardController.accountId.value != null
? controller.mailboxDashBoardController.getOwnEmailAddress()
: '',
subtitle: AppLocalizations.of(context).manage_account,
onSubtitleClick: controller.mailboxDashBoardController.goToSettings,
border: const Border(
bottom: BorderSide(
color: AppColor.colorDividerHorizontal,
width: 0.5,
)
),
)),
Obx(() => MailboxLoadingBarWidget(viewState: controller.viewState.value)),
Obx(() {
final linagoraApps = controller
.mailboxDashBoardController
.appGridDashboardController
.listLinagoraApp;
final mailboxListWidget = buildListMailbox(context);
if (linagoraApps.isNotEmpty && !controller.responsiveUtils.isDesktop(context)) {
return AppGridView(linagoraApps: linagoraApps);
} else {
return const SizedBox.shrink();
}
}),
const SizedBox(height: 8),
Obx(() {
if (controller.defaultMailboxIsNotEmpty) {
return _buildMailboxCategory(
context,
MailboxCategories.exchange,
controller.defaultRootNode
);
} else {
return const SizedBox.shrink();
}
}),
const SizedBox(height: 8),
const Divider(color: AppColor.colorDividerMailbox, height: 1),
FoldersBarWidget(
onOpenSearchFolder: () => controller.openSearchViewAction(context),
onAddNewFolder: () => controller.goToCreateNewMailboxView(context),
imagePaths: controller.imagePaths,
responsiveUtils: controller.responsiveUtils,
),
Obx(() {
if (controller.personalMailboxIsNotEmpty) {
return _buildMailboxCategory(
context,
MailboxCategories.personalFolders,
controller.personalRootNode
);
} else {
return const SizedBox.shrink();
}
}),
Obx(() {
if (controller.teamMailboxesIsNotEmpty) {
return _buildMailboxCategory(
context,
MailboxCategories.teamMailboxes,
controller.teamMailboxesRootNode
);
} else {
return const SizedBox.shrink();
}
}),
Obx(() {
final accountId = controller
.mailboxDashBoardController
.accountId
.value;
if (accountId == null) return const SizedBox.shrink();
final contactSupportCapability = controller
.mailboxDashBoardController
.sessionCurrent
?.getContactSupportCapability(accountId);
if (contactSupportCapability?.isAvailable != true) {
return const SizedBox.shrink();
}
return Column(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(height: 8),
const Divider(color: AppColor.colorDividerMailbox, height: 1),
const SizedBox(height: 8),
FolderWidget(
icon: controller.imagePaths.icHelp,
label: AppLocalizations.of(context).support,
onOpenFolderAction: () => controller
.mailboxDashBoardController
.onGetHelpOrReportBug(contactSupportCapability!),
tooltip: AppLocalizations.of(context).getHelpOrReportABug,
padding: !controller.responsiveUtils.isDesktop(context)
? const EdgeInsets.symmetric(horizontal: 16)
: null,
),
const SizedBox(height: 16),
],
);
}),
])
);
return Stack(
children: [
if (!PlatformInfo.isCanvasKit)
@@ -249,157 +105,4 @@ class MailboxView extends BaseMailboxView {
],
);
}
Widget _buildBodyMailboxCategory({
required BuildContext context,
required MailboxCategories categories,
required MailboxNode mailboxNode,
EdgeInsetsGeometry? padding,
}) {
final lastNode = mailboxNode.childrenItems?.last;
final treeView = TreeView(
key: Key('${categories.keyValue}_mailbox_list'),
children: _buildListChildTileWidget(
context,
mailboxNode,
lastNode: lastNode,
),
);
if (padding != null) {
return Padding(
padding: padding,
child: treeView,
);
} else {
return treeView;
}
}
Widget _buildMailboxCategory(
BuildContext context,
MailboxCategories categories,
MailboxNode mailboxNode
) {
if (categories == MailboxCategories.exchange) {
return _buildBodyMailboxCategory(
context: context,
categories: categories,
mailboxNode: mailboxNode,
padding: !controller.responsiveUtils.isDesktop(context)
? const EdgeInsets.symmetric(horizontal: 16)
: null,
);
} else {
return Column(
children: [
Obx(() => MailboxCategoryWidget(
categories: categories,
expandMode: categories.getExpandMode(
controller.mailboxCategoriesExpandMode.value,
),
onToggleMailboxCategories: (categories, itemKey) =>
controller.toggleMailboxCategories(
categories,
controller.mailboxListScrollController,
itemKey,
),
isArrangeLTR: false,
showIcon: true,
padding: !controller.responsiveUtils.isDesktop(context)
? const EdgeInsetsDirectional.only(start: 26, end: 16)
: const EdgeInsetsDirectional.only(start: 10),
)),
AnimatedContainer(
duration: const Duration(milliseconds: 400),
child: categories.getExpandMode(controller.mailboxCategoriesExpandMode.value) == ExpandMode.EXPAND
? _buildBodyMailboxCategory(
context: context,
categories: categories,
mailboxNode: mailboxNode,
padding: !controller.responsiveUtils.isDesktop(context)
? const EdgeInsetsDirectional.only(start: 30, end: 16)
: const EdgeInsetsDirectional.only(start: 10),
)
: const Offstage()
),
const SizedBox(height: 8)
],
);
}
}
List<Widget> _buildListChildTileWidget(
BuildContext context,
MailboxNode parentNode,
{MailboxNode? lastNode}
) {
return parentNode.childrenItems?.map((mailboxNode) {
if (mailboxNode.hasChildren()) {
return TreeViewChild(
context,
key: const Key('children_tree_mailbox_child'),
isExpanded: mailboxNode.expandMode == ExpandMode.EXPAND,
paddingChild: const EdgeInsetsDirectional.only(start: 14),
parent: Obx(() => MailboxItemWidget(
mailboxNode: mailboxNode,
mailboxNodeSelected: controller.mailboxDashBoardController.selectedMailbox.value,
onOpenMailboxFolderClick: (mailboxNode) => controller.openMailbox(context, mailboxNode.item),
onExpandFolderActionClick: (mailboxNode, itemKey) =>
controller.toggleMailboxFolder(
mailboxNode,
controller.mailboxListScrollController,
itemKey,
),
onSelectMailboxFolderClick: controller.selectMailboxNode,
onDragItemAccepted: _handleDragItemAccepted,
onMenuActionClick: (position, mailboxNode) {
openMailboxMenuActionOnWeb(
context,
controller.imagePaths,
controller.responsiveUtils,
position,
mailboxNode.item,
controller
);
},
onEmptyMailboxActionCallback: (mailboxNode) => controller.emptyMailboxAction(context, mailboxNode.item),
)),
children: _buildListChildTileWidget(context, mailboxNode)
).build();
} else {
return Obx(() => MailboxItemWidget(
mailboxNode: mailboxNode,
mailboxNodeSelected: controller.mailboxDashBoardController.selectedMailbox.value,
onOpenMailboxFolderClick: (mailboxNode) => controller.openMailbox(context, mailboxNode.item),
onSelectMailboxFolderClick: controller.selectMailboxNode,
onDragItemAccepted: _handleDragItemAccepted,
onMenuActionClick: (position, mailboxNode) {
openMailboxMenuActionOnWeb(
context,
controller.imagePaths,
controller.responsiveUtils,
position,
mailboxNode.item,
controller
);
},
onEmptyMailboxActionCallback: (mailboxNode) => controller.emptyMailboxAction(context, mailboxNode.item),
));
}
}).toList() ?? <Widget>[];
}
void _handleDragItemAccepted(List<PresentationEmail> listEmails, PresentationMailbox presentationMailbox) {
final mailboxPath = controller.findNodePath(presentationMailbox.id)
?? presentationMailbox.name?.name;
log('MailboxView::_handleDragItemAccepted(): mailboxPath: $mailboxPath');
if (mailboxPath != null) {
final newMailbox = presentationMailbox.toPresentationMailboxWithMailboxPath(mailboxPath);
controller.mailboxDashBoardController.dragSelectedMultipleEmailToMailboxAction(listEmails, newMailbox);
} else {
controller.mailboxDashBoardController.dragSelectedMultipleEmailToMailboxAction(listEmails, presentationMailbox);
}
}
}
@@ -2,18 +2,11 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:model/mailbox/mailbox_constants.dart';
import 'package:model/model.dart';
import 'package:tmail_ui_user/features/base/widget/popup_item_widget.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_controller.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/context_item_mailbox_action.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/utils/mailbox_utils.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_bottom_sheet_action_tile_builder.dart';
import 'package:tmail_ui_user/main/error/capability_validator.dart';
mixin MailboxWidgetMixin {
@@ -105,43 +98,6 @@ mixin MailboxWidgetMixin {
}
}
void openMailboxMenuActionOnMobile(
BuildContext context,
ImagePaths imagePaths,
PresentationMailbox mailbox,
MailboxController controller
) {
final bool deletedMessageVaultSupported = MailboxUtils.isDeletedMessageVaultSupported(
controller.mailboxDashBoardController.sessionCurrent,
controller.mailboxDashBoardController.accountId.value);
final bool subaddressingSupported = isSubaddressingSupported(
controller.mailboxDashBoardController.sessionCurrent,
controller.mailboxDashBoardController.accountId.value);
final contextMenuActions = listContextMenuItemAction(
mailbox,
controller.mailboxDashBoardController.enableSpamReport,
deletedMessageVaultSupported,
subaddressingSupported,
);
if (contextMenuActions.isEmpty) {
return;
}
controller.openContextMenuAction(
context,
contextMenuMailboxActionTiles(
context,
imagePaths,
mailbox,
contextMenuActions,
handleMailboxAction: controller.handleMailboxAction
)
);
}
List<Widget> contextMenuMailboxActionTiles(
BuildContext context,
ImagePaths imagePaths,
@@ -207,59 +163,6 @@ mixin MailboxWidgetMixin {
return listContextMenuItemAction;
}
void openMailboxMenuActionOnWeb(
BuildContext context,
ImagePaths imagePaths,
ResponsiveUtils responsiveUtils,
RelativeRect position,
PresentationMailbox mailbox,
MailboxController controller
) {
final bool deletedMessageVaultSupported = MailboxUtils.isDeletedMessageVaultSupported(
controller.mailboxDashBoardController.sessionCurrent,
controller.mailboxDashBoardController.accountId.value);
final bool subaddressingSupported = isSubaddressingSupported(
controller.mailboxDashBoardController.sessionCurrent,
controller.mailboxDashBoardController.accountId.value);
final contextMenuActions = listContextMenuItemAction(
mailbox,
controller.mailboxDashBoardController.enableSpamReport,
deletedMessageVaultSupported,
subaddressingSupported,
);
if (contextMenuActions.isEmpty) {
return;
}
if (responsiveUtils.isScreenWithShortestSide(context)) {
controller.openContextMenuAction(
context,
contextMenuMailboxActionTiles(
context,
imagePaths,
mailbox,
contextMenuActions,
handleMailboxAction: controller.handleMailboxAction
)
);
} else {
controller.openPopupMenuAction(
context,
position,
popupMenuMailboxActionTiles(
context,
imagePaths,
mailbox,
contextMenuActions,
handleMailboxAction: controller.handleMailboxAction
)
);
}
}
List<PopupMenuEntry> popupMenuMailboxActionTiles(
BuildContext context,
ImagePaths imagePaths,
@@ -280,20 +183,6 @@ mixin MailboxWidgetMixin {
.toList();
}
static bool isSubaddressingSupported(Session? session, AccountId? accountId) {
if (session == null || accountId == null) {
return false;
}
if (!CapabilityIdentifier.jmapTeamMailboxes.isSupported(session, accountId)) {
return false;
}
return (session.getCapabilityProperties(accountId, CapabilityIdentifier.jmapTeamMailboxes)
?.props[0] as Map<String, dynamic>?)
?[subaddressingSupported]
?? false;
}
PopupMenuItem _buildPopupMenuItem(
BuildContext context,
ImagePaths imagePaths,
@@ -17,7 +17,7 @@ class CountOfEmailsWidget extends StatelessWidget {
overflow: TextOverflow.ellipsis,
style: PlatformInfo.isMobile
? ThemeUtils.textStyleInter400()
: Theme.of(context).textTheme.bodyMedium?.copyWith(
: Theme.of(context).textTheme.bodySmall?.copyWith(
color: Colors.black,
),
);
@@ -72,7 +72,6 @@ class FolderWidget extends StatelessWidget {
borderRadius: BorderRadius.all(
Radius.circular(borderRadius ?? MailboxItemWidgetStyles.borderRadius),
),
hoverColor: AppColor.blue100,
child: item,
),
);
@@ -4,7 +4,6 @@ import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart';
import 'package:model/mailbox/expand_mode.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/extensions/expand_mode_extension.dart';
@@ -36,6 +35,8 @@ class FoldersBarWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isDesktop = responsiveUtils.isDesktop(context);
Widget searchBarIcon = TMailButtonWidget.fromIcon(
icon: imagePaths.icSearchBar,
backgroundColor: Colors.transparent,
@@ -45,7 +46,7 @@ class FoldersBarWidget extends StatelessWidget {
onTapActionCallback: onOpenSearchFolder,
);
if (responsiveUtils.isWebDesktop(context)) {
if (isDesktop) {
searchBarIcon = Transform(
transform: Matrix4.translationValues(8, 0, 0),
child: searchBarIcon,
@@ -62,7 +63,7 @@ class FoldersBarWidget extends StatelessWidget {
onTapActionCallback: onAddNewFolder,
);
if (responsiveUtils.isWebDesktop(context)) {
if (isDesktop) {
newFolderIcon = Transform(
transform: Matrix4.translationValues(8, 0, 0),
child: newFolderIcon,
@@ -78,8 +79,8 @@ class FoldersBarWidget extends StatelessWidget {
return Container(
padding: padding ?? EdgeInsetsDirectional.only(
start: responsiveUtils.isWebDesktop(context) ? 10 : 26,
end: responsiveUtils.isWebDesktop(context) ? 0 : 8,
start: isDesktop ? 10 : 26,
end: isDesktop ? 0 : 8,
),
height: height ?? 48,
child: Row(
@@ -94,9 +95,12 @@ class FoldersBarWidget extends StatelessWidget {
DirectionUtils.isDirectionRTLByLanguage(context),
),
iconColor: Colors.black,
iconSize: PlatformInfo.isMobile ? 17 : 20,
iconSize: 17,
margin: isDesktop
? const EdgeInsetsDirectional.only(start: 8)
: null,
backgroundColor: Colors.transparent,
padding: EdgeInsets.all(PlatformInfo.isMobile ? 3 : 5),
padding: const EdgeInsets.all(3),
tooltipMessage: expandMode!.getTooltipMessage(AppLocalizations.of(context)),
onTapActionCallback: () => onToggleExpandFolder?.call(),
)
@@ -3,7 +3,6 @@ import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:core/presentation/views/text/text_overflow_builder.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart';
import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart';
@@ -72,7 +71,8 @@ class _LabelMailboxItemWidgetState extends State<LabelMailboxItemWidget> {
itemKey: widget.itemKey,
mailboxNode: widget.mailboxNode,
imagePaths: widget.imagePaths,
onExpandFolderActionClick: widget.onClickExpandMailboxNodeAction,
responsiveUtils: widget.responsiveUtils,
onExpandFolderActionClick: onClickExpandMailboxNodeAction,
),
],
);
@@ -163,13 +163,13 @@ class _LabelMailboxItemWidgetState extends State<LabelMailboxItemWidget> {
TextStyle get _displayNameTextStyle {
if (isSelected) {
return ThemeUtils.textStyleInter700(
color: PlatformInfo.isMobile ? AppColor.iconFolder : null,
color: responsiveUtils.isDesktop(context) ? null : AppColor.iconFolder,
fontSize: 14,
);
} else {
return PlatformInfo.isMobile
? ThemeUtils.textStyleInter500()
: ThemeUtils.textStyleBodyBody3(color: Colors.black);
return responsiveUtils.isWebDesktop(context)
? ThemeUtils.textStyleBodyBody3(color: Colors.black)
: ThemeUtils.textStyleInter500();
}
}
}
@@ -2,7 +2,6 @@ import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
@@ -79,12 +78,10 @@ class _MailboxCategoryWidgetState extends State<MailboxCategoryWidget> {
DirectionUtils.isDirectionRTLByLanguage(context),
),
iconColor: Colors.black,
iconSize: PlatformInfo.isMobile ? 17 : 20,
iconSize: 17,
backgroundColor: Colors.transparent,
margin: PlatformInfo.isMobile
? const EdgeInsetsDirectional.only(start: 8)
: null,
padding: EdgeInsets.all(PlatformInfo.isMobile ? 3 : 5),
margin: const EdgeInsetsDirectional.only(start: 8),
padding: const EdgeInsets.all(3),
tooltipMessage: widget.expandMode.getTooltipMessage(AppLocalizations.of(context)),
onTapActionCallback: () =>
widget.onToggleMailboxCategories(widget.categories, _key)),
@@ -1,8 +1,8 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:core/utils/direction_utils.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart';
import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/extensions/expand_mode_extension.dart';
@@ -14,6 +14,7 @@ class MailboxExpandButton extends StatelessWidget {
final GlobalKey itemKey;
final MailboxNode mailboxNode;
final ImagePaths imagePaths;
final ResponsiveUtils responsiveUtils;
final OnClickExpandMailboxNodeAction? onExpandFolderActionClick;
const MailboxExpandButton({
@@ -21,6 +22,7 @@ class MailboxExpandButton extends StatelessWidget {
required this.itemKey,
required this.mailboxNode,
required this.imagePaths,
required this.responsiveUtils,
required this.onExpandFolderActionClick,
});
@@ -32,11 +34,9 @@ class MailboxExpandButton extends StatelessWidget {
DirectionUtils.isDirectionRTLByLanguage(context),
),
iconColor: _expandIconColor,
iconSize: PlatformInfo.isMobile ? 17 : 20,
margin: PlatformInfo.isMobile
? const EdgeInsetsDirectional.only(start: 8)
: null,
padding: EdgeInsets.all(PlatformInfo.isMobile ? 3 : 5),
iconSize: 17,
margin: const EdgeInsetsDirectional.only(start: 8),
padding: const EdgeInsets.all(3),
backgroundColor: Colors.transparent,
tooltipMessage: mailboxNode.expandMode.getTooltipMessage(AppLocalizations.of(context)),
onTapActionCallback: () => onExpandFolderActionClick?.call(mailboxNode, itemKey),
@@ -142,11 +142,17 @@ class _MailboxItemWidgetState extends State<MailboxItemWidget> {
),
height: widget.mailboxNode.item.isTeamMailboxes
? MailboxItemWidgetStyles.teamMailboxHeight
: MailboxItemWidgetStyles.height,
: MailboxItemWidgetStyles.mobileHeight,
child: Row(
children: [
if (_isIconDisplayed)
MailboxIconWidget(icon: _iconMailbox),
MailboxIconWidget(
icon: _iconMailbox,
padding: const EdgeInsetsDirectional.only(
end: MailboxItemWidgetStyles.mobileLabelIconSpace,
),
color: AppColor.iconFolder,
),
Expanded(
child: LabelMailboxItemWidget(
itemKey: _key,
@@ -36,8 +36,12 @@ class TrailingMailboxItemWidget extends StatelessWidget {
? EdgeInsets.zero
: TrailingMailboxItemWidgetStyles.menuIconMargin,
icon: imagePaths.icMoreVertical,
iconSize: TrailingMailboxItemWidgetStyles.menuIconSize,
padding: TrailingMailboxItemWidgetStyles.menuIconPadding,
iconSize: mailboxNode.item.isTeamMailboxes
? 17
: TrailingMailboxItemWidgetStyles.menuIconSize,
padding: mailboxNode.item.isTeamMailboxes
? const EdgeInsets.all(3)
: TrailingMailboxItemWidgetStyles.menuIconPadding,
backgroundColor: TrailingMailboxItemWidgetStyles.menuIconBackgroundColor,
onTapActionAtPositionCallback: (position) => onMenuActionClick?.call(position, mailboxNode),
);
@@ -258,6 +258,7 @@ class MailboxDashBoardController extends ReloadableController
final isRecoveringDeletedMessage = RxBool(false);
final localFileDraggableAppState = Rxn<DraggableAppState>();
final isSenderImportantFlagEnabled = RxBool(true);
final isAppGridDialogDisplayed = RxBool(false);
Session? sessionCurrent;
Map<Role, MailboxId> mapDefaultMailboxIdByRole = {};
@@ -264,7 +264,10 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView {
key: controller.scaffoldKey,
drawer: ResponsiveWidget(
responsiveUtils: controller.responsiveUtils,
mobile: SizedBox(width: ResponsiveUtils.defaultSizeDrawer, child: MailboxView()),
mobile: SizedBox(
width: ResponsiveUtils.mobileLeftMenuSize,
child: MailboxView(),
),
tabletLarge: SizedBox(width: ResponsiveUtils.defaultSizeLeftMenuMobile, child: MailboxView()),
desktop: const SizedBox.shrink()
),
@@ -11,114 +11,96 @@ import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class QuotasView extends GetWidget<QuotasController> {
final EdgeInsetsGeometry? padding;
final TextStyle? labelStyle;
final int? labelMaxLines;
const QuotasView({
super.key,
this.padding,
this.labelStyle,
this.labelMaxLines,
});
const QuotasView({super.key});
@override
Widget build(BuildContext context) {
return Obx(() {
final octetQuota = controller.octetsQuota.value;
if (octetQuota != null && octetQuota.storageAvailable) {
return LayoutBuilder(builder: (context, constraints) {
return Container(
padding: padding ?? const EdgeInsetsDirectional.only(
start: QuotasViewStyles.padding,
top: QuotasViewStyles.padding,
),
margin: controller.responsiveUtils.isWebDesktop(context)
? const EdgeInsetsDirectional.only(end: QuotasViewStyles.margin)
: null,
decoration: BoxDecoration(
color: controller.responsiveUtils.isWebDesktop(context)
? QuotasViewStyles.webBackgroundColor
: QuotasViewStyles.mobileBackgroundColor,
),
alignment: AlignmentDirectional.centerStart,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
SvgPicture.asset(
controller.imagePaths.icQuotas,
width: QuotasViewStyles.iconSize,
height: QuotasViewStyles.iconSize,
colorFilter: AppColor.steelGray400.asFilter(),
fit: BoxFit.fill,
),
const SizedBox(width: QuotasViewStyles.iconPadding),
Flexible(
child: Text(
AppLocalizations.of(context).storageQuotas,
style: labelStyle ?? const TextStyle(
fontSize: QuotasViewStyles.labelTextSize,
fontWeight: QuotasViewStyles.labelFontWeight,
color: QuotasViewStyles.labelTextColor,
),
maxLines: labelMaxLines ?? 2,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(width: QuotasViewStyles.iconPadding),
Obx(() {
final isLoading = controller.viewState.value.fold(
(failure) => false,
(success) => success is GetQuotasLoading,
);
bool isDesktop = controller.responsiveUtils.isDesktop(context);
return QuotaReloadButton(
icon: controller.imagePaths.icRefresh,
isLoading: isLoading,
onTap: controller.reloadQuota,
);
}),
],
),
const SizedBox(height: QuotasViewStyles.space),
SizedBox(
width: _getProgressBarMaxWith(constraints.maxWidth),
child: LinearProgressIndicator(
color: octetQuota.getQuotasStateProgressBarColor(),
minHeight: QuotasViewStyles.progressBarHeight,
backgroundColor: QuotasViewStyles.progressBarBackgroundColor,
value: octetQuota.usedStoragePercent,
if (octetQuota != null && octetQuota.storageAvailable) {
final quotasWidget = Container(
padding: isDesktop
? const EdgeInsets.all(16)
: const EdgeInsets.symmetric(vertical: 16),
margin: isDesktop
? const EdgeInsetsDirectional.only(start: 8)
: const EdgeInsetsDirectional.only(start: 24),
width: isDesktop ? 224 : 196,
alignment: isDesktop
? AlignmentDirectional.center
: AlignmentDirectional.centerStart,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
SvgPicture.asset(
controller.imagePaths.icQuotas,
width: QuotasViewStyles.iconSize,
height: QuotasViewStyles.iconSize,
colorFilter: AppColor.steelGray400.asFilter(),
fit: BoxFit.fill,
),
),
const SizedBox(height: QuotasViewStyles.space),
Text(
octetQuota.getQuotasStateTitle(context),
style: labelStyle ?? TextStyle(
fontSize: QuotasViewStyles.progressStateTextSize,
fontWeight: QuotasViewStyles.progressStateFontWeight,
color: octetQuota.getQuotasStateTitleColor(),
const SizedBox(width: QuotasViewStyles.iconPadding),
Expanded(
child: Text(
AppLocalizations.of(context).storageQuotas,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: AppColor.steelGray400,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
maxLines: labelMaxLines ?? 2,
overflow: TextOverflow.ellipsis,
)
],
),
);
});
const SizedBox(width: QuotasViewStyles.iconPadding),
Obx(() {
final isLoading = controller.viewState.value.fold(
(failure) => false,
(success) => success is GetQuotasLoading,
);
return QuotaReloadButton(
icon: controller.imagePaths.icRefreshQuotas,
isLoading: isLoading,
onTap: controller.reloadQuota,
);
}),
],
),
const SizedBox(height: QuotasViewStyles.space),
SizedBox(
width: QuotasViewStyles.progressBarMaxWidth,
child: LinearProgressIndicator(
color: octetQuota.getQuotasStateProgressBarColor(),
minHeight: QuotasViewStyles.progressBarHeight,
backgroundColor: QuotasViewStyles.progressBarBackgroundColor,
value: octetQuota.usedStoragePercent,
),
),
const SizedBox(height: QuotasViewStyles.space),
Text(
octetQuota.getQuotasStateTitle(context),
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: octetQuota.getQuotasStateTitleColor(),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
)
],
),
);
if (isDesktop) {
return Center(child: quotasWidget);
} else {
return quotasWidget;
}
} else {
return const SizedBox.shrink();
return const SizedBox(height: 16);
}
});
}
double _getProgressBarMaxWith(double maxWith) {
if (maxWith > QuotasViewStyles.progressBarMaxWidth) {
return QuotasViewStyles.progressBarMaxWidth;
} else {
return maxWith;
}
}
}
@@ -3,24 +3,13 @@ import 'package:core/presentation/extensions/color_extension.dart';
import 'package:flutter/material.dart';
class QuotasViewStyles {
static const double padding = 16;
static const double margin = 16;
static const double bottomPadding = 24;
static const double iconPadding = 12;
static const double iconSize = 24;
static const double labelTextSize = 13;
static const double space = 8;
static const double progressBarHeight = 3;
static const double progressBarMaxWidth = 168;
static const double progressStateTextSize = 13;
static const double topLineSize = 1;
static const double progressBarMaxWidth = 193;
static const Color labelTextColor = AppColor.steelGray400;
static const Color webBackgroundColor = AppColor.colorBgDesktop;
static const Color mobileBackgroundColor = Colors.white;
static const Color progressBarBackgroundColor = AppColor.colorDivider;
static const Color topLineColor = AppColor.colorDividerHorizontal;
static const FontWeight labelFontWeight = FontWeight.w400;
static const FontWeight progressStateFontWeight = FontWeight.w400;
}
@@ -12,6 +12,7 @@ import 'package:get/get.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart';
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
import 'package:tmail_ui_user/features/mailbox/domain/state/search_mailbox_state.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/context_item_mailbox_action.dart';
@@ -196,19 +197,20 @@ class SearchMailboxView extends GetWidget<SearchMailboxController>
}
List<FocusedMenuItem> _listPopupMenuItemAction(BuildContext context, PresentationMailbox mailbox) {
final bool deletedMessageVaultSupported = MailboxUtils.isDeletedMessageVaultSupported(
controller.dashboardController.sessionCurrent,
controller.dashboardController.accountId.value);
final session = controller.dashboardController.sessionCurrent;
final accountId = controller.dashboardController.accountId.value;
final bool subaddressingSupported = MailboxWidgetMixin.isSubaddressingSupported(
controller.dashboardController.sessionCurrent,
controller.dashboardController.accountId.value);
final deletedMessageVaultSupported =
MailboxUtils.isDeletedMessageVaultSupported(session, accountId);
final isSubAddressingSupported =
session?.isSubAddressingSupported(accountId) ?? false;
final contextMenuActions = listContextMenuItemAction(
mailbox,
controller.dashboardController.enableSpamReport,
deletedMessageVaultSupported,
subaddressingSupported,
isSubAddressingSupported,
);
return contextMenuActions
.map((action) => _mailboxFocusedMenuItem(context, action, mailbox))
@@ -262,19 +264,20 @@ class SearchMailboxView extends GetWidget<SearchMailboxController>
PresentationMailbox mailbox,
{RelativeRect? position}
) {
final bool deletedMessageVaultSupported = MailboxUtils.isDeletedMessageVaultSupported(
controller.dashboardController.sessionCurrent,
controller.dashboardController.accountId.value);
final session = controller.dashboardController.sessionCurrent;
final accountId = controller.dashboardController.accountId.value;
final bool subaddressingSupported = MailboxWidgetMixin.isSubaddressingSupported(
controller.dashboardController.sessionCurrent,
controller.dashboardController.accountId.value);
final deletedMessageVaultSupported =
MailboxUtils.isDeletedMessageVaultSupported(session, accountId);
final isSubAddressingSupported =
session?.isSubAddressingSupported(accountId) ?? false;
final contextMenuActions = listContextMenuItemAction(
mailbox,
controller.dashboardController.enableSpamReport,
deletedMessageVaultSupported,
subaddressingSupported,
isSubAddressingSupported,
);
if (contextMenuActions.isEmpty) {