diff --git a/mobile/lib/ui/payment/skip_subscription_widget.dart b/mobile/lib/ui/payment/skip_subscription_widget.dart deleted file mode 100644 index c2949a238a..0000000000 --- a/mobile/lib/ui/payment/skip_subscription_widget.dart +++ /dev/null @@ -1,55 +0,0 @@ -import "dart:async"; - -import 'package:flutter/material.dart'; -import 'package:photos/core/event_bus.dart'; -import 'package:photos/events/subscription_purchased_event.dart'; -import "package:photos/generated/l10n.dart"; -import 'package:photos/models/billing_plan.dart'; -import 'package:photos/models/subscription.dart'; -import 'package:photos/services/billing_service.dart'; -import "package:photos/ui/tabs/home_widget.dart"; - -class SkipSubscriptionWidget extends StatelessWidget { - const SkipSubscriptionWidget({ - Key? key, - required this.freePlan, - }) : super(key: key); - - final FreePlan freePlan; - - @override - Widget build(BuildContext context) { - return Container( - width: double.infinity, - height: 64, - margin: const EdgeInsets.fromLTRB(0, 30, 0, 0), - padding: const EdgeInsets.fromLTRB(20, 0, 20, 0), - child: OutlinedButton( - style: Theme.of(context).outlinedButtonTheme.style?.copyWith( - textStyle: MaterialStateProperty.resolveWith( - (Set states) { - return Theme.of(context).textTheme.titleMedium!; - }, - ), - ), - onPressed: () async { - Bus.instance.fire(SubscriptionPurchasedEvent()); - // ignore: unawaited_futures - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (BuildContext context) { - return const HomeWidget(); - }, - ), - (route) => false, - ); - unawaited( - BillingService.instance - .verifySubscription(freeProductID, "", paymentProvider: "ente"), - ); - }, - child: Text(S.of(context).continueOnFreeTrial), - ), - ); - } -} diff --git a/mobile/lib/ui/payment/store_subscription_page.dart b/mobile/lib/ui/payment/store_subscription_page.dart index 94b999b850..6dc9d02fc9 100644 --- a/mobile/lib/ui/payment/store_subscription_page.dart +++ b/mobile/lib/ui/payment/store_subscription_page.dart @@ -13,20 +13,20 @@ import 'package:photos/models/billing_plan.dart'; import 'package:photos/models/subscription.dart'; import 'package:photos/models/user_details.dart'; import 'package:photos/services/billing_service.dart'; -import "package:photos/services/update_service.dart"; import 'package:photos/services/user_service.dart'; import "package:photos/theme/colors.dart"; import "package:photos/theme/ente_theme.dart"; 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/divider_widget.dart"; import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart"; import "package:photos/ui/components/title_bar_title_widget.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/ui/tabs/home_widget.dart"; import "package:photos/utils/data_util.dart"; import 'package:photos/utils/dialog_util.dart'; import 'package:photos/utils/toast_util.dart'; @@ -69,9 +69,9 @@ class _StoreSubscriptionPageState extends State { @override void initState() { + super.initState(); _billingService.setIsOnSubscriptionPage(true); _setupPurchaseUpdateStreamListener(); - super.initState(); } void _setupPurchaseUpdateStreamListener() { @@ -162,13 +162,8 @@ class _StoreSubscriptionPageState extends State { _fetchSubData(); } _dialog = createProgressDialog(context, S.of(context).pleaseWait); - final appBar = AppBar( - title: widget.isOnboarding - ? null - : Text("${S.of(context).subscription}${kDebugMode ? ' Store' : ''}"), - ); return Scaffold( - appBar: appBar, + appBar: AppBar(), body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -178,8 +173,9 @@ class _StoreSubscriptionPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ TitleBarTitleWidget( - title: - widget.isOnboarding ? "Select your plan" : "Subscription", + title: widget.isOnboarding + ? "Select your plan" + : "${S.of(context).subscription}${kDebugMode ? ' Store' : ''}", ), _isFreePlanUser() || !_hasLoadedData ? const SizedBox.shrink() @@ -192,7 +188,7 @@ class _StoreSubscriptionPageState extends State { ], ), ), - _getBody(), + Expanded(child: _getBody()), ], ), ); @@ -259,6 +255,17 @@ class _StoreSubscriptionPageState extends State { ), ); + if (hasYearlyPlans) { + widgets.add( + SubscriptionToggle( + onToggle: (p0) { + showYearlyPlan = p0; + _filterStorePlansForUi(); + }, + ), + ); + } + widgets.addAll([ Column( mainAxisAlignment: MainAxisAlignment.center, @@ -266,13 +273,9 @@ class _StoreSubscriptionPageState extends State { ? _getStripePlanWidgets() : _getMobilePlanWidgets(), ), - const Padding(padding: EdgeInsets.all(8)), + const Padding(padding: EdgeInsets.all(4)), ]); - if (hasYearlyPlans) { - widgets.add(_showSubscriptionToggle()); - } - if (_currentSubscription != null) { widgets.add( ValidityWidget( @@ -280,15 +283,11 @@ class _StoreSubscriptionPageState extends State { bonusData: _userDetails.bonusData, ), ); - } - - if (_currentSubscription!.productID == freeProductID) { - if (widget.isOnboarding) { - widgets.add(SkipSubscriptionWidget(freePlan: _freePlan)); - } - widgets.add( - SubFaqWidget(isOnboarding: widget.isOnboarding), - ); + widgets.add(const DividerWidget(dividerType: DividerType.bottomBar)); + widgets.add(const SizedBox(height: 20)); + } else { + widgets.add(const DividerWidget(dividerType: DividerType.bottomBar)); + const SizedBox(height: 56); } if (_hasActiveSubscription && @@ -311,7 +310,7 @@ class _StoreSubscriptionPageState extends State { padding: const EdgeInsets.fromLTRB(16, 40, 16, 4), child: MenuItemWidget( captionedTextWidget: CaptionedTextWidget( - title: S.of(context).paymentDetails, + title: "Manage payment method", ), menuItemColor: colorScheme.fillFaint, trailingWidget: Icon( @@ -328,10 +327,15 @@ class _StoreSubscriptionPageState extends State { ); } } + + widgets.add( + SubFaqWidget(isOnboarding: widget.isOnboarding), + ); + if (!widget.isOnboarding) { widgets.add( Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 0), + padding: const EdgeInsets.fromLTRB(16, 2, 16, 2), child: MenuItemWidget( captionedTextWidget: CaptionedTextWidget( title: _isFreePlanUser() @@ -354,8 +358,10 @@ class _StoreSubscriptionPageState extends State { ), ); widgets.add(ViewAddOnButton(_userDetails.bonusData)); - widgets.add(const SizedBox(height: 80)); } + + widgets.add(const SizedBox(height: 80)); + return SingleChildScrollView( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, @@ -411,64 +417,6 @@ class _StoreSubscriptionPageState extends State { setState(() {}); } - Widget _showSubscriptionToggle() { - return Container( - padding: const EdgeInsets.only(left: 8, right: 8, top: 2, bottom: 2), - margin: const EdgeInsets.only(bottom: 6), - child: Column( - children: [ - RepaintBoundary( - child: SizedBox( - width: 250, - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: SegmentedButton( - style: SegmentedButton.styleFrom( - selectedBackgroundColor: - getEnteColorScheme(context).fillMuted, - selectedForegroundColor: - getEnteColorScheme(context).textBase, - side: BorderSide( - color: getEnteColorScheme(context).strokeMuted, - width: 1, - ), - ), - segments: >[ - ButtonSegment( - label: Text(S.of(context).monthly), - value: false, - ), - ButtonSegment( - label: Text(S.of(context).yearly), - value: true, - ), - ], - selected: {showYearlyPlan}, - onSelectionChanged: (p0) { - showYearlyPlan = p0.first; - _filterStorePlansForUi(); - }, - ), - ), - ], - ), - ), - ), - _isFreePlanUser() && !UpdateService.instance.isPlayStoreFlavor() - ? Text( - S.of(context).twoMonthsFreeOnYearlyPlans, - style: getEnteTextTheme(context).miniMuted, - ) - : const SizedBox.shrink(), - const Padding(padding: EdgeInsets.all(8)), - ], - ), - ); - } - List _getStripePlanWidgets() { final List planWidgets = []; bool foundActivePlan = false; @@ -483,10 +431,27 @@ class _StoreSubscriptionPageState extends State { foundActivePlan = true; } planWidgets.add( - Material( - color: Colors.transparent, - child: InkWell( - onTap: () async { + GestureDetector( + onTap: () async { + if (widget.isOnboarding && plan.id == freeProductID) { + Bus.instance.fire(SubscriptionPurchasedEvent()); + // ignore: unawaited_futures + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (BuildContext context) { + return const HomeWidget(); + }, + ), + (route) => false, + ); + unawaited( + BillingService.instance.verifySubscription( + freeProductID, + "", + paymentProvider: "ente", + ), + ); + } else { if (isActive) { return; } @@ -496,13 +461,15 @@ class _StoreSubscriptionPageState extends State { S.of(context).sorry, S.of(context).visitWebToManage, ); - }, - child: SubscriptionPlanWidget( - storage: plan.storage, - price: plan.price, - period: plan.period, - isActive: isActive && !_hideCurrentPlanSelection, - ), + } + }, + child: SubscriptionPlanWidget( + storage: plan.storage, + price: plan.price, + period: plan.period, + isActive: isActive && !_hideCurrentPlanSelection, + isPopular: _isPopularPlan(plan), + isOnboarding: widget.isOnboarding, ), ), ); @@ -522,9 +489,10 @@ class _StoreSubscriptionPageState extends State { planWidgets.add( SubscriptionPlanWidget( storage: _freePlan.storage, - price: S.of(context).freeTrial, - period: "", + price: "", + period: S.of(context).freeTrial, isActive: true, + isOnboarding: widget.isOnboarding, ), ); } @@ -536,71 +504,71 @@ class _StoreSubscriptionPageState extends State { foundActivePlan = true; } planWidgets.add( - Material( - child: InkWell( - onTap: () async { - if (isActive) { - return; - } - 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))}", - ); - // ignore: unawaited_futures - showErrorDialog( - context, - S.of(context).sorry, - S.of(context).youCannotDowngradeToThisPlan, - ); - return; - } - await _dialog.show(); - final ProductDetailsResponse response = - await InAppPurchase.instance.queryProductDetails({productID}); - if (response.notFoundIDs.isNotEmpty) { - final errMsg = "Could not find products: " + - response.notFoundIDs.toString(); - _logger.severe(errMsg); - await _dialog.hide(); - await showGenericErrorDialog( - context: context, - error: Exception(errMsg), - ); - return; - } - final isCrossGradingOnAndroid = Platform.isAndroid && - _hasActiveSubscription && - _currentSubscription!.productID != freeProductID && - _currentSubscription!.productID != plan.androidID; - if (isCrossGradingOnAndroid) { - await _dialog.hide(); - // ignore: unawaited_futures - showErrorDialog( - context, - S.of(context).couldNotUpdateSubscription, - S.of(context).pleaseContactSupportAndWeWillBeHappyToHelp, - ); - return; - } else { - await InAppPurchase.instance.buyNonConsumable( - purchaseParam: PurchaseParam( - productDetails: response.productDetails[0], - ), - ); - } - }, - child: SubscriptionPlanWidget( - storage: plan.storage, - price: plan.price, - period: plan.period, - isActive: isActive, - ), + GestureDetector( + onTap: () async { + if (isActive) { + return; + } + 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))}", + ); + // ignore: unawaited_futures + showErrorDialog( + context, + S.of(context).sorry, + S.of(context).youCannotDowngradeToThisPlan, + ); + return; + } + await _dialog.show(); + final ProductDetailsResponse response = + await InAppPurchase.instance.queryProductDetails({productID}); + if (response.notFoundIDs.isNotEmpty) { + final errMsg = + "Could not find products: " + response.notFoundIDs.toString(); + _logger.severe(errMsg); + await _dialog.hide(); + await showGenericErrorDialog( + context: context, + error: Exception(errMsg), + ); + return; + } + final isCrossGradingOnAndroid = Platform.isAndroid && + _hasActiveSubscription && + _currentSubscription!.productID != freeProductID && + _currentSubscription!.productID != plan.androidID; + if (isCrossGradingOnAndroid) { + await _dialog.hide(); + // ignore: unawaited_futures + showErrorDialog( + context, + S.of(context).couldNotUpdateSubscription, + S.of(context).pleaseContactSupportAndWeWillBeHappyToHelp, + ); + return; + } else { + await InAppPurchase.instance.buyNonConsumable( + purchaseParam: PurchaseParam( + productDetails: response.productDetails[0], + ), + ); + } + }, + child: SubscriptionPlanWidget( + storage: plan.storage, + price: plan.price, + period: plan.period, + isActive: isActive, + isPopular: _isPopularPlan(plan), + isOnboarding: widget.isOnboarding, ), ), ); @@ -620,17 +588,40 @@ class _StoreSubscriptionPageState extends State { } planWidgets.insert( activePlanIndex, - Material( - child: InkWell( - onTap: () {}, - child: SubscriptionPlanWidget( - storage: _currentSubscription!.storage, - price: _currentSubscription!.price, - period: _currentSubscription!.period, - isActive: true, - ), + GestureDetector( + onTap: () { + if (_currentSubscription!.isFreePlan()) { + Bus.instance.fire(SubscriptionPurchasedEvent()); + // ignore: unawaited_futures + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (BuildContext context) { + return const HomeWidget(); + }, + ), + (route) => false, + ); + unawaited( + BillingService.instance.verifySubscription( + freeProductID, + "", + paymentProvider: "ente", + ), + ); + } + }, + child: SubscriptionPlanWidget( + storage: _currentSubscription!.storage, + price: _currentSubscription!.price, + period: _currentSubscription!.period, + isActive: true, + isOnboarding: widget.isOnboarding, ), ), ); } + + bool _isPopularPlan(BillingPlan plan) { + return popularProductIDs.contains(plan.id); + } }