TF-3260 Load & Display App Grid Linagora Ecosystem

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2024-12-09 12:40:57 +07:00
committed by Dat H. Pham
parent 95f05c4715
commit dce74b43e9
48 changed files with 894 additions and 435 deletions
-2
View File
@@ -11,8 +11,6 @@
<uses-sdk tools:overrideLibrary="io.flutter.plugins.webviewflutter, com.pichillilorenzo.flutter_inappwebview" />
<queries>
<package android:name="com.linagora.android.linshare" />
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
+1
View File
@@ -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';
+2
View File
@@ -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 = '&';
}
@@ -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;
}
@@ -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();
}
}
}
+4
View File
@@ -69,4 +69,8 @@ class StringConvert {
throw const UnsupportedCharsetException();
}
}
static String toUrlScheme(String hostScheme) {
return '$hostScheme://';
}
}
+1
View File
@@ -50,6 +50,7 @@
<string>sms</string>
<string>tel</string>
<string>linshare.mobile</string>
<string>twake.chat</string>
</array>
<key>LSRequiresIPhoneOS</key>
<true/>
@@ -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<void> 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<void> openAndroidApplication(String androidPackageId) async {
await LaunchApp.openApp(androidPackageName: androidPackageId);
}
Future<void> openIOSApplication(String? iosScheme, String? iosStoreLink) async {
await LaunchApp.openApp(
iosUrlScheme: iosScheme != null
? StringConvert.toUrlScheme(iosScheme)
: null,
appStoreLink: iosStoreLink,
);
}
Future<void> openWebApplication(Uri uri) async {
await launcher.launchUrl(uri);
}
Future<void> openOtherApplication(Uri uri) async {
await launcher.launchUrl(
uri,
mode: launcher.LaunchMode.externalApplication,
);
}
}
@@ -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<QueryParameter> 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');
}
}
}
@@ -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');
}
@@ -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),
@@ -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,
@@ -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) {
@@ -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) {
@@ -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();
});
}
}
@@ -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;
}
@@ -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<AppLinagoraEcosystem> linagoraApps;
const AppGridView({super.key, required this.linagoraApps});
@override
State<AppGridView> createState() => _AppGridViewState();
}
class _AppGridViewState extends State<AppGridView> {
final ValueNotifier<bool> _isCollapsedNotifier = ValueNotifier<bool>(true);
final _imagePaths = Get.find<ImagePaths>();
@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;
}
}
@@ -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<LinagoraApplications> getLinagoraApplications(String path);
Future<LinagoraEcosystem> getLinagoraEcosystem(String baseUrl);
}
@@ -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<LinagoraApplications> getLinagoraApplications(String path) {
throw UnimplementedError();
}
@override
Future<LinagoraEcosystem> getLinagoraEcosystem(String baseUrl) {
return Future.sync(() async {
return await _linagoraEcosystemApi.getLinagoraEcosystem(baseUrl);
}).catchError(_exceptionThrower.throwException);
}
}
@@ -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<LinagoraApplications> getLinagoraApplications(String path) {
return Future.sync(() async {
return await _appConfigLoader.load<LinagoraApplications>(
path,
AppDashboardConfigurationParser(),
);
}).catchError(_exceptionThrower.throwException);
}
@override
Future<LinagoraEcosystem> getLinagoraEcosystem(String baseUrl) {
throw UnimplementedError();
}
}
@@ -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<LinagoraEcosystem> getLinagoraEcosystem(String baseUrl) async {
final result = await _dioClient.get(
Endpoint.linagoraEcosystem.usingBaseUrl(baseUrl).generateEndpointPath(),
);
log('LinagoraEcosystemApi::getLinagoraEcosystem: $result');
if (result is Map<String, dynamic>) {
return LinagoraEcosystem.deserialize(result);
} else if (result is String) {
return LinagoraEcosystem.deserialize(jsonDecode(result));
} else {
throw NotFoundLinagoraEcosystem();
}
}
}
@@ -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<DataSourceType, AppGridDatasource> _mapDataSource;
AppGridRepositoryImpl(this._mapDataSource);
@override
Future<LinagoraApplications> getLinagoraApplications(String path) {
return _mapDataSource[DataSourceType.local]!.getLinagoraApplications(path);
}
@override
Future<LinagoraEcosystem> getLinagoraEcosystem(String baseUrl) {
return _mapDataSource[DataSourceType.network]!.getLinagoraEcosystem(baseUrl);
}
}
@@ -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<String, dynamic> json) => _$LinagoraAppFromJson(json);
@override
Map<String, dynamic> toJson() => _$LinagoraAppToJson(this);
@override
String? getIconPath(ImagePaths imagePaths) => iconName != null
? imagePaths.getConfigurationImagePath(iconName!)
: publicIconUri?.toString();
@override
Uri? get appRedirectLink => appUri;
@override
List<Object?> get props => [
appName,
@@ -0,0 +1,2 @@
class NotFoundLinagoraEcosystem implements Exception {}
@@ -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<Object?> get props => [
appName,
@@ -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<LinagoraEcosystemIdentifier, LinagoraEcosystemProperties?>? properties;
@@ -16,3 +18,30 @@ class LinagoraEcosystem with EquatableMixin {
@override
List<Object?> get props => [properties];
}
extension LinagoraEcosystemExtension on LinagoraEcosystem {
List<AppLinagoraEcosystem> get listAppLinagoraEcosystem {
if (properties == null) return [];
final listWebAppLinagora = properties
!.values
.whereType<AppLinagoraEcosystem>()
.toList();
final listMobileAppLinagora = (properties![LinagoraEcosystemIdentifier.mobileApps] as MobileAppsLinagoraEcosystem?)
?.apps
?.values
.whereType<AppLinagoraEcosystem>()
.toList() ?? [];
return listWebAppLinagora + listMobileAppLinagora;
}
List<AppLinagoraEcosystem> get listAppLinagoraEcosystemOnIOS {
return listAppLinagoraEcosystem.where((app) => app.isAppIOSEnabled).toList();
}
List<AppLinagoraEcosystem> get listAppLinagoraEcosystemOnAndroid {
return listAppLinagoraEcosystem.where((app) => app.isAppAndroidEnabled).toList();
}
}
@@ -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<LinagoraApplications> getLinagoraApplications(String path);
Future<LinagoraEcosystem> getLinagoraEcosystem(String baseUrl);
}
@@ -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<AppLinagoraEcosystem> listLinagoraApp;
GetAppDashboardConfigurationSuccess(this.linagoraApplications);
GetAppDashboardConfigurationSuccess(this.listLinagoraApp);
@override
List<Object> get props => [linagoraApplications];
List<Object> get props => [listLinagoraApp];
}
class GetAppDashboardConfigurationFailure extends FeatureFailure {
@@ -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<AppLinagoraEcosystem> listAppLinagoraEcosystem;
GetAppGridLinagraEcosystemSuccess(this.listAppLinagoraEcosystem);
@override
List<Object> get props => [listAppLinagoraEcosystem];
}
class GetAppGridLinagraEcosystemFailure extends FeatureFailure {
GetAppGridLinagraEcosystemFailure(dynamic exception) : super(exception: exception);
}
@@ -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<Either<Failure, Success>> execute(String appDashboardConfigurationPath) async* {
Stream<Either<Failure, Success>> execute(String path) async* {
try {
yield Right(LoadingAppDashboardConfiguration());
final linagoraApps = await _appConfigLoader.load<LinagoraApplications>(
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));
@@ -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<Either<Failure, Success>> execute(String baseUrl) async* {
try {
yield Right(LoadingAppGridLinagraEcosystem());
final linagoraEcosystem = await _appGridRepository.getLinagoraEcosystem(baseUrl);
List<AppLinagoraEcosystem> 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));
}
}
}
@@ -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<GetAppDashboardConfigurationInteractor>()));
Get.put(AppGridDashboardController(
Get.find<GetAppDashboardConfigurationInteractor>(),
Get.find<GetAppGridLinagraEcosystemInteractor>(),
));
Get.put(DownloadController());
Get.put(SearchController(
Get.find<QuickSearchEmailInteractor>(),
@@ -202,6 +212,7 @@ class MailboxDashBoardBindings extends BaseBindings {
Get.lazyPut<ServerSettingsDataSource>(
() => Get.find<RemoteServerSettingsDataSourceImpl>());
Get.lazyPut<IdentityCreatorDataSource>(() => Get.find<LocalIdentityCreatorDataSourceImpl>());
Get.lazyPut<AppGridDatasource>(() => Get.find<AppGridDatasourceImpl>());
}
@override
@@ -271,6 +282,14 @@ class MailboxDashBoardBindings extends BaseBindings {
Get.lazyPut(() => LocalIdentityCreatorDataSourceImpl(
Get.find<CacheExceptionThrower>()
));
Get.lazyPut(() => AppGridDatasourceImpl(
Get.find<LinagoraEcosystemApi>(),
Get.find<RemoteExceptionThrower>(),
));
Get.lazyPut(() => LocalAppGridDatasourceImpl(
Get.find<AppConfigLoader>(),
Get.find<CacheExceptionThrower>(),
));
}
@override
@@ -299,8 +318,8 @@ class MailboxDashBoardBindings extends BaseBindings {
Get.lazyPut(() => DeleteMultipleEmailsPermanentlyInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => EmptyTrashFolderInteractor(Get.find<ThreadRepository>()));
Get.lazyPut(() => EmptySpamFolderInteractor(Get.find<ThreadRepository>()));
Get.lazyPut(() => GetAppDashboardConfigurationInteractor(
Get.find<AppConfigLoader>()));
Get.lazyPut(() => GetAppDashboardConfigurationInteractor(Get.find<AppGridRepository>()));
Get.lazyPut(() => GetAppGridLinagraEcosystemInteractor(Get.find<AppGridRepository>()));
Get.lazyPut(() => GetEmailByIdInteractor(
Get.find<ThreadRepository>(),
Get.find<EmailRepository>()));
@@ -343,6 +362,7 @@ class MailboxDashBoardBindings extends BaseBindings {
Get.lazyPut<MailboxRepository>(() => Get.find<MailboxRepositoryImpl>());
Get.lazyPut<ServerSettingsRepository>(() => Get.find<ServerSettingsRepositoryImpl>());
Get.lazyPut<IdentityCreatorRepository>(() => Get.find<IdentityCreatorRepositoryImpl>());
Get.lazyPut<AppGridRepository>(() => Get.find<AppGridRepositoryImpl>());
}
@override
@@ -391,5 +411,9 @@ class MailboxDashBoardBindings extends BaseBindings {
Get.lazyPut(() => IdentityCreatorRepositoryImpl(
Get.find<IdentityCreatorDataSource>()
));
Get.lazyPut(() => AppGridRepositoryImpl({
DataSourceType.network: Get.find<AppGridDatasource>(),
DataSourceType.local: Get.find<LocalAppGridDatasourceImpl>()
},));
}
}
@@ -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<LinagoraApplications>();
class AppGridDashboardController extends BaseController {
final listLinagoraApp = RxList<AppLinagoraEcosystem>();
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<Either<Failure, Success>> 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<AppLinagoraEcosystem> linagoraApps) {
log('AppGridDashboardController::setListLinagoraApp:linagoraApps = $linagoraApps');
listLinagoraApp.value = linagoraApps;
}
}
@@ -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) {
@@ -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;
}
@@ -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) {
@@ -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) =>
@@ -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<AppLinagoraEcosystem> linagoraApps;
const AppGridDashboardIcon({
super.key,
required this.imagePaths,
required this.appGridController,
this.onShowAppDashboardAction,
required this.linagoraApps,
});
@override
State<AppGridDashboardIcon> createState() => _AppGridDashboardIconState();
}
class _AppGridDashboardIconState extends State<AppGridDashboardIcon> {
final ValueNotifier<bool> _isExpandedNotifier = ValueNotifier<bool>(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;
}
}
@@ -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<ImagePaths>();
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,
@@ -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<AppLinagoraEcosystem> 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;
@@ -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<ImagePaths>();
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<void> _handleOpenApp() async {
await launchApplication(
androidPackageId: app.androidPackageId,
iosScheme: app.iosUrlScheme,
iosStoreLink: app.iosAppStoreLink,
uri: app.appRedirectLink,
);
}
}
@@ -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)
@@ -152,8 +152,6 @@ class MailboxVisibilityController extends BaseMailboxController {
mailboxCategoriesExpandMode.value.teamMailboxes = newExpandMode;
mailboxCategoriesExpandMode.refresh();
break;
case MailboxCategories.appGrid:
break;
}
}
@@ -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<HttpClient>()));
Get.put(ServerSettingsAPI(Get.find<HttpClient>()));
Get.put(WebSocketApi(Get.find<DioClient>()));
Get.put(LinagoraEcosystemApi(Get.find<DioClient>()));
}
void _bindingConnection() {
+6 -5
View File
@@ -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:
+7 -2
View File
@@ -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
@@ -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(
@@ -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, '/');
});
});
}
@@ -132,7 +132,7 @@ const fallbackGenerators = {
MockSpec<RemoveEmailDraftsInteractor>(),
MockSpec<EmailReceiveManager>(),
MockSpec<DownloadController>(fallbackGenerators: fallbackGenerators),
MockSpec<AppGridDashboardController>(),
MockSpec<AppGridDashboardController>(fallbackGenerators: fallbackGenerators),
MockSpec<SpamReportController>(fallbackGenerators: fallbackGenerators),
MockSpec<NetworkConnectionController>(fallbackGenerators: fallbackGenerators),
MockSpec<QuickSearchEmailInteractor>(),
@@ -136,7 +136,7 @@ const fallbackGenerators = {
MockSpec<RemoveEmailDraftsInteractor>(),
MockSpec<EmailReceiveManager>(),
MockSpec<DownloadController>(fallbackGenerators: fallbackGenerators),
MockSpec<AppGridDashboardController>(),
MockSpec<AppGridDashboardController>(fallbackGenerators: fallbackGenerators),
MockSpec<SpamReportController>(fallbackGenerators: fallbackGenerators),
MockSpec<NetworkConnectionController>(fallbackGenerators: fallbackGenerators),
MockSpec<QuickSearchEmailInteractor>(),