From 8e3e6170942535b38b3be2e1bdaded21bb97c211 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 7 Nov 2023 19:00:37 +0530 Subject: [PATCH 1/8] Parse and persist storage bonus Signed-off-by: Neeraj Gupta <254676+ua741@users.noreply.github.com> --- lib/models/api/storage_bonus/bonus.dart | 50 +++++++++++++++++++ .../api/storage_bonus/storage_bonus.dart | 30 +---------- lib/models/user_details.dart | 8 ++- 3 files changed, 59 insertions(+), 29 deletions(-) create mode 100644 lib/models/api/storage_bonus/bonus.dart diff --git a/lib/models/api/storage_bonus/bonus.dart b/lib/models/api/storage_bonus/bonus.dart new file mode 100644 index 000000000..6a85b8278 --- /dev/null +++ b/lib/models/api/storage_bonus/bonus.dart @@ -0,0 +1,50 @@ +class Bonus { + int storage; + String type; + int validTill; + bool isRevoked; + + Bonus(this.storage, this.type, this.validTill, this.isRevoked); + + // fromJson + factory Bonus.fromJson(Map json) { + return Bonus( + json['storage'], + json['type'], + json['validTill'], + json['isRevoked'], + ); + } + + Map toJson() { + return { + 'storage': storage, + 'type': type, + 'validTill': validTill, + 'isRevoked': isRevoked, + }; + } +} + +class BonusData { + final List storageBonuses; + + BonusData(this.storageBonuses); + + factory BonusData.fromJson(Map? json) { + if (json == null || json['storageBonuses'] == null) { + return BonusData([]); + } + return BonusData( + (json['storageBonuses'] as List) + .map((bonus) => Bonus.fromJson(bonus)) + .toList(), + ); + } + + Map toJson() { + return { + 'storageBonuses': storageBonuses.map((bonus) => bonus.toJson()).toList(), + }; + } +} diff --git a/lib/models/api/storage_bonus/storage_bonus.dart b/lib/models/api/storage_bonus/storage_bonus.dart index 3a62beeb5..a789f85b8 100644 --- a/lib/models/api/storage_bonus/storage_bonus.dart +++ b/lib/models/api/storage_bonus/storage_bonus.dart @@ -1,3 +1,5 @@ +import "package:photos/models/api/storage_bonus/bonus.dart"; + class ReferralView { PlanInfo planInfo; String code; @@ -86,34 +88,6 @@ class ReferralStat { } } -class Bonus { - int storage; - String type; - int validTill; - bool isRevoked; - - Bonus(this.storage, this.type, this.validTill, this.isRevoked); - - // fromJson - factory Bonus.fromJson(Map json) { - return Bonus( - json['storage'], - json['type'], - json['validTill'], - json['isRevoked'], - ); - } - - Map toJson() { - return { - 'storage': storage, - 'type': type, - 'validTill': validTill, - 'isRevoked': isRevoked, - }; - } -} - class BonusDetails { List referralStats; List bonuses; diff --git a/lib/models/user_details.dart b/lib/models/user_details.dart index 98fa3412a..0089ccc04 100644 --- a/lib/models/user_details.dart +++ b/lib/models/user_details.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:math'; import 'package:collection/collection.dart'; +import "package:photos/models/api/storage_bonus/bonus.dart"; import 'package:photos/models/file/file_type.dart'; import 'package:photos/models/subscription.dart'; @@ -14,6 +15,7 @@ class UserDetails { final Subscription subscription; final FamilyData? familyData; final ProfileData? profileData; + final BonusData? bonusData; const UserDetails( this.email, @@ -24,6 +26,7 @@ class UserDetails { this.subscription, this.familyData, this.profileData, + this.bonusData, ); bool isPartOfFamily() { @@ -65,6 +68,7 @@ class UserDetails { Subscription.fromMap(map['subscription']), FamilyData.fromMap(map['familyData']), ProfileData.fromJson(map['profileData']), + BonusData.fromJson(map['bonusData']), ); } @@ -78,6 +82,7 @@ class UserDetails { 'subscription': subscription.toMap(), 'familyData': familyData?.toMap(), 'profileData': profileData?.toJson(), + 'bonusData': bonusData?.toJson(), }; } @@ -123,6 +128,7 @@ class FamilyMember { factory FamilyMember.fromJson(String source) => FamilyMember.fromMap(json.decode(source)); } + class ProfileData { bool canDisableEmailMFA; bool isEmailMFAEnabled; @@ -135,7 +141,6 @@ class ProfileData { this.isTwoFactorEnabled = false, }); - // Factory method to create ProfileData instance from JSON factory ProfileData.fromJson(Map? json) { return ProfileData( @@ -153,6 +158,7 @@ class ProfileData { 'isTwoFactorEnabled': isTwoFactorEnabled, }; } + String toJsonString() => json.encode(toJson()); } From c5a2b63ba92c9659ff1047ea7bfb92ee4e7fb11d Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 7 Nov 2023 19:01:05 +0530 Subject: [PATCH 2/8] Add View Add-on button if bonus is available Signed-off-by: Neeraj Gupta <254676+ua741@users.noreply.github.com> --- lib/generated/intl/messages_en.dart | 3 +- lib/generated/l10n.dart | 20 +++++------ lib/l10n/intl_en.arb | 3 +- lib/ui/payment/add_on_page.dart | 37 ++++++++++++++++++++ lib/ui/payment/store_subscription_page.dart | 5 ++- lib/ui/payment/stripe_subscription_page.dart | 5 ++- 6 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 lib/ui/payment/add_on_page.dart diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 6d6e3ecf4..89db75e2e 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -694,8 +694,6 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("Group nearby photos"), "hearUsExplanation": MessageLookupByLibrary.simpleMessage( "We don\'t track app installs. It\'d help if you told us where you found us!"), - "hearUsHint": MessageLookupByLibrary.simpleMessage( - "friend, reddit, ad, search, etc."), "hearUsWhereTitle": MessageLookupByLibrary.simpleMessage( "How did you hear about Ente? (optional)"), "hidden": MessageLookupByLibrary.simpleMessage("Hidden"), @@ -1342,6 +1340,7 @@ class MessageLookup extends MessageLookupByLibrary { "videoSmallCase": MessageLookupByLibrary.simpleMessage("video"), "viewActiveSessions": MessageLookupByLibrary.simpleMessage("View active sessions"), + "viewAddOnButton": MessageLookupByLibrary.simpleMessage("View Add-ons"), "viewAll": MessageLookupByLibrary.simpleMessage("View all"), "viewAllExifData": MessageLookupByLibrary.simpleMessage("View all EXIF data"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index cd1518a04..246073fd2 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -7725,16 +7725,6 @@ class S { ); } - /// `friend, reddit, ad, search, etc.` - String get hearUsHint { - return Intl.message( - 'friend, reddit, ad, search, etc.', - name: 'hearUsHint', - desc: '', - args: [], - ); - } - /// `We don't track app installs. It'd help if you told us where you found us!` String get hearUsExplanation { return Intl.message( @@ -7744,6 +7734,16 @@ class S { args: [], ); } + + /// `View Add-ons` + String get viewAddOnButton { + return Intl.message( + 'View Add-ons', + name: 'viewAddOnButton', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 5fd3307e3..534725dad 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1104,5 +1104,6 @@ "moveToHiddenAlbum": "Move to hidden album", "deleteConfirmDialogBody": "This account is linked to other ente apps, if you use any.\\n\\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.", "hearUsWhereTitle": "How did you hear about Ente? (optional)", - "hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!" + "hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!", + "viewAddOnButton": "View Add-ons" } \ No newline at end of file diff --git a/lib/ui/payment/add_on_page.dart b/lib/ui/payment/add_on_page.dart new file mode 100644 index 000000000..a64930763 --- /dev/null +++ b/lib/ui/payment/add_on_page.dart @@ -0,0 +1,37 @@ +import "package:flutter/material.dart"; +import "package:photos/generated/l10n.dart"; +import "package:photos/models/api/storage_bonus/bonus.dart"; +import "package:photos/theme/colors.dart"; +import "package:photos/theme/ente_theme.dart"; +import "package:photos/ui/components/captioned_text_widget.dart"; +import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart"; + +class ViewAddOnButton extends StatelessWidget { + final BonusData? bonusData; + + const ViewAddOnButton(this.bonusData, {super.key}); + + @override + Widget build(BuildContext context) { + if (bonusData?.storageBonuses.isEmpty ?? true) { + return const SizedBox.shrink(); + } + final EnteColorScheme colorScheme = getEnteColorScheme(context); + return Padding( + padding: const EdgeInsets.fromLTRB(16, 4, 16, 0), + child: MenuItemWidget( + captionedTextWidget: CaptionedTextWidget( + title: S.of(context).viewAddOnButton, + ), + menuItemColor: colorScheme.fillFaint, + trailingWidget: Icon( + Icons.chevron_right_outlined, + color: colorScheme.strokeBase, + ), + singleBorderRadius: 4, + alignCaptionedTextToLeft: true, + onTap: () async {}, + ), + ); + } +} diff --git a/lib/ui/payment/store_subscription_page.dart b/lib/ui/payment/store_subscription_page.dart index 4e0f8dc4b..01343959a 100644 --- a/lib/ui/payment/store_subscription_page.dart +++ b/lib/ui/payment/store_subscription_page.dart @@ -21,6 +21,7 @@ import 'package:photos/ui/common/loading_widget.dart'; import 'package:photos/ui/common/progress_dialog.dart'; import "package:photos/ui/components/captioned_text_widget.dart"; import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart"; +import "package:photos/ui/payment/add_on_page.dart"; import 'package:photos/ui/payment/child_subscription_widget.dart'; import 'package:photos/ui/payment/skip_subscription_widget.dart'; import 'package:photos/ui/payment/subscription_common_widgets.dart'; @@ -290,7 +291,7 @@ class _StoreSubscriptionPageState extends State { if (!widget.isOnboarding) { widgets.add( Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 80), + padding: const EdgeInsets.fromLTRB(16, 0, 16, 0), child: MenuItemWidget( captionedTextWidget: CaptionedTextWidget( title: _isFreePlanUser() @@ -310,6 +311,8 @@ class _StoreSubscriptionPageState extends State { ), ), ); + widgets.add(ViewAddOnButton(_userDetails.bonusData)); + widgets.add(const SizedBox(height: 80)); } return SingleChildScrollView( child: Column( diff --git a/lib/ui/payment/stripe_subscription_page.dart b/lib/ui/payment/stripe_subscription_page.dart index 5a9f1e5c8..62911f40a 100644 --- a/lib/ui/payment/stripe_subscription_page.dart +++ b/lib/ui/payment/stripe_subscription_page.dart @@ -20,6 +20,7 @@ import 'package:photos/ui/common/web_page.dart'; import 'package:photos/ui/components/buttons/button_widget.dart'; import "package:photos/ui/components/captioned_text_widget.dart"; import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart"; +import "package:photos/ui/payment/add_on_page.dart"; import 'package:photos/ui/payment/child_subscription_widget.dart'; import 'package:photos/ui/payment/payment_web_page.dart'; import 'package:photos/ui/payment/skip_subscription_widget.dart'; @@ -252,7 +253,7 @@ class _StripeSubscriptionPageState extends State { if (!widget.isOnboarding) { widgets.add( Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 80), + padding: const EdgeInsets.fromLTRB(16, 0, 16, 0), child: MenuItemWidget( captionedTextWidget: CaptionedTextWidget( title: S.of(context).manageFamily, @@ -270,6 +271,8 @@ class _StripeSubscriptionPageState extends State { ), ), ); + widgets.add(ViewAddOnButton(_userDetails.bonusData)); + widgets.add(const SizedBox(height: 80)); } return SingleChildScrollView( From 1aa4f7161e692d69677294f530c48bf585384bac Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Tue, 7 Nov 2023 20:44:39 +0530 Subject: [PATCH 3/8] Hide View Add-on if only sign up bonus are present Signed-off-by: Neeraj Gupta <254676+ua741@users.noreply.github.com> --- lib/models/api/storage_bonus/bonus.dart | 6 ++++++ lib/ui/payment/add_on_page.dart | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/models/api/storage_bonus/bonus.dart b/lib/models/api/storage_bonus/bonus.dart index 6a85b8278..235382e67 100644 --- a/lib/models/api/storage_bonus/bonus.dart +++ b/lib/models/api/storage_bonus/bonus.dart @@ -27,10 +27,16 @@ class Bonus { } class BonusData { + static Set signUpBonusTypes = {'SIGN_UP', 'REFERRAL'}; final List storageBonuses; BonusData(this.storageBonuses); + List getAddOnBonuses() { + return storageBonuses + ..removeWhere((b) => signUpBonusTypes.contains(b.type)); + } + factory BonusData.fromJson(Map? json) { if (json == null || json['storageBonuses'] == null) { return BonusData([]); diff --git a/lib/ui/payment/add_on_page.dart b/lib/ui/payment/add_on_page.dart index a64930763..3dca1bf32 100644 --- a/lib/ui/payment/add_on_page.dart +++ b/lib/ui/payment/add_on_page.dart @@ -13,7 +13,7 @@ class ViewAddOnButton extends StatelessWidget { @override Widget build(BuildContext context) { - if (bonusData?.storageBonuses.isEmpty ?? true) { + if (bonusData?.getAddOnBonuses().isEmpty ?? true) { return const SizedBox.shrink(); } final EnteColorScheme colorScheme = getEnteColorScheme(context); From b921416f4d2e4a06aa0c3e067e1ae2ba81005727 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:06:11 +0530 Subject: [PATCH 4/8] Add breakup of add-on-storage --- lib/generated/intl/messages_en.dart | 6 + lib/generated/l10n.dart | 30 ++++ lib/l10n/intl_en.arb | 5 +- lib/ui/payment/add_on_page.dart | 145 ++++++++++++++++--- lib/ui/payment/store_subscription_page.dart | 2 +- lib/ui/payment/stripe_subscription_page.dart | 2 +- lib/ui/payment/view_add_on_widget.dart | 41 ++++++ 7 files changed, 204 insertions(+), 27 deletions(-) create mode 100644 lib/ui/payment/view_add_on_widget.dart diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 89db75e2e..754f3d124 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -187,6 +187,8 @@ class MessageLookup extends MessageLookupByLibrary { static String m59(count) => "${Intl.plural(count, zero: '', one: '1 day', other: '${count} days')}"; + static String m64(endDate) => "Valid till ${endDate}"; + static String m60(email) => "Verify ${email}"; static String m61(email) => "We have sent a mail to ${email}"; @@ -218,6 +220,9 @@ class MessageLookup extends MessageLookupByLibrary { "addLocation": MessageLookupByLibrary.simpleMessage("Add location"), "addLocationButton": MessageLookupByLibrary.simpleMessage("Add"), "addMore": MessageLookupByLibrary.simpleMessage("Add more"), + "addOnPageSubtitle": + MessageLookupByLibrary.simpleMessage("Break up of add on storage"), + "addOns": MessageLookupByLibrary.simpleMessage("Add ons"), "addPhotos": MessageLookupByLibrary.simpleMessage("Add photos"), "addSelected": MessageLookupByLibrary.simpleMessage("Add selected"), "addToAlbum": MessageLookupByLibrary.simpleMessage("Add to album"), @@ -1323,6 +1328,7 @@ class MessageLookup extends MessageLookupByLibrary { "useSelectedPhoto": MessageLookupByLibrary.simpleMessage("Use selected photo"), "usedSpace": MessageLookupByLibrary.simpleMessage("Used space"), + "validTill": m64, "verificationFailedPleaseTryAgain": MessageLookupByLibrary.simpleMessage( "Verification failed, please try again"), diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 246073fd2..00f64bf1f 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -3914,6 +3914,16 @@ class S { ); } + /// `Valid till {endDate}` + String validTill(Object endDate) { + return Intl.message( + 'Valid till $endDate', + name: 'validTill', + desc: '', + args: [endDate], + ); + } + /// `Free trial valid till {endDate}.\nYou can choose a paid plan afterwards.` String playStoreFreeTrialValidTill(Object endDate) { return Intl.message( @@ -7744,6 +7754,26 @@ class S { args: [], ); } + + /// `Add ons` + String get addOns { + return Intl.message( + 'Add ons', + name: 'addOns', + desc: '', + args: [], + ); + } + + /// `Break up of add on storage` + String get addOnPageSubtitle { + return Intl.message( + 'Break up of add on storage', + name: 'addOnPageSubtitle', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 534725dad..67ed6b1f2 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -557,6 +557,7 @@ "faqs": "FAQs", "renewsOn": "Renews on {endDate}", "freeTrialValidTill": "Free trial valid till {endDate}", + "validTill": "Valid till {endDate}", "playStoreFreeTrialValidTill": "Free trial valid till {endDate}.\nYou can choose a paid plan afterwards.", "subWillBeCancelledOn": "Your subscription will be cancelled on {endDate}", "subscription": "Subscription", @@ -1105,5 +1106,7 @@ "deleteConfirmDialogBody": "This account is linked to other ente apps, if you use any.\\n\\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.", "hearUsWhereTitle": "How did you hear about Ente? (optional)", "hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!", - "viewAddOnButton": "View Add-ons" + "viewAddOnButton": "View Add-ons", + "addOns": "Add ons", + "addOnPageSubtitle": "Break up of add on storage" } \ No newline at end of file diff --git a/lib/ui/payment/add_on_page.dart b/lib/ui/payment/add_on_page.dart index 3dca1bf32..6b97f319e 100644 --- a/lib/ui/payment/add_on_page.dart +++ b/lib/ui/payment/add_on_page.dart @@ -1,37 +1,134 @@ import "package:flutter/material.dart"; +import "package:intl/intl.dart"; import "package:photos/generated/l10n.dart"; import "package:photos/models/api/storage_bonus/bonus.dart"; -import "package:photos/theme/colors.dart"; import "package:photos/theme/ente_theme.dart"; -import "package:photos/ui/components/captioned_text_widget.dart"; -import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart"; +import 'package:photos/ui/components/buttons/icon_button_widget.dart'; +import "package:photos/ui/components/title_bar_title_widget.dart"; +import "package:photos/ui/components/title_bar_widget.dart"; +import "package:photos/utils/data_util.dart"; -class ViewAddOnButton extends StatelessWidget { - final BonusData? bonusData; +class AddOnPage extends StatefulWidget { + final BonusData bonusData; - const ViewAddOnButton(this.bonusData, {super.key}); + const AddOnPage(this.bonusData, {super.key}); + + @override + State createState() => _StorageDetailsScreenState(); +} + +class _StorageDetailsScreenState extends State { + bool canApplyCode = true; + + @override + void initState() { + super.initState(); + } @override Widget build(BuildContext context) { - if (bonusData?.getAddOnBonuses().isEmpty ?? true) { - return const SizedBox.shrink(); - } - final EnteColorScheme colorScheme = getEnteColorScheme(context); - return Padding( - padding: const EdgeInsets.fromLTRB(16, 4, 16, 0), - child: MenuItemWidget( - captionedTextWidget: CaptionedTextWidget( - title: S.of(context).viewAddOnButton, - ), - menuItemColor: colorScheme.fillFaint, - trailingWidget: Icon( - Icons.chevron_right_outlined, - color: colorScheme.strokeBase, - ), - singleBorderRadius: 4, - alignCaptionedTextToLeft: true, - onTap: () async {}, + return Scaffold( + body: CustomScrollView( + primary: false, + slivers: [ + TitleBarWidget( + flexibleSpaceTitle: TitleBarTitleWidget( + title: S.of(context).addOns, + ), + flexibleSpaceCaption: S.of(context).addOnPageSubtitle, + actionIcons: [ + IconButtonWidget( + icon: Icons.close_outlined, + iconButtonType: IconButtonType.secondary, + onTap: () { + Navigator.of(context)..pop(); + }, + ), + ], + ), + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16), + sliver: SliverList( + delegate: SliverChildBuilderDelegate( + (delegateBuildContext, index) { + Bonus bonus = widget.bonusData!.getAddOnBonuses()[index]; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: AddOnViewSection( + sectionName: bonus.type == 'ADD_ON_BF_2023' + ? "Black friday 2023" + : bonus.type.replaceAll('_', ' '), + bonus: bonus, + ), + ); + }, + childCount: widget.bonusData?.getAddOnBonuses().length ?? 0, + ), + ), + ), + ], ), ); } } + +class AddOnViewSection extends StatelessWidget { + final String sectionName; + final Bonus bonus; + + const AddOnViewSection({ + super.key, + required this.sectionName, + required this.bonus, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = getEnteColorScheme(context); + final textStyle = getEnteTextTheme(context); + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + sectionName, + style: textStyle.body.copyWith( + color: colorScheme.textMuted, + ), + ), + if (bonus.validTill != 0) + Text( + S.of(context).validTill( + DateFormat.yMMMd( + Localizations.localeOf(context).languageCode, + ) + .format( + DateTime.fromMicrosecondsSinceEpoch( + bonus.validTill), + ) + .toString(), + ), + style: textStyle.body.copyWith( + color: colorScheme.textMuted, + ), + ), + ], + ), + const SizedBox(height: 2), + RichText( + text: TextSpan( + children: [ + TextSpan( + text: convertBytesToReadableFormat(bonus.storage).toString(), + style: textStyle.h3, + ), + ], + ), + ), + const SizedBox(height: 24), + ], + ); + } +} diff --git a/lib/ui/payment/store_subscription_page.dart b/lib/ui/payment/store_subscription_page.dart index 01343959a..628e06f96 100644 --- a/lib/ui/payment/store_subscription_page.dart +++ b/lib/ui/payment/store_subscription_page.dart @@ -21,11 +21,11 @@ import 'package:photos/ui/common/loading_widget.dart'; import 'package:photos/ui/common/progress_dialog.dart'; import "package:photos/ui/components/captioned_text_widget.dart"; import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart"; -import "package:photos/ui/payment/add_on_page.dart"; import 'package:photos/ui/payment/child_subscription_widget.dart'; import 'package:photos/ui/payment/skip_subscription_widget.dart'; import 'package:photos/ui/payment/subscription_common_widgets.dart'; import 'package:photos/ui/payment/subscription_plan_widget.dart'; +import "package:photos/ui/payment/view_add_on_widget.dart"; import 'package:photos/utils/dialog_util.dart'; import 'package:photos/utils/toast_util.dart'; import 'package:url_launcher/url_launcher_string.dart'; diff --git a/lib/ui/payment/stripe_subscription_page.dart b/lib/ui/payment/stripe_subscription_page.dart index 62911f40a..b0de23d5e 100644 --- a/lib/ui/payment/stripe_subscription_page.dart +++ b/lib/ui/payment/stripe_subscription_page.dart @@ -20,12 +20,12 @@ import 'package:photos/ui/common/web_page.dart'; import 'package:photos/ui/components/buttons/button_widget.dart'; import "package:photos/ui/components/captioned_text_widget.dart"; import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart"; -import "package:photos/ui/payment/add_on_page.dart"; import 'package:photos/ui/payment/child_subscription_widget.dart'; import 'package:photos/ui/payment/payment_web_page.dart'; import 'package:photos/ui/payment/skip_subscription_widget.dart'; import 'package:photos/ui/payment/subscription_common_widgets.dart'; import 'package:photos/ui/payment/subscription_plan_widget.dart'; +import "package:photos/ui/payment/view_add_on_widget.dart"; import 'package:photos/utils/dialog_util.dart'; import 'package:photos/utils/toast_util.dart'; import 'package:step_progress_indicator/step_progress_indicator.dart'; diff --git a/lib/ui/payment/view_add_on_widget.dart b/lib/ui/payment/view_add_on_widget.dart new file mode 100644 index 000000000..5da9e7107 --- /dev/null +++ b/lib/ui/payment/view_add_on_widget.dart @@ -0,0 +1,41 @@ +import "package:flutter/material.dart"; +import "package:photos/generated/l10n.dart"; +import "package:photos/models/api/storage_bonus/bonus.dart"; +import "package:photos/theme/colors.dart"; +import "package:photos/theme/ente_theme.dart"; +import "package:photos/ui/components/captioned_text_widget.dart"; +import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart"; +import "package:photos/ui/payment/add_on_page.dart"; +import "package:photos/utils/navigation_util.dart"; + +class ViewAddOnButton extends StatelessWidget { + final BonusData? bonusData; + + const ViewAddOnButton(this.bonusData, {super.key}); + + @override + Widget build(BuildContext context) { + if (bonusData?.getAddOnBonuses().isEmpty ?? true) { + return const SizedBox.shrink(); + } + final EnteColorScheme colorScheme = getEnteColorScheme(context); + return Padding( + padding: const EdgeInsets.fromLTRB(16, 4, 16, 0), + child: MenuItemWidget( + captionedTextWidget: CaptionedTextWidget( + title: S.of(context).viewAddOnButton, + ), + menuItemColor: colorScheme.fillFaint, + trailingWidget: Icon( + Icons.chevron_right_outlined, + color: colorScheme.strokeBase, + ), + singleBorderRadius: 4, + alignCaptionedTextToLeft: true, + onTap: () async { + routeToPage(context, AddOnPage(bonusData!)); + }, + ), + ); + } +} From 1c5956fe52568f6c28557054849e4544d56e65a7 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed, 8 Nov 2023 13:12:18 +0530 Subject: [PATCH 5/8] Make widget stateless Signed-off-by: Neeraj Gupta <254676+ua741@users.noreply.github.com> --- lib/ui/payment/add_on_page.dart | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/lib/ui/payment/add_on_page.dart b/lib/ui/payment/add_on_page.dart index 6b97f319e..6272bc75c 100644 --- a/lib/ui/payment/add_on_page.dart +++ b/lib/ui/payment/add_on_page.dart @@ -8,23 +8,11 @@ import "package:photos/ui/components/title_bar_title_widget.dart"; import "package:photos/ui/components/title_bar_widget.dart"; import "package:photos/utils/data_util.dart"; -class AddOnPage extends StatefulWidget { +class AddOnPage extends StatelessWidget { final BonusData bonusData; const AddOnPage(this.bonusData, {super.key}); - @override - State createState() => _StorageDetailsScreenState(); -} - -class _StorageDetailsScreenState extends State { - bool canApplyCode = true; - - @override - void initState() { - super.initState(); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -41,7 +29,7 @@ class _StorageDetailsScreenState extends State { icon: Icons.close_outlined, iconButtonType: IconButtonType.secondary, onTap: () { - Navigator.of(context)..pop(); + Navigator.of(context).pop(); }, ), ], @@ -51,7 +39,7 @@ class _StorageDetailsScreenState extends State { sliver: SliverList( delegate: SliverChildBuilderDelegate( (delegateBuildContext, index) { - Bonus bonus = widget.bonusData!.getAddOnBonuses()[index]; + Bonus bonus = bonusData.getAddOnBonuses()[index]; return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: AddOnViewSection( @@ -62,7 +50,7 @@ class _StorageDetailsScreenState extends State { ), ); }, - childCount: widget.bonusData?.getAddOnBonuses().length ?? 0, + childCount: bonusData?.getAddOnBonuses().length ?? 0, ), ), ), @@ -106,7 +94,8 @@ class AddOnViewSection extends StatelessWidget { ) .format( DateTime.fromMicrosecondsSinceEpoch( - bonus.validTill), + bonus.validTill, + ), ) .toString(), ), From ac14215eebfe0bceafa26b99936c38e8e11d3eee Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed, 8 Nov 2023 13:17:24 +0530 Subject: [PATCH 6/8] SubDowngrade: Check for add-on storage Signed-off-by: Neeraj Gupta <254676+ua741@users.noreply.github.com> --- lib/models/api/storage_bonus/bonus.dart | 8 ++++++-- lib/ui/payment/store_subscription_page.dart | 12 +++++++++++- lib/ui/payment/stripe_subscription_page.dart | 12 +++++++++++- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/models/api/storage_bonus/bonus.dart b/lib/models/api/storage_bonus/bonus.dart index 235382e67..a41ea6249 100644 --- a/lib/models/api/storage_bonus/bonus.dart +++ b/lib/models/api/storage_bonus/bonus.dart @@ -6,7 +6,6 @@ class Bonus { Bonus(this.storage, this.type, this.validTill, this.isRevoked); - // fromJson factory Bonus.fromJson(Map json) { return Bonus( json['storage'], @@ -34,7 +33,12 @@ class BonusData { List getAddOnBonuses() { return storageBonuses - ..removeWhere((b) => signUpBonusTypes.contains(b.type)); + .where((b) => !signUpBonusTypes.contains(b.type)) + .toList(); + } + + int totalAddOnBonus() { + return getAddOnBonuses().fold(0, (sum, bonus) => sum + bonus.storage); } factory BonusData.fromJson(Map? json) { diff --git a/lib/ui/payment/store_subscription_page.dart b/lib/ui/payment/store_subscription_page.dart index 628e06f96..b4f3e064d 100644 --- a/lib/ui/payment/store_subscription_page.dart +++ b/lib/ui/payment/store_subscription_page.dart @@ -26,6 +26,7 @@ import 'package:photos/ui/payment/skip_subscription_widget.dart'; import 'package:photos/ui/payment/subscription_common_widgets.dart'; import 'package:photos/ui/payment/subscription_plan_widget.dart'; import "package:photos/ui/payment/view_add_on_widget.dart"; +import "package:photos/utils/data_util.dart"; import 'package:photos/utils/dialog_util.dart'; import 'package:photos/utils/toast_util.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -493,7 +494,16 @@ class _StoreSubscriptionPageState extends State { if (isActive) { return; } - if (_userDetails.getFamilyOrPersonalUsage() > plan.storage) { + final int addOnBonus = + _userDetails.bonusData?.totalAddOnBonus() ?? 0; + if (_userDetails.getFamilyOrPersonalUsage() > + (plan.storage + addOnBonus)) { + _logger.warning( + " familyUsage ${convertBytesToReadableFormat(_userDetails.getFamilyOrPersonalUsage())}" + " plan storage ${convertBytesToReadableFormat(plan.storage)} " + "addOnBonus ${convertBytesToReadableFormat(addOnBonus)}," + "overshooting by ${convertBytesToReadableFormat(_userDetails.getFamilyOrPersonalUsage() - (plan.storage + addOnBonus))}", + ); showErrorDialog( context, S.of(context).sorry, diff --git a/lib/ui/payment/stripe_subscription_page.dart b/lib/ui/payment/stripe_subscription_page.dart index b0de23d5e..367356ead 100644 --- a/lib/ui/payment/stripe_subscription_page.dart +++ b/lib/ui/payment/stripe_subscription_page.dart @@ -26,6 +26,7 @@ import 'package:photos/ui/payment/skip_subscription_widget.dart'; import 'package:photos/ui/payment/subscription_common_widgets.dart'; import 'package:photos/ui/payment/subscription_plan_widget.dart'; import "package:photos/ui/payment/view_add_on_widget.dart"; +import "package:photos/utils/data_util.dart"; import 'package:photos/utils/dialog_util.dart'; import 'package:photos/utils/toast_util.dart'; import 'package:step_progress_indicator/step_progress_indicator.dart'; @@ -449,7 +450,16 @@ class _StripeSubscriptionPageState extends State { ); return; } - if (_userDetails.getFamilyOrPersonalUsage() > plan.storage) { + final int addOnBonus = + _userDetails.bonusData?.totalAddOnBonus() ?? 0; + if (_userDetails.getFamilyOrPersonalUsage() > + (plan.storage + addOnBonus)) { + logger.warning( + " familyUsage ${convertBytesToReadableFormat(_userDetails.getFamilyOrPersonalUsage())}" + " plan storage ${convertBytesToReadableFormat(plan.storage)} " + "addOnBonus ${convertBytesToReadableFormat(addOnBonus)}," + "overshooting by ${convertBytesToReadableFormat(_userDetails.getFamilyOrPersonalUsage() - (plan.storage + addOnBonus))}", + ); showErrorDialog( context, S.of(context).sorry, From f623912b03671a541dc89f0c82036eb3f000fa14 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed, 8 Nov 2023 13:59:17 +0530 Subject: [PATCH 7/8] Fix calculation for referral view --- lib/models/user_details.dart | 6 ++++++ lib/ui/growth/storage_details_screen.dart | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/models/user_details.dart b/lib/models/user_details.dart index 0089ccc04..ef1fdbbd7 100644 --- a/lib/models/user_details.dart +++ b/lib/models/user_details.dart @@ -58,6 +58,12 @@ class UserDetails { storageBonus; } + // This is the total storage for which user has paid for. + int getPlanPlusAddonStorage() { + return (isPartOfFamily() ? familyData!.storage : subscription.storage) + + bonusData!.totalAddOnBonus(); + } + factory UserDetails.fromMap(Map map) { return UserDetails( map['email'] as String, diff --git a/lib/ui/growth/storage_details_screen.dart b/lib/ui/growth/storage_details_screen.dart index 0b13e6aaa..941f9f359 100644 --- a/lib/ui/growth/storage_details_screen.dart +++ b/lib/ui/growth/storage_details_screen.dart @@ -122,12 +122,14 @@ class _StorageDetailsScreenState extends State { leftValue: convertBytesToAbsoluteGBs( min( widget.referralView.claimedStorage, - widget.userDetails.getTotalStorage(), + widget.userDetails + .getPlanPlusAddonStorage(), ), ), leftUnitName: "GB", rightValue: convertBytesToAbsoluteGBs( - widget.userDetails.getTotalStorage(), + widget.userDetails + .getPlanPlusAddonStorage(), ), rightUnitName: "GB", ), From ca282385e4929220a62428811e55d3f9682a9549 Mon Sep 17 00:00:00 2001 From: Vishnu Mohandas Date: Wed, 8 Nov 2023 16:32:14 +0530 Subject: [PATCH 8/8] Update intl_en.arb --- lib/l10n/intl_en.arb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 67ed6b1f2..c90d79c44 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1106,7 +1106,7 @@ "deleteConfirmDialogBody": "This account is linked to other ente apps, if you use any.\\n\\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.", "hearUsWhereTitle": "How did you hear about Ente? (optional)", "hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!", - "viewAddOnButton": "View Add-ons", - "addOns": "Add ons", - "addOnPageSubtitle": "Break up of add on storage" -} \ No newline at end of file + "viewAddOnButton": "View add-ons", + "addOns": "Add-ons", + "addOnPageSubtitle": "Details of add-ons" +}