From a10ad8c2792ef98e3e6975e86dc901d8f2791aea Mon Sep 17 00:00:00 2001 From: Vishnu Mohandas Date: Tue, 2 Feb 2021 22:05:38 +0530 Subject: [PATCH] Handle subscription expired errors --- lib/events/subscription_purchased_event.dart | 3 ++ lib/events/user_authenticated_event.dart | 1 - lib/services/billing_service.dart | 12 ++--- lib/services/sync_service.dart | 17 +++++-- lib/ui/gallery_app_bar_widget.dart | 4 +- lib/ui/sign_in_header_widget.dart | 20 ++------ lib/ui/subscription_page.dart | 11 +++-- lib/ui/sync_indicator.dart | 2 +- lib/utils/file_uploader.dart | 50 ++++++++++++-------- pubspec.lock | 7 +++ pubspec.yaml | 2 +- 11 files changed, 73 insertions(+), 56 deletions(-) create mode 100644 lib/events/subscription_purchased_event.dart delete mode 100644 lib/events/user_authenticated_event.dart diff --git a/lib/events/subscription_purchased_event.dart b/lib/events/subscription_purchased_event.dart new file mode 100644 index 000000000..bc90b0987 --- /dev/null +++ b/lib/events/subscription_purchased_event.dart @@ -0,0 +1,3 @@ +import 'package:photos/events/event.dart'; + +class SubscriptionPurchasedEvent extends Event {} diff --git a/lib/events/user_authenticated_event.dart b/lib/events/user_authenticated_event.dart deleted file mode 100644 index 3493c9207..000000000 --- a/lib/events/user_authenticated_event.dart +++ /dev/null @@ -1 +0,0 @@ -class UserAuthenticatedEvent {} diff --git a/lib/services/billing_service.dart b/lib/services/billing_service.dart index 63aca9768..114f54e62 100644 --- a/lib/services/billing_service.dart +++ b/lib/services/billing_service.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart'; // import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:logging/logging.dart'; @@ -29,10 +30,10 @@ class BillingService { Future init() async { _prefs = await SharedPreferences.getInstance(); InAppPurchaseConnection.enablePendingPurchases(); - // if (Platform.isIOS && kDebugMode) { - // await FlutterInappPurchase.instance.initConnection; - // FlutterInappPurchase.instance.clearTransactionIOS(); - // } + if (Platform.isIOS && kDebugMode) { + await FlutterInappPurchase.instance.initConnection; + FlutterInappPurchase.instance.clearTransactionIOS(); + } InAppPurchaseConnection.instance.purchaseUpdatedStream.listen((purchases) { if (_isOnSubscriptionPage) { return; @@ -51,9 +52,6 @@ class BillingService { } } }); - if (_config.hasConfiguredAccount() && !hasActiveSubscription()) { - fetchSubscription(); - } } Future> getBillingPlans() { diff --git a/lib/services/sync_service.dart b/lib/services/sync_service.dart index 939ed364d..3c5d7885c 100644 --- a/lib/services/sync_service.dart +++ b/lib/services/sync_service.dart @@ -11,7 +11,7 @@ import 'package:photos/core/network.dart'; import 'package:photos/db/files_db.dart'; import 'package:photos/events/collection_updated_event.dart'; import 'package:photos/events/sync_status_update_event.dart'; -import 'package:photos/events/user_authenticated_event.dart'; +import 'package:photos/events/subscription_purchased_event.dart'; import 'package:photos/models/file_type.dart'; import 'package:photos/services/billing_service.dart'; import 'package:photos/services/collections_service.dart'; @@ -44,7 +44,7 @@ class SyncService { static final _diffLimit = 200; SyncService._privateConstructor() { - Bus.instance.on().listen((event) { + Bus.instance.on().listen((event) { sync(); }); @@ -94,6 +94,9 @@ class SyncService { _syncStopRequested = false; Bus.instance .fire(SyncStatusUpdate(SyncStatus.completed, wasStopped: true)); + } on NoActiveSubscriptionError { + Bus.instance.fire(SyncStatusUpdate(SyncStatus.error, + reason: "your subscription has expired")); } catch (e, s) { _logger.severe(e, s); Bus.instance.fire(SyncStatusUpdate(SyncStatus.error)); @@ -230,6 +233,12 @@ class SyncService { } Future _uploadDiff() async { + if (!BillingService.instance.hasActiveSubscription()) { + await BillingService.instance.fetchSubscription(); + if (!BillingService.instance.hasActiveSubscription()) { + throw NoActiveSubscriptionError(); + } + } final foldersToBackUp = Configuration.instance.getPathsToBackUp(); final filesToBeUploaded = await _db.getFilesToBeUploadedWithinFolders(foldersToBackUp); @@ -277,13 +286,15 @@ class SyncService { futures.add(future); } try { - await Future.wait(futures); + await Future.wait(futures, eagerError: true); } on InvalidFileError { // Do nothing } on WiFiUnavailableError { throw WiFiUnavailableError(); } on SyncStopRequestedError { throw SyncStopRequestedError(); + } on NoActiveSubscriptionError { + throw NoActiveSubscriptionError(); } catch (e, s) { _isSyncInProgress = false; Bus.instance.fire(SyncStatusUpdate(SyncStatus.error)); diff --git a/lib/ui/gallery_app_bar_widget.dart b/lib/ui/gallery_app_bar_widget.dart index d4ea9a548..422b0e404 100644 --- a/lib/ui/gallery_app_bar_widget.dart +++ b/lib/ui/gallery_app_bar_widget.dart @@ -6,7 +6,7 @@ import 'package:logging/logging.dart'; import 'package:page_transition/page_transition.dart'; import 'package:photos/core/configuration.dart'; import 'package:photos/core/event_bus.dart'; -import 'package:photos/events/user_authenticated_event.dart'; +import 'package:photos/events/subscription_purchased_event.dart'; import 'package:photos/models/collection.dart'; import 'package:photos/models/selected_files.dart'; import 'package:photos/services/collections_service.dart'; @@ -60,7 +60,7 @@ class _GalleryAppBarWidgetState extends State { }; widget.selectedFiles.addListener(_selectedFilesListener); _userAuthEventSubscription = - Bus.instance.on().listen((event) { + Bus.instance.on().listen((event) { setState(() {}); }); super.initState(); diff --git a/lib/ui/sign_in_header_widget.dart b/lib/ui/sign_in_header_widget.dart index 3266ad1eb..b6ed46e56 100644 --- a/lib/ui/sign_in_header_widget.dart +++ b/lib/ui/sign_in_header_widget.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:photos/core/configuration.dart'; import 'package:photos/core/event_bus.dart'; -import 'package:photos/events/user_authenticated_event.dart'; +import 'package:photos/events/subscription_purchased_event.dart'; import 'package:photos/services/billing_service.dart'; import 'package:photos/ui/email_entry_page.dart'; import 'package:photos/ui/password_entry_page.dart'; @@ -25,7 +25,7 @@ class _SignInHeaderState extends State { @override void initState() { _userAuthEventSubscription = - Bus.instance.on().listen((event) { + Bus.instance.on().listen((event) { setState(() {}); }); super.initState(); @@ -43,22 +43,8 @@ class _SignInHeaderState extends State { var hasSubscription = BillingService.instance.getSubscription() != null; if (hasConfiguredAccount && hasSubscription) { return Container(); - } else if (!hasConfiguredAccount) { - return _getBody(context); } else { - return FutureBuilder( - future: BillingService.instance.fetchSubscription(), - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.hasData || snapshot.hasError) { - if (BillingService.instance.hasActiveSubscription()) { - return Container(); - } - return _getBody(context); - } else { - return Container(); - } - }, - ); + return _getBody(context); } } diff --git a/lib/ui/subscription_page.dart b/lib/ui/subscription_page.dart index f22bc8e0b..282fc7faa 100644 --- a/lib/ui/subscription_page.dart +++ b/lib/ui/subscription_page.dart @@ -8,7 +8,7 @@ import 'package:flutter_inappwebview/flutter_inappwebview.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:logging/logging.dart'; import 'package:photos/core/event_bus.dart'; -import 'package:photos/events/user_authenticated_event.dart'; +import 'package:photos/events/subscription_purchased_event.dart'; import 'package:photos/models/billing_plan.dart'; import 'package:photos/models/subscription.dart'; import 'package:photos/services/billing_service.dart'; @@ -41,7 +41,7 @@ class _SubscriptionPageState extends State { _currentSubscription = _billingService.getSubscription(); _hasActiveSubscription = _currentSubscription != null && _currentSubscription.isValid(); - if (_hasActiveSubscription) { + if (_currentSubscription != null) { _usageFuture = _billingService.fetchUsage(); } @@ -62,7 +62,7 @@ class _SubscriptionPageState extends State { purchase.verificationData.serverVerificationData, ); await InAppPurchaseConnection.instance.completePurchase(purchase); - Bus.instance.fire(UserAuthenticatedEvent()); + Bus.instance.fire(SubscriptionPurchasedEvent()); final isUpgrade = _hasActiveSubscription && newSubscription.storage > _currentSubscription.storage; final isDowngrade = _hasActiveSubscription && @@ -170,9 +170,10 @@ class _SubscriptionPageState extends State { showGenericErrorDialog(context); return; } - if (Platform.isAndroid && + final isCrossGradingOnAndroid = Platform.isAndroid && _hasActiveSubscription && - _currentSubscription.productID != plan.androidID) { + _currentSubscription.productID != plan.androidID; + if (isCrossGradingOnAndroid) { final existingProductDetailsResponse = await InAppPurchaseConnection.instance.queryProductDetails( [_currentSubscription.productID].toSet()); diff --git a/lib/ui/sync_indicator.dart b/lib/ui/sync_indicator.dart index c2e289f22..0371be345 100644 --- a/lib/ui/sync_indicator.dart +++ b/lib/ui/sync_indicator.dart @@ -126,6 +126,6 @@ class _SyncIndicatorState extends State { } } // _event.status == SyncStatus.error) - return "upload failed"; + return _event.reason ?? "upload failed"; } } diff --git a/lib/utils/file_uploader.dart b/lib/utils/file_uploader.dart index 666ead94d..41385fe64 100644 --- a/lib/utils/file_uploader.dart +++ b/lib/utils/file_uploader.dart @@ -108,14 +108,15 @@ class FileUploader { void _pollQueue() { if (SyncService.instance.shouldStopSync()) { + final uploadsToBeRemoved = List(); _queue.entries .where((entry) => entry.value.status == UploadStatus.not_started) .forEach((pendingUpload) { - _queue - .remove(pendingUpload.key) - .completer - .completeError(SyncStopRequestedError()); + uploadsToBeRemoved.add(pendingUpload.key); }); + for (final id in uploadsToBeRemoved) { + _queue.remove(id).completer.completeError(SyncStopRequestedError()); + } } if (_queue.length > 0 && _currentlyUploading < _maximumConcurrentUploads) { final firstPendingEntry = _queue.entries @@ -262,8 +263,10 @@ class FileUploader { return uploadedFile; } } catch (e, s) { - _logger.severe( - "File upload failed for " + file.generatedID.toString(), e, s); + if (!(e is NoActiveSubscriptionError)) { + _logger.severe( + "File upload failed for " + file.generatedID.toString(), e, s); + } throw e; } finally { if (io.Platform.isIOS && sourceFile != null) { @@ -379,24 +382,31 @@ class FileUploader { Future _uploadURLFetchInProgress; - Future _fetchUploadURLs() { + Future _fetchUploadURLs() async { if (_uploadURLFetchInProgress == null) { - _uploadURLFetchInProgress = _dio - .get( - Configuration.instance.getHttpEndpoint() + "/files/upload-urls", - queryParameters: { - "count": 42, // m4gic number - }, - options: Options( - headers: {"X-Auth-Token": Configuration.instance.getToken()}), - ) - .then((response) { - _uploadURLFetchInProgress = null; + final completer = Completer(); + _uploadURLFetchInProgress = completer.future; + try { + final response = await _dio.get( + Configuration.instance.getHttpEndpoint() + "/files/upload-urls", + queryParameters: { + "count": 42, // m4gic number + }, + options: Options( + headers: {"X-Auth-Token": Configuration.instance.getToken()}), + ); final urls = (response.data["urls"] as List) .map((e) => UploadURL.fromMap(e)) .toList(); _uploadURLs.addAll(urls); - }); + } on DioError catch (e) { + if (e.response.statusCode == 402) { + throw NoActiveSubscriptionError(); + } + throw e; + } + _uploadURLFetchInProgress = null; + completer.complete(); } return _uploadURLFetchInProgress; } @@ -444,3 +454,5 @@ class InvalidFileError extends Error {} class WiFiUnavailableError extends Error {} class SyncStopRequestedError extends Error {} + +class NoActiveSubscriptionError extends Error {} diff --git a/pubspec.lock b/pubspec.lock index ae35ccc15..b720e62e8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -251,6 +251,13 @@ packages: relative: true source: path version: "0.7.0" + flutter_inapp_purchase: + dependency: "direct main" + description: + name: flutter_inapp_purchase + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" flutter_inappwebview: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 2420a2086..8bfcb845b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -73,7 +73,7 @@ dependencies: flutter_password_strength: ^0.1.4 flutter_inappwebview: ^4.0.0+4 background_fetch: ^0.5.1 - # flutter_inapp_purchase: ^3.0.1 + flutter_inapp_purchase: ^3.0.1 dev_dependencies: flutter_test: