From dce74b43e9994b09193e0fb99cb5cd0657df6e2a Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 9 Dec 2024 12:40:57 +0700 Subject: [PATCH] TF-3260 Load & Display App Grid Linagora Ecosystem Signed-off-by: dab246 --- android/app/src/main/AndroidManifest.xml | 2 - core/lib/core.dart | 1 + core/lib/data/constants/constant.dart | 2 + .../views/image/image_loader_mixin.dart | 70 ++++++++++ .../views/text/slogan_builder.dart | 128 +++++++---------- core/lib/utils/string_convert.dart | 4 + ios/Runner/Info.plist | 1 + .../mixin/launcher_application_mixin.dart | 58 ++++++++ .../extensions/service_path_extension.dart | 23 ++- lib/features/login/data/network/endpoint.dart | 3 +- .../login/data/network/oidc_http_client.dart | 2 +- .../presentation/mailbox_controller.dart | 12 -- .../mailbox/presentation/mailbox_view.dart | 17 ++- .../presentation/mailbox_view_web.dart | 17 ++- .../mixin/mailbox_widget_mixin.dart | 90 ------------ .../model/mailbox_categories.dart | 5 - .../presentation/widgets/app_grid_view.dart | 131 ++++++++++++++++++ .../data/datasource/app_grid_datasource.dart | 9 ++ .../app_grid_datasource_impl.dart | 26 ++++ .../local_app_grid_datasource_impl.dart | 30 ++++ .../data/network/linagora_ecosystem_api.dart | 29 ++++ .../repository/app_grid_repository_impl.dart | 23 +++ .../domain/app_dashboard/linagora_app.dart | 40 +++--- .../linagora_ecosystem_exceptions.dart | 2 + .../app_linagora_ecosystem.dart | 14 +- .../linagora_ecosystem.dart | 29 ++++ .../repository/app_grid_repository.dart | 9 ++ ...get_app_dashboard_configuration_state.dart | 10 +- ...get_app_grid_linagora_ecosystem_state.dart | 20 +++ ...pp_dashboard_configuration_interactor.dart | 19 +-- ...app_grid_linagra_ecosystem_interactor.dart | 30 ++++ .../bindings/mailbox_dashboard_bindings.dart | 30 +++- .../app_grid_dashboard_controller.dart | 54 +++++--- .../mailbox_dashboard_controller.dart | 34 +++-- .../mailbox_dashboard_view_web.dart | 1 - .../app_grid_dashboard_icon.dart | 94 +++++++------ .../app_grid_dashboard_item.dart | 55 +++----- .../app_grid_dashboard_overlay.dart | 20 ++- .../app_list_dashboard_item.dart | 55 +++----- .../navigation_bar/navigation_bar_widget.dart | 44 +++--- .../mailbox_visibility_controller.dart | 2 - .../bindings/network/network_bindings.dart | 2 + pubspec.lock | 11 +- pubspec.yaml | 9 +- .../deserialize_linagora_ecosystem_test.dart | 2 +- .../service_path_extension_test.dart | 56 ++++++++ .../mailbox_dashboard_controller_test.dart | 2 +- .../mailbox_dashboard_view_widget_test.dart | 2 +- 48 files changed, 894 insertions(+), 435 deletions(-) create mode 100644 core/lib/presentation/views/image/image_loader_mixin.dart create mode 100644 lib/features/base/mixin/launcher_application_mixin.dart create mode 100644 lib/features/mailbox/presentation/widgets/app_grid_view.dart create mode 100644 lib/features/mailbox_dashboard/data/datasource/app_grid_datasource.dart create mode 100644 lib/features/mailbox_dashboard/data/datasource_impl/app_grid_datasource_impl.dart create mode 100644 lib/features/mailbox_dashboard/data/datasource_impl/local_app_grid_datasource_impl.dart create mode 100644 lib/features/mailbox_dashboard/data/network/linagora_ecosystem_api.dart create mode 100644 lib/features/mailbox_dashboard/data/repository/app_grid_repository_impl.dart create mode 100644 lib/features/mailbox_dashboard/domain/exceptions/linagora_ecosystem_exceptions.dart create mode 100644 lib/features/mailbox_dashboard/domain/repository/app_grid_repository.dart create mode 100644 lib/features/mailbox_dashboard/domain/state/get_app_grid_linagora_ecosystem_state.dart create mode 100644 lib/features/mailbox_dashboard/domain/usecases/get_app_grid_linagra_ecosystem_interactor.dart create mode 100644 test/features/login/data/extensions/service_path_extension_test.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index bb7ec1782..0e5e7e687 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -11,8 +11,6 @@ - - diff --git a/core/lib/core.dart b/core/lib/core.dart index 9c04f50a9..3062bdfc9 100644 --- a/core/lib/core.dart +++ b/core/lib/core.dart @@ -96,6 +96,7 @@ export 'presentation/views/container/tmail_container_widget.dart'; export 'presentation/views/clipper/side_arrow_clipper.dart'; export 'presentation/views/avatar/gradient_circle_avatar_icon.dart'; export 'presentation/views/loading/cupertino_loading_widget.dart'; +export 'presentation/views/image/image_loader_mixin.dart'; // Resources export 'presentation/resources/assets_paths.dart'; diff --git a/core/lib/data/constants/constant.dart b/core/lib/data/constants/constant.dart index 525655ecc..7f7a87ae8 100644 --- a/core/lib/data/constants/constant.dart +++ b/core/lib/data/constants/constant.dart @@ -14,4 +14,6 @@ class Constant { static const attachmentScheme = 'attachment'; static const emlPreviewerScheme = 'eml-previewer'; static const htmlExtension = '.html'; + static const slashCharacter = '/'; + static const andCharacter = '&'; } \ No newline at end of file diff --git a/core/lib/presentation/views/image/image_loader_mixin.dart b/core/lib/presentation/views/image/image_loader_mixin.dart new file mode 100644 index 000000000..faee9cca7 --- /dev/null +++ b/core/lib/presentation/views/image/image_loader_mixin.dart @@ -0,0 +1,70 @@ + +import 'package:core/utils/app_logger.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +mixin ImageLoaderMixin { + + Widget buildImage({ + required String imagePath, + double? imageSize + }) { + if (isImageNetworkLink(imagePath) && isImageSVG(imagePath)) { + return SvgPicture.network( + imagePath, + width: imageSize ?? 150, + height: imageSize ?? 150, + fit: BoxFit.fill, + placeholderBuilder: (_) { + return const CupertinoActivityIndicator(); + }, + ); + } else if (isImageNetworkLink(imagePath)) { + return Image.network( + imagePath, + fit: BoxFit.fill, + width: imageSize ?? 150, + height: imageSize ?? 150, + loadingBuilder: (_, child, loadingProgress) { + if (loadingProgress != null && + loadingProgress.cumulativeBytesLoaded != loadingProgress.expectedTotalBytes) { + return const Center( + child: CupertinoActivityIndicator(), + ); + } + return child; + }, + 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), + ); + }, + ); + } else if (isImageSVG(imagePath)) { + return SvgPicture.asset( + imagePath, + width: imageSize ?? 150, + height: imageSize ?? 150, + ); + } else { + return Image.asset( + imagePath, + fit: BoxFit.fill, + width: imageSize ?? 150, + height: imageSize ?? 150, + ); + } + } + + bool isImageNetworkLink(String imagePath) { + return imagePath.startsWith('http') == true || + imagePath.startsWith('https') == true; + } + + bool isImageSVG(String imagePath) => imagePath.endsWith('svg') == true; +} \ No newline at end of file diff --git a/core/lib/presentation/views/text/slogan_builder.dart b/core/lib/presentation/views/text/slogan_builder.dart index 170b64b13..82348cd58 100644 --- a/core/lib/presentation/views/text/slogan_builder.dart +++ b/core/lib/presentation/views/text/slogan_builder.dart @@ -1,7 +1,5 @@ -import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/utils/style_utils.dart'; +import 'package:core/presentation/views/image/image_loader_mixin.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; /// A builder which builds a reusable slogan widget. /// This contains the logo and the slogan text. @@ -9,14 +7,13 @@ import 'package:flutter_svg/flutter_svg.dart'; typedef OnTapCallback = void Function(); -class SloganBuilder extends StatelessWidget { +class SloganBuilder extends StatelessWidget with ImageLoaderMixin { final bool arrangedByHorizontal; final String? text; final TextStyle? textStyle; final TextAlign? textAlign; final String? logo; - final Uri? publicLogoUri; final double? sizeLogo; final OnTapCallback? onTapCallback; final EdgeInsetsGeometry? paddingText; @@ -33,7 +30,6 @@ class SloganBuilder extends StatelessWidget { this.textStyle, this.textAlign, this.logo, - this.publicLogoUri, this.sizeLogo, this.onTapCallback, this.padding, @@ -45,82 +41,64 @@ class SloganBuilder extends StatelessWidget { @override Widget build(BuildContext context) { if (!arrangedByHorizontal) { - return InkWell( - onTap: onTapCallback, - hoverColor: hoverColor, - borderRadius: BorderRadius.all(Radius.circular(hoverRadius ?? 8)), - child: Padding( - padding: padding ?? EdgeInsets.zero, - child: Column(children: [ - _logoApp(), - Padding( - padding: paddingText ?? const EdgeInsetsDirectional.only(top: 16, start: 16, end: 16), - child: Text( - text ?? '', - style: textStyle, - textAlign: textAlign, - overflow: enableOverflow ? CommonTextStyle.defaultTextOverFlow : null, - softWrap: enableOverflow ? CommonTextStyle.defaultSoftWrap : null, - maxLines: enableOverflow ? 1 : null, + return Material( + type: MaterialType.transparency, + child: InkWell( + onTap: onTapCallback, + hoverColor: hoverColor, + borderRadius: BorderRadius.all(Radius.circular(hoverRadius ?? 8)), + child: Padding( + padding: padding ?? EdgeInsets.zero, + child: Column(children: [ + if (logo != null) + buildImage( + imagePath: logo!, + imageSize: sizeLogo, + ), + Padding( + padding: paddingText ?? const EdgeInsetsDirectional.only(top: 16, start: 16, end: 16), + child: Text( + text ?? '', + style: textStyle, + textAlign: textAlign, + overflow: enableOverflow ? TextOverflow.ellipsis : null, + maxLines: enableOverflow ? 1 : null, + ), ), - ), - ]), + ]), + ), ), ); } else { - return InkWell( - onTap: onTapCallback, - hoverColor: hoverColor, - radius: hoverRadius ?? 8, - borderRadius: BorderRadius.all(Radius.circular(hoverRadius ?? 8)), - child: Padding( - padding: padding ?? EdgeInsets.zero, - child: Row(children: [ - _logoApp(), - Padding( - padding: paddingText ?? const EdgeInsets.symmetric(horizontal: 10), - child: Text( - text ?? '', - style: textStyle, - textAlign: textAlign, - overflow: enableOverflow ? CommonTextStyle.defaultTextOverFlow : null, - softWrap: enableOverflow ? CommonTextStyle.defaultSoftWrap : null, - maxLines: enableOverflow ? 1 : null, + return Material( + type: MaterialType.transparency, + child: InkWell( + onTap: onTapCallback, + hoverColor: hoverColor, + radius: hoverRadius ?? 8, + borderRadius: BorderRadius.all(Radius.circular(hoverRadius ?? 8)), + child: Padding( + padding: padding ?? EdgeInsets.zero, + child: Row(children: [ + if (logo != null) + buildImage( + imagePath: logo!, + imageSize: sizeLogo, + ), + Padding( + padding: paddingText ?? const EdgeInsets.symmetric(horizontal: 10), + child: Text( + text ?? '', + style: textStyle, + textAlign: textAlign, + overflow: enableOverflow ? TextOverflow.ellipsis : null, + maxLines: enableOverflow ? 1 : null, + ), ), - ), - ]), + ]), + ), ), ); } } - - Widget _logoApp() { - if (logo != null && logo!.endsWith('svg')) { - return SvgPicture.asset( - logo!, - width: sizeLogo ?? 150, - height: sizeLogo ?? 150); - } else if (logo != null) { - return Image( - image: AssetImage(logo!), - fit: BoxFit.fill, - width: sizeLogo ?? 150, - height: sizeLogo ?? 150); - } else if (publicLogoUri != null) { - return Image.network( - publicLogoUri.toString(), - fit: BoxFit.fill, - width: sizeLogo ?? 150, - height: sizeLogo ?? 150, - errorBuilder: (_, error, stackTrace) { - return Container( - width: sizeLogo ?? 150, - height: sizeLogo ?? 150, - color: AppColor.textFieldHintColor, - ); - }); - } else { - return const SizedBox.shrink(); - } - } } diff --git a/core/lib/utils/string_convert.dart b/core/lib/utils/string_convert.dart index 58d10fa55..217d0a6fe 100644 --- a/core/lib/utils/string_convert.dart +++ b/core/lib/utils/string_convert.dart @@ -69,4 +69,8 @@ class StringConvert { throw const UnsupportedCharsetException(); } } + + static String toUrlScheme(String hostScheme) { + return '$hostScheme://'; + } } diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 9fad3f0df..0264ea0f9 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -50,6 +50,7 @@ sms tel linshare.mobile + twake.chat LSRequiresIPhoneOS diff --git a/lib/features/base/mixin/launcher_application_mixin.dart b/lib/features/base/mixin/launcher_application_mixin.dart new file mode 100644 index 000000000..c3f20a966 --- /dev/null +++ b/lib/features/base/mixin/launcher_application_mixin.dart @@ -0,0 +1,58 @@ + +import 'package:core/utils/platform_info.dart'; +import 'package:core/utils/string_convert.dart'; +import 'package:external_app_launcher/external_app_launcher.dart'; +import 'package:rich_text_composer/views/commons/logger.dart'; +import 'package:url_launcher/url_launcher.dart' as launcher; + +mixin LauncherApplicationMixin { + + Future launchApplication({ + String? androidPackageId, + String? iosScheme, + String? iosStoreLink, + Uri? uri, + }) async { + try { + if (PlatformInfo.isWeb && uri != null) { + await openWebApplication(uri); + } else if (PlatformInfo.isAndroid && androidPackageId != null) { + await openAndroidApplication(androidPackageId); + } else if (PlatformInfo.isIOS && + (iosScheme != null || iosStoreLink != null)) { + await openIOSApplication( + iosScheme, + iosStoreLink, + ); + } else if (uri != null) { + await openOtherApplication(uri); + } + } catch (e) { + logError('LauncherApplicationMixin::launchApplication:Exception = $e'); + } + } + + Future openAndroidApplication(String androidPackageId) async { + await LaunchApp.openApp(androidPackageName: androidPackageId); + } + + Future openIOSApplication(String? iosScheme, String? iosStoreLink) async { + await LaunchApp.openApp( + iosUrlScheme: iosScheme != null + ? StringConvert.toUrlScheme(iosScheme) + : null, + appStoreLink: iosStoreLink, + ); + } + + Future openWebApplication(Uri uri) async { + await launcher.launchUrl(uri); + } + + Future openOtherApplication(Uri uri) async { + await launcher.launchUrl( + uri, + mode: launcher.LaunchMode.externalApplication, + ); + } +} \ No newline at end of file diff --git a/lib/features/login/data/extensions/service_path_extension.dart b/lib/features/login/data/extensions/service_path_extension.dart index 55d80db04..bc2742c7a 100644 --- a/lib/features/login/data/extensions/service_path_extension.dart +++ b/lib/features/login/data/extensions/service_path_extension.dart @@ -1,4 +1,5 @@ +import 'package:core/data/constants/constant.dart'; import 'package:core/data/model/query/query_parameter.dart'; import 'package:core/data/network/config/service_path.dart'; @@ -7,24 +8,32 @@ extension ServicePathExtension on ServicePath { return path; } - ServicePath generateOIDCPath(Uri baseUrl) { - return ServicePath(baseUrl.toString() + path); + ServicePath usingBaseUrl(String baseUrl) { + String normalizedBaseUrl = baseUrl.endsWith(Constant.slashCharacter) + ? baseUrl.substring(0, baseUrl.length - 1) + : baseUrl; + + String normalizedPath = path.startsWith(Constant.slashCharacter) + ? path.substring(1) + : path; + + return ServicePath('$normalizedBaseUrl${Constant.slashCharacter}$normalizedPath'); } ServicePath withQueryParameters(List queryParameters) { if (queryParameters.isEmpty) { return this; } - if (path.lastIndexOf('/') == path.length - 1) { + if (path.lastIndexOf(Constant.slashCharacter) == path.length - 1) { final newPath = path.substring(0, path.length - 1); return ServicePath('$newPath?${queryParameters .map((query) => '${query.queryName}=${query.queryValue}') - .join('&')}'); + .join(Constant.andCharacter)}'); } else { return ServicePath('$path?${queryParameters .map((query) => '${query.queryName}=${query.queryValue}') - .join('&')}'); + .join(Constant.andCharacter)}'); } } @@ -33,10 +42,10 @@ extension ServicePathExtension on ServicePath { return this; } - if (path.lastIndexOf('/') == path.length - 1) { + if (path.lastIndexOf(Constant.slashCharacter) == path.length - 1) { return ServicePath('$path$pathParameter'); } else { - return ServicePath('$path/$pathParameter'); + return ServicePath('$path${Constant.slashCharacter}$pathParameter'); } } } \ No newline at end of file diff --git a/lib/features/login/data/network/endpoint.dart b/lib/features/login/data/network/endpoint.dart index 3429b27d4..d69e1386f 100644 --- a/lib/features/login/data/network/endpoint.dart +++ b/lib/features/login/data/network/endpoint.dart @@ -1,5 +1,6 @@ import 'package:core/data/network/config/service_path.dart'; class Endpoint { - static final ServicePath webFinger = ServicePath('/.well-known/webfinger'); + static final ServicePath webFinger = ServicePath('.well-known/webfinger'); + static final ServicePath linagoraEcosystem = ServicePath('.well-known/linagora-ecosystem'); } diff --git a/lib/features/login/data/network/oidc_http_client.dart b/lib/features/login/data/network/oidc_http_client.dart index 757fec05f..61cc3c881 100644 --- a/lib/features/login/data/network/oidc_http_client.dart +++ b/lib/features/login/data/network/oidc_http_client.dart @@ -28,7 +28,7 @@ class OIDCHttpClient { try { final result = await _dioClient.get( Endpoint.webFinger - .generateOIDCPath(Uri.parse(oidcRequest.baseUrl)) + .usingBaseUrl(oidcRequest.baseUrl) .withQueryParameters([ StringQueryParameter('resource', oidcRequest.resourceUrl), StringQueryParameter('rel', OIDCRequest.relUrl), diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index 5b90997f5..f2b0eb9cb 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -1172,14 +1172,6 @@ class MailboxController extends BaseMailboxController _triggerToggleMailboxCategories(); } break; - case MailboxCategories.appGrid: - final currentExpandMode = mailboxDashBoardController.appGridDashboardController.appDashboardExpandMode.value; - if (currentExpandMode == ExpandMode.COLLAPSE) { - _showAppDashboardAction(); - } else { - mailboxDashBoardController.appGridDashboardController.toggleAppGridDashboard(); - } - break; } } @@ -1200,10 +1192,6 @@ class MailboxController extends BaseMailboxController } } - void _showAppDashboardAction() { - mailboxDashBoardController.showAppDashboardAction(); - } - void handleMailboxAction( BuildContext context, MailboxActions actions, diff --git a/lib/features/mailbox/presentation/mailbox_view.dart b/lib/features/mailbox/presentation/mailbox_view.dart index 3eb09b7a3..fb4ccfd32 100644 --- a/lib/features/mailbox/presentation/mailbox_view.dart +++ b/lib/features/mailbox/presentation/mailbox_view.dart @@ -9,6 +9,7 @@ import 'package:tmail_ui_user/features/base/widget/application_version_widget.da 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/mailbox_item_widget.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_loading_bar_widget.dart'; @@ -17,7 +18,6 @@ import 'package:tmail_ui_user/features/mailbox/presentation/widgets/user_informa 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'; -import 'package:tmail_ui_user/main/utils/app_config.dart'; class MailboxView extends BaseMailboxView { @@ -238,9 +238,18 @@ class MailboxView extends BaseMailboxView { ); }), Obx(() => MailboxLoadingBarWidget(viewState: controller.viewState.value)), - AppConfig.appGridDashboardAvailable && !PlatformInfo.isMobile - ? buildAppGridDashboard(context, controller.responsiveUtils, controller.imagePaths, controller) - : const SizedBox.shrink(), + 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) { diff --git a/lib/features/mailbox/presentation/mailbox_view_web.dart b/lib/features/mailbox/presentation/mailbox_view_web.dart index 84ccd0ae5..206356d40 100644 --- a/lib/features/mailbox/presentation/mailbox_view_web.dart +++ b/lib/features/mailbox/presentation/mailbox_view_web.dart @@ -10,13 +10,13 @@ import 'package:tmail_ui_user/features/base/widget/scrollbar_list_view.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/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'; -import 'package:tmail_ui_user/main/utils/app_config.dart'; class MailboxView extends BaseMailboxView { @@ -154,9 +154,18 @@ class MailboxView extends BaseMailboxView { ), )), Obx(() => MailboxLoadingBarWidget(viewState: controller.viewState.value)), - AppConfig.appGridDashboardAvailable && controller.responsiveUtils.isWebNotDesktop(context) - ? buildAppGridDashboard(context, controller.responsiveUtils, controller.imagePaths, controller) - : const SizedBox.shrink(), + Obx(() { + final linagoraApps = controller + .mailboxDashBoardController + .appGridDashboardController + .listLinagoraApp; + + if (linagoraApps.isNotEmpty && !controller.responsiveUtils.isDesktop(context)) { + return AppGridView(linagoraApps: linagoraApps); + } else { + return const SizedBox.shrink(); + } + }), const SizedBox(height: 8), Obx(() { if (controller.defaultMailboxIsNotEmpty) { diff --git a/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart b/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart index fa145f8b0..6d4d2b0d0 100644 --- a/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart +++ b/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart @@ -15,7 +15,6 @@ import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_action import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_categories.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/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_list_dashboard_item.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; mixin MailboxWidgetMixin { @@ -341,93 +340,4 @@ mixin MailboxWidgetMixin { ]) ); } - - Widget buildAppGridDashboard( - BuildContext context, - ResponsiveUtils responsiveUtils, - ImagePaths imagePaths, - MailboxController controller - ) { - return Column(children: [ - _buildGoToApplicationsCategory( - context, - responsiveUtils, - imagePaths, - MailboxCategories.appGrid, - controller), - AnimatedContainer( - duration: const Duration(milliseconds: 400), - child: Obx(() { - return controller.mailboxDashBoardController.appGridDashboardController.appDashboardExpandMode.value == ExpandMode.EXPAND - ? _buildAppGridInMailboxView(context, controller) - : const Offstage(); - }) - ), - const Divider(color: AppColor.colorDividerMailbox, height: 1) - ]); - } - - Widget _buildGoToApplicationsCategory( - BuildContext context, - ResponsiveUtils responsiveUtils, - ImagePaths imagePaths, - MailboxCategories categories, - MailboxController controller - ) { - return Padding( - padding: const EdgeInsetsDirectional.only(start: 32, end: 4), - child: Row(children: [ - SvgPicture.asset( - imagePaths.icAppDashboard, - colorFilter: AppColor.primaryColor.asFilter(), - width: 20, - height: 20, - fit: BoxFit.fill), - Expanded(child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Text(categories.getTitle(context), - maxLines: 1, - overflow: CommonTextStyle.defaultTextOverFlow, - softWrap: CommonTextStyle.defaultSoftWrap, - style: const TextStyle( - fontSize: 16, - color: AppColor.colorTextButton, - fontWeight: FontWeight.w500 - ) - ) - )), - buildIconWeb( - icon: Obx(() => SvgPicture.asset( - controller.mailboxDashBoardController.appGridDashboardController.appDashboardExpandMode.value == ExpandMode.COLLAPSE - ? DirectionUtils.isDirectionRTLByLanguage(context) ? imagePaths.icBack : imagePaths.icCollapseFolder - : imagePaths.icExpandFolder, - colorFilter: controller.mailboxDashBoardController.appGridDashboardController.appDashboardExpandMode.value == ExpandMode.COLLAPSE - ? AppColor.colorIconUnSubscribedMailbox.asFilter() - : AppColor.primaryColor.asFilter(), - fit: BoxFit.fill - )), - tooltip: AppLocalizations.of(context).appGridTittle, - onTap: () => controller.toggleMailboxCategories(categories) - ) - ]) - ); - } - - Widget _buildAppGridInMailboxView(BuildContext context, MailboxController controller) { - return Obx(() { - final linagoraApps = controller.mailboxDashBoardController.appGridDashboardController.linagoraApplications.value; - if (linagoraApps != null && linagoraApps.apps.isNotEmpty) { - return ListView.builder( - shrinkWrap: true, - primary: false, - padding: const EdgeInsetsDirectional.only(start: 16, end: 16, bottom: 8), - itemCount: linagoraApps.apps.length, - itemBuilder: (context, index) { - return AppListDashboardItem(linagoraApps.apps[index]); - } - ); - } - return const SizedBox.shrink(); - }); - } } \ No newline at end of file diff --git a/lib/features/mailbox/presentation/model/mailbox_categories.dart b/lib/features/mailbox/presentation/model/mailbox_categories.dart index 0c05ec080..e37af365e 100644 --- a/lib/features/mailbox/presentation/model/mailbox_categories.dart +++ b/lib/features/mailbox/presentation/model/mailbox_categories.dart @@ -7,7 +7,6 @@ import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; enum MailboxCategories { exchange, personalFolders, - appGrid, teamMailboxes } @@ -19,8 +18,6 @@ extension MailboxCategoriessExtension on MailboxCategories { return 'exchange'; case MailboxCategories.personalFolders: return 'personalFolders'; - case MailboxCategories.appGrid: - return 'appGrid'; case MailboxCategories.teamMailboxes: return 'teamMailboxes'; } @@ -32,8 +29,6 @@ extension MailboxCategoriessExtension on MailboxCategories { return AppLocalizations.of(context).exchange; case MailboxCategories.personalFolders: return AppLocalizations.of(context).personalFolders; - case MailboxCategories.appGrid: - return AppLocalizations.of(context).appGridTittle; case MailboxCategories.teamMailboxes: return AppLocalizations.of(context).teamMailBoxes; } diff --git a/lib/features/mailbox/presentation/widgets/app_grid_view.dart b/lib/features/mailbox/presentation/widgets/app_grid_view.dart new file mode 100644 index 000000000..313575121 --- /dev/null +++ b/lib/features/mailbox/presentation/widgets/app_grid_view.dart @@ -0,0 +1,131 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; +import 'package:core/utils/direction_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/app_linagora_ecosystem.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_list_dashboard_item.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class AppGridView extends StatefulWidget { + + final List linagoraApps; + + const AppGridView({super.key, required this.linagoraApps}); + + @override + State createState() => _AppGridViewState(); +} + +class _AppGridViewState extends State { + final ValueNotifier _isCollapsedNotifier = ValueNotifier(true); + final _imagePaths = Get.find(); + + @override + void dispose() { + _isCollapsedNotifier.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column(children: [ + Padding( + padding: const EdgeInsetsDirectional.only( + start: 32, + end: 4, + top: 4, + bottom: 4, + ), + child: Row( + children: [ + SvgPicture.asset( + _imagePaths.icAppDashboard, + colorFilter: AppColor.primaryColor.asFilter(), + width: 20, + height: 20, + fit: BoxFit.fill, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Text( + AppLocalizations.of(context).appGridTittle, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + fontSize: 16, + color: AppColor.colorTextButton, + fontWeight: FontWeight.w500, + ), + ), + ) + ), + ValueListenableBuilder( + valueListenable: _isCollapsedNotifier, + builder: (context, isCollapsed, child) { + return TMailButtonWidget.fromIcon( + icon: _getCollapseIcon(context, isCollapsed), + iconColor: isCollapsed + ? AppColor.colorIconUnSubscribedMailbox + : AppColor.primaryColor, + iconSize: 32, + padding: const EdgeInsets.all(3), + backgroundColor: Colors.transparent, + tooltipMessage: AppLocalizations.of(context).appGridTittle, + onTapActionCallback: _toggleAppGridDashboard, + ); + }, + ), + ], + ), + ), + AnimatedContainer( + duration: const Duration(milliseconds: 400), + child: ValueListenableBuilder( + valueListenable: _isCollapsedNotifier, + builder: (context, isCollapsed, child) { + if (isCollapsed) { + return const Offstage(); + } else { + return child ?? const Offstage(); + } + }, + child: ListView.builder( + shrinkWrap: true, + primary: false, + padding: const EdgeInsetsDirectional.only( + start: 16, + end: 16, + bottom: 8, + ), + itemCount: widget.linagoraApps.length, + itemBuilder: (context, index) { + return AppListDashboardItem( + app: widget.linagoraApps[index], + imagePaths: _imagePaths, + ); + }, + ), + ), + ), + const Divider(color: AppColor.colorDividerMailbox, height: 1) + ]); + } + + String _getCollapseIcon(BuildContext context, bool isCollapsed) { + if (isCollapsed) { + return DirectionUtils.isDirectionRTLByLanguage(context) + ? _imagePaths.icArrowLeft + : _imagePaths.icArrowRight; + } else { + return _imagePaths.icArrowBottom; + } + } + + void _toggleAppGridDashboard() { + _isCollapsedNotifier.value = !_isCollapsedNotifier.value; + } +} diff --git a/lib/features/mailbox_dashboard/data/datasource/app_grid_datasource.dart b/lib/features/mailbox_dashboard/data/datasource/app_grid_datasource.dart new file mode 100644 index 000000000..2921ff2cf --- /dev/null +++ b/lib/features/mailbox_dashboard/data/datasource/app_grid_datasource.dart @@ -0,0 +1,9 @@ + +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/app_dashboard/linagora_applications.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/linagora_ecosystem.dart'; + +abstract class AppGridDatasource { + Future getLinagoraApplications(String path); + + Future getLinagoraEcosystem(String baseUrl); +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/data/datasource_impl/app_grid_datasource_impl.dart b/lib/features/mailbox_dashboard/data/datasource_impl/app_grid_datasource_impl.dart new file mode 100644 index 000000000..aa6343584 --- /dev/null +++ b/lib/features/mailbox_dashboard/data/datasource_impl/app_grid_datasource_impl.dart @@ -0,0 +1,26 @@ + +import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource/app_grid_datasource.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/data/network/linagora_ecosystem_api.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/app_dashboard/linagora_applications.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/linagora_ecosystem.dart'; +import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; + +class AppGridDatasourceImpl extends AppGridDatasource { + + final LinagoraEcosystemApi _linagoraEcosystemApi; + final ExceptionThrower _exceptionThrower; + + AppGridDatasourceImpl(this._linagoraEcosystemApi, this._exceptionThrower); + + @override + Future getLinagoraApplications(String path) { + throw UnimplementedError(); + } + + @override + Future getLinagoraEcosystem(String baseUrl) { + return Future.sync(() async { + return await _linagoraEcosystemApi.getLinagoraEcosystem(baseUrl); + }).catchError(_exceptionThrower.throwException); + } +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/data/datasource_impl/local_app_grid_datasource_impl.dart b/lib/features/mailbox_dashboard/data/datasource_impl/local_app_grid_datasource_impl.dart new file mode 100644 index 000000000..34f4c0bb6 --- /dev/null +++ b/lib/features/mailbox_dashboard/data/datasource_impl/local_app_grid_datasource_impl.dart @@ -0,0 +1,30 @@ + +import 'package:core/utils/config/app_config_loader.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource/app_grid_datasource.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/app_dashboard/app_dashboard_configuration_parser.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/app_dashboard/linagora_applications.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/linagora_ecosystem.dart'; +import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; + +class LocalAppGridDatasourceImpl extends AppGridDatasource { + + final AppConfigLoader _appConfigLoader; + final ExceptionThrower _exceptionThrower; + + LocalAppGridDatasourceImpl(this._appConfigLoader, this._exceptionThrower); + + @override + Future getLinagoraApplications(String path) { + return Future.sync(() async { + return await _appConfigLoader.load( + path, + AppDashboardConfigurationParser(), + ); + }).catchError(_exceptionThrower.throwException); + } + + @override + Future getLinagoraEcosystem(String baseUrl) { + throw UnimplementedError(); + } +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/data/network/linagora_ecosystem_api.dart b/lib/features/mailbox_dashboard/data/network/linagora_ecosystem_api.dart new file mode 100644 index 000000000..3d86b73c3 --- /dev/null +++ b/lib/features/mailbox_dashboard/data/network/linagora_ecosystem_api.dart @@ -0,0 +1,29 @@ + +import 'dart:convert'; + +import 'package:core/data/network/dio_client.dart'; +import 'package:core/utils/app_logger.dart'; +import 'package:tmail_ui_user/features/login/data/extensions/service_path_extension.dart'; +import 'package:tmail_ui_user/features/login/data/network/endpoint.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/exceptions/linagora_ecosystem_exceptions.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/linagora_ecosystem.dart'; + +class LinagoraEcosystemApi { + final DioClient _dioClient; + + LinagoraEcosystemApi(this._dioClient); + + Future getLinagoraEcosystem(String baseUrl) async { + final result = await _dioClient.get( + Endpoint.linagoraEcosystem.usingBaseUrl(baseUrl).generateEndpointPath(), + ); + log('LinagoraEcosystemApi::getLinagoraEcosystem: $result'); + if (result is Map) { + return LinagoraEcosystem.deserialize(result); + } else if (result is String) { + return LinagoraEcosystem.deserialize(jsonDecode(result)); + } else { + throw NotFoundLinagoraEcosystem(); + } + } +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/data/repository/app_grid_repository_impl.dart b/lib/features/mailbox_dashboard/data/repository/app_grid_repository_impl.dart new file mode 100644 index 000000000..d851007f7 --- /dev/null +++ b/lib/features/mailbox_dashboard/data/repository/app_grid_repository_impl.dart @@ -0,0 +1,23 @@ + +import 'package:core/data/model/source_type/data_source_type.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource/app_grid_datasource.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/app_dashboard/linagora_applications.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/linagora_ecosystem.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/app_grid_repository.dart'; + +class AppGridRepositoryImpl extends AppGridRepository { + + final Map _mapDataSource; + + AppGridRepositoryImpl(this._mapDataSource); + + @override + Future getLinagoraApplications(String path) { + return _mapDataSource[DataSourceType.local]!.getLinagoraApplications(path); + } + + @override + Future getLinagoraEcosystem(String baseUrl) { + return _mapDataSource[DataSourceType.network]!.getLinagoraEcosystem(baseUrl); + } +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/domain/app_dashboard/linagora_app.dart b/lib/features/mailbox_dashboard/domain/app_dashboard/linagora_app.dart index dc8254359..c3e24b092 100644 --- a/lib/features/mailbox_dashboard/domain/app_dashboard/linagora_app.dart +++ b/lib/features/mailbox_dashboard/domain/app_dashboard/linagora_app.dart @@ -1,40 +1,42 @@ -import 'package:equatable/equatable.dart'; +import 'package:core/presentation/resources/image_paths.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/app_linagora_ecosystem.dart'; part 'linagora_app.g.dart'; @JsonSerializable(explicitToJson: true, includeIfNull: false) -class LinagoraApp with EquatableMixin{ - @JsonKey(name: 'appName') - final String appName; - +class LinagoraApp extends AppLinagoraEcosystem { @JsonKey(name: 'icon') final String? iconName; @JsonKey(name: 'appLink') final Uri appUri; - final String? androidPackageId; - final String? iosUrlScheme; - final String? iosAppStoreLink; final Uri? publicIconUri; - LinagoraApp( - this.appName, - this.appUri, - { - this.iconName, - this.androidPackageId, - this.iosUrlScheme, - this.iosAppStoreLink, - this.publicIconUri - } - ); + LinagoraApp({ + required this.appUri, + this.iconName, + this.publicIconUri, + super.appName, + super.androidPackageId, + super.iosUrlScheme, + super.iosAppStoreLink, + }); factory LinagoraApp.fromJson(Map json) => _$LinagoraAppFromJson(json); + @override Map toJson() => _$LinagoraAppToJson(this); + @override + String? getIconPath(ImagePaths imagePaths) => iconName != null + ? imagePaths.getConfigurationImagePath(iconName!) + : publicIconUri?.toString(); + + @override + Uri? get appRedirectLink => appUri; + @override List get props => [ appName, diff --git a/lib/features/mailbox_dashboard/domain/exceptions/linagora_ecosystem_exceptions.dart b/lib/features/mailbox_dashboard/domain/exceptions/linagora_ecosystem_exceptions.dart new file mode 100644 index 000000000..edd236cc5 --- /dev/null +++ b/lib/features/mailbox_dashboard/domain/exceptions/linagora_ecosystem_exceptions.dart @@ -0,0 +1,2 @@ + +class NotFoundLinagoraEcosystem implements Exception {} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/domain/linagora_ecosystem/app_linagora_ecosystem.dart b/lib/features/mailbox_dashboard/domain/linagora_ecosystem/app_linagora_ecosystem.dart index 7af7bd2e6..d01b49aba 100644 --- a/lib/features/mailbox_dashboard/domain/linagora_ecosystem/app_linagora_ecosystem.dart +++ b/lib/features/mailbox_dashboard/domain/linagora_ecosystem/app_linagora_ecosystem.dart @@ -1,3 +1,4 @@ +import 'package:core/core.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/empty_linagora_ecosystem.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/linagora_ecosystem_properties.dart'; @@ -11,7 +12,7 @@ class AppLinagoraEcosystem extends LinagoraEcosystemProperties { final String? androidPackageId; final String? iosUrlScheme; final String? iosAppStoreLink; - final String? webLink; + final Uri? webLink; AppLinagoraEcosystem({ this.appName, @@ -34,6 +35,17 @@ class AppLinagoraEcosystem extends LinagoraEcosystemProperties { } } + String? getIconPath(ImagePaths imagePaths) => logoURL; + + Uri? get appRedirectLink => webLink; + + bool get isAppIOSEnabled => iosUrlScheme?.isNotEmpty == true || + iosAppStoreLink?.isNotEmpty == true || + appRedirectLink != null; + + bool get isAppAndroidEnabled => androidPackageId?.isNotEmpty == true || + appRedirectLink != null; + @override List get props => [ appName, diff --git a/lib/features/mailbox_dashboard/domain/linagora_ecosystem/linagora_ecosystem.dart b/lib/features/mailbox_dashboard/domain/linagora_ecosystem/linagora_ecosystem.dart index 024e44288..6519a1d53 100644 --- a/lib/features/mailbox_dashboard/domain/linagora_ecosystem/linagora_ecosystem.dart +++ b/lib/features/mailbox_dashboard/domain/linagora_ecosystem/linagora_ecosystem.dart @@ -1,8 +1,10 @@ import 'package:equatable/equatable.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/app_linagora_ecosystem.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/converters/linagora_ecosystem_converter.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/linagora_ecosystem_identifier.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/linagora_ecosystem_properties.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/mobile_apps_linagora_ecosystem.dart'; class LinagoraEcosystem with EquatableMixin { final Map? properties; @@ -15,4 +17,31 @@ class LinagoraEcosystem with EquatableMixin { @override List get props => [properties]; +} + +extension LinagoraEcosystemExtension on LinagoraEcosystem { + List get listAppLinagoraEcosystem { + if (properties == null) return []; + + final listWebAppLinagora = properties + !.values + .whereType() + .toList(); + + final listMobileAppLinagora = (properties![LinagoraEcosystemIdentifier.mobileApps] as MobileAppsLinagoraEcosystem?) + ?.apps + ?.values + .whereType() + .toList() ?? []; + + return listWebAppLinagora + listMobileAppLinagora; + } + + List get listAppLinagoraEcosystemOnIOS { + return listAppLinagoraEcosystem.where((app) => app.isAppIOSEnabled).toList(); + } + + List get listAppLinagoraEcosystemOnAndroid { + return listAppLinagoraEcosystem.where((app) => app.isAppAndroidEnabled).toList(); + } } \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/domain/repository/app_grid_repository.dart b/lib/features/mailbox_dashboard/domain/repository/app_grid_repository.dart new file mode 100644 index 000000000..d9dec79fc --- /dev/null +++ b/lib/features/mailbox_dashboard/domain/repository/app_grid_repository.dart @@ -0,0 +1,9 @@ + +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/app_dashboard/linagora_applications.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/linagora_ecosystem.dart'; + +abstract class AppGridRepository { + Future getLinagoraApplications(String path); + + Future getLinagoraEcosystem(String baseUrl); +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/domain/state/get_app_dashboard_configuration_state.dart b/lib/features/mailbox_dashboard/domain/state/get_app_dashboard_configuration_state.dart index 644fc1ff0..3e7ba06b7 100644 --- a/lib/features/mailbox_dashboard/domain/state/get_app_dashboard_configuration_state.dart +++ b/lib/features/mailbox_dashboard/domain/state/get_app_dashboard_configuration_state.dart @@ -1,17 +1,17 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/app_dashboard/linagora_applications.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/app_linagora_ecosystem.dart'; -class LoadingAppDashboardConfiguration extends UIState {} +class LoadingAppDashboardConfiguration extends LoadingState {} class GetAppDashboardConfigurationSuccess extends UIState { - final LinagoraApplications linagoraApplications; + final List listLinagoraApp; - GetAppDashboardConfigurationSuccess(this.linagoraApplications); + GetAppDashboardConfigurationSuccess(this.listLinagoraApp); @override - List get props => [linagoraApplications]; + List get props => [listLinagoraApp]; } class GetAppDashboardConfigurationFailure extends FeatureFailure { diff --git a/lib/features/mailbox_dashboard/domain/state/get_app_grid_linagora_ecosystem_state.dart b/lib/features/mailbox_dashboard/domain/state/get_app_grid_linagora_ecosystem_state.dart new file mode 100644 index 000000000..2e75f644a --- /dev/null +++ b/lib/features/mailbox_dashboard/domain/state/get_app_grid_linagora_ecosystem_state.dart @@ -0,0 +1,20 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/app_linagora_ecosystem.dart'; + +class LoadingAppGridLinagraEcosystem extends LoadingState {} + +class GetAppGridLinagraEcosystemSuccess extends UIState { + + final List listAppLinagoraEcosystem; + + GetAppGridLinagraEcosystemSuccess(this.listAppLinagoraEcosystem); + + @override + List get props => [listAppLinagoraEcosystem]; +} + +class GetAppGridLinagraEcosystemFailure extends FeatureFailure { + + GetAppGridLinagraEcosystemFailure(dynamic exception) : super(exception: exception); +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/domain/usecases/get_app_dashboard_configuration_interactor.dart b/lib/features/mailbox_dashboard/domain/usecases/get_app_dashboard_configuration_interactor.dart index fec5053e4..81e4d0702 100644 --- a/lib/features/mailbox_dashboard/domain/usecases/get_app_dashboard_configuration_interactor.dart +++ b/lib/features/mailbox_dashboard/domain/usecases/get_app_dashboard_configuration_interactor.dart @@ -1,27 +1,20 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:core/utils/app_logger.dart'; -import 'package:core/utils/config/app_config_loader.dart'; import 'package:dartz/dartz.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/app_dashboard/app_dashboard_configuration_parser.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/app_dashboard/linagora_applications.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/app_grid_repository.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_app_dashboard_configuration_state.dart'; class GetAppDashboardConfigurationInteractor { - final AppConfigLoader _appConfigLoader; + final AppGridRepository _appGridRepository; - GetAppDashboardConfigurationInteractor(this._appConfigLoader); + GetAppDashboardConfigurationInteractor(this._appGridRepository); - Stream> execute(String appDashboardConfigurationPath) async* { + Stream> execute(String path) async* { try { yield Right(LoadingAppDashboardConfiguration()); - - final linagoraApps = await _appConfigLoader.load( - appDashboardConfigurationPath, - AppDashboardConfigurationParser() - ); - - yield Right(GetAppDashboardConfigurationSuccess(linagoraApps)); + final linagoraApps = await _appGridRepository.getLinagoraApplications(path); + yield Right(GetAppDashboardConfigurationSuccess(linagoraApps.apps)); } catch (e) { logError('GetAppDashboardConfigurationInteractor::execute(): $e'); yield Left(GetAppDashboardConfigurationFailure(e)); diff --git a/lib/features/mailbox_dashboard/domain/usecases/get_app_grid_linagra_ecosystem_interactor.dart b/lib/features/mailbox_dashboard/domain/usecases/get_app_grid_linagra_ecosystem_interactor.dart new file mode 100644 index 000000000..1a648d59e --- /dev/null +++ b/lib/features/mailbox_dashboard/domain/usecases/get_app_grid_linagra_ecosystem_interactor.dart @@ -0,0 +1,30 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:core/utils/platform_info.dart'; +import 'package:dartz/dartz.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/app_linagora_ecosystem.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/linagora_ecosystem.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/app_grid_repository.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_app_grid_linagora_ecosystem_state.dart'; + +class GetAppGridLinagraEcosystemInteractor { + final AppGridRepository _appGridRepository; + + GetAppGridLinagraEcosystemInteractor(this._appGridRepository); + + Stream> execute(String baseUrl) async* { + try { + yield Right(LoadingAppGridLinagraEcosystem()); + final linagoraEcosystem = await _appGridRepository.getLinagoraEcosystem(baseUrl); + List listMobileAppLinagora = linagoraEcosystem.listAppLinagoraEcosystem; + if (PlatformInfo.isAndroid) { + listMobileAppLinagora = linagoraEcosystem.listAppLinagoraEcosystemOnAndroid; + } else if (PlatformInfo.isIOS) { + listMobileAppLinagora = linagoraEcosystem.listAppLinagoraEcosystemOnIOS; + } + yield Right(GetAppGridLinagraEcosystemSuccess(listMobileAppLinagora)); + } catch (e) { + yield Left(GetAppGridLinagraEcosystemFailure(e)); + } + } +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart index 9b8489535..c5d8039bc 100644 --- a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart +++ b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart @@ -54,21 +54,28 @@ import 'package:tmail_ui_user/features/mailbox/data/repository/mailbox_repositor import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/mark_as_mailbox_read_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_bindings.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource/app_grid_datasource.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource/search_datasource.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource/session_storage_composer_datasource.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource_impl/app_grid_datasource_impl.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource_impl/hive_spam_report_datasource_impl.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource_impl/local_app_grid_datasource_impl.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource_impl/local_spam_report_datasource_impl.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource_impl/search_datasource_impl.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/datasource_impl/session_storage_composer_datasoure_impl.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/local/local_spam_report_manager.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/data/network/linagora_ecosystem_api.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/data/repository/app_grid_repository_impl.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/repository/composer_cache_repository_impl.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/repository/search_repository_impl.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/repository/spam_report_repository_impl.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/app_grid_repository.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/composer_cache_repository.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/search_repository.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/spam_report_repository.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/get_all_recent_search_latest_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/get_app_dashboard_configuration_interactor.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/get_app_grid_linagra_ecosystem_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/get_composer_cache_on_web_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/get_spam_mailbox_cached_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/get_spam_report_state_interactor.dart'; @@ -143,7 +150,10 @@ class MailboxDashBoardBindings extends BaseBindings { @override void bindingsController() { - Get.put(AppGridDashboardController(Get.find())); + Get.put(AppGridDashboardController( + Get.find(), + Get.find(), + )); Get.put(DownloadController()); Get.put(SearchController( Get.find(), @@ -202,6 +212,7 @@ class MailboxDashBoardBindings extends BaseBindings { Get.lazyPut( () => Get.find()); Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); } @override @@ -271,6 +282,14 @@ class MailboxDashBoardBindings extends BaseBindings { Get.lazyPut(() => LocalIdentityCreatorDataSourceImpl( Get.find() )); + Get.lazyPut(() => AppGridDatasourceImpl( + Get.find(), + Get.find(), + )); + Get.lazyPut(() => LocalAppGridDatasourceImpl( + Get.find(), + Get.find(), + )); } @override @@ -299,8 +318,8 @@ class MailboxDashBoardBindings extends BaseBindings { Get.lazyPut(() => DeleteMultipleEmailsPermanentlyInteractor(Get.find())); Get.lazyPut(() => EmptyTrashFolderInteractor(Get.find())); Get.lazyPut(() => EmptySpamFolderInteractor(Get.find())); - Get.lazyPut(() => GetAppDashboardConfigurationInteractor( - Get.find())); + Get.lazyPut(() => GetAppDashboardConfigurationInteractor(Get.find())); + Get.lazyPut(() => GetAppGridLinagraEcosystemInteractor(Get.find())); Get.lazyPut(() => GetEmailByIdInteractor( Get.find(), Get.find())); @@ -343,6 +362,7 @@ class MailboxDashBoardBindings extends BaseBindings { Get.lazyPut(() => Get.find()); Get.lazyPut(() => Get.find()); Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); } @override @@ -391,5 +411,9 @@ class MailboxDashBoardBindings extends BaseBindings { Get.lazyPut(() => IdentityCreatorRepositoryImpl( Get.find() )); + Get.lazyPut(() => AppGridRepositoryImpl({ + DataSourceType.network: Get.find(), + DataSourceType.local: Get.find() + },)); } } \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/controller/app_grid_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/app_grid_dashboard_controller.dart index bb6c7317c..efe81cc10 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/app_grid_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/app_grid_dashboard_controller.dart @@ -1,38 +1,48 @@ -import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:core/utils/app_logger.dart'; -import 'package:dartz/dartz.dart'; import 'package:get/get_rx/get_rx.dart'; -import 'package:model/mailbox/expand_mode.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/app_dashboard/linagora_applications.dart'; +import 'package:tmail_ui_user/features/base/base_controller.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/app_linagora_ecosystem.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_app_dashboard_configuration_state.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_app_grid_linagora_ecosystem_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/get_app_dashboard_configuration_interactor.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/get_app_grid_linagra_ecosystem_interactor.dart'; import 'package:tmail_ui_user/main/utils/app_config.dart'; -class AppGridDashboardController { - final isAppGridDashboardOverlayOpen = false.obs; - final appDashboardExpandMode = ExpandMode.COLLAPSE.obs; - final linagoraApplications = Rxn(); +class AppGridDashboardController extends BaseController { + final listLinagoraApp = RxList(); final GetAppDashboardConfigurationInteractor _getAppDashboardConfigurationInteractor; + final GetAppGridLinagraEcosystemInteractor _getAppGridLinagraEcosystemInteractor; - AppGridDashboardController(this._getAppDashboardConfigurationInteractor); + AppGridDashboardController( + this._getAppDashboardConfigurationInteractor, + this._getAppGridLinagraEcosystemInteractor, + ); - void toggleAppGridDashboard() { - isAppGridDashboardOverlayOpen.toggle(); - final newExpandMode = appDashboardExpandMode.value == ExpandMode.EXPAND - ? ExpandMode.COLLAPSE - : ExpandMode.EXPAND; - appDashboardExpandMode.value = newExpandMode; + @override + void handleSuccessViewState(Success success) { + if (success is GetAppDashboardConfigurationSuccess) { + syncLinagoraApps(success.listLinagoraApp); + } else if (success is GetAppGridLinagraEcosystemSuccess) { + syncLinagoraApps(success.listAppLinagoraEcosystem); + } else { + super.handleSuccessViewState(success); + } } - Stream> showDashboardAction() { - return _getAppDashboardConfigurationInteractor.execute(AppConfig.appDashboardConfigurationPath); + void loadAppDashboardConfiguration() { + consumeState(_getAppDashboardConfigurationInteractor.execute( + AppConfig.appDashboardConfigurationPath, + )); } - void handleShowAppDashboard(LinagoraApplications linagoraApps) { - log('AppGridDashboardController::handleShowAppDashboard(): $linagoraApps'); - isAppGridDashboardOverlayOpen.value = true; - appDashboardExpandMode.value = ExpandMode.EXPAND; - linagoraApplications.value = linagoraApps; + void loadAppGridLinagraEcosystem(String baseUrl) { + consumeState(_getAppGridLinagraEcosystemInteractor.execute(baseUrl)); + } + + void syncLinagoraApps(List linagoraApps) { + log('AppGridDashboardController::setListLinagoraApp:linagoraApps = $linagoraApps'); + listLinagoraApp.value = linagoraApps; } } \ No newline at end of file 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 5fdc04e8a..cb3b444f3 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -91,7 +91,6 @@ import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentat import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/exceptions/spam_report_exception.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/spam_report_state.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_app_dashboard_configuration_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_composer_cache_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/remove_email_drafts_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/get_composer_cache_on_web_interactor.dart'; @@ -329,6 +328,7 @@ class MailboxDashBoardController extends ReloadableController _registerPendingCurrentEmailIdInNotification(); } _handleArguments(); + _loadAppGrid(); super.onReady(); } @@ -407,8 +407,6 @@ class MailboxDashBoardController extends ReloadableController } else if (success is DeleteMultipleEmailsPermanentlyAllSuccess || success is DeleteMultipleEmailsPermanentlyHasSomeEmailFailure) { _deleteMultipleEmailsPermanentlySuccess(success); - } else if (success is GetAppDashboardConfigurationSuccess) { - appGridDashboardController.handleShowAppDashboard(success.linagoraApplications); } else if(success is GetEmailByIdSuccess) { openEmailDetailedView(success.email); } else if (success is StoreSendingEmailSuccess) { @@ -1590,8 +1588,8 @@ class MailboxDashBoardController extends ReloadableController } void emptyTrashFolderAction({ - Function? onCancelSelectionEmail, - MailboxId? trashFolderId, + Function? onCancelSelectionEmail, + MailboxId? trashFolderId, int totalEmails = 0, }) { onCancelSelectionEmail?.call(); @@ -1601,8 +1599,8 @@ class MailboxDashBoardController extends ReloadableController final totalEmailsInTrash = totalEmails == 0 ? trashMailbox?.countTotalEmails : totalEmails; if (sessionCurrent != null && accountId.value != null && trashMailboxId != null) { consumeState(_emptyTrashFolderInteractor.execute( - sessionCurrent!, - accountId.value!, + sessionCurrent!, + accountId.value!, trashMailboxId, totalEmailsInTrash ?? 0, _progressStateController @@ -2186,16 +2184,6 @@ class MailboxDashBoardController extends ReloadableController dispatchAction(EmptyTrashAction()); } - void showAppDashboardAction() async { - log('MailboxDashBoardController::showAppDashboardAction(): begin'); - final apps = appGridDashboardController.linagoraApplications.value; - if (apps != null) { - consumeState(Stream.value(Right(GetAppDashboardConfigurationSuccess(apps)))); - return; - } - consumeState(appGridDashboardController.showDashboardAction()); - } - bool isAbleMarkAllAsRead(){ return !searchController.isSearchEmailRunning && selectedMailbox.value != null && selectedMailbox.value!.isDrafts; } @@ -2605,7 +2593,7 @@ class MailboxDashBoardController extends ReloadableController } void emptySpamFolderAction({ - Function? onCancelSelectionEmail, + Function? onCancelSelectionEmail, MailboxId? spamFolderId, int totalEmails = 0 }) { @@ -3220,6 +3208,16 @@ class MailboxDashBoardController extends ReloadableController jmap.State? get currentEmailState => _currentEmailState; + void _loadAppGrid() { + if (PlatformInfo.isWeb && AppConfig.appGridDashboardAvailable) { + appGridDashboardController.loadAppDashboardConfiguration(); + } else if (PlatformInfo.isMobile) { + appGridDashboardController.loadAppGridLinagraEcosystem( + dynamicUrlInterceptors.jmapUrl ?? '', + ); + } + } + @override void onClose() { if (PlatformInfo.isWeb) { diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index f731e0d75..4591219a1 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -73,7 +73,6 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { contactSupportCapability: controller.sessionCurrent?.getContactSupportCapability(accountId), searchForm: SearchInputFormWidget(), appGridController: controller.appGridDashboardController, - onShowAppDashboardAction: controller.showAppDashboardAction, onTapApplicationLogoAction: controller.redirectToInboxAction, onTapAvatarAction: (position) => controller.handleClickAvatarAction(context, position), onTapContactSupportAction: (contactSupport) => diff --git a/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_icon.dart b/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_icon.dart index 885fdfbae..11e6b1a8a 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_icon.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_icon.dart @@ -2,60 +2,74 @@ import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; -import 'package:get/get.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/app_grid_dashboard_controller.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/app_linagora_ecosystem.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_overlay.dart'; import 'package:tmail_ui_user/main/utils/app_utils.dart'; -class AppGridDashboardIcon extends StatelessWidget { +class AppGridDashboardIcon extends StatefulWidget { final ImagePaths imagePaths; - final AppGridDashboardController appGridController; - final VoidCallback? onShowAppDashboardAction; + final List linagoraApps; const AppGridDashboardIcon({ super.key, required this.imagePaths, - required this.appGridController, - this.onShowAppDashboardAction, + required this.linagoraApps, }); + @override + State createState() => _AppGridDashboardIconState(); +} + +class _AppGridDashboardIconState extends State { + + final ValueNotifier _isExpandedNotifier = ValueNotifier(false); + + @override + void dispose() { + _isExpandedNotifier.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return Obx(() { - final isAppGridOpen = appGridController.isAppGridDashboardOverlayOpen.value; - return PortalTarget( - visible: isAppGridOpen, - portalFollower: GestureDetector( - behavior: HitTestBehavior.opaque, - onTap: appGridController.toggleAppGridDashboard - ), - child: PortalTarget( - anchor: Aligned( - follower: AppUtils.isDirectionRTL(context) - ? Alignment.topLeft - : Alignment.topRight, - target: AppUtils.isDirectionRTL(context) - ? Alignment.bottomLeft - : Alignment.bottomRight + return ValueListenableBuilder( + valueListenable: _isExpandedNotifier, + builder: (context, isExpanded, child) { + return PortalTarget( + visible: isExpanded, + portalFollower: GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: _toggleAppGridDashboard, ), - portalFollower: Obx(() { - final listApps = appGridController.linagoraApplications.value; - if (listApps?.apps.isNotEmpty == true) { - return AppDashboardOverlay(listApps!); - } - return const SizedBox.shrink(); - }), - visible: isAppGridOpen, - child: TMailButtonWidget.fromIcon( - icon: imagePaths.icAppDashboard, - backgroundColor: Colors.transparent, - iconSize: 30, - padding: const EdgeInsets.all(6), - onTapActionCallback: onShowAppDashboardAction, + child: PortalTarget( + anchor: Aligned( + follower: AppUtils.isDirectionRTL(context) + ? Alignment.topLeft + : Alignment.topRight, + target: AppUtils.isDirectionRTL(context) + ? Alignment.bottomLeft + : Alignment.bottomRight, + ), + portalFollower: AppDashboardOverlay( + listLinagoraApp: widget.linagoraApps, + imagePaths: widget.imagePaths, + ), + visible: isExpanded, + child: TMailButtonWidget.fromIcon( + icon: widget.imagePaths.icAppDashboard, + backgroundColor: Colors.transparent, + iconSize: 30, + padding: const EdgeInsets.all(6), + onTapActionCallback: _toggleAppGridDashboard, + ), ), - ) - ); - }); + ); + }, + ); + } + + void _toggleAppGridDashboard() { + _isExpandedNotifier.value = !_isExpandedNotifier.value; } } diff --git a/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_item.dart b/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_item.dart index ade8417d5..ef38db5ac 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_item.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_item.dart @@ -1,64 +1,47 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; +import 'package:core/presentation/views/image/image_loader_mixin.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/base/mixin/launcher_application_mixin.dart'; import 'package:tmail_ui_user/features/base/widget/link_browser_widget.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/app_dashboard/linagora_app.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/app_linagora_ecosystem.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/app_grid_dashboard_style.dart'; -import 'package:tmail_ui_user/main/utils/app_utils.dart'; -class AppGridDashboardItem extends StatelessWidget { - final LinagoraApp app; +class AppGridDashboardItem extends StatelessWidget + with LauncherApplicationMixin, ImageLoaderMixin { - AppGridDashboardItem(this.app, {Key? key}) : super(key: key); + final AppLinagoraEcosystem app; + final ImagePaths imagePaths; - final ImagePaths _imagePaths = Get.find(); + const AppGridDashboardItem({ + Key? key, + required this.app, + required this.imagePaths, + }) : super(key: key); @override Widget build(BuildContext context) { return LinkBrowserWidget( - uri: app.appUri, + uri: app.appRedirectLink!, child: Material( color: Colors.transparent, child: InkWell( - onTap: () => AppUtils.launchLink(app.appUri.toString()), + onTap: () => launchApplication(uri: app.appRedirectLink), borderRadius: const BorderRadius.all(Radius.circular(16)), hoverColor: AppColor.colorBgMailboxSelected, child: Container( width: AppGridDashboardStyle.hoverIconSize, padding: const EdgeInsets.symmetric(vertical: 8), child: Column(children: [ - if (app.iconName?.isNotEmpty == true) - app.iconName!.endsWith("svg") - ? SvgPicture.asset( - _imagePaths.getConfigurationImagePath(app.iconName!), - width: AppGridDashboardStyle.iconSize, - height: AppGridDashboardStyle.iconSize, - fit: BoxFit.fill) - : Image.asset( - _imagePaths.getConfigurationImagePath(app.iconName!), - width: AppGridDashboardStyle.iconSize, - height: AppGridDashboardStyle.iconSize, - fit: BoxFit.fill) - else if (app.publicIconUri != null) - Image.network( - app.publicIconUri.toString(), - width: AppGridDashboardStyle.iconSize, - height: AppGridDashboardStyle.iconSize, - fit: BoxFit.fill, - errorBuilder: (_, error, stackTrace) { - return Container( - width: AppGridDashboardStyle.iconSize, - height: AppGridDashboardStyle.iconSize, - color: AppColor.textFieldHintColor, - ); - } + if (app.getIconPath(imagePaths) != null) + buildImage( + imagePath: app.getIconPath(imagePaths)!, + imageSize: AppGridDashboardStyle.iconSize, ), Padding( padding: const EdgeInsets.only(top: 8), child: Text( - app.appName, + app.appName ?? '', maxLines: 1, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, diff --git a/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_overlay.dart b/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_overlay.dart index 065060d48..9b0407078 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_overlay.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_overlay.dart @@ -1,12 +1,18 @@ +import 'package:core/presentation/resources/image_paths.dart'; import 'package:flutter/material.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/app_dashboard/linagora_applications.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/app_linagora_ecosystem.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/app_grid_dashboard_style.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_item.dart'; class AppDashboardOverlay extends StatelessWidget { - final LinagoraApplications _linagoraApplications; + final List listLinagoraApp; + final ImagePaths imagePaths; - const AppDashboardOverlay(this._linagoraApplications, {Key? key}) : super(key: key); + const AppDashboardOverlay({ + Key? key, + required this.listLinagoraApp, + required this.imagePaths, + }) : super(key: key); @override Widget build(BuildContext context) { @@ -20,17 +26,17 @@ class AppDashboardOverlay extends StatelessWidget { boxShadow: AppGridDashboardStyle.cardShadow ), padding: AppGridDashboardStyle.padding, - child: Wrap(children: _linagoraApplications.apps - .map((app) => AppGridDashboardItem(app)) + child: Wrap(children: listLinagoraApp + .map((app) => AppGridDashboardItem(app: app, imagePaths: imagePaths)) .toList()), ), ); } double get _widthAppGrid { - if (_linagoraApplications.apps.length >= 3) { + if (listLinagoraApp.length >= 3) { return AppGridDashboardStyle.hoverIconSize * 3 + AppGridDashboardStyle.padding.horizontal; - } else if (_linagoraApplications.apps.length == 2) { + } else if (listLinagoraApp.length == 2) { return AppGridDashboardStyle.hoverIconSize * 2 + AppGridDashboardStyle.padding.horizontal; } else { return AppGridDashboardStyle.hoverIconSize + AppGridDashboardStyle.padding.horizontal; diff --git a/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_list_dashboard_item.dart b/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_list_dashboard_item.dart index bb51f2086..95ac7d439 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_list_dashboard_item.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_list_dashboard_item.dart @@ -1,60 +1,43 @@ -import 'dart:io'; import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/views/text/slogan_builder.dart'; -import 'package:core/utils/platform_info.dart'; -import 'package:external_app_launcher/external_app_launcher.dart'; import 'package:flutter/material.dart'; -import 'package:get/get_core/get_core.dart'; -import 'package:get/get_instance/get_instance.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/domain/app_dashboard/linagora_app.dart'; -import 'package:url_launcher/url_launcher.dart' as launcher; +import 'package:tmail_ui_user/features/base/mixin/launcher_application_mixin.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/domain/linagora_ecosystem/app_linagora_ecosystem.dart'; -class AppListDashboardItem extends StatelessWidget { +class AppListDashboardItem extends StatelessWidget with LauncherApplicationMixin { - final LinagoraApp app; + final AppLinagoraEcosystem app; + final ImagePaths imagePaths; - const AppListDashboardItem(this.app, {Key? key}) : super(key: key); + const AppListDashboardItem({ + super.key, + required this.app, + required this.imagePaths, + }); @override Widget build(BuildContext context) { - final imagePaths = Get.find(); return SloganBuilder( sizeLogo: 32, paddingText: const EdgeInsetsDirectional.only(start: 12), text: app.appName, textAlign: TextAlign.center, textStyle: const TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: AppColor.colorNameEmail), - logo: app.iconName?.isNotEmpty == true - ? imagePaths.getConfigurationImagePath(app.iconName!) - : null, - publicLogoUri: app.publicIconUri, - onTapCallback: () => _openApp(context, app), + logo: app.getIconPath(imagePaths), + onTapCallback: _handleOpenApp, padding: const EdgeInsetsDirectional.symmetric(vertical: 12, horizontal: 20), hoverColor: AppColor.colorBgMailboxSelected ); } - void _openApp(BuildContext context, LinagoraApp app) async { - if (PlatformInfo.isWeb) { - if (await launcher.canLaunchUrl(app.appUri)) { - await launcher.launchUrl(app.appUri); - } - } else if (Platform.isAndroid && app.androidPackageId?.isNotEmpty == true) { - await LaunchApp.openApp(androidPackageName: app.androidPackageId); - } else if (Platform.isIOS && app.iosUrlScheme?.isNotEmpty == true) { - await LaunchApp.openApp( - iosUrlScheme: '${app.iosUrlScheme}://', - appStoreLink: app.iosAppStoreLink - ); - } else { - if (await launcher.canLaunchUrl(app.appUri)) { - await launcher.launchUrl( - app.appUri, - mode: launcher.LaunchMode.externalApplication - ); - } - } + Future _handleOpenApp() async { + await launchApplication( + androidPackageId: app.androidPackageId, + iosScheme: app.iosUrlScheme, + iosStoreLink: app.iosAppStoreLink, + uri: app.appRedirectLink, + ); } } diff --git a/lib/features/mailbox_dashboard/presentation/widgets/navigation_bar/navigation_bar_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/navigation_bar/navigation_bar_widget.dart index 5b729aa4c..41af26cdb 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/navigation_bar/navigation_bar_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/navigation_bar/navigation_bar_widget.dart @@ -4,6 +4,7 @@ import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:core/presentation/views/image/avatar_builder.dart'; import 'package:flutter/material.dart'; +import 'package:get/get.dart'; import 'package:model/support/contact_support_capability.dart'; import 'package:tmail_ui_user/features/base/mixin/contact_support_mixin.dart'; import 'package:tmail_ui_user/features/base/widget/application_logo_with_text_widget.dart'; @@ -11,7 +12,6 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/navigation_bar_style.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_grid_dashboard_icon.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; -import 'package:tmail_ui_user/main/utils/app_config.dart'; class NavigationBarWidget extends StatelessWidget { @@ -21,7 +21,6 @@ class NavigationBarWidget extends StatelessWidget { final Widget? searchForm; final AppGridDashboardController? appGridController; final VoidCallback? onTapApplicationLogoAction; - final VoidCallback? onShowAppDashboardAction; final OnTapAvatarActionWithPositionClick? onTapAvatarAction; final OnTapContactSupportAction? onTapContactSupportAction; @@ -32,7 +31,6 @@ class NavigationBarWidget extends StatelessWidget { this.contactSupportCapability, this.searchForm, this.appGridController, - this.onShowAppDashboardAction, this.onTapApplicationLogoAction, this.onTapAvatarAction, this.onTapContactSupportAction, @@ -74,15 +72,17 @@ class NavigationBarWidget extends StatelessWidget { tooltipMessage: AppLocalizations.of(context).getHelpOrReportABug, onTapActionCallback: () => onTapContactSupportAction?.call(contactSupportCapability!), ), - if (AppConfig.appGridDashboardAvailable && appGridController != null) - Padding( - padding: const EdgeInsetsDirectional.only(end: 16), - child: AppGridDashboardIcon( - imagePaths: imagePaths, - appGridController: appGridController!, - onShowAppDashboardAction: onShowAppDashboardAction, - ), - ), + if (appGridController != null) + Obx(() { + if (appGridController!.listLinagoraApp.isNotEmpty) { + return AppGridDashboardIcon( + imagePaths: imagePaths, + linagoraApps: appGridController!.listLinagoraApp, + ); + } + return const SizedBox.shrink(); + }), + const SizedBox(width: 16), (AvatarBuilder() ..text(avatarUserName) ..backgroundColor(Colors.white) @@ -114,15 +114,17 @@ class NavigationBarWidget extends StatelessWidget { tooltipMessage: AppLocalizations.of(context).getHelpOrReportABug, onTapActionCallback: () => onTapContactSupportAction?.call(contactSupportCapability!), ), - if (AppConfig.appGridDashboardAvailable && appGridController != null) - Padding( - padding: const EdgeInsetsDirectional.only(end: 16), - child: AppGridDashboardIcon( - imagePaths: imagePaths, - appGridController: appGridController!, - onShowAppDashboardAction: onShowAppDashboardAction, - ), - ), + if (appGridController != null) + Obx(() { + if (appGridController!.listLinagoraApp.isNotEmpty) { + return AppGridDashboardIcon( + imagePaths: imagePaths, + linagoraApps: appGridController!.listLinagoraApp, + ); + } + return const SizedBox.shrink(); + }), + const SizedBox(width: 16), (AvatarBuilder() ..text(avatarUserName) ..backgroundColor(Colors.white) diff --git a/lib/features/manage_account/presentation/mailbox_visibility/mailbox_visibility_controller.dart b/lib/features/manage_account/presentation/mailbox_visibility/mailbox_visibility_controller.dart index d405b92fa..3b19a95e1 100644 --- a/lib/features/manage_account/presentation/mailbox_visibility/mailbox_visibility_controller.dart +++ b/lib/features/manage_account/presentation/mailbox_visibility/mailbox_visibility_controller.dart @@ -152,8 +152,6 @@ class MailboxVisibilityController extends BaseMailboxController { mailboxCategoriesExpandMode.value.teamMailboxes = newExpandMode; mailboxCategoriesExpandMode.refresh(); break; - case MailboxCategories.appGrid: - break; } } diff --git a/lib/main/bindings/network/network_bindings.dart b/lib/main/bindings/network/network_bindings.dart index 10a8bdf1e..4b8ed746c 100644 --- a/lib/main/bindings/network/network_bindings.dart +++ b/lib/main/bindings/network/network_bindings.dart @@ -26,6 +26,7 @@ import 'package:tmail_ui_user/features/login/data/utils/library_platform/app_aut import 'package:tmail_ui_user/features/mailbox/data/local/mailbox_cache_manager.dart'; import 'package:tmail_ui_user/features/mailbox/data/local/state_cache_manager.dart'; import 'package:tmail_ui_user/features/mailbox/data/network/mailbox_api.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/data/network/linagora_ecosystem_api.dart'; import 'package:tmail_ui_user/features/manage_account/data/network/forwarding_api.dart'; import 'package:tmail_ui_user/features/manage_account/data/network/identity_api.dart'; import 'package:tmail_ui_user/features/manage_account/data/network/rule_filter_api.dart'; @@ -124,6 +125,7 @@ class NetworkBindings extends Bindings { Get.put(FcmApi(Get.find())); Get.put(ServerSettingsAPI(Get.find())); Get.put(WebSocketApi(Get.find())); + Get.put(LinagoraEcosystemApi(Get.find())); } void _bindingConnection() { diff --git a/pubspec.lock b/pubspec.lock index 14b812273..6000f0da0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -524,11 +524,12 @@ packages: external_app_launcher: dependency: "direct main" description: - name: external_app_launcher - sha256: fb55cddd706c62ede11056750d5e018ef379820e09739e967873211dd537d833 - url: "https://pub.dev" - source: hosted - version: "3.1.0" + path: "." + ref: master + resolved-ref: cb31d10b5a7261f774b6e312d82c5cf1899979b6 + url: "https://github.com/putnokiabel/external_app_launcher.git" + source: git + version: "4.0.0" external_path: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index affb5ab95..9b482d932 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -106,6 +106,13 @@ dependencies: url: https://github.com/linagora/linagora-design-flutter.git ref: master + # TODO: Fix bug where apps cannot be launched on iOS 18. We will change it when the PR in upstream repository will be merged + # https://github.com/GeekyAnts/external_app_launcher/pull/42 + external_app_launcher: + git: + url: https://github.com/putnokiabel/external_app_launcher.git + ref: master + ### Dependencies from pub.dev ### super_tag_editor: 0.3.1 @@ -207,8 +214,6 @@ dependencies: intl: 0.19.0 - external_app_launcher: 3.1.0 - workmanager: 0.5.1 flutter_typeahead: 5.0.2 diff --git a/test/features/linagora_ecosystem/deserialize_linagora_ecosystem_test.dart b/test/features/linagora_ecosystem/deserialize_linagora_ecosystem_test.dart index 7853900e2..f0d285e96 100644 --- a/test/features/linagora_ecosystem/deserialize_linagora_ecosystem_test.dart +++ b/test/features/linagora_ecosystem/deserialize_linagora_ecosystem_test.dart @@ -56,7 +56,7 @@ void main() { LinagoraEcosystemIdentifier.twakeDrive: AppLinagoraEcosystem( appName: 'Twake Drive', logoURL: 'https://xyz', - webLink: 'https://abc', + webLink: Uri.parse('https://abc'), ), LinagoraEcosystemIdentifier.mobileApps: MobileAppsLinagoraEcosystem({ LinagoraEcosystemIdentifier.twakeChat: AppLinagoraEcosystem( diff --git a/test/features/login/data/extensions/service_path_extension_test.dart b/test/features/login/data/extensions/service_path_extension_test.dart new file mode 100644 index 000000000..5c7a25f8d --- /dev/null +++ b/test/features/login/data/extensions/service_path_extension_test.dart @@ -0,0 +1,56 @@ + +import 'package:core/data/network/config/service_path.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tmail_ui_user/features/login/data/extensions/service_path_extension.dart'; + +void main() { + group('ServicePathExtension::test', () { + test('Should correctly combines baseUrl and path without trailing or leading slashes', () { + final servicePath = ServicePath('/api/v1/resource'); + final result = servicePath.usingBaseUrl('https://example.com'); + expect(result.path, 'https://example.com/api/v1/resource'); + }); + + test('Should handles baseUrl with trailing slash', () { + final servicePath = ServicePath('/api/v1/resource'); + final result = servicePath.usingBaseUrl('https://example.com/'); + expect(result.path, 'https://example.com/api/v1/resource'); + }); + + test('Should handles path without leading slash', () { + final servicePath = ServicePath('api/v1/resource'); + final result = servicePath.usingBaseUrl('https://example.com'); + expect(result.path, 'https://example.com/api/v1/resource'); + }); + + test('Should handles both baseUrl with trailing slash and path without leading slash', () { + final servicePath = ServicePath('api/v1/resource'); + final result = servicePath.usingBaseUrl('https://example.com/'); + expect(result.path, 'https://example.com/api/v1/resource'); + }); + + test('Should handles both baseUrl without trailing slash and path with leading slash', () { + final servicePath = ServicePath('/api/v1/resource'); + final result = servicePath.usingBaseUrl('https://example.com'); + expect(result.path, 'https://example.com/api/v1/resource'); + }); + + test('Should handles empty path', () { + final servicePath = ServicePath(''); + final result = servicePath.usingBaseUrl('https://example.com'); + expect(result.path, 'https://example.com/'); + }); + + test('Should handles empty baseUrl', () { + final servicePath = ServicePath('/api/v1/resource'); + final result = servicePath.usingBaseUrl(''); + expect(result.path, '/api/v1/resource'); + }); + + test('Should handles both baseUrl and path being empty', () { + final servicePath = ServicePath(''); + final result = servicePath.usingBaseUrl(''); + expect(result.path, '/'); + }); + }); +} diff --git a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart index 104dca869..706467b32 100644 --- a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart +++ b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart @@ -132,7 +132,7 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(fallbackGenerators: fallbackGenerators), - MockSpec(), + MockSpec(fallbackGenerators: fallbackGenerators), MockSpec(fallbackGenerators: fallbackGenerators), MockSpec(fallbackGenerators: fallbackGenerators), MockSpec(), diff --git a/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart b/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart index 2b2e77e41..2484a4c71 100644 --- a/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart +++ b/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart @@ -136,7 +136,7 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(fallbackGenerators: fallbackGenerators), - MockSpec(), + MockSpec(fallbackGenerators: fallbackGenerators), MockSpec(fallbackGenerators: fallbackGenerators), MockSpec(fallbackGenerators: fallbackGenerators), MockSpec(),