From 16f0eed73096430fbf2ec8594b6d8f48cf00995e Mon Sep 17 00:00:00 2001 From: dab246 Date: Wed, 10 Sep 2025 11:48:50 +0700 Subject: [PATCH] TF-3986 Change style for alert quota --- assets/images/ic_cloud.svg | 21 ++++ .../presentation/resources/image_paths.dart | 1 + ...date_saas_premium_available_extension.dart | 0 .../domain/extensions/quota_extensions.dart | 43 +------- .../presentation/quotas_controller.dart | 30 +++++- .../styles/quotas_banner_styles.dart | 36 +++++-- .../widget/quotas_banner_widget.dart | 97 +++++++++++++------ lib/l10n/intl_messages.arb | 44 +++++---- lib/main/localizations/app_localizations.dart | 49 ++++++---- model/lib/saas/saas_account_capability.dart | 2 + 10 files changed, 200 insertions(+), 123 deletions(-) create mode 100644 assets/images/ic_cloud.svg create mode 100644 lib/features/mailbox_dashboard/presentation/extensions/validate_saas_premium_available_extension.dart diff --git a/assets/images/ic_cloud.svg b/assets/images/ic_cloud.svg new file mode 100644 index 000000000..98e17b7d5 --- /dev/null +++ b/assets/images/ic_cloud.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart index 24f6a9d6d..7fa2d3397 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -240,6 +240,7 @@ class ImagePaths { String get animLottieTmail => _getAnimationPath('lottie-tmail.json'); String get icExpandArrows => _getImagePath('ic_expand_arrows.svg'); String get icPremium => _getImagePath('ic_premium.svg'); + String get icCloud => _getImagePath('ic_cloud.svg'); String _getImagePath(String imageName) { return AssetsPaths.images + imageName; diff --git a/lib/features/mailbox_dashboard/presentation/extensions/validate_saas_premium_available_extension.dart b/lib/features/mailbox_dashboard/presentation/extensions/validate_saas_premium_available_extension.dart new file mode 100644 index 000000000..e69de29bb diff --git a/lib/features/quotas/domain/extensions/quota_extensions.dart b/lib/features/quotas/domain/extensions/quota_extensions.dart index 108a6ab6b..64828530f 100644 --- a/lib/features/quotas/domain/extensions/quota_extensions.dart +++ b/lib/features/quotas/domain/extensions/quota_extensions.dart @@ -1,6 +1,5 @@ import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:core/presentation/resources/image_paths.dart'; import 'package:filesize/filesize.dart'; import 'package:flutter/material.dart'; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; @@ -79,56 +78,16 @@ extension QuotasExtensions on Quota { } } - Color getQuotaBannerBackgroundColor() { - if (isHardLimitReached) { - return AppColor.colorQuotaError.withValues(alpha: 0.12); - } else if (isWarnLimitReached) { - return AppColor.colorBackgroundQuotasWarning.withValues(alpha: 0.12); - } else { - return AppColor.colorNetworkConnectionBannerBackground; - } - } - - String getQuotaBannerIcon(ImagePaths imagePaths) { - if (isHardLimitReached) { - return imagePaths.icQuotasOutOfStorage; - } else if (isWarnLimitReached) { - return imagePaths.icQuotasWarning; - } else { - return ''; - } - } - String getQuotaBannerTitle(BuildContext context) { if (isHardLimitReached) { return AppLocalizations.of(context).quotaErrorBannerTitle; } else if (isWarnLimitReached) { - return AppLocalizations.of(context).quotaWarningBannerTitle; + return AppLocalizations.of(context).quotaBannerWarningTitle; } else { return ''; } } - String getQuotaBannerMessage(BuildContext context) { - if (isHardLimitReached) { - return AppLocalizations.of(context).quotaErrorBannerMessage; - } else if (isWarnLimitReached) { - return AppLocalizations.of(context).quotaWarningBannerMessage; - } else { - return ''; - } - } - - Color getQuotaBannerTitleColor() { - if (isHardLimitReached) { - return AppColor.colorQuotaError; - } else if (isWarnLimitReached) { - return AppColor.colorQuotaWarning; - } else { - return Colors.black; - } - } - Color getQuotaBannerMessageColor() { if (isHardLimitReached) { return AppColor.colorQuotaError; diff --git a/lib/features/quotas/presentation/quotas_controller.dart b/lib/features/quotas/presentation/quotas_controller.dart index 9581e204d..931c01bec 100644 --- a/lib/features/quotas/presentation/quotas_controller.dart +++ b/lib/features/quotas/presentation/quotas_controller.dart @@ -1,10 +1,13 @@ +import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart'; import 'package:jmap_dart_client/jmap/quotas/quota.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/validate_saas_premium_available_extension.dart'; import 'package:tmail_ui_user/features/quotas/domain/extensions/list_quotas_extensions.dart'; import 'package:tmail_ui_user/features/quotas/domain/state/get_quotas_state.dart'; import 'package:tmail_ui_user/features/quotas/domain/use_case/get_quotas_interactor.dart'; @@ -17,6 +20,7 @@ class QuotasController extends BaseController { final GetQuotasInteractor _getQuotasInteractor; final octetsQuota = Rxn(); + final isBannerEnabled = RxBool(false); late Worker accountIdListener; @@ -28,6 +32,7 @@ class QuotasController extends BaseController { void _handleGetQuotasSuccess(GetQuotasSuccess success) { octetsQuota.value = success.quotas.octetsQuota; + isBannerEnabled.value = true; } void _initWorker() { @@ -62,9 +67,32 @@ class QuotasController extends BaseController { @override void handleSuccessViewState(Success success) { - super.handleSuccessViewState(success); if (success is GetQuotasSuccess) { _handleGetQuotasSuccess(success); + } else { + super.handleSuccessViewState(success); } } + + @override + void handleFailureViewState(Failure failure) { + if (failure is GetQuotasFailure) { + isBannerEnabled.value = true; + } else { + super.handleFailureViewState(failure); + } + } + + bool get isManageMyStorageIsEnabled { + return mailboxDashBoardController.isPremiumAvailable && + !mailboxDashBoardController.isAlreadyHighestSubscription; + } + + void handleManageMyStorage(BuildContext context) { + mailboxDashBoardController.navigateToPaywall(context); + } + + void closeBanner() { + isBannerEnabled.value = false; + } } diff --git a/lib/features/quotas/presentation/styles/quotas_banner_styles.dart b/lib/features/quotas/presentation/styles/quotas_banner_styles.dart index db7d5d077..c73328840 100644 --- a/lib/features/quotas/presentation/styles/quotas_banner_styles.dart +++ b/lib/features/quotas/presentation/styles/quotas_banner_styles.dart @@ -1,25 +1,45 @@ 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:core/utils/platform_info.dart'; import 'package:flutter/material.dart'; class QuotasBannerStyles { static const double iconPadding = 16; static const double iconSize = 32; - static const double titleTextSize = 17; - static const double messageTextSize = 15; - static const double space = 4; - static const double borderRadius = 12; + static const double titleTextSize = 15; + static const double borderRadius = 8; static const Color messageTextColor = AppColor.steelGray400; + static const Color backgroundColor = AppColor.lightGrayEAEDF2; - static const FontWeight titleFontWeight = FontWeight.w700; static const FontWeight messageFontWeight = FontWeight.w400; - static const EdgeInsetsGeometry bannerPadding = EdgeInsetsDirectional.symmetric( - horizontal: 16, - vertical: 8, + static TextStyle titleTextStyle = ThemeUtils.textStyleInter500().copyWith( + fontSize: 15, + height: 20 / 15, + letterSpacing: 0.0, + color: Colors.black, + ); + static TextStyle subTitleTextStyle = ThemeUtils.textStyleInter400.copyWith( + fontSize: 13, + height: 16 / 13, + letterSpacing: 0.0, + color: AppColor.steelGray400, + ); + static TextStyle manageStorageButtonTextStyle = ThemeUtils.textStyleInter700().copyWith( + fontSize: 14, + height: 20 / 14, + letterSpacing: 0.0, + color: AppColor.blue700, + ); + + static const EdgeInsetsGeometry bannerPadding = EdgeInsetsDirectional.only( + start: 16, + end: 40, + top: 12, + bottom: 12, ); static EdgeInsetsGeometry getBannerMargin( BuildContext context, diff --git a/lib/features/quotas/presentation/widget/quotas_banner_widget.dart b/lib/features/quotas/presentation/widget/quotas_banner_widget.dart index faacee883..3d30e7371 100644 --- a/lib/features/quotas/presentation/widget/quotas_banner_widget.dart +++ b/lib/features/quotas/presentation/widget/quotas_banner_widget.dart @@ -1,10 +1,13 @@ -import 'package:core/presentation/utils/theme_utils.dart'; +import 'package:core/presentation/views/button/tmail_button_widget.dart'; +import 'package:core/utils/platform_info.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/widget/default_field/default_close_button_widget.dart'; import 'package:tmail_ui_user/features/quotas/domain/extensions/quota_extensions.dart'; import 'package:tmail_ui_user/features/quotas/presentation/quotas_controller.dart'; import 'package:tmail_ui_user/features/quotas/presentation/styles/quotas_banner_styles.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class QuotasBannerWidget extends StatelessWidget { @@ -16,49 +19,79 @@ class QuotasBannerWidget extends StatelessWidget { Widget build(BuildContext context) { return Obx(() { final octetQuota = _quotasController.octetsQuota.value; - if (octetQuota != null && octetQuota.allowedDisplayToQuotaBanner) { + + if (octetQuota != null && + octetQuota.allowedDisplayToQuotaBanner && + _quotasController.isBannerEnabled.isTrue) { + final appLocalizations = AppLocalizations.of(context); + return Container( - decoration: BoxDecoration( - color: octetQuota.getQuotaBannerBackgroundColor(), - borderRadius: const BorderRadius.all(Radius.circular(QuotasBannerStyles.borderRadius)), + decoration: const BoxDecoration( + color: QuotasBannerStyles.backgroundColor, + borderRadius: BorderRadius.all( + Radius.circular(QuotasBannerStyles.borderRadius), + ), ), margin: QuotasBannerStyles.getBannerMargin( context, - _quotasController.responsiveUtils), - padding: QuotasBannerStyles.bannerPadding, - child: Row( + _quotasController.responsiveUtils, + ), + child: Stack( children: [ - SvgPicture.asset( - octetQuota.getQuotaBannerIcon(_quotasController.imagePaths), - width: QuotasBannerStyles.iconSize, - height: QuotasBannerStyles.iconSize, - fit: BoxFit.fill, - ), - const SizedBox(width: QuotasBannerStyles.iconPadding), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + Padding( + padding: QuotasBannerStyles.bannerPadding, + child: Row( children: [ - Text( - octetQuota.getQuotaBannerTitle(context), - style: ThemeUtils.defaultTextStyleInterFont.copyWith( - fontSize: QuotasBannerStyles.titleTextSize, - fontWeight: QuotasBannerStyles.titleFontWeight, - color: octetQuota.getQuotaBannerTitleColor(), - ), + SvgPicture.asset( + _quotasController.imagePaths.icCloud, + width: QuotasBannerStyles.iconSize, + height: QuotasBannerStyles.iconSize, + fit: BoxFit.fill, ), - const SizedBox(height: QuotasBannerStyles.space), - Text( - octetQuota.getQuotaBannerMessage(context), - style: ThemeUtils.defaultTextStyleInterFont.copyWith( - fontSize: QuotasBannerStyles.messageTextSize, - fontWeight: QuotasBannerStyles.messageFontWeight, - color: QuotasBannerStyles.messageTextColor, + const SizedBox(width: QuotasBannerStyles.iconPadding), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + octetQuota.getQuotaBannerTitle(context), + style: QuotasBannerStyles.titleTextStyle, + ), + const SizedBox(height: 8), + if (!PlatformInfo.isWeb || + !_quotasController.isManageMyStorageIsEnabled) + Text( + appLocalizations.quotaBannerWarningSubtitleWithoutPremium, + style: QuotasBannerStyles.subTitleTextStyle, + ) + else + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Text( + '${appLocalizations.quotaBannerWarningSubtitleWithPremium} ', + style: QuotasBannerStyles.subTitleTextStyle, + ), + TMailButtonWidget.fromText( + text: appLocalizations.manageMyStorage, + backgroundColor: QuotasBannerStyles.backgroundColor, + textStyle: QuotasBannerStyles.manageStorageButtonTextStyle, + padding: EdgeInsets.zero, + onTapActionCallback: () => + _quotasController.handleManageMyStorage(context), + ), + ], + ), + ], ), ), ], ), ), + DefaultCloseButtonWidget( + iconClose: _quotasController.imagePaths.icCloseDialog, + onTapActionCallback: _quotasController.closeBanner, + ), ], ), ); diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 483c978de..3dcb5f88b 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2025-09-09T15:28:33.596868", + "@@last_modified": "2025-09-10T11:45:56.814190", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -3176,24 +3176,6 @@ "placeholders_order": [], "placeholders": {} }, - "quotaWarningBannerTitle": "You are running out of storage (90%).", - "@quotaWarningBannerTitle": { - "type": "text", - "placeholders_order": [], - "placeholders": {} - }, - "quotaWarningBannerMessage": "Soon you won't be able to email in Tmail. Please clean your storage or upgrade your storage to get full features in Tmail.", - "@quotaWarningBannerMessage": { - "type": "text", - "placeholders_order": [], - "placeholders": {} - }, - "quotaErrorBannerMessage": "Soon you won't be able to email in Tmail. Please clean your storage or upgrade your storage to get full features in Tmail.", - "@quotaErrorBannerMessage": { - "type": "text", - "placeholders_order": [], - "placeholders": {} - }, "createFolderSuccessfullyMessage": "You successfully created {folderName} folder", "@createFolderSuccessfullyMessage": { "type": "text", @@ -4693,5 +4675,29 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "quotaBannerWarningTitle": "You are running low on storage (90%)", + "@quotaBannerWarningTitle": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "quotaBannerWarningSubtitleWithPremium": "To keep sending messages and enjoying all Twake Mail features, please consider cleaning up or upgrading your storage.", + "@quotaBannerWarningSubtitleWithPremium": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "quotaBannerWarningSubtitleWithoutPremium": "To keep sending messages and enjoying all Twake Mail features, please consider cleaning up.", + "@quotaBannerWarningSubtitleWithoutPremium": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "manageMyStorage": "Manage my storage", + "@manageMyStorage": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index ea466574d..d4324f0c5 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -3261,27 +3261,6 @@ class AppLocalizations { ); } - String get quotaWarningBannerTitle { - return Intl.message( - 'You are running out of storage (90%).', - name: 'quotaWarningBannerTitle' - ); - } - - String get quotaWarningBannerMessage { - return Intl.message( - 'Soon you won\'t be able to email in Tmail. Please clean your storage or upgrade your storage to get full features in Tmail.', - name: 'quotaWarningBannerMessage' - ); - } - - String get quotaErrorBannerMessage { - return Intl.message( - 'Soon you won\'t be able to email in Tmail. Please clean your storage or upgrade your storage to get full features in Tmail.', - name: 'quotaErrorBannerMessage' - ); - } - String createFolderSuccessfullyMessage(String folderName) { return Intl.message( 'You successfully created $folderName folder', @@ -4950,4 +4929,32 @@ class AppLocalizations { name: 'paywallUrlNotAvailable', ); } + + String get quotaBannerWarningTitle { + return Intl.message( + 'You are running low on storage (90%)', + name: 'quotaBannerWarningTitle', + ); + } + + String get quotaBannerWarningSubtitleWithPremium { + return Intl.message( + 'To keep sending messages and enjoying all Twake Mail features, please consider cleaning up or upgrading your storage.', + name: 'quotaBannerWarningSubtitleWithPremium', + ); + } + + String get quotaBannerWarningSubtitleWithoutPremium { + return Intl.message( + 'To keep sending messages and enjoying all Twake Mail features, please consider cleaning up.', + name: 'quotaBannerWarningSubtitleWithoutPremium', + ); + } + + String get manageMyStorage { + return Intl.message( + 'Manage my storage', + name: 'manageMyStorage', + ); + } } diff --git a/model/lib/saas/saas_account_capability.dart b/model/lib/saas/saas_account_capability.dart index 15f2edccd..0d7a44d0e 100644 --- a/model/lib/saas/saas_account_capability.dart +++ b/model/lib/saas/saas_account_capability.dart @@ -21,6 +21,8 @@ class SaaSAccountCapability extends CapabilityProperties { bool get isPremiumAvailable => canUpgrade; + bool get isAlreadyHighestSubscription => isPaying && !canUpgrade; + @override List get props => [isPaying, canUpgrade]; }