diff --git a/lib/main.dart b/lib/main.dart index 8fe1712c5..87b78c2cd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -42,7 +42,11 @@ void main() async { /// Then where ever you need language functions simply call PangeaLanguage pangeaLanguage = PangeaLanguage() /// pangeaLanguage.getList or whatever function you need /// - await GetStorage.init(); + final List initFutures = [ + GetStorage.init(), + GetStorage.init("subscription_storage"), + ]; + await Future.wait(initFutures); // Pangea# // Our background push shared isolate accesses flutter-internal things very early in the startup proccess diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index b6ae1c447..c5020e9ea 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -19,7 +19,6 @@ import 'package:fluffychat/pangea/choreographer/widgets/igc/paywall_card.dart'; import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/common/utils/any_state_holder.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; -import 'package:fluffychat/pangea/common/utils/overlay.dart'; import 'package:fluffychat/pangea/events/models/representation_content_model.dart'; import 'package:fluffychat/pangea/events/models/tokens_event_content_model.dart'; import 'package:fluffychat/pangea/events/repo/token_api_models.dart'; @@ -107,15 +106,7 @@ class Choreographer { // show the paywall if applicable or just send the message final status = pangeaController.subscriptionController.subscriptionStatus; status == SubscriptionStatus.shouldShowPaywall - ? OverlayUtil.showPositionedCard( - context: context, - cardToShow: PaywallCard( - chatController: chatController, - ), - maxHeight: 325, - maxWidth: 325, - transformTargetId: inputTransformTargetKey, - ) + ? PaywallCard.show(context, chatController) : chatController.send( message: chatController.sendController.text, ); diff --git a/lib/pangea/choreographer/widgets/igc/pangea_text_controller.dart b/lib/pangea/choreographer/widgets/igc/pangea_text_controller.dart index 98384a32a..30f612a0b 100644 --- a/lib/pangea/choreographer/widgets/igc/pangea_text_controller.dart +++ b/lib/pangea/choreographer/widgets/igc/pangea_text_controller.dart @@ -50,15 +50,8 @@ class PangeaTextController extends TextEditingController { SubscriptionStatus.shouldShowPaywall && !choreographer.isFetching && text.isNotEmpty) { - OverlayUtil.showPositionedCard( - context: context, - cardToShow: PaywallCard( - chatController: choreographer.chatController, - ), - maxHeight: 325, - maxWidth: 325, - transformTargetId: choreographer.inputTransformTargetKey, - ); + PaywallCard.show(context, choreographer.chatController); + return; } // if there is no igc text data, then don't do anything diff --git a/lib/pangea/choreographer/widgets/igc/paywall_card.dart b/lib/pangea/choreographer/widgets/igc/paywall_card.dart index 86328f224..0f534b7ac 100644 --- a/lib/pangea/choreographer/widgets/igc/paywall_card.dart +++ b/lib/pangea/choreographer/widgets/igc/paywall_card.dart @@ -5,6 +5,8 @@ import 'package:fluffychat/pages/chat/chat.dart'; import 'package:fluffychat/pangea/bot/utils/bot_style.dart'; import 'package:fluffychat/pangea/bot/widgets/bot_face_svg.dart'; import 'package:fluffychat/pangea/choreographer/widgets/igc/card_header.dart'; +import 'package:fluffychat/pangea/common/utils/overlay.dart'; +import 'package:fluffychat/pangea/subscription/repo/subscription_management_repo.dart'; import 'package:fluffychat/widgets/matrix.dart'; class PaywallCard extends StatelessWidget { @@ -14,6 +16,27 @@ class PaywallCard extends StatelessWidget { required this.chatController, }); + static Future show( + BuildContext context, + ChatController chatController, + ) async { + if (!MatrixState + .pangeaController.subscriptionController.shouldShowPaywall) { + return; + } + + await SubscriptionManagementRepo.setDismissedPaywall(); + OverlayUtil.showPositionedCard( + context: context, + cardToShow: PaywallCard( + chatController: chatController, + ), + maxHeight: 325, + maxWidth: 325, + transformTargetId: chatController.choreographer.inputTransformTargetKey, + ); + } + @override Widget build(BuildContext context) { final bool inTrialWindow = @@ -26,10 +49,6 @@ class PaywallCard extends StatelessWidget { CardHeader( text: L10n.of(context).clickMessageTitle, botExpression: BotExpression.addled, - onClose: () { - MatrixState.pangeaController.subscriptionController - .dismissPaywall(); - }, ), Padding( padding: const EdgeInsets.all(8), diff --git a/lib/pangea/choreographer/widgets/start_igc_button.dart b/lib/pangea/choreographer/widgets/start_igc_button.dart index ceef200c5..698801e1f 100644 --- a/lib/pangea/choreographer/widgets/start_igc_button.dart +++ b/lib/pangea/choreographer/widgets/start_igc_button.dart @@ -7,7 +7,6 @@ import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/l10n/l10n.dart'; import 'package:fluffychat/pangea/choreographer/enums/assistance_state_enum.dart'; import 'package:fluffychat/pangea/choreographer/widgets/igc/paywall_card.dart'; -import 'package:fluffychat/pangea/common/utils/overlay.dart'; import 'package:fluffychat/pangea/learning_settings/pages/settings_learning.dart'; import '../../../pages/chat/chat.dart'; @@ -81,16 +80,7 @@ class StartIGCButtonState extends State Future _onTap() async { switch (assistanceState) { case AssistanceState.noSub: - OverlayUtil.showPositionedCard( - context: context, - cardToShow: PaywallCard( - chatController: widget.controller, - ), - maxHeight: 325, - maxWidth: 325, - transformTargetId: - widget.controller.choreographer.inputTransformTargetKey, - ); + await PaywallCard.show(context, widget.controller); return; case AssistanceState.noMessage: showDialog( diff --git a/lib/pangea/subscription/controllers/subscription_controller.dart b/lib/pangea/subscription/controllers/subscription_controller.dart index 395a1c07f..cca7bf4e0 100644 --- a/lib/pangea/subscription/controllers/subscription_controller.dart +++ b/lib/pangea/subscription/controllers/subscription_controller.dart @@ -5,7 +5,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; -import 'package:get_storage/get_storage.dart'; import 'package:http/http.dart'; import 'package:purchases_flutter/purchases_flutter.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -13,7 +12,6 @@ import 'package:url_launcher/url_launcher_string.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/constants/local.key.dart'; import 'package:fluffychat/pangea/common/controllers/base_controller.dart'; import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/common/network/requests.dart'; @@ -23,6 +21,7 @@ import 'package:fluffychat/pangea/common/utils/firebase_analytics.dart'; import 'package:fluffychat/pangea/subscription/models/base_subscription_info.dart'; import 'package:fluffychat/pangea/subscription/models/mobile_subscriptions.dart'; import 'package:fluffychat/pangea/subscription/models/web_subscriptions.dart'; +import 'package:fluffychat/pangea/subscription/repo/subscription_management_repo.dart'; import 'package:fluffychat/pangea/subscription/repo/subscription_repo.dart'; import 'package:fluffychat/pangea/subscription/utils/subscription_app_id.dart'; import 'package:fluffychat/pangea/subscription/widgets/subscription_paywall.dart'; @@ -37,7 +36,6 @@ enum SubscriptionStatus { } class SubscriptionController extends BaseController { - static final GetStorage subscriptionBox = GetStorage("subscription_storage"); late PangeaController _pangeaController; CurrentSubscriptionInfo? currentSubscriptionInfo; @@ -129,12 +127,8 @@ class SubscriptionController extends BaseController { }, ); } else { - final bool? beganWebPayment = - subscriptionBox.read(PLocalKey.beganWebPayment); - if (beganWebPayment ?? false) { - await subscriptionBox.remove( - PLocalKey.beganWebPayment, - ); + if (SubscriptionManagementRepo.getBeganWebPayment()) { + await SubscriptionManagementRepo.removeBeganWebPayment(); if (isSubscribed != null && isSubscribed!) { subscriptionStream.add(true); } @@ -202,10 +196,7 @@ class SubscriptionController extends BaseController { selectedSubscription.duration!, isPromo: isPromo, ); - await subscriptionBox.write( - PLocalKey.beganWebPayment, - true, - ); + await SubscriptionManagementRepo.setBeganWebPayment(); setState(null); launchUrlString( paymentLink, @@ -255,54 +246,16 @@ class SubscriptionController extends BaseController { return isSubscribed! ? SubscriptionStatus.subscribed - : _shouldShowPaywall + : shouldShowPaywall ? SubscriptionStatus.shouldShowPaywall : SubscriptionStatus.dimissedPaywall; } - DateTime? get _lastDismissedPaywall { - final lastDismissed = subscriptionBox.read( - PLocalKey.dismissedPaywall, - ); - if (lastDismissed == null) return null; - return DateTime.tryParse(lastDismissed); - } - - int? get _paywallBackoff { - final backoff = subscriptionBox.read( - PLocalKey.paywallBackoff, - ); - if (backoff == null) return null; - return backoff; - } - /// whether or not the paywall should be shown - bool get _shouldShowPaywall { + bool get shouldShowPaywall { return initCompleter.isCompleted && - isSubscribed != null && - !isSubscribed! && - (_lastDismissedPaywall == null || - DateTime.now().difference(_lastDismissedPaywall!).inHours > - (1 * (_paywallBackoff ?? 1))); - } - - void dismissPaywall() async { - await subscriptionBox.write( - PLocalKey.dismissedPaywall, - DateTime.now().toString(), - ); - - if (_paywallBackoff == null) { - await subscriptionBox.write( - PLocalKey.paywallBackoff, - 1, - ); - } else { - await subscriptionBox.write( - PLocalKey.paywallBackoff, - _paywallBackoff! + 1, - ); - } + isSubscribed == false && + !SubscriptionManagementRepo.getDismissedPaywall(); } Future showPaywall(BuildContext context) async { @@ -330,7 +283,7 @@ class SubscriptionController extends BaseController { ); }, ); - dismissPaywall(); + await SubscriptionManagementRepo.setDismissedPaywall(); } catch (e, s) { ErrorHandler.logError( e: e, @@ -369,37 +322,6 @@ class SubscriptionController extends BaseController { String? get defaultManagementURL => currentSubscriptionInfo?.currentSubscription ?.defaultManagementURL(availableSubscriptionInfo?.appIds); - - Future setCachedSubscriptionInfo( - AvailableSubscriptionsInfo info, - ) => - subscriptionBox.write( - PLocalKey.availableSubscriptionInfo, - info.toJson(), - ); - - Future getCachedSubscriptionInfo() async { - final entry = subscriptionBox.read( - PLocalKey.availableSubscriptionInfo, - ); - if (entry is! Map) { - return null; - } - - try { - final resp = AvailableSubscriptionsInfo.fromJson(entry); - return resp.lastUpdated == null ? null : resp; - } catch (e, s) { - ErrorHandler.logError( - e: e, - s: s, - data: { - "entry": entry, - }, - ); - return null; - } - } } enum SubscriptionDuration { diff --git a/lib/pangea/subscription/models/base_subscription_info.dart b/lib/pangea/subscription/models/base_subscription_info.dart index 208df6339..a9ae276a0 100644 --- a/lib/pangea/subscription/models/base_subscription_info.dart +++ b/lib/pangea/subscription/models/base_subscription_info.dart @@ -1,10 +1,9 @@ import 'package:collection/collection.dart'; -import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/subscription/controllers/subscription_controller.dart'; +import 'package:fluffychat/pangea/subscription/repo/subscription_management_repo.dart'; import 'package:fluffychat/pangea/subscription/repo/subscription_repo.dart'; import 'package:fluffychat/pangea/subscription/utils/subscription_app_id.dart'; -import 'package:fluffychat/widgets/matrix.dart'; /// Contains information about the users's current subscription class CurrentSubscriptionInfo { @@ -78,13 +77,16 @@ class AvailableSubscriptionsInfo { }); Future setAvailableSubscriptions() async { - final cachedInfo = await MatrixState.pangeaController.subscriptionController - .getCachedSubscriptionInfo(); + final cachedInfo = + SubscriptionManagementRepo.getAvailableSubscriptionsInfo(); + appIds ??= cachedInfo?.appIds ?? await SubscriptionRepo.getAppIds(); allProducts ??= cachedInfo?.allProducts ?? await SubscriptionRepo.getAllProducts(); - if (cachedInfo == null) await _cacheSubscriptionInfo(); + if (cachedInfo == null) { + await SubscriptionManagementRepo.setAvailableSubscriptionsInfo(this); + } availableSubscriptions = (allProducts ?? []) .where( @@ -95,22 +97,6 @@ class AvailableSubscriptionsInfo { .toList(); } - Future _cacheSubscriptionInfo() async { - try { - MatrixState.pangeaController.subscriptionController - .setCachedSubscriptionInfo(this); - } catch (e, s) { - ErrorHandler.logError( - e: e, - s: s, - data: { - "appIds": appIds, - "allProducts": allProducts, - }, - ); - } - } - factory AvailableSubscriptionsInfo.fromJson(Map json) { if (!json.containsKey('app_ids') || !json.containsKey('all_products')) { throw "Cached subscription info is missing required fields"; diff --git a/lib/pangea/subscription/repo/subscription_management_repo.dart b/lib/pangea/subscription/repo/subscription_management_repo.dart new file mode 100644 index 000000000..c25d572d3 --- /dev/null +++ b/lib/pangea/subscription/repo/subscription_management_repo.dart @@ -0,0 +1,72 @@ +import 'package:get_storage/get_storage.dart'; + +import 'package:fluffychat/pangea/common/constants/local.key.dart'; +import 'package:fluffychat/pangea/subscription/models/base_subscription_info.dart'; + +class SubscriptionManagementRepo { + static final GetStorage _cache = GetStorage("subscription_storage"); + + static AvailableSubscriptionsInfo? getAvailableSubscriptionsInfo() { + final entry = _cache.read(PLocalKey.availableSubscriptionInfo); + if (entry == null) return null; + try { + return AvailableSubscriptionsInfo.fromJson(entry); + } catch (e) { + _cache.remove(PLocalKey.availableSubscriptionInfo); + return null; + } + } + + static Future setAvailableSubscriptionsInfo( + AvailableSubscriptionsInfo info, + ) async { + await _cache.write( + PLocalKey.availableSubscriptionInfo, + info.toJson(), + ); + } + + static bool getBeganWebPayment() { + return _cache.read(PLocalKey.beganWebPayment) ?? false; + } + + static Future setBeganWebPayment() async { + await _cache.write(PLocalKey.beganWebPayment, true); + } + + static Future removeBeganWebPayment() async { + await _cache.remove(PLocalKey.beganWebPayment); + } + + static bool getDismissedPaywall() { + final entry = _cache.read(PLocalKey.dismissedPaywall); + if (entry == null) return false; + try { + final dismissed = DateTime.parse(entry); + final nextValidShowtime = dismissed.add( + Duration(hours: 1 * _getPaywallBackoff()), + ); + return DateTime.now().isBefore(nextValidShowtime); + } catch (e) { + _cache.remove(PLocalKey.dismissedPaywall); + return false; + } + } + + static Future setDismissedPaywall() async { + await _cache.write( + PLocalKey.dismissedPaywall, + DateTime.now().toIso8601String(), + ); + await _incrementPaywallBackoff(); + } + + static int _getPaywallBackoff() { + return _cache.read(PLocalKey.paywallBackoff) ?? 0; + } + + static Future _incrementPaywallBackoff() async { + final int backoff = _getPaywallBackoff() + 1; + await _cache.write(PLocalKey.paywallBackoff, backoff); + } +}