From 6b09fb9faf145ec5bf456292cc40529f94029217 Mon Sep 17 00:00:00 2001 From: dab246 Date: Thu, 29 May 2025 20:25:14 +0700 Subject: [PATCH] TF-3758 Update app grid in left menu on mobile Signed-off-by: dab246 --- .../extensions/color_extension.dart | 3 + core/lib/presentation/utils/theme_utils.dart | 9 ++ .../views/image/image_loader_mixin.dart | 16 ++- .../base/widget/user_avatar_builder.dart | 11 +- .../presentation/base_mailbox_view.dart | 25 ++++ .../extensions/open_app_grid_extension.dart | 69 +++++++++++ .../presentation/mailbox_controller.dart | 5 +- .../mailbox/presentation/mailbox_view.dart | 112 ++---------------- .../widgets/app_grid/app_grid_icon.dart | 27 +++++ .../widgets/app_grid/app_shortcut.dart | 50 ++++++++ .../presentation/widgets/mailbox_app_bar.dart | 13 ++ .../mailbox_dashboard_controller.dart | 10 -- 12 files changed, 230 insertions(+), 120 deletions(-) create mode 100644 lib/features/mailbox/presentation/extensions/open_app_grid_extension.dart create mode 100644 lib/features/mailbox/presentation/widgets/app_grid/app_grid_icon.dart create mode 100644 lib/features/mailbox/presentation/widgets/app_grid/app_shortcut.dart diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index 29d5819cc..c7a85799d 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -251,6 +251,9 @@ extension AppColor on Color { static const warningColor = Color(0xFFFFC107); static const primaryMain = Color(0xFF0A84FF); static const m3LayerDarkOutline = Color(0xFF938F99); + static const blackAlpha40 = Color.fromRGBO(0, 0, 0, 0.4); + static const blackAlpha20 = Color.fromRGBO(0, 0, 0, 0.2); + static const textPrimary = Color(0xFF424244); static const mapGradientColor = [ [Color(0xFF21D4FD), Color(0xFFB721FF)], diff --git a/core/lib/presentation/utils/theme_utils.dart b/core/lib/presentation/utils/theme_utils.dart index f24f51143..44118c6b3 100644 --- a/core/lib/presentation/utils/theme_utils.dart +++ b/core/lib/presentation/utils/theme_utils.dart @@ -234,6 +234,15 @@ class ThemeUtils { color: color ??AppColor.primaryMain, ); + static TextStyle textStyleAppShortcut() => const TextStyle( + fontFamily: ConstantsUI.fontApp, + fontWeight: FontWeight.normal, + letterSpacing: 0.0, + fontSize: 12, + height: 22.5 / 12, + color: AppColor.textPrimary, + ); + static TextSelectionThemeData get _textSelectionTheme { return const TextSelectionThemeData( cursorColor: AppColor.primaryColor, diff --git a/core/lib/presentation/views/image/image_loader_mixin.dart b/core/lib/presentation/views/image/image_loader_mixin.dart index faee9cca7..7c8656b61 100644 --- a/core/lib/presentation/views/image/image_loader_mixin.dart +++ b/core/lib/presentation/views/image/image_loader_mixin.dart @@ -37,12 +37,7 @@ mixin ImageLoaderMixin { }, errorBuilder: (context, error, stackTrace) { logError('ImageLoaderMixin::buildImage:Exception = $error'); - return Container( - width: imageSize ?? 150, - height: imageSize ?? 150, - alignment: Alignment.center, - child: const Icon(Icons.error_outline), - ); + return buildNoImage(imageSize ?? 150); }, ); } else if (isImageSVG(imagePath)) { @@ -61,6 +56,15 @@ mixin ImageLoaderMixin { } } + Widget buildNoImage(double imageSize) { + return Container( + width: imageSize, + height: imageSize, + alignment: Alignment.center, + child: const Icon(Icons.error_outline), + ); + } + bool isImageNetworkLink(String imagePath) { return imagePath.startsWith('http') == true || imagePath.startsWith('https') == true; diff --git a/lib/features/base/widget/user_avatar_builder.dart b/lib/features/base/widget/user_avatar_builder.dart index 83a6e9a15..e72f80d9d 100644 --- a/lib/features/base/widget/user_avatar_builder.dart +++ b/lib/features/base/widget/user_avatar_builder.dart @@ -5,12 +5,14 @@ import 'package:flutter/material.dart'; class UserAvatarBuilder extends StatelessWidget { final String username; final double? size; + final EdgeInsetsGeometry? padding; final VoidCallback? onTapAction; const UserAvatarBuilder({ Key? key, required this.username, this.size, + this.padding, this.onTapAction, }) : super(key: key); @@ -28,6 +30,13 @@ class UserAvatarBuilder extends StatelessWidget { avatarBuilder.addOnTapActionClick(onTapAction!); } - return avatarBuilder.build(); + if (padding != null) { + return Padding( + padding: padding!, + child: avatarBuilder.build(), + ); + } else { + return avatarBuilder.build(); + } } } diff --git a/lib/features/mailbox/presentation/base_mailbox_view.dart b/lib/features/mailbox/presentation/base_mailbox_view.dart index 73e310ca5..d8bbd794a 100644 --- a/lib/features/mailbox/presentation/base_mailbox_view.dart +++ b/lib/features/mailbox/presentation/base_mailbox_view.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/extensions/open_app_grid_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/widgets/mailbox_app_bar.dart'; abstract class BaseMailboxView extends GetWidget with @@ -10,4 +12,27 @@ abstract class BaseMailboxView extends GetWidget MailboxWidgetMixin { BaseMailboxView({Key? key}) : super(key: key); + + Widget buildMailboxAppBar() { + return Obx(() { + final dashboardController = controller.mailboxDashBoardController; + final accountId = dashboardController.accountId.value; + final username = accountId != null + ? dashboardController.getOwnEmailAddress() + : ''; + + final linagoraApps = dashboardController + .appGridDashboardController + .listLinagoraApp; + + return MailboxAppBar( + imagePaths: controller.imagePaths, + username: username, + openSettingsAction: controller.mailboxDashBoardController.goToSettings, + onOpenAppGridAction: linagoraApps.isNotEmpty + ? () => controller.openAppGrid(linagoraApps) + : null, + ); + }); + } } \ No newline at end of file diff --git a/lib/features/mailbox/presentation/extensions/open_app_grid_extension.dart b/lib/features/mailbox/presentation/extensions/open_app_grid_extension.dart new file mode 100644 index 000000000..4ccf97808 --- /dev/null +++ b/lib/features/mailbox/presentation/extensions/open_app_grid_extension.dart @@ -0,0 +1,69 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/views/list/sliver_grid_delegate_fixed_height.dart'; +import 'package:core/utils/app_logger.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_controller.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/widgets/app_grid/app_shortcut.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/app_linagora_ecosystem.dart'; + +extension OpenAppGridExtension on MailboxController { + void openAppGrid(List linagoraApps) { + log('OpenAppGridExtension::openAppGrid: length of linagoraApps = ${linagoraApps.length}'); + Get.dialog( + Center( + child: Dialog( + backgroundColor: Colors.white, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(14)), + ), + child: Container( + width: 254, + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 14), + decoration: const BoxDecoration( + boxShadow: [ + BoxShadow( + color: Color(0x14000000), + offset: Offset(0, 2), + blurRadius: 24, + ), + BoxShadow( + color: Color(0x14000000), + blurRadius: 2, + ), + ], + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(14)), + ), + child: GridView( + gridDelegate: const SliverGridDelegateFixedHeight( + height: 80.58, + crossAxisCount: 3, + mainAxisSpacing: 10, + ), + shrinkWrap: true, + physics: const ClampingScrollPhysics(), + children: linagoraApps.map((app) { + return AppShortcut( + icon: app.getIconPath(imagePaths), + label: app.appName ?? '', + onTapAction: () => _handleOpenApp(app), + ); + }).toList(), + ), + ), + ), + ), + barrierColor: AppColor.blackAlpha20, + ); + } + + Future _handleOpenApp(AppLinagoraEcosystem app) async { + await launchApplication( + androidPackageId: app.androidPackageId, + iosScheme: app.iosUrlScheme, + iosStoreLink: app.iosAppStoreLink, + uri: app.appRedirectLink, + ); + } +} diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index abbfc995e..249033bc4 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -20,6 +20,7 @@ import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:rxdart/transformers.dart'; import 'package:tmail_ui_user/features/base/base_mailbox_controller.dart'; import 'package:tmail_ui_user/features/base/mixin/contact_support_mixin.dart'; +import 'package:tmail_ui_user/features/base/mixin/launcher_application_mixin.dart'; import 'package:tmail_ui_user/features/base/mixin/mailbox_action_handler_mixin.dart'; import 'package:tmail_ui_user/features/composer/domain/state/save_email_as_drafts_state.dart'; import 'package:tmail_ui_user/features/email/domain/model/move_action.dart'; @@ -102,7 +103,9 @@ import 'package:tmail_ui_user/main/routes/route_utils.dart'; import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart'; class MailboxController extends BaseMailboxController - with MailboxActionHandlerMixin, ContactSupportMixin { + with MailboxActionHandlerMixin, + ContactSupportMixin, + LauncherApplicationMixin { final mailboxDashBoardController = Get.find(); final isMailboxListScrollable = false.obs; diff --git a/lib/features/mailbox/presentation/mailbox_view.dart b/lib/features/mailbox/presentation/mailbox_view.dart index 70325cf4b..72c331b1e 100644 --- a/lib/features/mailbox/presentation/mailbox_view.dart +++ b/lib/features/mailbox/presentation/mailbox_view.dart @@ -1,6 +1,5 @@ import 'package:core/core.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/base/widget/application_version_widget.dart'; @@ -8,11 +7,9 @@ import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions 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/bottom_bar_selection_mailbox_widget.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'; @@ -27,44 +24,20 @@ class MailboxView extends BaseMailboxView { @override Widget build(BuildContext context) { - return SafeArea( - child: Drawer( - backgroundColor: Colors.white, - shape: InputBorder.none, + return Drawer( + backgroundColor: Colors.white, + shape: InputBorder.none, + child: SafeArea( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Column(children: [ - Obx(() { - final accountId = controller - .mailboxDashBoardController - .accountId - .value; - - final username = accountId != null - ? controller.mailboxDashBoardController.getOwnEmailAddress() - : ''; - - return MailboxAppBar( - username: username, - openSettingsAction: - controller.mailboxDashBoardController.goToSettings, - ); - }), - _buildHeaderMailbox(context), - Expanded(child: Container( - color: Colors.white, - child: RefreshIndicator( - color: AppColor.primaryColor, - onRefresh: controller.refreshAllMailbox, - child: SafeArea( - top: false, - right: false, - bottom: false, - child: _buildListMailbox(context) - ) - ), + buildMailboxAppBar(), + Expanded(child: RefreshIndicator( + color: AppColor.primaryColor, + onRefresh: controller.refreshAllMailbox, + child: _buildListMailbox(context) )), Obx(() { if (controller.isSelectionEnabled() @@ -127,79 +100,14 @@ class MailboxView extends BaseMailboxView { ); } - Widget _buildHeaderMailbox(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.only(top: controller.responsiveUtils.isMobile(context) ? 10 : 30, bottom: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsetsDirectional.only(start: 10), - child: _buildCloseScreenButton(context)), - SizedBox(width: controller.isSelectionEnabled() ? 49 : 40), - Expanded(child: Text( - AppLocalizations.of(context).folders, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 21, color: Colors.black, fontWeight: FontWeight.bold))), - Obx(() => _buildEditMailboxButton(context, controller.isSelectionEnabled())) - ] - ) - ), - if (!controller.responsiveUtils.isTabletLarge(context)) - const Divider(color: AppColor.colorDividerMailbox, height: 1), - ] - ); - } - - Widget _buildCloseScreenButton(BuildContext context) { - return buildIconWeb( - icon: SvgPicture.asset(controller.imagePaths.icCircleClose, width: 28, height: 28, fit: BoxFit.fill), - tooltip: AppLocalizations.of(context).close, - onTap: () => controller.closeMailboxScreen(context)); - } - - Widget _buildEditMailboxButton(BuildContext context, bool isSelectionEnabled) { - return Padding( - padding: const EdgeInsetsDirectional.only(end: 10), - child: Material( - shape: const CircleBorder(), - color: Colors.transparent, - child: TextButton( - child: Text( - !isSelectionEnabled ? AppLocalizations.of(context).select : AppLocalizations.of(context).cancel, - style: const TextStyle(fontSize: 17, color: AppColor.colorTextButton, fontWeight: FontWeight.normal)), - onPressed: () => !isSelectionEnabled - ? controller.enableSelectionMailbox() - : controller.disableSelectionMailbox() - ) - ), - ); - } - Widget _buildListMailbox(BuildContext context) { return SingleChildScrollView( controller: controller.mailboxListScrollController, key: const PageStorageKey('mailbox_list'), physics: const AlwaysScrollableScrollPhysics(), - padding: const EdgeInsets.only(bottom: 16), + padding: const EdgeInsets.only(bottom: 16, top: 16), child: Column(children: [ Obx(() => MailboxLoadingBarWidget(viewState: controller.viewState.value)), - Obx(() { - final linagoraApps = controller - .mailboxDashBoardController - .appGridDashboardController - .listLinagoraApp; - - if (linagoraApps.isNotEmpty) { - return AppGridView(linagoraApps: linagoraApps); - } else { - return const SizedBox.shrink(); - } - }), - const SizedBox(height: 8), Obx(() { if (controller.defaultMailboxIsNotEmpty) { return _buildMailboxCategory( diff --git a/lib/features/mailbox/presentation/widgets/app_grid/app_grid_icon.dart b/lib/features/mailbox/presentation/widgets/app_grid/app_grid_icon.dart new file mode 100644 index 000000000..23a797b54 --- /dev/null +++ b/lib/features/mailbox/presentation/widgets/app_grid/app_grid_icon.dart @@ -0,0 +1,27 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; +import 'package:flutter/material.dart'; + +class AppGridIcon extends StatelessWidget { + + final String icon; + final VoidCallback onTapAction; + + const AppGridIcon({ + super.key, + required this.icon, + required this.onTapAction, + }); + + @override + Widget build(BuildContext context) { + return TMailButtonWidget.fromIcon( + icon: icon, + backgroundColor: Colors.transparent, + iconSize: 24, + iconColor: AppColor.blackAlpha40, + padding: const EdgeInsets.all(8), + onTapActionCallback: onTapAction, + ); + } +} diff --git a/lib/features/mailbox/presentation/widgets/app_grid/app_shortcut.dart b/lib/features/mailbox/presentation/widgets/app_grid/app_shortcut.dart new file mode 100644 index 000000000..9a3e830da --- /dev/null +++ b/lib/features/mailbox/presentation/widgets/app_grid/app_shortcut.dart @@ -0,0 +1,50 @@ +import 'package:core/presentation/utils/theme_utils.dart'; +import 'package:core/presentation/views/image/image_loader_mixin.dart'; +import 'package:flutter/material.dart'; + +class AppShortcut extends StatelessWidget with ImageLoaderMixin { + final String? icon; + final String label; + final VoidCallback onTapAction; + + const AppShortcut({ + super.key, + required this.icon, + required this.label, + required this.onTapAction, + }); + + @override + Widget build(BuildContext context) { + return Material( + type: MaterialType.transparency, + child: InkWell( + onTap: onTapAction, + child: Container( + width: 78, + height: 80.58, + alignment: Alignment.center, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + if (icon != null) + buildImage( + imagePath: icon!, + imageSize: 42, + ) + else + buildNoImage(42), + const SizedBox(height: 4), + Text( + label, + style: ThemeUtils.textStyleAppShortcut(), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/mailbox/presentation/widgets/mailbox_app_bar.dart b/lib/features/mailbox/presentation/widgets/mailbox_app_bar.dart index 6109055eb..0bebb39e9 100644 --- a/lib/features/mailbox/presentation/widgets/mailbox_app_bar.dart +++ b/lib/features/mailbox/presentation/widgets/mailbox_app_bar.dart @@ -1,17 +1,24 @@ +import 'package:core/presentation/resources/image_paths.dart'; import 'package:flutter/material.dart'; import 'package:tmail_ui_user/features/base/widget/application_logo_with_text_widget.dart'; import 'package:tmail_ui_user/features/base/widget/user_avatar_builder.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/widgets/app_grid/app_grid_icon.dart'; typedef OnOpenSettingsAction = void Function(); +typedef OnOpenAppGridAction = void Function(); class MailboxAppBar extends StatelessWidget { + final ImagePaths imagePaths; final String username; final OnOpenSettingsAction? openSettingsAction; + final OnOpenAppGridAction? onOpenAppGridAction; const MailboxAppBar({ super.key, + required this.imagePaths, required this.username, this.openSettingsAction, + this.onOpenAppGridAction, }); @override @@ -26,8 +33,14 @@ class MailboxAppBar extends StatelessWidget { children: [ ApplicationLogoWidthTextWidget(iconSize: 22), const Spacer(), + if (onOpenAppGridAction != null) + AppGridIcon( + icon: imagePaths.icAppDashboard, + onTapAction: onOpenAppGridAction!, + ), UserAvatarBuilder( username: username, + padding: const EdgeInsetsDirectional.only(start: 4), onTapAction: openSettingsAction, ), ], diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index 563eb8b89..feb2e29df 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -2280,19 +2280,9 @@ class MailboxDashBoardController extends ReloadableController void handleOnForegroundGained() { log('MailboxDashBoardController::handleOnForegroundGained():'); - if (PlatformInfo.isMobile) { - _updateTheme(); - } refreshActionWhenBackToApp(); } - void _updateTheme() { - ThemeUtils.setSystemDarkUIStyle(); - if (isDrawerOpen) { - ThemeUtils.setStatusBarTransparentColor(); - } - } - void updateEmailList(List newEmailList) { emailsInCurrentMailbox.value = newEmailList; }