diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index bb7ec1782e..0e5e7e687d 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -11,8 +11,6 @@
-
-
diff --git a/backend-docker/docker-compose.yaml b/backend-docker/docker-compose.yaml
index c42db448ca..74acc5279b 100644
--- a/backend-docker/docker-compose.yaml
+++ b/backend-docker/docker-compose.yaml
@@ -2,7 +2,7 @@ version: "3"
services:
tmail-backend:
- image: linagora/tmail-backend:memory-1.0.0
+ image: linagora/tmail-backend:memory-1.0.1-rc1
container_name: tmail-backend
volumes:
- ./jwt_publickey:/root/conf/jwt_publickey
@@ -10,6 +10,7 @@ services:
- ./mailetcontainer.xml:/root/conf/mailetcontainer.xml
- ./imapserver.xml:/root/conf/imapserver.xml
- ./jmap.properties:/root/conf/jmap.properties
+ - ./linagora-ecosystem.properties:/root/conf/linagora-ecosystem.properties
- ../provisioning/integration_test/search_email_with_sort_order/provisioning.sh:/root/conf/integration_test/search_email_with_sort_order/provisioning.sh
- ../provisioning/integration_test/search_email_with_sort_order/eml:/root/conf/integration_test/search_email_with_sort_order/eml
ports:
diff --git a/backend-docker/linagora-ecosystem.properties b/backend-docker/linagora-ecosystem.properties
new file mode 100644
index 0000000000..3ca4d78ce5
--- /dev/null
+++ b/backend-docker/linagora-ecosystem.properties
@@ -0,0 +1,11 @@
+Twake_Drive.appName=Twake Drive
+Twake_Drive.logoURL=https://sign-up.stg.lin-saas.com/images/tdrive.svg
+Twake_Drive.webLink=https://drive.stg.lin-saas.com/
+mobileApps.Twake_Chat.appName=Twake Chat
+mobileApps.Twake_Chat.logoURL=https://sign-up.stg.lin-saas.com/images/twakechat.svg
+mobileApps.Twake_Chat.androidPackageId=app.twake.android.chat
+mobileApps.Twake_Chat.iosUrlScheme=twake.chat
+mobileApps.Twake_Chat.iosAppStoreLink=itms-apps://itunes.apple.com/us/app/twake-chat/id6473384641
+mobileApps.Twake_Sync.appName=Twake Sync
+mobileApps.Twake_Sync.logoURL=https://twake.app/tild3364-6130-4763-b634-343435643861__twp-logo_1.svg
+mobileApps.Twake_Sync.androidPackageId=com.twake.android.sync
\ No newline at end of file
diff --git a/backend-docker/mailetcontainer.xml b/backend-docker/mailetcontainer.xml
index 383173ef8b..a0c694fd62 100644
--- a/backend-docker/mailetcontainer.xml
+++ b/backend-docker/mailetcontainer.xml
@@ -65,6 +65,10 @@
bcc
ignore
+
rrt-error
@@ -88,9 +92,6 @@
-
- ContactAttribute1
-
@@ -151,5 +152,4 @@
-
-
+
\ No newline at end of file
diff --git a/core/lib/core.dart b/core/lib/core.dart
index 6245d16725..a919c323db 100644
--- a/core/lib/core.dart
+++ b/core/lib/core.dart
@@ -94,6 +94,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 06b5d45eae..575efb4eae 100644
--- a/core/lib/data/constants/constant.dart
+++ b/core/lib/data/constants/constant.dart
@@ -1,12 +1,14 @@
class Constant {
- static const acceptHeaderDefault = 'application/json';
- static const contentTypeHeaderDefault = 'application/json';
- static const pdfMimeType = 'application/pdf';
- static const base64Charset = 'base64';
- static const textHtmlMimeType = 'text/html';
- static const octetStreamMimeType = 'application/octet-stream';
- static const pdfExtension = '.pdf';
- static const imageType = 'image';
- static const textVCardMimeType = 'text/x-vcard';
- static const textPlainMimeType = 'text/plain';
+ static const String acceptHeaderDefault = 'application/json';
+ static const String contentTypeHeaderDefault = 'application/json';
+ static const String pdfMimeType = 'application/pdf';
+ static const String base64Charset = 'base64';
+ static const String textHtmlMimeType = 'text/html';
+ static const String octetStreamMimeType = 'application/octet-stream';
+ static const String pdfExtension = '.pdf';
+ static const String imageType = 'image';
+ static const String textVCardMimeType = 'text/x-vcard';
+ static const String textPlainMimeType = 'text/plain';
+ static const String slashCharacter = '/';
+ static const String 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 0000000000..faee9cca79
--- /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 170b64b13e..82348cd586 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 a025322cbb..cf22d9cf19 100644
--- a/core/lib/utils/string_convert.dart
+++ b/core/lib/utils/string_convert.dart
@@ -20,4 +20,8 @@ class StringConvert {
return text;
}
}
+
+ static String toUrlScheme(String hostScheme) {
+ return '$hostScheme://';
+ }
}
diff --git a/integration_test/base/base_scenario.dart b/integration_test/base/base_scenario.dart
index 1bd8e923f5..7422522c16 100644
--- a/integration_test/base/base_scenario.dart
+++ b/integration_test/base/base_scenario.dart
@@ -12,4 +12,8 @@ abstract class BaseScenario {
await $.waitUntilVisible(patrolFinder);
expect(patrolFinder, findsWidgets);
}
+
+ Future expectViewInVisible(PatrolFinder patrolFinder) async {
+ expect(patrolFinder, findsNothing);
+ }
}
\ No newline at end of file
diff --git a/integration_test/robots/app_grid_robot.dart b/integration_test/robots/app_grid_robot.dart
new file mode 100644
index 0000000000..724f87b04c
--- /dev/null
+++ b/integration_test/robots/app_grid_robot.dart
@@ -0,0 +1,12 @@
+
+import 'package:flutter_test/flutter_test.dart';
+
+import '../base/core_robot.dart';
+
+class AppGridRobot extends CoreRobot {
+ AppGridRobot(super.$);
+
+ Future openAppInAppGridByAppName(String appName) async {
+ await $(find.text(appName)).tap();
+ }
+}
\ No newline at end of file
diff --git a/integration_test/robots/mailbox_menu_robot.dart b/integration_test/robots/mailbox_menu_robot.dart
new file mode 100644
index 0000000000..f2a7a1a512
--- /dev/null
+++ b/integration_test/robots/mailbox_menu_robot.dart
@@ -0,0 +1,10 @@
+
+import '../base/core_robot.dart';
+
+class MailboxMenuRobot extends CoreRobot {
+ MailboxMenuRobot(super.$);
+
+ Future openAppGrid() async {
+ await $(#toggle_app_grid_button).tap();
+ }
+}
\ No newline at end of file
diff --git a/integration_test/robots/thread_robot.dart b/integration_test/robots/thread_robot.dart
index 8789a7af76..cc2f9c595c 100644
--- a/integration_test/robots/thread_robot.dart
+++ b/integration_test/robots/thread_robot.dart
@@ -19,4 +19,8 @@ class ThreadRobot extends CoreRobot {
Future tapOnSearchField() async {
await $(ThreadView).$(SearchBarView).tap();
}
+
+ Future openMailbox() async {
+ await $(#mobile_mailbox_menu_button).tap();
+ }
}
\ No newline at end of file
diff --git a/integration_test/scenarios/app_grid_scenario.dart b/integration_test/scenarios/app_grid_scenario.dart
new file mode 100644
index 0000000000..2dd6f65b83
--- /dev/null
+++ b/integration_test/scenarios/app_grid_scenario.dart
@@ -0,0 +1,88 @@
+
+import 'package:core/utils/platform_info.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_view.dart';
+import 'package:tmail_ui_user/features/mailbox/presentation/widgets/app_grid_view.dart';
+import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/app_dashboard/app_list_dashboard_item.dart';
+
+import '../base/base_scenario.dart';
+import '../robots/app_grid_robot.dart';
+import '../robots/mailbox_menu_robot.dart';
+import '../robots/thread_robot.dart';
+import 'login_with_basic_auth_scenario.dart';
+
+class AppGridScenario extends BaseScenario {
+
+ final LoginWithBasicAuthScenario loginWithBasicAuthScenario;
+
+ AppGridScenario(
+ super.$,
+ {
+ required this.loginWithBasicAuthScenario
+ }
+ );
+
+ @override
+ Future execute() async {
+ final threadRobot = ThreadRobot($);
+ final mailboxMenuRobot = MailboxMenuRobot($);
+ final appGridRobot = AppGridRobot($);
+
+ await loginWithBasicAuthScenario.execute();
+
+ await threadRobot.openMailbox();
+ await _expectMailboxViewVisible();
+ await _expectAppGridViewVisible();
+
+ await Future.delayed(const Duration(seconds: 2));
+
+ await mailboxMenuRobot.openAppGrid();
+ await _expectListViewAppGridVisible();
+ await _expectAllAppInAppGridDisplayedIsFull();
+
+ await appGridRobot.openAppInAppGridByAppName('Twake Drive');
+ await Future.delayed(const Duration(seconds: 2));
+
+ if (PlatformInfo.isAndroid) {
+ await $.native.pressBack();
+
+ await appGridRobot.openAppInAppGridByAppName('Twake Sync');
+ await Future.delayed(const Duration(seconds: 2));
+
+ await $.native.pressBack();
+
+ await appGridRobot.openAppInAppGridByAppName('Twake Chat');
+ await Future.delayed(const Duration(seconds: 2));
+
+ await $.native.pressBack();
+
+ await _expectMailboxViewVisible();
+ } else if (PlatformInfo.isIOS) {
+ await _expectMailboxViewInVisible();
+ }
+ }
+
+ Future _expectMailboxViewVisible() => expectViewVisible($(MailboxView));
+
+ Future _expectAppGridViewVisible() => expectViewVisible($(AppGridView));
+
+ Future _expectListViewAppGridVisible() => expectViewVisible($(#list_view_app_grid));
+
+ Future _expectAllAppInAppGridDisplayedIsFull() async {
+ int totalApp = PlatformInfo.isIOS ? 2 : 3;
+ expect(find.byType(AppListDashboardItem), findsNWidgets(totalApp));
+
+ final listAppItem = $.tester
+ .widgetList(find.byType(AppListDashboardItem));
+
+ final listAppNames = listAppItem.map((item) => item.app.appName).toList();
+
+ if (PlatformInfo.isIOS) {
+ expect(listAppNames, equals(['Twake Drive', 'Twake Chat']));
+ } else {
+ expect(listAppNames, equals(['Twake Drive', 'Twake Chat', 'Twake Sync']));
+ }
+ }
+
+ Future _expectMailboxViewInVisible() => expectViewInVisible($(MailboxView));
+}
\ No newline at end of file
diff --git a/integration_test/tests/app_grid/app_grid_test.dart b/integration_test/tests/app_grid/app_grid_test.dart
new file mode 100644
index 0000000000..3ea0b7a692
--- /dev/null
+++ b/integration_test/tests/app_grid/app_grid_test.dart
@@ -0,0 +1,24 @@
+import '../../base/test_base.dart';
+import '../../scenarios/app_grid_scenario.dart';
+import '../../scenarios/login_with_basic_auth_scenario.dart';
+
+void main() {
+ TestBase().runPatrolTest(
+ description: 'Should display and navigate app grid correctly when clicked',
+ test: ($) async {
+ final loginWithBasicAuthScenario = LoginWithBasicAuthScenario($,
+ username: const String.fromEnvironment('USERNAME'),
+ hostUrl: const String.fromEnvironment('BASIC_AUTH_URL'),
+ email: const String.fromEnvironment('BASIC_AUTH_EMAIL'),
+ password: const String.fromEnvironment('PASSWORD'),
+ );
+
+ final appGridScenario = AppGridScenario(
+ $,
+ loginWithBasicAuthScenario: loginWithBasicAuthScenario,
+ );
+
+ await appGridScenario.execute();
+ }
+ );
+}
\ No newline at end of file
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 9fad3f0dfc..0264ea0f9e 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 0000000000..c3f20a9666
--- /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 55d80db042..bc2742c7a7 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 3429b27d44..d69e1386f2 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 1d14c38421..5a6c32c3e3 100644
--- a/lib/features/login/data/network/oidc_http_client.dart
+++ b/lib/features/login/data/network/oidc_http_client.dart
@@ -25,7 +25,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 ef6fc7cb7a..4692ee88d4 100644
--- a/lib/features/mailbox/presentation/mailbox_controller.dart
+++ b/lib/features/mailbox/presentation/mailbox_controller.dart
@@ -1002,14 +1002,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;
}
}
@@ -1030,10 +1022,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 6117a92acd..8d58187e40 100644
--- a/lib/features/mailbox/presentation/mailbox_view.dart
+++ b/lib/features/mailbox/presentation/mailbox_view.dart
@@ -8,6 +8,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';
@@ -16,7 +17,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 0be90fdbf6..432a6b1f65 100644
--- a/lib/features/mailbox/presentation/mailbox_view_web.dart
+++ b/lib/features/mailbox/presentation/mailbox_view_web.dart
@@ -9,13 +9,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 f313f9d465..da099fc735 100644
--- a/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart
+++ b/lib/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart
@@ -16,7 +16,6 @@ import 'package:tmail_ui_user/features/mailbox/presentation/model/context_item_m
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_categories.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/error/capability_validator.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
@@ -363,93 +362,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 0c05ec080f..e37af365ee 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 0000000000..3603e210a1
--- /dev/null
+++ b/lib/features/mailbox/presentation/widgets/app_grid_view.dart
@@ -0,0 +1,133 @@
+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(
+ key: const Key('toggle_app_grid_button'),
+ 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(
+ key: const Key('list_view_app_grid'),
+ 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 0000000000..2921ff2cf1
--- /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 0000000000..aa63435848
--- /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 0000000000..34f4c0bb6d
--- /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 0000000000..3d86b73c32
--- /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 0000000000..d851007f79
--- /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 dc82543592..c3e24b092d 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