[mob][photos] Copy stripe subscription page changes to store subscription page

This commit is contained in:
ashilkn
2024-07-29 19:22:53 +05:30
parent 7d94ef0bbd
commit 2a3fe8c49f
2 changed files with 165 additions and 229 deletions

View File

@@ -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<TextStyle>(
(Set<MaterialState> 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),
),
);
}
}

View File

@@ -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<StoreSubscriptionPage> {
@override
void initState() {
super.initState();
_billingService.setIsOnSubscriptionPage(true);
_setupPurchaseUpdateStreamListener();
super.initState();
}
void _setupPurchaseUpdateStreamListener() {
@@ -162,13 +162,8 @@ class _StoreSubscriptionPageState extends State<StoreSubscriptionPage> {
_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<StoreSubscriptionPage> {
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<StoreSubscriptionPage> {
],
),
),
_getBody(),
Expanded(child: _getBody()),
],
),
);
@@ -259,6 +255,17 @@ class _StoreSubscriptionPageState extends State<StoreSubscriptionPage> {
),
);
if (hasYearlyPlans) {
widgets.add(
SubscriptionToggle(
onToggle: (p0) {
showYearlyPlan = p0;
_filterStorePlansForUi();
},
),
);
}
widgets.addAll([
Column(
mainAxisAlignment: MainAxisAlignment.center,
@@ -266,13 +273,9 @@ class _StoreSubscriptionPageState extends State<StoreSubscriptionPage> {
? _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<StoreSubscriptionPage> {
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<StoreSubscriptionPage> {
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<StoreSubscriptionPage> {
);
}
}
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<StoreSubscriptionPage> {
),
);
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<StoreSubscriptionPage> {
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<bool>>[
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<Widget> _getStripePlanWidgets() {
final List<Widget> planWidgets = [];
bool foundActivePlan = false;
@@ -483,10 +431,27 @@ class _StoreSubscriptionPageState extends State<StoreSubscriptionPage> {
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<StoreSubscriptionPage> {
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<StoreSubscriptionPage> {
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<StoreSubscriptionPage> {
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<StoreSubscriptionPage> {
}
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);
}
}