TF-3758 Update app grid in left menu on mobile

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-05-29 20:25:14 +07:00
committed by Dat H. Pham
parent d7c4418674
commit 6b09fb9faf
12 changed files with 230 additions and 120 deletions
@@ -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)],
@@ -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,
@@ -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;
@@ -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();
}
}
}
@@ -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<MailboxController>
with
@@ -10,4 +12,27 @@ abstract class BaseMailboxView extends GetWidget<MailboxController>
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,
);
});
}
}
@@ -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<AppLinagoraEcosystem> 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<void> _handleOpenApp(AppLinagoraEcosystem app) async {
await launchApplication(
androidPackageId: app.androidPackageId,
iosScheme: app.iosUrlScheme,
iosStoreLink: app.iosAppStoreLink,
uri: app.appRedirectLink,
);
}
}
@@ -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<MailboxDashBoardController>();
final isMailboxListScrollable = false.obs;
@@ -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(
@@ -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,
);
}
}
@@ -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,
),
],
),
),
),
);
}
}
@@ -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,
),
],
@@ -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<PresentationEmail> newEmailList) {
emailsInCurrentMailbox.value = newEmailList;
}