diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index 7f5ef0877..25c8efa72 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -268,6 +268,7 @@ extension AppColor on Color { static const primaryLinShare = Color(0xFF007AFF); static const lightGrayEAEDF2 = Color(0xFFEAEDF2); static const lightIconTertiary = Color(0xFFB8C1CC); + static const gray6D7885 = Color(0xFF6D7885); static const mapGradientColor = [ [Color(0xFF21D4FD), Color(0xFFB721FF)], diff --git a/lib/features/base/widget/email_avatar_builder.dart b/lib/features/base/widget/email_avatar_builder.dart index 67612181f..f3d978dd0 100644 --- a/lib/features/base/widget/email_avatar_builder.dart +++ b/lib/features/base/widget/email_avatar_builder.dart @@ -10,18 +10,20 @@ class EmailAvatarBuilder extends StatelessWidget { final PresentationEmail emailSelected; final OnTapAvatarActionClick? onTapAvatarActionClick; + final double? size; const EmailAvatarBuilder({ Key? key, required this.emailSelected, this.onTapAvatarActionClick, + this.size, }) : super(key: key); @override Widget build(BuildContext context) { return (AvatarBuilder() ..text(emailSelected.getAvatarText()) - ..size(50) + ..size(size ?? 50) ..addTextStyle(ThemeUtils.textStyleHeadingH4(color: Colors.white)) ..backgroundColor(AppColor.colorAvatar) ..addOnTapActionClick(onTapAvatarActionClick ?? () {}) diff --git a/lib/features/composer/presentation/extensions/email_action_type_extension.dart b/lib/features/composer/presentation/extensions/email_action_type_extension.dart index 6e67a9359..99758aefc 100644 --- a/lib/features/composer/presentation/extensions/email_action_type_extension.dart +++ b/lib/features/composer/presentation/extensions/email_action_type_extension.dart @@ -180,7 +180,7 @@ extension EmailActionTypeExtension on EmailActionType { String getTitle(AppLocalizations appLocalizations) { switch(this) { case EmailActionType.markAsRead: - return AppLocalizations.of(context).mark_as_read; + return appLocalizations.mark_as_read; case EmailActionType.markAsUnread: return appLocalizations.mark_as_unread; case EmailActionType.unSpam: diff --git a/lib/features/email/presentation/widgets/email_receiver_widget.dart b/lib/features/email/presentation/widgets/email_receiver_widget.dart index f7245c648..86c319f21 100644 --- a/lib/features/email/presentation/widgets/email_receiver_widget.dart +++ b/lib/features/email/presentation/widgets/email_receiver_widget.dart @@ -103,7 +103,7 @@ class _EmailReceiverWidgetState extends State { ConstrainedBox( constraints: BoxConstraints( maxWidth: _getMaxWidth(context), - maxHeight: 34, + maxHeight: _responsiveUtils.isMobile(context) ? 22 : 28, ), child: ListView( scrollDirection: Axis.horizontal, @@ -239,10 +239,18 @@ class _EmailReceiverWidgetState extends State { ? emailAddress.asString() : '${emailAddress.asString()},', textStyle: ThemeUtils.textStyleHeadingHeadingSmall( - color: Colors.black, + color: _responsiveUtils.isMobile(context) ? AppColor.gray6D7885 : Colors.black, fontWeight: FontWeight.w400, - ).copyWith(height: 17), - padding: const EdgeInsetsDirectional.symmetric(vertical: 2, horizontal: 8), + ).copyWith( + fontSize: _responsiveUtils.isMobile(context) ? 14 : 17, + height: 1, + letterSpacing: _responsiveUtils.isMobile(context) ? -0.14 : -0.17, + ), + padding: EdgeInsetsDirectional.symmetric( + vertical: _responsiveUtils.isMobile(context) ? 2.5 : 3.5, + horizontal: 8, + ), + margin: EdgeInsetsDirectional.only(top: _responsiveUtils.isMobile(context) ? 1.5 : 2), backgroundColor: Colors.transparent, onTapActionCallback: () => widget.openEmailAddressDetailAction?.call(context, emailAddress), onLongPressActionCallback: () => AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress), @@ -259,7 +267,10 @@ class _EmailReceiverWidgetState extends State { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - PrefixRecipientWidget(prefixEmailAddress: prefixEmailAddress), + PrefixRecipientWidget( + prefixEmailAddress: prefixEmailAddress, + responsiveUtils: _responsiveUtils, + ), Expanded( child: Wrap( children: _buildRecipientsTag(listEmailAddress: listEmailAddress) @@ -275,7 +286,10 @@ class _EmailReceiverWidgetState extends State { required List listEmailAddress, }) { return [ - PrefixRecipientWidget(prefixEmailAddress: prefixEmailAddress), + PrefixRecipientWidget( + prefixEmailAddress: prefixEmailAddress, + responsiveUtils: _responsiveUtils, + ), ..._buildRecipientsTag(listEmailAddress: listEmailAddress) ]; } diff --git a/lib/features/email/presentation/widgets/email_sender_builder.dart b/lib/features/email/presentation/widgets/email_sender_builder.dart index 9ae11e662..ef07e8110 100644 --- a/lib/features/email/presentation/widgets/email_sender_builder.dart +++ b/lib/features/email/presentation/widgets/email_sender_builder.dart @@ -25,45 +25,52 @@ class EmailSenderBuilder extends StatelessWidget { @override Widget build(BuildContext context) { - return SingleChildScrollView( - scrollDirection: Axis.horizontal, - physics: const NeverScrollableScrollPhysics(), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - if (emailAddress.displayName.isNotEmpty) - MaterialTextButton( - label: emailAddress.displayName, - onTap: () => openEmailAddressDetailAction?.call(context, emailAddress), - onLongPress: () { - AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress); - }, - borderRadius: 8, - padding: EdgeInsets.zero, - customStyle: ThemeUtils.textStyleHeadingH6( - color: Colors.black, - fontWeight: FontWeight.w500, - ).copyWith(height: 20), - overflow: CommonTextStyle.defaultTextOverFlow, - softWrap: CommonTextStyle.defaultSoftWrap - ), - if (showSenderEmail) - MaterialTextButton( - label: '<${emailAddress.emailAddress}>', - onTap: () => openEmailAddressDetailAction?.call(context, emailAddress), - onLongPress: () { - AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress); - }, - borderRadius: 8, - padding: EdgeInsets.zero, - customStyle: ThemeUtils.textStyleBodyBody1( - color: AppColor.steelGray400, + return Transform.translate( + offset: const Offset(-2, 0), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + physics: const NeverScrollableScrollPhysics(), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + if (emailAddress.displayName.isNotEmpty) + MaterialTextButton( + label: emailAddress.displayName, + onTap: () => openEmailAddressDetailAction?.call(context, emailAddress), + onLongPress: () { + AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress); + }, + borderRadius: 8, + padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 4), + customStyle: ThemeUtils.textStyleHeadingH6( + color: Colors.black, + fontWeight: FontWeight.w500, + ).copyWith(height: 1, letterSpacing: -0.2), + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap ), - overflow: CommonTextStyle.defaultTextOverFlow, - softWrap: CommonTextStyle.defaultSoftWrap - ) - ] + if (showSenderEmail) + MaterialTextButton( + label: '<${emailAddress.emailAddress}>', + onTap: () => openEmailAddressDetailAction?.call(context, emailAddress), + onLongPress: () { + AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress); + }, + borderRadius: 8, + padding: const EdgeInsets.symmetric(horizontal: 2, vertical: 4), + customStyle: ThemeUtils.textStyleBodyBody1( + color: AppColor.gray6D7885, + ).copyWith( + fontSize: 17, + height: 1, + letterSpacing: -0.17, + ), + overflow: CommonTextStyle.defaultTextOverFlow, + softWrap: CommonTextStyle.defaultSoftWrap + ) + ] + ), ), ); } diff --git a/lib/features/email/presentation/widgets/information_sender_and_receiver_builder.dart b/lib/features/email/presentation/widgets/information_sender_and_receiver_builder.dart index 28071ac1c..a03aa2ec5 100644 --- a/lib/features/email/presentation/widgets/information_sender_and_receiver_builder.dart +++ b/lib/features/email/presentation/widgets/information_sender_and_receiver_builder.dart @@ -2,6 +2,7 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/utils/icon_utils.dart'; import 'package:core/presentation/utils/responsive_utils.dart'; +import 'package:core/presentation/utils/theme_utils.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:core/presentation/views/image/avatar_builder.dart'; import 'package:core/utils/platform_info.dart'; @@ -74,6 +75,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget { EmailAvatarBuilder( emailSelected: emailSelected, onTapAvatarActionClick: onTapAvatarActionClick, + size: 56, ), const SizedBox(width: 16), Expanded(child: LayoutBuilder(builder: (context, constraints) { @@ -82,7 +84,6 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height: 4), Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -92,13 +93,13 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget { if (showUnreadVisualization && !emailSelected.hasRead && responsiveUtils.isMobile(context)) - Padding( - padding: const EdgeInsetsDirectional.only(end: 8), - child: SvgPicture.asset( - imagePaths.icUnreadStatus, - width: 9, - height: 9, - fit: BoxFit.fill, + TMailButtonWidget.fromIcon( + icon: imagePaths.icUnreadStatus, + backgroundColor: Colors.transparent, + iconSize: 9, + onTapActionCallback: () => onEmailActionClick?.call( + emailSelected, + EmailActionType.markAsRead, ), ), if (emailSelected.from?.isNotEmpty == true) @@ -129,7 +130,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget { color: AppColor.colorTextBody, decoration: TextDecoration.underline, ), - padding: const EdgeInsetsDirectional.symmetric(vertical: 5, horizontal: 8), + padding: const EdgeInsetsDirectional.symmetric(vertical: 4, horizontal: 8), backgroundColor: Colors.transparent, onTapActionCallback: () => onEmailActionClick?.call(emailSelected, EmailActionType.unsubscribe), ), @@ -154,7 +155,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget { ], ), ), - if (!isInsideThreadDetailView && !responsiveUtils.isMobile(context)) + if (!isInsideThreadDetailView) ReceivedTimeBuilder( emailSelected: emailSelected, padding: const EdgeInsetsDirectional.only(start: 16, top: 2), @@ -187,11 +188,10 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget { ), ], ), - const SizedBox(height: 4), - if (responsiveUtils.isMobile(context)) + if (responsiveUtils.isMobile(context) && isInsideThreadDetailView) ReceivedTimeBuilder( emailSelected: emailSelected, - padding: const EdgeInsetsDirectional.only(top: 5), + padding: const EdgeInsetsDirectional.symmetric(vertical: 5), ), if (emailSelected.countRecipients > 0 && showRecipients) EmailReceiverWidget( diff --git a/lib/features/email/presentation/widgets/prefix_recipient_widget.dart b/lib/features/email/presentation/widgets/prefix_recipient_widget.dart index 5cf76da0a..b7244c232 100644 --- a/lib/features/email/presentation/widgets/prefix_recipient_widget.dart +++ b/lib/features/email/presentation/widgets/prefix_recipient_widget.dart @@ -1,5 +1,6 @@ import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:core/presentation/utils/theme_utils.dart'; import 'package:flutter/material.dart'; import 'package:model/email/prefix_email_address.dart'; @@ -7,14 +8,29 @@ import 'package:tmail_ui_user/features/composer/presentation/extensions/prefix_e class PrefixRecipientWidget extends StatelessWidget { final PrefixEmailAddress prefixEmailAddress; + final ResponsiveUtils responsiveUtils; - const PrefixRecipientWidget({super.key, required this.prefixEmailAddress}); + const PrefixRecipientWidget({ + super.key, + required this.prefixEmailAddress, + required this.responsiveUtils, + }); @override Widget build(BuildContext context) { - return Text( - '${prefixEmailAddress.asName(context)}:', - style: ThemeUtils.textStyleBodyBody1(color: AppColor.steelGray400), + return Padding( + padding: EdgeInsets.symmetric(vertical: responsiveUtils.isMobile(context) ? 4 : 5.5), + child: Text( + '${prefixEmailAddress.asName(context)}:', + style: ThemeUtils.textStyleBodyBody1( + color: AppColor.steelGray400, + fontWeight: FontWeight.w400, + ).copyWith( + fontSize: responsiveUtils.isMobile(context) ? 14 : 17, + height: 1, + letterSpacing: responsiveUtils.isMobile(context) ? -0.14 : -0.17 + ), + ), ); } } \ No newline at end of file diff --git a/lib/features/email/presentation/widgets/received_time_builder.dart b/lib/features/email/presentation/widgets/received_time_builder.dart index b35b8f240..b6ab585ac 100644 --- a/lib/features/email/presentation/widgets/received_time_builder.dart +++ b/lib/features/email/presentation/widgets/received_time_builder.dart @@ -30,7 +30,10 @@ class ReceivedTimeBuilder extends StatelessWidget { maxLines: 1, overflow: CommonTextStyle.defaultTextOverFlow, softWrap: CommonTextStyle.defaultSoftWrap, - style: ThemeUtils.textStyleBodyBody3(color: AppColor.steelGray400) + style: ThemeUtils.textStyleBodyBody3(color: AppColor.steelGray400).copyWith( + height: 1, + letterSpacing: -0.14 + ), ), ); } diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index 544872f32..612cd634b 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -115,6 +115,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_preferences_setting_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_save_email_as_draft_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/initialize_app_language.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/notify_thread_detail_setting_updated.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/reopen_composer_cache_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/set_error_extension.dart'; @@ -806,6 +807,7 @@ class MailboxDashBoardController extends ReloadableController _getVacationResponse(); spamReportController.getSpamReportStateAction(); _getAllIdentities(); + notifyThreadDetailSettingUpdated(); getServerSetting(); cleanupRecentSearch(); @@ -1928,6 +1930,7 @@ class MailboxDashBoardController extends ReloadableController } _getAllIdentities(); + notifyThreadDetailSettingUpdated(); getServerSetting(); } @@ -1996,6 +1999,7 @@ class MailboxDashBoardController extends ReloadableController } _getAllIdentities(); + notifyThreadDetailSettingUpdated(); getServerSetting(); } diff --git a/lib/features/mailbox_dashboard/presentation/extensions/notify_thread_detail_setting_updated.dart b/lib/features/mailbox_dashboard/presentation/extensions/notify_thread_detail_setting_updated.dart new file mode 100644 index 000000000..5d66588f2 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/extensions/notify_thread_detail_setting_updated.dart @@ -0,0 +1,8 @@ +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; +import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_detail_ui_action.dart'; + +extension NotifyThreadDetailSettingUpdated on MailboxDashBoardController { + void notifyThreadDetailSettingUpdated() { + dispatchThreadDetailUIAction(UpdatedThreadDetailSettingAction()); + } +} \ No newline at end of file diff --git a/lib/features/manage_account/data/datasource/manage_account_datasource.dart b/lib/features/manage_account/data/datasource/manage_account_datasource.dart new file mode 100644 index 000000000..146c6671e --- /dev/null +++ b/lib/features/manage_account/data/datasource/manage_account_datasource.dart @@ -0,0 +1,15 @@ +import 'dart:ui'; + +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_options.dart'; + +abstract class ManageAccountDataSource { + Future persistLanguage(Locale localeCurrent); + + Future updateLocalSettings( + Map localSettings, + ); + + Future> getLocalSettings( + List supportedLocalSettings, + ); +} \ No newline at end of file diff --git a/lib/features/manage_account/data/datasource/preferences_datasource.dart b/lib/features/manage_account/data/datasource/preferences_datasource.dart deleted file mode 100644 index 005048f86..000000000 --- a/lib/features/manage_account/data/datasource/preferences_datasource.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'dart:ui'; - -abstract class PreferencesDataSource { - Future persistLanguage(Locale localeCurrent); -} \ No newline at end of file diff --git a/lib/features/manage_account/data/datasource_impl/manage_account_datasource_impl.dart b/lib/features/manage_account/data/datasource_impl/manage_account_datasource_impl.dart new file mode 100644 index 000000000..303123468 --- /dev/null +++ b/lib/features/manage_account/data/datasource_impl/manage_account_datasource_impl.dart @@ -0,0 +1,45 @@ +import 'dart:ui'; + +import 'package:tmail_ui_user/features/manage_account/data/datasource/manage_account_datasource.dart'; +import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart'; +import 'package:tmail_ui_user/features/manage_account/data/local/local_setting_cache_manager.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_options.dart'; +import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; + +class ManageAccountDataSourceImpl extends ManageAccountDataSource { + + final LanguageCacheManager _languageCacheManager; + final LocalSettingCacheManager _localSettingCacheManager; + final ExceptionThrower _exceptionThrower; + + ManageAccountDataSourceImpl( + this._languageCacheManager, + this._localSettingCacheManager, + this._exceptionThrower + ); + + @override + Future persistLanguage(Locale localeCurrent) { + return Future.sync(() async { + return await _languageCacheManager.persistLanguage(localeCurrent); + }).catchError(_exceptionThrower.throwException); + } + + @override + Future updateLocalSettings( + Map localSettings, + ) { + return Future.sync(() async { + return await _localSettingCacheManager.update(localSettings); + }).catchError(_exceptionThrower.throwException); + } + + @override + Future> getLocalSettings( + List supportedLocalSettings, + ) { + return Future.sync(() async { + return await _localSettingCacheManager.get(supportedLocalSettings); + }).catchError(_exceptionThrower.throwException); + } +} \ No newline at end of file diff --git a/lib/features/manage_account/data/datasource_impl/preferences_datasource_impl.dart b/lib/features/manage_account/data/datasource_impl/preferences_datasource_impl.dart deleted file mode 100644 index 74d5a10c4..000000000 --- a/lib/features/manage_account/data/datasource_impl/preferences_datasource_impl.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'dart:ui'; - -import 'package:tmail_ui_user/features/manage_account/data/datasource/preferences_datasource.dart'; -import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart'; -import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; - -class PreferencesDataSourceImpl extends PreferencesDataSource { - - final LanguageCacheManager _languageCacheManager; - final ExceptionThrower _exceptionThrower; - - PreferencesDataSourceImpl( - this._languageCacheManager, - this._exceptionThrower - ); - - @override - Future persistLanguage(Locale localeCurrent) { - return Future.sync(() async { - return await _languageCacheManager.persistLanguage(localeCurrent); - }).catchError(_exceptionThrower.throwException); - } -} \ No newline at end of file diff --git a/lib/features/manage_account/data/local/local_setting_cache_manager.dart b/lib/features/manage_account/data/local/local_setting_cache_manager.dart new file mode 100644 index 000000000..a241edcbe --- /dev/null +++ b/lib/features/manage_account/data/local/local_setting_cache_manager.dart @@ -0,0 +1,41 @@ +import 'dart:convert'; + +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_options.dart'; + +class LocalSettingCacheManager { + const LocalSettingCacheManager(this._sharedPreferences); + + final SharedPreferences _sharedPreferences; + + Future update( + Map localSettings, + ) async { + await Future.wait(localSettings.entries.map((entry) async { + if (entry.value == null) { + await _sharedPreferences.remove(entry.key.name); + return; + } + + await _sharedPreferences.setString( + entry.key.name, + jsonEncode(entry.value!.toJson()), + ); + })); + } + + Future> get( + List supportedLocalSettings, + ) async { + await _sharedPreferences.reload(); + + return Map.fromEntries(supportedLocalSettings.map((supportedLocalSetting) { + final data = _sharedPreferences.getString(supportedLocalSetting.name); + + return MapEntry( + supportedLocalSetting, + data == null ? null : LocalSettingOptions.fromJson(jsonDecode(data)), + ); + })); + } +} \ No newline at end of file diff --git a/lib/features/manage_account/data/repository/manage_account_repository_impl.dart b/lib/features/manage_account/data/repository/manage_account_repository_impl.dart new file mode 100644 index 000000000..bccc521f9 --- /dev/null +++ b/lib/features/manage_account/data/repository/manage_account_repository_impl.dart @@ -0,0 +1,31 @@ +import 'dart:ui'; + +import 'package:tmail_ui_user/features/manage_account/data/datasource/manage_account_datasource.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/repository/manage_account_repository.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_options.dart'; + +class ManageAccountRepositoryImpl extends ManageAccountRepository { + + final ManageAccountDataSource dataSource; + + ManageAccountRepositoryImpl(this.dataSource); + + @override + Future persistLanguage(Locale localeCurrent) { + return dataSource.persistLanguage(localeCurrent); + } + + @override + Future updateLocalSettings( + Map localSettings, + ) { + return dataSource.updateLocalSettings(localSettings); + } + + @override + Future> getLocalSettings( + List supportedLocalSettings, + ) { + return dataSource.getLocalSettings(supportedLocalSettings); + } +} \ No newline at end of file diff --git a/lib/features/manage_account/data/repository/preferences_repository_impl.dart b/lib/features/manage_account/data/repository/preferences_repository_impl.dart deleted file mode 100644 index 57914c0d6..000000000 --- a/lib/features/manage_account/data/repository/preferences_repository_impl.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'dart:ui'; - -import 'package:tmail_ui_user/features/manage_account/data/datasource/preferences_datasource.dart'; -import 'package:tmail_ui_user/features/manage_account/domain/repository/preferences_repository.dart'; - -class PreferencesRepositoryImpl extends PreferencesRepository { - - final PreferencesDataSource dataSource; - - PreferencesRepositoryImpl(this.dataSource); - - @override - Future persistLanguage(Locale localeCurrent) { - return dataSource.persistLanguage(localeCurrent); - } -} \ No newline at end of file diff --git a/lib/features/manage_account/domain/repository/manage_account_repository.dart b/lib/features/manage_account/domain/repository/manage_account_repository.dart new file mode 100644 index 000000000..c5d88c247 --- /dev/null +++ b/lib/features/manage_account/domain/repository/manage_account_repository.dart @@ -0,0 +1,15 @@ +import 'dart:ui'; + +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_options.dart'; + +abstract class ManageAccountRepository { + Future persistLanguage(Locale localeCurrent); + + Future updateLocalSettings( + Map localSettings, + ); + + Future> getLocalSettings( + List supportedLocalSettings, + ); +} \ No newline at end of file diff --git a/lib/features/manage_account/domain/repository/preferences_repository.dart b/lib/features/manage_account/domain/repository/preferences_repository.dart deleted file mode 100644 index 144c6260f..000000000 --- a/lib/features/manage_account/domain/repository/preferences_repository.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'dart:ui'; - -abstract class PreferencesRepository { - Future persistLanguage(Locale localeCurrent); -} \ No newline at end of file diff --git a/lib/features/manage_account/domain/state/get_local_settings_state.dart b/lib/features/manage_account/domain/state/get_local_settings_state.dart new file mode 100644 index 000000000..7dded8a8f --- /dev/null +++ b/lib/features/manage_account/domain/state/get_local_settings_state.dart @@ -0,0 +1,18 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_options.dart'; + +class GettingLocalSettingsState extends LoadingState {} + +class GetLocalSettingsSuccess extends UIState { + GetLocalSettingsSuccess(this.localSettings); + + final Map localSettings; + + @override + List get props => [localSettings]; +} + +class GetLocalSettingsFailure extends FeatureFailure { + GetLocalSettingsFailure({super.exception}); +} \ No newline at end of file diff --git a/lib/features/manage_account/domain/state/update_local_settings_state.dart b/lib/features/manage_account/domain/state/update_local_settings_state.dart new file mode 100644 index 000000000..13d1cf8f5 --- /dev/null +++ b/lib/features/manage_account/domain/state/update_local_settings_state.dart @@ -0,0 +1,18 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_options.dart'; + +class UpdatingLocalSettingsState extends LoadingState {} + +class UpdateLocalSettingsSuccess extends UIState { + final Map localSettings; + + UpdateLocalSettingsSuccess(this.localSettings); + + @override + List get props => [localSettings]; +} + +class UpdateLocalSettingsFailure extends FeatureFailure { + UpdateLocalSettingsFailure({super.exception}); +} \ No newline at end of file diff --git a/lib/features/manage_account/domain/usecases/get_local_settings_interactor.dart b/lib/features/manage_account/domain/usecases/get_local_settings_interactor.dart new file mode 100644 index 000000000..66f1d8869 --- /dev/null +++ b/lib/features/manage_account/domain/usecases/get_local_settings_interactor.dart @@ -0,0 +1,26 @@ +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:tmail_ui_user/features/manage_account/domain/repository/manage_account_repository.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/state/get_local_settings_state.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_options.dart'; + +class GetLocalSettingsInteractor { + const GetLocalSettingsInteractor(this._manageAccountRepository); + + final ManageAccountRepository _manageAccountRepository; + + Stream> execute( + List supportedLocalSettings, + ) async* { + try { + yield Right(GettingLocalSettingsState()); + final result = await _manageAccountRepository.getLocalSettings(supportedLocalSettings); + yield Right(GetLocalSettingsSuccess(result)); + } catch (e) { + logError('$runtimeType::execute(): exception: $e'); + yield Left(GetLocalSettingsFailure(exception: e)); + } + } +} \ No newline at end of file diff --git a/lib/features/manage_account/domain/usecases/save_language_interactor.dart b/lib/features/manage_account/domain/usecases/save_language_interactor.dart index 2f2cd89c1..c547a09da 100644 --- a/lib/features/manage_account/domain/usecases/save_language_interactor.dart +++ b/lib/features/manage_account/domain/usecases/save_language_interactor.dart @@ -4,11 +4,11 @@ import 'dart:ui'; import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:dartz/dartz.dart'; -import 'package:tmail_ui_user/features/manage_account/domain/repository/preferences_repository.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/repository/manage_account_repository.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/save_language_state.dart'; class SaveLanguageInteractor { - final PreferencesRepository manageAccountRepository; + final ManageAccountRepository manageAccountRepository; SaveLanguageInteractor(this.manageAccountRepository); diff --git a/lib/features/manage_account/domain/usecases/update_local_settings_interactor.dart b/lib/features/manage_account/domain/usecases/update_local_settings_interactor.dart new file mode 100644 index 000000000..c9f82a259 --- /dev/null +++ b/lib/features/manage_account/domain/usecases/update_local_settings_interactor.dart @@ -0,0 +1,26 @@ +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:tmail_ui_user/features/manage_account/domain/repository/manage_account_repository.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/state/update_local_settings_state.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_options.dart'; + +class UpdateLocalSettingsInteractor { + const UpdateLocalSettingsInteractor(this._manageAccountRepository); + + final ManageAccountRepository _manageAccountRepository; + + Stream> execute( + Map localSettings, + ) async* { + try { + yield Right(UpdatingLocalSettingsState()); + await _manageAccountRepository.updateLocalSettings(localSettings); + yield Right(UpdateLocalSettingsSuccess(localSettings)); + } catch (e) { + logError('$runtimeType::execute(): exception: $e'); + yield Left(UpdateLocalSettingsFailure(exception: e)); + } + } +} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/extensions/local_settings_map_extension.dart b/lib/features/manage_account/presentation/extensions/local_settings_map_extension.dart new file mode 100644 index 000000000..ae348fea3 --- /dev/null +++ b/lib/features/manage_account/presentation/extensions/local_settings_map_extension.dart @@ -0,0 +1,6 @@ +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_detail/thread_detail_local_setting_detail.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_options.dart'; + +extension LocalSettingsMapExtension on Map { + bool? get threadDetailEnabled => (this[SupportedLocalSetting.threadDetail]?.setting as ThreadDetailLocalSettingDetail?)?.value; +} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/manage_account_dashboard_bindings.dart b/lib/features/manage_account/presentation/manage_account_dashboard_bindings.dart index bf06fd7b0..e698b5dab 100644 --- a/lib/features/manage_account/presentation/manage_account_dashboard_bindings.dart +++ b/lib/features/manage_account/presentation/manage_account_dashboard_bindings.dart @@ -1,15 +1,16 @@ import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/base_bindings.dart'; -import 'package:tmail_ui_user/features/manage_account/data/datasource/preferences_datasource.dart'; -import 'package:tmail_ui_user/features/manage_account/data/datasource_impl/preferences_datasource_impl.dart'; +import 'package:tmail_ui_user/features/manage_account/data/datasource/manage_account_datasource.dart'; +import 'package:tmail_ui_user/features/manage_account/data/datasource_impl/manage_account_datasource_impl.dart'; import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart'; -import 'package:tmail_ui_user/features/manage_account/data/repository/preferences_repository_impl.dart'; -import 'package:tmail_ui_user/features/manage_account/domain/repository/preferences_repository.dart'; +import 'package:tmail_ui_user/features/manage_account/data/local/local_setting_cache_manager.dart'; +import 'package:tmail_ui_user/features/manage_account/data/repository/manage_account_repository_impl.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/repository/manage_account_repository.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/manage_account_dashboard_controller.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/menu/manage_account_menu_bindings.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/menu/settings/settings_bindings.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/profiles/profiles_bindings.dart'; -import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart'; +import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart'; class ManageAccountDashBoardBindings extends BaseBindings { @@ -28,14 +29,15 @@ class ManageAccountDashBoardBindings extends BaseBindings { @override void bindingsDataSource() { - Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); } @override void bindingsDataSourceImpl() { - Get.lazyPut(() => PreferencesDataSourceImpl( + Get.lazyPut(() => ManageAccountDataSourceImpl( Get.find(), - Get.find())); + Get.find(), + Get.find())); } @override @@ -43,11 +45,11 @@ class ManageAccountDashBoardBindings extends BaseBindings { @override void bindingsRepository() { - Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); } @override void bindingsRepositoryImpl() { - Get.lazyPut(() => PreferencesRepositoryImpl(Get.find())); + Get.lazyPut(() => ManageAccountRepositoryImpl(Get.find())); } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/model/local_setting_detail/local_setting_detail.dart b/lib/features/manage_account/presentation/model/local_setting_detail/local_setting_detail.dart new file mode 100644 index 000000000..c39b334e0 --- /dev/null +++ b/lib/features/manage_account/presentation/model/local_setting_detail/local_setting_detail.dart @@ -0,0 +1,32 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_detail/thread_detail_local_setting_detail.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_options.dart'; + +abstract class LocalSettingDetail extends Equatable { + const LocalSettingDetail(this.value); + + final T value; + + Map toJson(); +} + +class LocalSettingDetailConverter extends JsonConverter> { + const LocalSettingDetailConverter(); + + @override + LocalSettingDetail fromJson(Map json) { + final type = json['type'] as String?; + + if (type == SupportedLocalSetting.threadDetail.name) { + return ThreadDetailLocalSettingDetail.fromJson(json); + } + + throw UnimplementedError(); + } + + @override + Map toJson(LocalSettingDetail object) { + return object.toJson(); + } +} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/model/local_setting_detail/thread_detail_local_setting_detail.dart b/lib/features/manage_account/presentation/model/local_setting_detail/thread_detail_local_setting_detail.dart new file mode 100644 index 000000000..8b45d3dae --- /dev/null +++ b/lib/features/manage_account/presentation/model/local_setting_detail/thread_detail_local_setting_detail.dart @@ -0,0 +1,24 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_detail/local_setting_detail.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_options.dart'; + +part 'thread_detail_local_setting_detail.g.dart'; + +@JsonSerializable() +class ThreadDetailLocalSettingDetail extends LocalSettingDetail { + const ThreadDetailLocalSettingDetail( + super.value, [ + this.type = SupportedLocalSetting.threadDetail, + ]); + + @JsonKey(defaultValue: SupportedLocalSetting.threadDetail) + final SupportedLocalSetting type; + + factory ThreadDetailLocalSettingDetail.fromJson(Map json) => + _$ThreadDetailLocalSettingDetailFromJson(json); + @override + Map toJson() => _$ThreadDetailLocalSettingDetailToJson(this); + + @override + List get props => [value, type]; +} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/model/local_setting_options.dart b/lib/features/manage_account/presentation/model/local_setting_options.dart new file mode 100644 index 000000000..32fe58414 --- /dev/null +++ b/lib/features/manage_account/presentation/model/local_setting_options.dart @@ -0,0 +1,26 @@ +import 'package:equatable/equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_detail/local_setting_detail.dart'; + +part 'local_setting_options.g.dart'; + +enum SupportedLocalSetting { + threadDetail, +} + +@JsonSerializable(converters: [LocalSettingDetailConverter()]) +class LocalSettingOptions with EquatableMixin { + const LocalSettingOptions({ + required this.setting, + }); + + final LocalSettingDetail? setting; + + factory LocalSettingOptions.fromJson(Map json) => + _$LocalSettingOptionsFromJson(json); + + Map toJson() => _$LocalSettingOptionsToJson(this); + + @override + List get props => [setting]; +} \ No newline at end of file diff --git a/lib/features/manage_account/presentation/model/setting_option_type.dart b/lib/features/manage_account/presentation/model/setting_option_type.dart index e7ef2bce2..c09ecbbbb 100644 --- a/lib/features/manage_account/presentation/model/setting_option_type.dart +++ b/lib/features/manage_account/presentation/model/setting_option_type.dart @@ -1,11 +1,14 @@ import 'package:server_settings/server_settings/tmail_server_settings.dart'; import 'package:server_settings/server_settings/tmail_server_settings_extension.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/extensions/local_settings_map_extension.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_options.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; enum SettingOptionType { readReceipt, - senderPriority; + senderPriority, + thread; String getTitle(AppLocalizations appLocalizations) { switch(this) { @@ -13,6 +16,8 @@ enum SettingOptionType { return appLocalizations.emailReadReceipts; case SettingOptionType.senderPriority: return appLocalizations.senderSetImportantFlag; + case SettingOptionType.thread: + return appLocalizations.thread; } } @@ -22,6 +27,8 @@ enum SettingOptionType { return appLocalizations.emailReadReceiptsSettingExplanation; case SettingOptionType.senderPriority: return appLocalizations.senderImportantSettingExplanation; + case SettingOptionType.thread: + return appLocalizations.threadSettingExplanation; } } @@ -31,15 +38,29 @@ enum SettingOptionType { return appLocalizations.emailReadReceiptsToggleDescription; case SettingOptionType.senderPriority: return appLocalizations.senderImportantSettingToggleDescription; + case SettingOptionType.thread: + return appLocalizations.threadToggleDescription; } } - bool isEnabled(TMailServerSettingOptions settingOption) { + bool isEnabled( + TMailServerSettingOptions? settingOption, + Map localSettings, + ) { switch(this) { case SettingOptionType.readReceipt: - return settingOption.isAlwaysReadReceipts; + return settingOption?.isAlwaysReadReceipts ?? false; case SettingOptionType.senderPriority: - return settingOption.isDisplaySenderPriority; + return settingOption?.isDisplaySenderPriority ?? false; + case SettingOptionType.thread: + return localSettings.threadDetailEnabled ?? true; } } + + bool get isLocal { + return switch (this) { + thread => true, + _ => false, + }; + } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/preferences/bindings/preferences_bindings.dart b/lib/features/manage_account/presentation/preferences/bindings/preferences_bindings.dart index deef60812..1e80d931c 100644 --- a/lib/features/manage_account/presentation/preferences/bindings/preferences_bindings.dart +++ b/lib/features/manage_account/presentation/preferences/bindings/preferences_bindings.dart @@ -1,4 +1,7 @@ import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/repository/manage_account_repository.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_local_settings_interactor.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/usecases/update_local_settings_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/preferences/bindings/preferences_interactors_bindings.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/preferences/preferences_controller.dart'; import 'package:tmail_ui_user/features/server_settings/domain/usecases/get_server_setting_interactor.dart'; @@ -13,6 +16,15 @@ class PreferencesBindings extends Bindings { Get.lazyPut(() => PreferencesController( Get.find(), Get.find(), + Get.find(), + Get.find(), + )); + + Get.lazyPut(() => GetLocalSettingsInteractor( + Get.find(), + )); + Get.lazyPut(() => UpdateLocalSettingsInteractor( + Get.find(), )); } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/preferences/bindings/preferences_interactors_bindings.dart b/lib/features/manage_account/presentation/preferences/bindings/preferences_interactors_bindings.dart index 9e793e6ac..2fa2a8b43 100644 --- a/lib/features/manage_account/presentation/preferences/bindings/preferences_interactors_bindings.dart +++ b/lib/features/manage_account/presentation/preferences/bindings/preferences_interactors_bindings.dart @@ -1,10 +1,11 @@ import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/interactors_bindings.dart'; -import 'package:tmail_ui_user/features/manage_account/data/datasource/preferences_datasource.dart'; -import 'package:tmail_ui_user/features/manage_account/data/datasource_impl/preferences_datasource_impl.dart'; +import 'package:tmail_ui_user/features/manage_account/data/datasource/manage_account_datasource.dart'; +import 'package:tmail_ui_user/features/manage_account/data/datasource_impl/manage_account_datasource_impl.dart'; import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart'; -import 'package:tmail_ui_user/features/manage_account/data/repository/preferences_repository_impl.dart'; -import 'package:tmail_ui_user/features/manage_account/domain/repository/preferences_repository.dart'; +import 'package:tmail_ui_user/features/manage_account/data/local/local_setting_cache_manager.dart'; +import 'package:tmail_ui_user/features/manage_account/data/repository/manage_account_repository_impl.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/repository/manage_account_repository.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/save_language_interactor.dart'; import 'package:tmail_ui_user/features/server_settings/data/datasource/server_settings_data_source.dart'; import 'package:tmail_ui_user/features/server_settings/data/datasource_impl/remote_server_settings_data_source_impl.dart'; @@ -28,8 +29,8 @@ class PreferencesInteractorsBindings extends InteractorsBindings { () => Get.find(tag: composerId), tag: composerId, ); - Get.lazyPut( - () => Get.find(tag: composerId), + Get.lazyPut( + () => Get.find(tag: composerId), tag: composerId, ); } @@ -40,8 +41,9 @@ class PreferencesInteractorsBindings extends InteractorsBindings { Get.find(), Get.find(), ), tag: composerId); - Get.lazyPut(() => PreferencesDataSourceImpl( + Get.lazyPut(() => ManageAccountDataSourceImpl( Get.find(), + Get.find(), Get.find(), ), tag: composerId); } @@ -57,7 +59,7 @@ class PreferencesInteractorsBindings extends InteractorsBindings { tag: composerId, ); Get.lazyPut( - () => SaveLanguageInteractor(Get.find(tag: composerId)), + () => SaveLanguageInteractor(Get.find(tag: composerId)), tag: composerId, ); } @@ -68,8 +70,8 @@ class PreferencesInteractorsBindings extends InteractorsBindings { () => Get.find(tag: composerId), tag: composerId, ); - Get.lazyPut( - () => Get.find(tag: composerId), + Get.lazyPut( + () => Get.find(tag: composerId), tag: composerId, ); } @@ -81,7 +83,7 @@ class PreferencesInteractorsBindings extends InteractorsBindings { tag: composerId, ); Get.lazyPut( - () => PreferencesRepositoryImpl(Get.find(tag: composerId)), + () => ManageAccountRepositoryImpl(Get.find(tag: composerId)), tag: composerId, ); } @@ -94,10 +96,10 @@ class PreferencesInteractorsBindings extends InteractorsBindings { Get.delete(tag: composerId); Get.delete(tag: composerId); - Get.delete(tag: composerId); - Get.delete(tag: composerId); - Get.delete(tag: composerId); - Get.delete(tag: composerId); + Get.delete(tag: composerId); + Get.delete(tag: composerId); + Get.delete(tag: composerId); + Get.delete(tag: composerId); Get.delete(tag: composerId); } } \ No newline at end of file diff --git a/lib/features/manage_account/presentation/preferences/preferences_controller.dart b/lib/features/manage_account/presentation/preferences/preferences_controller.dart index 6e2e36fca..ae76147e9 100644 --- a/lib/features/manage_account/presentation/preferences/preferences_controller.dart +++ b/lib/features/manage_account/presentation/preferences/preferences_controller.dart @@ -5,7 +5,13 @@ import 'package:get/get.dart'; import 'package:server_settings/server_settings/tmail_server_settings.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/state/get_local_settings_state.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/state/update_local_settings_state.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_local_settings_interactor.dart'; +import 'package:tmail_ui_user/features/manage_account/domain/usecases/update_local_settings_interactor.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/manage_account_dashboard_controller.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_detail/thread_detail_local_setting_detail.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_options.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/model/setting_option_type.dart'; import 'package:tmail_ui_user/features/server_settings/domain/state/get_server_setting_state.dart'; import 'package:tmail_ui_user/features/server_settings/domain/state/update_server_setting_state.dart'; @@ -18,12 +24,17 @@ class PreferencesController extends BaseController { PreferencesController( this._getServerSettingInteractor, this._updateServerSettingInteractor, + this._getLocalSettingInteractor, + this._updateLocalSettingsInteractor, ); final GetServerSettingInteractor _getServerSettingInteractor; final UpdateServerSettingInteractor _updateServerSettingInteractor; + final GetLocalSettingsInteractor _getLocalSettingInteractor; + final UpdateLocalSettingsInteractor _updateLocalSettingsInteractor; final settingOption = Rxn(); + final localSettings = Rxn({}); bool get isLoading => viewState.value.fold( (failure) => false, @@ -43,6 +54,14 @@ class PreferencesController extends BaseController { _updateSettingOptionValue(newSettingOption: success.settingOption); } else if (success is UpdateServerSettingSuccess) { _updateSettingOptionValue(newSettingOption: success.settingOption); + } else if (success is GetLocalSettingsSuccess) { + _updateLocalSettingOptionValue( + newLocalSettings: success.localSettings, + ); + } else if (success is UpdateLocalSettingsSuccess) { + _updateLocalSettingOptionValue( + newLocalSettings: success.localSettings, + ); } else { super.handleSuccessViewState(success); } @@ -71,16 +90,41 @@ class PreferencesController extends BaseController { settingOption.value = newSettingOption; } + void _updateLocalSettingOptionValue({ + required Map newLocalSettings + }) { + localSettings.value = newLocalSettings; + } + void _getSettingOption() { + consumeState(_getLocalSettingInteractor.execute(SupportedLocalSetting.values)); final accountId = _manageAccountDashBoardController.accountId.value; if (accountId != null) { consumeState(_getServerSettingInteractor.execute(accountId)); } else { consumeState(Stream.value(Left(GetServerSettingFailure(NotFoundAccountIdException())))); + consumeState(Stream.value(Left(GetLocalSettingsFailure( + exception: NotFoundAccountIdException(), + )))); } } void updateStateSettingOption(SettingOptionType optionType, bool isEnabled) { + if (optionType.isLocal) { + Map newLocalSettings = {}; + switch(optionType) { + case SettingOptionType.thread: + var currentLocalSettings = Map.from(localSettings.value ?? {}); + currentLocalSettings[SupportedLocalSetting.threadDetail] = LocalSettingOptions(setting: ThreadDetailLocalSettingDetail(!isEnabled)); + newLocalSettings = currentLocalSettings; + break; + default: + break; + } + consumeState(_updateLocalSettingsInteractor.execute(newLocalSettings)); + return; + } + TMailServerSettingOptions? newSettingOption; switch(optionType) { case SettingOptionType.readReceipt: diff --git a/lib/features/manage_account/presentation/preferences/preferences_view.dart b/lib/features/manage_account/presentation/preferences/preferences_view.dart index febd37c07..b205026fb 100644 --- a/lib/features/manage_account/presentation/preferences/preferences_view.dart +++ b/lib/features/manage_account/presentation/preferences/preferences_view.dart @@ -57,19 +57,30 @@ class PreferencesView extends GetWidget with AppLoaderMix ), Obx(() { final settingOption = controller.settingOption.value; + final localSettingOption = controller.localSettings; - if (settingOption == null) return const SizedBox.shrink(); + if (settingOption == null && (localSettingOption.value?.isEmpty ?? true)) { + return const SizedBox.shrink(); + } - final settingOptionList = SettingOptionType.values.toList(); + final availableSettingOptions = [ + if (settingOption != null) ...SettingOptionType.values.where( + (optionType) => !optionType.isLocal, + ), + ...SettingOptionType.values.where( + (optionType) => optionType.isLocal, + ), + ]; return Expanded( child: ListView.separated( - itemCount: settingOptionList.length, + itemCount: availableSettingOptions.length, itemBuilder: (context, index) { return SettingOptionItem( imagePaths: controller.imagePaths, settingOption: settingOption, - optionType: settingOptionList[index], + localSettings: localSettingOption.value ?? {}, + optionType: availableSettingOptions[index], onTapSettingOptionAction: controller.updateStateSettingOption, ); }, diff --git a/lib/features/manage_account/presentation/preferences/widgets/setting_option_item.dart b/lib/features/manage_account/presentation/preferences/widgets/setting_option_item.dart index 99779e6a5..3f45b0431 100644 --- a/lib/features/manage_account/presentation/preferences/widgets/setting_option_item.dart +++ b/lib/features/manage_account/presentation/preferences/widgets/setting_option_item.dart @@ -4,6 +4,7 @@ import 'package:core/presentation/utils/theme_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:server_settings/server_settings/tmail_server_settings.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_options.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/model/setting_option_type.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -12,7 +13,8 @@ typedef OnTapSettingOptionAction = Function(SettingOptionType optionType, bool i class SettingOptionItem extends StatelessWidget { final ImagePaths imagePaths; - final TMailServerSettingOptions settingOption; + final TMailServerSettingOptions? settingOption; + final Map localSettings; final SettingOptionType optionType; final OnTapSettingOptionAction onTapSettingOptionAction; @@ -20,6 +22,7 @@ class SettingOptionItem extends StatelessWidget { super.key, required this.imagePaths, required this.settingOption, + required this.localSettings, required this.optionType, required this.onTapSettingOptionAction, }); @@ -58,10 +61,10 @@ class SettingOptionItem extends StatelessWidget { InkWell( onTap: () => onTapSettingOptionAction( optionType, - optionType.isEnabled(settingOption), + optionType.isEnabled(settingOption, localSettings), ), child: SvgPicture.asset( - optionType.isEnabled(settingOption) + optionType.isEnabled(settingOption, localSettings) ? imagePaths.icSwitchOn : imagePaths.icSwitchOff, fit: BoxFit.fill, diff --git a/lib/features/thread_detail/data/data_source/thread_detail_data_source.dart b/lib/features/thread_detail/data/data_source/thread_detail_data_source.dart index 38e270e59..f5414c075 100644 --- a/lib/features/thread_detail/data/data_source/thread_detail_data_source.dart +++ b/lib/features/thread_detail/data/data_source/thread_detail_data_source.dart @@ -15,4 +15,6 @@ abstract class ThreadDetailDataSource { List emailIds, { Properties? properties, }); + + Future getThreadDetailStatus(); } \ No newline at end of file diff --git a/lib/features/thread_detail/data/data_source/thread_detail_local_data_source_impl.dart b/lib/features/thread_detail/data/data_source/thread_detail_local_data_source_impl.dart new file mode 100644 index 000000000..288e10676 --- /dev/null +++ b/lib/features/thread_detail/data/data_source/thread_detail_local_data_source_impl.dart @@ -0,0 +1,39 @@ +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:tmail_ui_user/features/manage_account/data/local/local_setting_cache_manager.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_detail/thread_detail_local_setting_detail.dart'; +import 'package:tmail_ui_user/features/manage_account/presentation/model/local_setting_options.dart'; +import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_data_source.dart'; +import 'package:tmail_ui_user/main/exceptions/exception_thrower.dart'; + +class ThreadDetailLocalDataSourceImpl implements ThreadDetailDataSource { + const ThreadDetailLocalDataSourceImpl( + this._localSettingCacheManager, + this._exceptionThrower, + ); + + final LocalSettingCacheManager _localSettingCacheManager; + final ExceptionThrower _exceptionThrower; + + @override + Future> getThreadById(ThreadId threadId, AccountId accountId) { + throw UnimplementedError(); + } + + @override + Future> getEmailsByIds(Session session, AccountId accountId, List emailIds, {Properties? properties}) { + throw UnimplementedError(); + } + + @override + Future getThreadDetailStatus() { + return Future.sync(() async { + final localSetting = await _localSettingCacheManager.get([ + SupportedLocalSetting.threadDetail, + ]); + return (localSetting[SupportedLocalSetting.threadDetail]?.setting as ThreadDetailLocalSettingDetail?)?.value ?? true; + }).catchError(_exceptionThrower.throwException); + } +} \ No newline at end of file diff --git a/lib/features/thread_detail/data/data_source/thread_detail_remote_data_source_impl.dart b/lib/features/thread_detail/data/data_source/thread_detail_remote_data_source_impl.dart index eff6db02a..38261acca 100644 --- a/lib/features/thread_detail/data/data_source/thread_detail_remote_data_source_impl.dart +++ b/lib/features/thread_detail/data/data_source/thread_detail_remote_data_source_impl.dart @@ -41,4 +41,9 @@ class ThreadDetailRemoteDataSourceImpl implements ThreadDetailDataSource { ); }).catchError(exceptionThrower.throwException); } + + @override + Future getThreadDetailStatus() { + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/features/thread_detail/data/repository/thread_detail_repository_impl.dart b/lib/features/thread_detail/data/repository/thread_detail_repository_impl.dart index e368e349f..5f1c0b3e7 100644 --- a/lib/features/thread_detail/data/repository/thread_detail_repository_impl.dart +++ b/lib/features/thread_detail/data/repository/thread_detail_repository_impl.dart @@ -118,4 +118,10 @@ class ThreadDetailRepositoryImpl implements ThreadDetailRepository { return threadDetailDataSource[DataSourceType.network]! .getEmailsByIds(session, accountId, emailIds, properties: properties); } + + @override + Future getThreadDetailStatus() { + return threadDetailDataSource[DataSourceType.local]! + .getThreadDetailStatus(); + } } \ No newline at end of file diff --git a/lib/features/thread_detail/domain/repository/thread_detail_repository.dart b/lib/features/thread_detail/domain/repository/thread_detail_repository.dart index 6a47f59e8..08afb21ec 100644 --- a/lib/features/thread_detail/domain/repository/thread_detail_repository.dart +++ b/lib/features/thread_detail/domain/repository/thread_detail_repository.dart @@ -19,4 +19,6 @@ abstract class ThreadDetailRepository { List emailIds, { Properties? properties, }); + + Future getThreadDetailStatus(); } \ No newline at end of file diff --git a/lib/features/thread_detail/domain/state/get_thread_detail_status_state.dart b/lib/features/thread_detail/domain/state/get_thread_detail_status_state.dart new file mode 100644 index 000000000..0383069a0 --- /dev/null +++ b/lib/features/thread_detail/domain/state/get_thread_detail_status_state.dart @@ -0,0 +1,17 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; + +class GettingThreadDetailStatus extends LoadingState {} + +class GetThreadDetailStatusSuccess extends UIState { + GetThreadDetailStatusSuccess(this.threadDetailEnabled); + + final bool threadDetailEnabled; + + @override + List get props => [threadDetailEnabled]; +} + +class GetThreadDetailStatusFailure extends FeatureFailure { + GetThreadDetailStatusFailure({super.exception}); +} \ No newline at end of file diff --git a/lib/features/thread_detail/domain/usecases/get_thread_detail_status_interactor.dart b/lib/features/thread_detail/domain/usecases/get_thread_detail_status_interactor.dart new file mode 100644 index 000000000..3b3882d6d --- /dev/null +++ b/lib/features/thread_detail/domain/usecases/get_thread_detail_status_interactor.dart @@ -0,0 +1,23 @@ +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:tmail_ui_user/features/thread_detail/domain/repository/thread_detail_repository.dart'; +import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_detail_status_state.dart'; + +class GetThreadDetailStatusInteractor { + const GetThreadDetailStatusInteractor(this._threadDetailRepository); + + final ThreadDetailRepository _threadDetailRepository; + + Stream> execute() async* { + try { + yield Right(GettingThreadDetailStatus()); + final result = await _threadDetailRepository.getThreadDetailStatus(); + yield Right(GetThreadDetailStatusSuccess(result)); + } catch (e) { + logError('GetThreadDetailStatusInteractor::execute(): Exception: $e'); + yield Left(GetThreadDetailStatusFailure(exception: e)); + } + } +} \ No newline at end of file diff --git a/lib/features/thread_detail/presentation/action/thread_detail_ui_action.dart b/lib/features/thread_detail/presentation/action/thread_detail_ui_action.dart index 6cfe639d7..236cfdb4d 100644 --- a/lib/features/thread_detail/presentation/action/thread_detail_ui_action.dart +++ b/lib/features/thread_detail/presentation/action/thread_detail_ui_action.dart @@ -20,4 +20,6 @@ class UpdatedEmailKeywordsAction extends ThreadDetailUIAction { @override List get props => [emailId, updatedKeyword, value]; -} \ No newline at end of file +} + +class UpdatedThreadDetailSettingAction extends ThreadDetailUIAction {} \ No newline at end of file diff --git a/lib/features/thread_detail/presentation/extension/handle_get_emails_by_ids_success.dart b/lib/features/thread_detail/presentation/extension/handle_get_emails_by_ids_success.dart index 78c938310..5064119d4 100644 --- a/lib/features/thread_detail/presentation/extension/handle_get_emails_by_ids_success.dart +++ b/lib/features/thread_detail/presentation/extension/handle_get_emails_by_ids_success.dart @@ -15,12 +15,6 @@ extension HandleGetEmailsByIdsSuccess on ThreadDetailController { final selectedEmailId = mailboxDashBoardController.selectedEmail.value?.id; final isLoadMore = emailIdsPresentation.values.whereNotNull().isNotEmpty; - final currentScrollPosition = scrollController?.position.pixels; - final maxScrollExtent = scrollController?.position.maxScrollExtent; - final currentBottomScrollPosition = currentScrollPosition != null - && maxScrollExtent != null - ? maxScrollExtent - currentScrollPosition - : null; for (var presentationEmail in success.presentationEmails) { if (presentationEmail.id == null) continue; @@ -47,6 +41,13 @@ extension HandleGetEmailsByIdsSuccess on ThreadDetailController { if (currentExpandedEmailIndex == -1 || firstLoadedMoreEmailIndex == -1) { return; } + + final currentScrollPosition = scrollController?.position.pixels; + final maxScrollExtent = scrollController?.position.maxScrollExtent; + final currentBottomScrollPosition = currentScrollPosition != null + && maxScrollExtent != null + ? maxScrollExtent - currentScrollPosition + : null; if (currentBottomScrollPosition != null) { WidgetsBinding.instance.addPostFrameCallback((_) { diff --git a/lib/features/thread_detail/presentation/extension/handle_get_thread_by_id_failure.dart b/lib/features/thread_detail/presentation/extension/handle_get_thread_by_id_failure.dart new file mode 100644 index 000000000..e1247b25d --- /dev/null +++ b/lib/features/thread_detail/presentation/extension/handle_get_thread_by_id_failure.dart @@ -0,0 +1,15 @@ +import 'package:dartz/dartz.dart'; +import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart'; +import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart'; +import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart'; + +extension HandleGetThreadByIdFailure on ThreadDetailController { + void handleGetThreadByIdFailure(GetThreadByIdFailure failure) { + final selectedEmail = mailboxDashBoardController.selectedEmail.value; + if (selectedEmail != null) { + consumeState(Stream.value(Right(GetEmailsByIdsSuccess([selectedEmail])))); + } else { + showRetryToast(failure); + } + } +} \ No newline at end of file diff --git a/lib/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart b/lib/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart index 6590d81fa..6a338071b 100644 --- a/lib/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart +++ b/lib/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart @@ -7,6 +7,13 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/utils/thread_d extension InitializeThreadDetailEmails on ThreadDetailController { void initializeThreadDetailEmails() { + final threadDetailEnabled = isThreadDetailEnabled; + final selectedEmail = mailboxDashBoardController.selectedEmail.value; + if (!threadDetailEnabled && selectedEmail != null) { + consumeState(Stream.value(Right(GetEmailsByIdsSuccess([selectedEmail])))); + return; + } + final emailIdsToLoadMetaData = ThreadDetailPresentationUtils.getFirstLoadEmailIds( emailIdsPresentation.keys.toList(), selectedEmailId: mailboxDashBoardController.selectedEmail.value?.id, diff --git a/lib/features/thread_detail/presentation/extension/refresh_thread_detail_on_setting_changed.dart b/lib/features/thread_detail/presentation/extension/refresh_thread_detail_on_setting_changed.dart new file mode 100644 index 000000000..3aa53901f --- /dev/null +++ b/lib/features/thread_detail/presentation/extension/refresh_thread_detail_on_setting_changed.dart @@ -0,0 +1,10 @@ +import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart'; + +extension RefreshThreadDetailOnSettingChanged on ThreadDetailController { + void refreshThreadDetailOnSettingChanged() { + if (threadDetailWasEnabled != isThreadDetailEnabled) { + threadDetailWasEnabled = isThreadDetailEnabled; + mailboxDashBoardController.selectedEmail.refresh(); + } + } +} \ No newline at end of file diff --git a/lib/features/thread_detail/presentation/model/thread_detail_setting_status.dart b/lib/features/thread_detail/presentation/model/thread_detail_setting_status.dart new file mode 100644 index 000000000..f2d81ddac --- /dev/null +++ b/lib/features/thread_detail/presentation/model/thread_detail_setting_status.dart @@ -0,0 +1 @@ +enum ThreadDetailSettingStatus { loading, enabled, disabled } \ No newline at end of file diff --git a/lib/features/thread_detail/presentation/thread_detail_bindings.dart b/lib/features/thread_detail/presentation/thread_detail_bindings.dart index c1a8a8488..2c7cd73c3 100644 --- a/lib/features/thread_detail/presentation/thread_detail_bindings.dart +++ b/lib/features/thread_detail/presentation/thread_detail_bindings.dart @@ -7,14 +7,18 @@ import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_i import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart'; +import 'package:tmail_ui_user/features/manage_account/data/local/local_setting_cache_manager.dart'; import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_data_source.dart'; +import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_local_data_source_impl.dart'; import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_remote_data_source_impl.dart'; import 'package:tmail_ui_user/features/thread_detail/data/network/thread_detail_api.dart'; import 'package:tmail_ui_user/features/thread_detail/data/repository/thread_detail_repository_impl.dart'; import 'package:tmail_ui_user/features/thread_detail/domain/repository/thread_detail_repository.dart'; import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_emails_by_ids_interactor.dart'; import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart'; +import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_detail_status_interactor.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart'; +import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart'; class ThreadDetailBindings extends BaseBindings { @@ -28,6 +32,7 @@ class ThreadDetailBindings extends BaseBindings { @override void bindingsController() { Get.put(ThreadDetailController( + Get.find(), Get.find(), Get.find(), Get.find(), @@ -44,6 +49,10 @@ class ThreadDetailBindings extends BaseBindings { Get.find(), Get.find(), )); + Get.lazyPut(() => ThreadDetailLocalDataSourceImpl( + Get.find(), + Get.find(), + )); } @override @@ -55,6 +64,7 @@ class ThreadDetailBindings extends BaseBindings { void bindingsRepositoryImpl() { Get.lazyPut(() => ThreadDetailRepositoryImpl({ DataSourceType.network: Get.find(), + DataSourceType.local: Get.find(), })); } @@ -65,6 +75,7 @@ class ThreadDetailBindings extends BaseBindings { @override void bindingsInteractor() { + Get.lazyPut(() => GetThreadDetailStatusInteractor(Get.find())); Get.lazyPut(() => GetThreadByIdInteractor(Get.find())); Get.lazyPut(() => GetEmailsByIdsInteractor(Get.find())); } diff --git a/lib/features/thread_detail/presentation/thread_detail_controller.dart b/lib/features/thread_detail/presentation/thread_detail_controller.dart index 131f32268..0ccfcedf2 100644 --- a/lib/features/thread_detail/presentation/thread_detail_controller.dart +++ b/lib/features/thread_detail/presentation/thread_detail_controller.dart @@ -34,13 +34,18 @@ import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new import 'package:tmail_ui_user/features/search/email/presentation/search_email_controller.dart'; import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart'; import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart'; +import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_detail_status_state.dart'; import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_by_id_interactor.dart'; import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_emails_by_ids_interactor.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_detail_ui_action.dart'; +import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_detail_status_interactor.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_email_ids_by_thread_id_success.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_emails_by_ids_success.dart'; +import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_thread_by_id_failure.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart'; +import 'package:tmail_ui_user/features/thread_detail/presentation/extension/refresh_thread_detail_on_setting_changed.dart'; +import 'package:tmail_ui_user/features/thread_detail/presentation/model/thread_detail_setting_status.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_collapsed_email_download_states.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/mark_collapsed_email_star_success.dart'; @@ -48,6 +53,7 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/extension/mark import 'package:tmail_ui_user/features/thread_detail/presentation/extension/quick_create_rule_from_collapsed_email_success.dart'; class ThreadDetailController extends BaseController { + final GetThreadDetailStatusInteractor _getThreadDetailStatusInteractor; final GetThreadByIdInteractor _getEmailIdsByThreadIdInteractor; final GetEmailsByIdsInteractor getEmailsByIdsInteractor; final MarkAsEmailReadInteractor _markAsEmailReadInteractor; @@ -57,6 +63,7 @@ class ThreadDetailController extends BaseController { final DownloadAttachmentForWebInteractor _downloadAttachmentForWebInteractor; ThreadDetailController( + this._getThreadDetailStatusInteractor, this._getEmailIdsByThreadIdInteractor, this.getEmailsByIdsInteractor, this._markAsEmailReadInteractor, @@ -83,6 +90,9 @@ class ThreadDetailController extends BaseController { ScrollController? scrollController; CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor; + AppLifecycleListener? appLifecycleListener; + ThreadDetailSettingStatus threadDetailSettingStatus = ThreadDetailSettingStatus.loading; + bool threadDetailWasEnabled = true; AccountId? get accountId => mailboxDashBoardController.accountId.value; Session? get session => mailboxDashBoardController.sessionCurrent; @@ -99,10 +109,22 @@ class ThreadDetailController extends BaseController { .value == true; return isWebSearchRunning || isMobileSearchRunning; } + bool get isThreadDetailEnabled => + threadDetailSettingStatus == ThreadDetailSettingStatus.enabled; @override void onInit() { super.onInit(); + consumeState(_getThreadDetailStatusInteractor.execute()); + appLifecycleListener = AppLifecycleListener( + onResume: () { + if (threadDetailSettingStatus == ThreadDetailSettingStatus.loading) { + return; + } + + consumeState(_getThreadDetailStatusInteractor.execute()); + }, + ); ever(mailboxDashBoardController.accountId, (accountId) { if (accountId == null) return; @@ -118,11 +140,19 @@ class ThreadDetailController extends BaseController { ); }); downloadProgressState.stream.listen(handleDownloadProgressState); - ever(mailboxDashBoardController.selectedEmail, (presentationEmail) { + ever(mailboxDashBoardController.selectedEmail, (presentationEmail) async { if (presentationEmail?.threadId == null) { closeThreadDetailAction(currentContext); return; } + + if (!isThreadDetailEnabled && presentationEmail?.id != null) { + consumeState(Stream.value(Right(GetThreadByIdSuccess([ + presentationEmail!.id!, + ])))); + return; + } + if (session != null && accountId != null && sentMailboxId != null && @@ -151,6 +181,11 @@ class ThreadDetailController extends BaseController { }) ..listUnsubscribeHeader?.clear(); } + } else if (action is UpdatedThreadDetailSettingAction) { + consumeState(_getThreadDetailStatusInteractor.execute()); + mailboxDashBoardController.dispatchThreadDetailUIAction( + ThreadDetailUIAction(), + ); } }); } @@ -178,6 +213,13 @@ class ThreadDetailController extends BaseController { quickCreateRuleFromCollapsedEmailSuccess(success); } else if (success is DownloadAttachmentForWebSuccess) { handleDownloadSuccess(success); + } else if (success is GetThreadDetailStatusSuccess) { + threadDetailSettingStatus = success.threadDetailEnabled + ? ThreadDetailSettingStatus.enabled + : ThreadDetailSettingStatus.disabled; + refreshThreadDetailOnSettingChanged(); + } else if (success is GettingThreadDetailStatus) { + threadDetailSettingStatus = ThreadDetailSettingStatus.loading; } else { super.handleSuccessViewState(success); } @@ -186,8 +228,7 @@ class ThreadDetailController extends BaseController { @override void handleFailureViewState(failure) { if (failure is GetThreadByIdFailure) { - showRetryToast(failure); - return; + handleGetThreadByIdFailure(failure); } if (failure is GetEmailsByIdsFailure) { showRetryToast(failure); @@ -197,6 +238,10 @@ class ThreadDetailController extends BaseController { handleDownloadFailure(failure); return; } + if (failure is GetThreadDetailStatusFailure) { + threadDetailSettingStatus = ThreadDetailSettingStatus.enabled; + refreshThreadDetailOnSettingChanged(); + } super.handleFailureViewState(failure); } } \ No newline at end of file diff --git a/lib/features/thread_detail/presentation/thread_detail_view.dart b/lib/features/thread_detail/presentation/thread_detail_view.dart index ba89b9fe8..87f25e196 100644 --- a/lib/features/thread_detail/presentation/thread_detail_view.dart +++ b/lib/features/thread_detail/presentation/thread_detail_view.dart @@ -174,8 +174,11 @@ class ThreadDetailView extends GetWidget { if (controller.emailIdsPresentation.length == 1 && controller.emailIdsPresentation.values.firstOrNull != null) { + final emailId = controller.emailIdsPresentation.values.firstOrNull!.id; + return EmailView( - emailId: controller.emailIdsPresentation.values.firstOrNull?.id, + key: ValueKey(emailId?.id.value), + emailId: emailId, ); } diff --git a/lib/features/thread_detail/presentation/widgets/thread_detail_collapsed_email.dart b/lib/features/thread_detail/presentation/widgets/thread_detail_collapsed_email.dart index 30a2516b0..6ad5e94e6 100644 --- a/lib/features/thread_detail/presentation/widgets/thread_detail_collapsed_email.dart +++ b/lib/features/thread_detail/presentation/widgets/thread_detail_collapsed_email.dart @@ -86,7 +86,11 @@ class ThreadDetailCollapsedEmail extends StatelessWidget { fontWeight: presentationEmail.hasRead ? FontWeight.normal : FontWeight.w600, - ).copyWith(height: 24), + fontSize: presentationEmail.hasRead ? null : 15, + ).copyWith( + height: presentationEmail.hasRead ? 24 / 16 : 20 / 15, + letterSpacing: presentationEmail.hasRead ? -0.16 : 0, + ), ), ), ], diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 6ff76befb..1fb1872a9 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -4498,6 +4498,24 @@ "placeholders_order": [], "placeholders": {} }, + "thread": "Thread", + "@thread": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "threadSettingExplanation": "View multiple related emails like a conversation", + "@threadSettingExplanation": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "threadToggleDescription": "Enable thread", + "@threadToggleDescription": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, "exportTraceLogFailed": "Export trace log failed", "@exportTraceLogFailed": { "type": "text", diff --git a/lib/main/bindings/local/local_bindings.dart b/lib/main/bindings/local/local_bindings.dart index 03a32d844..e74db4ee9 100644 --- a/lib/main/bindings/local/local_bindings.dart +++ b/lib/main/bindings/local/local_bindings.dart @@ -35,6 +35,7 @@ import 'package:tmail_ui_user/features/mailbox/data/local/mailbox_cache_manager. import 'package:tmail_ui_user/features/mailbox/data/local/state_cache_manager.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/local/local_spam_report_manager.dart'; import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart'; +import 'package:tmail_ui_user/features/manage_account/data/local/local_setting_cache_manager.dart'; import 'package:tmail_ui_user/features/offline_mode/manager/new_email_cache_manager.dart'; import 'package:tmail_ui_user/features/offline_mode/manager/new_email_cache_worker_queue.dart'; import 'package:tmail_ui_user/features/offline_mode/manager/opened_email_cache_manager.dart'; @@ -75,6 +76,7 @@ class LocalBindings extends Bindings { Get.put(OidcConfigurationCacheClient()); Get.put(OidcConfigurationCacheManager(Get.find(), Get.find())); Get.put(LanguageCacheManager(Get.find())); + Get.put(LocalSettingCacheManager(Get.find())); Get.put(RecentLoginUrlCacheClient()); Get.put(RecentLoginUrlCacheManager((Get.find()))); Get.put(RecentLoginUsernameCacheClient()); diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index f021417e2..9f39c96a9 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4732,6 +4732,27 @@ class AppLocalizations { ); } + String get thread { + return Intl.message( + 'Thread', + name: 'thread', + ); + } + + String get threadSettingExplanation { + return Intl.message( + 'View multiple related emails like a conversation', + name: 'threadSettingExplanation', + ); + } + + String get threadToggleDescription { + return Intl.message( + 'Enable thread', + name: 'threadToggleDescription', + ); + } + String get exportTraceLogFailed { return Intl.message( 'Export trace log failed', diff --git a/test/features/thread_detail/presentation/extension/initialize_thread_detail_emails_test.dart b/test/features/thread_detail/presentation/extension/initialize_thread_detail_emails_test.dart index 0ca679894..d56c4df54 100644 --- a/test/features/thread_detail/presentation/extension/initialize_thread_detail_emails_test.dart +++ b/test/features/thread_detail/presentation/extension/initialize_thread_detail_emails_test.dart @@ -7,6 +7,7 @@ import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; +import 'package:model/email/presentation_email.dart'; import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_emails_by_ids_interactor.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart'; @@ -56,8 +57,12 @@ void main() { final session = SessionFixtures.aliceSession; when(threadDetailController.accountId).thenReturn(accountId); when(threadDetailController.session).thenReturn(session); - when(threadDetailController.mailboxDashBoardController).thenReturn(mailboxDashboardController); - when(mailboxDashboardController.selectedEmail).thenReturn(Rxn()); + when(threadDetailController.isThreadDetailEnabled) + .thenReturn(true); + when(threadDetailController.mailboxDashBoardController) + .thenReturn(mailboxDashboardController); + when(mailboxDashboardController.selectedEmail) + .thenReturn(Rxn(PresentationEmail())); // Act threadDetailController.initializeThreadDetailEmails(); diff --git a/test/features/thread_detail/presentation/extension/load_more_thread_detail_emails_test.dart b/test/features/thread_detail/presentation/extension/load_more_thread_detail_emails_test.dart index ebdfb568c..3ee6c8817 100644 --- a/test/features/thread_detail/presentation/extension/load_more_thread_detail_emails_test.dart +++ b/test/features/thread_detail/presentation/extension/load_more_thread_detail_emails_test.dart @@ -142,6 +142,7 @@ void main() { when(controller.currentExpandedEmailId).thenReturn(Rxn(EmailId(Id('30')))); when(controller.session).thenReturn(SessionFixtures.aliceSession); when(controller.accountId).thenReturn(AccountFixtures.aliceAccountId); + when(controller.additionalProperties).thenReturn(Properties.empty()); when(controller.getEmailsByIdsInteractor).thenReturn(getEmailsByIdsInteractor); when(controller.mailboxDashBoardController).thenReturn(mailboxDashBoardController); when(mailboxDashBoardController.selectedEmail).thenReturn(Rxn()); @@ -186,6 +187,7 @@ void main() { when(controller.currentExpandedEmailId).thenReturn(Rxn(EmailId(Id('30')))); when(controller.session).thenReturn(SessionFixtures.aliceSession); when(controller.accountId).thenReturn(AccountFixtures.aliceAccountId); + when(controller.additionalProperties).thenReturn(Properties.empty()); when(controller.getEmailsByIdsInteractor).thenReturn(getEmailsByIdsInteractor); when(controller.mailboxDashBoardController).thenReturn(mailboxDashBoardController); when(mailboxDashBoardController.selectedEmail).thenReturn(Rxn());