Handle subscription expired errors
This commit is contained in:
parent
0ab2fbd755
commit
a10ad8c279
11 changed files with 73 additions and 56 deletions
3
lib/events/subscription_purchased_event.dart
Normal file
3
lib/events/subscription_purchased_event.dart
Normal file
|
@ -0,0 +1,3 @@
|
|||
import 'package:photos/events/event.dart';
|
||||
|
||||
class SubscriptionPurchasedEvent extends Event {}
|
|
@ -1 +0,0 @@
|
|||
class UserAuthenticatedEvent {}
|
|
@ -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() {
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -126,6 +126,6 @@ class _SyncIndicatorState extends State<SyncIndicator> {
|
|||
}
|
||||
}
|
||||
// _event.status == SyncStatus.error)
|
||||
return "upload failed";
|
||||
return _event.reason ?? "upload failed";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Add table
Reference in a new issue