Bläddra i källkod

Handle subscription expired errors

Vishnu Mohandas 4 år sedan
förälder
incheckning
a10ad8c279

+ 3 - 0
lib/events/subscription_purchased_event.dart

@@ -0,0 +1,3 @@
+import 'package:photos/events/event.dart';
+
+class SubscriptionPurchasedEvent extends Event {}

+ 0 - 1
lib/events/user_authenticated_event.dart

@@ -1 +0,0 @@
-class UserAuthenticatedEvent {}

+ 5 - 7
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<void> 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<List<BillingPlan>> getBillingPlans() {

+ 14 - 3
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<UserAuthenticatedEvent>().listen((event) {
+    Bus.instance.on<SubscriptionPurchasedEvent>().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<bool> _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));

+ 2 - 2
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<GalleryAppBarWidget> {
     };
     widget.selectedFiles.addListener(_selectedFilesListener);
     _userAuthEventSubscription =
-        Bus.instance.on<UserAuthenticatedEvent>().listen((event) {
+        Bus.instance.on<SubscriptionPurchasedEvent>().listen((event) {
       setState(() {});
     });
     super.initState();

+ 3 - 17
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<SignInHeader> {
   @override
   void initState() {
     _userAuthEventSubscription =
-        Bus.instance.on<UserAuthenticatedEvent>().listen((event) {
+        Bus.instance.on<SubscriptionPurchasedEvent>().listen((event) {
       setState(() {});
     });
     super.initState();
@@ -43,22 +43,8 @@ class _SignInHeaderState extends State<SignInHeader> {
     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);
     }
   }
 

+ 6 - 5
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<SubscriptionPage> {
     _currentSubscription = _billingService.getSubscription();
     _hasActiveSubscription =
         _currentSubscription != null && _currentSubscription.isValid();
-    if (_hasActiveSubscription) {
+    if (_currentSubscription != null) {
       _usageFuture = _billingService.fetchUsage();
     }
 
@@ -62,7 +62,7 @@ class _SubscriptionPageState extends State<SubscriptionPage> {
               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<SubscriptionPage> {
                 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());

+ 1 - 1
lib/ui/sync_indicator.dart

@@ -126,6 +126,6 @@ class _SyncIndicatorState extends State<SyncIndicator> {
       }
     }
     // _event.status == SyncStatus.error)
-    return "upload failed";
+    return _event.reason ?? "upload failed";
   }
 }

+ 31 - 19
lib/utils/file_uploader.dart

@@ -108,14 +108,15 @@ class FileUploader {
 
   void _pollQueue() {
     if (SyncService.instance.shouldStopSync()) {
+      final uploadsToBeRemoved = List<int>();
       _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<void> _uploadURLFetchInProgress;
 
-  Future<void> _fetchUploadURLs() {
+  Future<void> _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<void>();
+      _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 {}

+ 7 - 0
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:

+ 1 - 1
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: