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
+5
View File
@@ -0,0 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M8.78067 0.923504C9.05962 0.644558 9.51188 0.644558 9.79082 0.923504L11.9337 3.06636C12.2126 3.34531 12.2126 3.79757 11.9337 4.07651L9.79082 6.21937C9.51188 6.49832 9.05962 6.49832 8.78067 6.21937C8.50173 5.94042 8.50173 5.48816 8.78067 5.20922L9.69533 4.29371C6.68108 4.45207 4.28575 6.94631 4.28575 10C4.28575 13.1559 6.84412 15.7143 10 15.7143C13.1559 15.7143 15.7143 13.1559 15.7143 10C15.7143 8.57513 15.1918 7.23343 14.2615 6.19291C13.9985 5.89882 14.0238 5.44727 14.3179 5.18433C14.6119 4.92139 15.0635 4.94665 15.3264 5.24073C16.4887 6.54067 17.1429 8.2204 17.1429 10C17.1429 13.9449 13.9449 17.1429 10 17.1429C6.05514 17.1429 2.85718 13.9449 2.85718 10C2.85718 6.15253 5.89916 3.01554 9.70921 2.86296L8.78067 1.93366C8.50173 1.65471 8.50173 1.20245 8.78067 0.923504Z"
fill="#818C99" />
</svg>

After

Width:  |  Height:  |  Size: 927 B

@@ -229,6 +229,7 @@ class ImagePaths {
String get icCloseDialog => _getImagePath('ic_close_dialog.svg');
String get icDoubleArrowUp => _getImagePath('ic_double_arrow_up.svg');
String get icDoubleArrowDown => _getImagePath('ic_double_arrow_down.svg');
String get icRefreshQuotas => _getImagePath('ic_refresh_quotas.svg');
String _getImagePath(String imageName) {
return AssetsPaths.images + imageName;
@@ -261,6 +261,15 @@ class ThemeUtils {
color: AppColor.textPrimary,
);
static TextStyle textStyleContentCaption() => const TextStyle(
fontFamily: ConstantsUI.fontApp,
fontWeight: FontWeight.normal,
letterSpacing: 0,
fontSize: 11,
height: 14 / 11,
color: AppColor.steelGray400,
);
static TextSelectionThemeData get _textSelectionTheme {
return const TextSelectionThemeData(
cursorColor: AppColor.primaryColor,
@@ -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) {
@@ -7,7 +7,7 @@ import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/core/state.dart';
import 'package:jmap_dart_client/jmap/core/user_name.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart';
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
void main() {
group('MailboxWidgetMixin::isSubaddressingSupported::test', () {
@@ -24,10 +24,10 @@ void main() {
{}, UserName(''), Uri(), Uri(), Uri(), Uri(), State(''));
// act
final subaddressingSupported = MailboxWidgetMixin.isSubaddressingSupported(session, AccountId(Id("1")));
final isSubAddressingSupported = session.isSubAddressingSupported(AccountId(Id("1")));
// assert
expect(subaddressingSupported, true);
expect(isSubAddressingSupported, true);
});
test(
@@ -42,10 +42,10 @@ void main() {
{}, UserName(''), Uri(), Uri(), Uri(), Uri(), State(''));
// act
final subaddressingSupported = MailboxWidgetMixin.isSubaddressingSupported(session, AccountId(Id("1")));
final isSubAddressingSupported = session.isSubAddressingSupported(AccountId(Id("1")));
// assert
expect(subaddressingSupported, false);
expect(isSubAddressingSupported, false);
});
test(
@@ -60,10 +60,10 @@ void main() {
{}, UserName(''), Uri(), Uri(), Uri(), Uri(), State(''));
// act
final subaddressingSupported = MailboxWidgetMixin.isSubaddressingSupported(session, AccountId(Id("1")));
final isSubAddressingSupported = session.isSubAddressingSupported(AccountId(Id("1")));
// assert
expect(subaddressingSupported, false);
expect(isSubAddressingSupported, false);
});
});
}