fluffychat/lib/pangea/subscription/pages/settings_subscription.dart

272 lines
8.8 KiB
Dart

import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/l10n/l10n.dart';
import 'package:fluffychat/pangea/common/config/environment.dart';
import 'package:fluffychat/pangea/common/utils/error_handler.dart';
import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.dart';
import 'package:fluffychat/pangea/subscription/pages/settings_subscription_view.dart';
import 'package:fluffychat/pangea/subscription/repo/subscription_management_repo.dart';
import 'package:fluffychat/pangea/subscription/utils/subscription_app_id.dart';
import 'package:fluffychat/pangea/subscription/widgets/subscription_snackbar.dart';
import 'package:fluffychat/widgets/matrix.dart';
class SubscriptionManagement extends StatefulWidget {
const SubscriptionManagement({super.key});
@override
SubscriptionManagementController createState() =>
SubscriptionManagementController();
}
class SubscriptionManagementController extends State<SubscriptionManagement>
with WidgetsBindingObserver {
final SubscriptionController subscriptionController =
MatrixState.pangeaController.subscriptionController;
SubscriptionDetails? selectedSubscription;
bool loading = false;
String? userEmail;
@override
void initState() {
WidgetsBinding.instance.addObserver(this);
_refreshSubscription();
if (!subscriptionController.initCompleter.isCompleted) {
subscriptionController.initialize().then((_) => setState(() {}));
}
subscriptionController.addListener(_onSubscriptionUpdate);
subscriptionController.subscriptionNotifier.addListener(_onSubscribe);
subscriptionController.updateCustomerInfo();
MatrixState.pangeaController.userController.userEmail.then((email) {
if (mounted) {
setState(() => userEmail = email);
}
});
super.initState();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
subscriptionController.subscriptionNotifier.removeListener(_onSubscribe);
subscriptionController.removeListener(_onSubscriptionUpdate);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_refreshSubscription();
}
super.didChangeAppLifecycleState(state);
}
bool get subscriptionsAvailable =>
subscriptionController
.availableSubscriptionInfo
?.availableSubscriptions
.isNotEmpty ??
false;
bool get currentSubscriptionAvailable =>
subscriptionController.isSubscribed != null &&
subscriptionController.isSubscribed! &&
subscriptionController.currentSubscriptionInfo?.currentSubscription !=
null;
bool get currentSubscriptionIsTrial =>
currentSubscriptionAvailable &&
(subscriptionController
.currentSubscriptionInfo
?.currentSubscription
?.isTrial ??
false);
String? get purchasePlatformDisplayName => subscriptionController
.currentSubscriptionInfo
?.purchasePlatformDisplayName;
bool get currentSubscriptionIsPromotional =>
subscriptionController
.currentSubscriptionInfo
?.currentSubscriptionIsPromotional ??
false;
String get currentSubscriptionTitle =>
subscriptionController.currentSubscriptionInfo?.currentSubscription
?.displayName(context) ??
"";
String get currentSubscriptionPrice =>
subscriptionController.currentSubscriptionInfo?.currentSubscription
?.displayPrice(context) ??
"";
bool get showManagementOptions {
if (!currentSubscriptionAvailable) {
return false;
}
if (subscriptionController.currentSubscriptionInfo!.purchasedOnWeb) {
return true;
}
return subscriptionController
.currentSubscriptionInfo!
.currentPlatformMatchesPurchasePlatform;
}
DateTime? get expirationDate =>
subscriptionController.currentSubscriptionInfo?.expirationDate;
DateTime? get subscriptionEndDate =>
subscriptionController.currentSubscriptionInfo?.subscriptionEndDate;
void _onSubscriptionUpdate() => setState(() {});
void _onSubscribe() => showSubscribedSnackbar(context);
Future<void> _refreshSubscription() async {
if (!kIsWeb) return;
// if the user previously clicked cancel, check if the subscription end date has changed
final prevEndDate = SubscriptionManagementRepo.getSubscriptionEndDate();
final clickedCancel =
SubscriptionManagementRepo.getClickedCancelSubscription();
if (clickedCancel == null) return;
await subscriptionController.reinitialize();
final newEndDate =
subscriptionController.currentSubscriptionInfo?.subscriptionEndDate;
if (prevEndDate != newEndDate) {
SubscriptionManagementRepo.removeClickedCancelSubscription();
SubscriptionManagementRepo.setSubscriptionEndDate(newEndDate);
if (mounted) setState(() {});
return;
}
// if more than 10 minutes have passed since the user clicked cancel, remove the click flag
if (DateTime.now().difference(clickedCancel).inMinutes >= 10) {
SubscriptionManagementRepo.removeClickedCancelSubscription();
if (mounted) setState(() {});
}
}
Future<void> submitChange(
SubscriptionDetails subscription, {
bool isPromo = false,
}) async {
setState(() => loading = true);
try {
await subscriptionController.submitSubscriptionChange(
subscription,
context,
isPromo: isPromo,
);
} catch (e, s) {
ErrorHandler.logError(
e: e,
s: s,
data: {"subscription_id": subscription.id, "is_promo": isPromo},
);
} finally {
if (mounted) setState(() => loading = false);
}
}
Future<void> onClickCancelSubscription() async {
final uri = await launchMangementUrl(ManagementOption.cancel);
if (uri != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
showCloseIcon: true,
duration: const Duration(seconds: 30),
content: Row(
children: [
Expanded(child: Text(L10n.of(context).managementSnackbarMessage)),
TextButton(
child: Text(
L10n.of(context).tryAgain,
style: TextStyle(
color: Theme.of(context).colorScheme.primaryContainer,
),
),
onPressed: () {
launchUrl(uri, mode: LaunchMode.externalApplication);
},
),
],
),
),
);
}
await SubscriptionManagementRepo.setClickedCancelSubscription();
await SubscriptionManagementRepo.setSubscriptionEndDate(
subscriptionEndDate,
);
if (mounted) setState(() {});
}
Future<Uri?> launchMangementUrl(ManagementOption option) async {
String managementUrl = Environment.stripeManagementUrl;
if (userEmail != null) {
managementUrl += "?prefilled_email=${Uri.encodeComponent(userEmail!)}";
}
final String? purchaseAppId = subscriptionController
.currentSubscriptionInfo
?.currentSubscription
?.appId;
if (purchaseAppId == null) return null;
final SubscriptionAppIds? appIds =
subscriptionController.availableSubscriptionInfo!.appIds;
if (purchaseAppId == appIds?.stripeId) {
final uri = Uri.parse(managementUrl);
launchUrl(uri, mode: LaunchMode.externalApplication);
return uri;
}
if (purchaseAppId == appIds?.appleId) {
final uri = Uri.parse(AppConfig.appleMangementUrl);
launchUrl(uri, mode: LaunchMode.externalApplication);
return uri;
}
switch (option) {
case ManagementOption.history:
final uri = Uri.parse(AppConfig.googlePlayHistoryUrl);
launchUrl(uri, mode: LaunchMode.externalApplication);
return uri;
case ManagementOption.paymentMethod:
final uri = Uri.parse(AppConfig.googlePlayPaymentMethodUrl);
launchUrl(uri, mode: LaunchMode.externalApplication);
return uri;
default:
final uri = Uri.parse(AppConfig.googlePlayMangementUrl);
launchUrl(uri, mode: LaunchMode.externalApplication);
return uri;
}
}
void selectSubscription(SubscriptionDetails? subscription) {
if (selectedSubscription == subscription) {
setState(() => selectedSubscription = null);
return;
}
setState(() => selectedSubscription = subscription);
}
bool isCurrentSubscription(SubscriptionDetails subscription) =>
subscriptionController.currentSubscriptionInfo?.currentSubscription ==
subscription;
@override
Widget build(BuildContext context) => SettingsSubscriptionView(this);
}
enum ManagementOption { cancel, paymentMethod, history }