diff --git a/lib/ui/app/app_drawer.dart b/lib/ui/app/app_drawer.dart index a0bc400d9..520892ea2 100644 --- a/lib/ui/app/app_drawer.dart +++ b/lib/ui/app/app_drawer.dart @@ -1,10 +1,7 @@ -import 'dart:async'; -import 'package:in_app_purchase/in_app_purchase.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:invoiceninja_flutter/ui/app/dialogs/error_dialog.dart'; -import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart'; import 'package:invoiceninja_flutter/ui/app/resources/cached_image.dart'; +import 'package:invoiceninja_flutter/ui/app/upgrade_dialog.dart'; import 'package:redux/redux.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:invoiceninja_flutter/constants.dart'; @@ -500,103 +497,4 @@ class SidebarFooter extends StatelessWidget { ), ); } -} - -class UpgradeDialog extends StatefulWidget { - @override - _UpgradeDialogState createState() => _UpgradeDialogState(); -} - -class _UpgradeDialogState extends State { - StreamSubscription> _subscription; - List products; - - @override - void initState() { - final Stream purchaseUpdates = - InAppPurchaseConnection.instance.purchaseUpdatedStream; - - _subscription = purchaseUpdates.listen((dynamic purchases) { - showDialog( - context: context, - builder: (BuildContext context) { - return ErrorDialog('PURCHASE STREAM UPDATE: $purchases'); - }); - }, onDone: () { - _subscription.cancel(); - _subscription = null; - }, onError: (dynamic error) { - showDialog( - context: context, - builder: (BuildContext context) { - return ErrorDialog('PURCHASE STREAM ERROR: $error'); - }); - }); - - initStore(); - - super.initState(); - } - - void initStore() async { - final bool available = await InAppPurchaseConnection.instance.isAvailable(); - - if (!available) { - showDialog( - context: context, - builder: (BuildContext context) { - return ErrorDialog('Store is not available'); - }); - return; - } - - final productIds = Set.from(kProductPlans); - final ProductDetailsResponse response = - await InAppPurchaseConnection.instance.queryProductDetails(productIds); - - setState(() { - products = response.productDetails; - }); - } - - @override - void dispose() { - _subscription.cancel(); - super.dispose(); - } - - void upgrade(BuildContext context, ProductDetails productDetails) { - final store = StoreProvider.of(context); - final company = store.state.selectedCompany; - - InAppPurchaseConnection.instance.buyNonConsumable( - purchaseParam: PurchaseParam( - productDetails: productDetails, - applicationUserName: company.companyKey, - sandboxTesting: false, - )); - } - - @override - Widget build(BuildContext context) { - final localization = AppLocalization.of(context); - - if (products == null) { - return LoadingIndicator(height: 50); - } - - return SimpleDialog( - title: Text(localization.annualSubscription), - contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 15), - children: products.reversed - .map((productDetails) => ListTile( - title: Text(productDetails.title), - subtitle: Text(productDetails.description), - trailing: - Text(productDetails.price, style: TextStyle(fontSize: 18)), - onTap: () => upgrade(context, productDetails), - )) - .toList(), - ); - } -} +} \ No newline at end of file diff --git a/lib/ui/app/dialogs/alert_dialog.dart b/lib/ui/app/dialogs/alert_dialog.dart index b664ac15b..fbf320c79 100644 --- a/lib/ui/app/dialogs/alert_dialog.dart +++ b/lib/ui/app/dialogs/alert_dialog.dart @@ -3,9 +3,10 @@ import 'package:invoiceninja_flutter/ui/app/buttons/elevated_button.dart'; import 'package:invoiceninja_flutter/utils/localization.dart'; class MessageDialog extends StatelessWidget { - const MessageDialog(this.message); + const MessageDialog(this.message, {this.onDismiss}); final String message; + final Function onDismiss; @override Widget build(BuildContext context) { @@ -32,7 +33,12 @@ class MessageDialog extends StatelessWidget { ), SizedBox(height: 40.0), ElevatedButton( - onPressed: () => Navigator.of(context).pop(), + onPressed: () { + Navigator.of(context).pop(); + if (onDismiss != null) { + onDismiss(); + } + }, label: localization.dismiss, ), ], diff --git a/lib/ui/app/upgrade_dialog.dart b/lib/ui/app/upgrade_dialog.dart new file mode 100644 index 000000000..fe2e4c1fd --- /dev/null +++ b/lib/ui/app/upgrade_dialog.dart @@ -0,0 +1,156 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'package:flutter_redux/flutter_redux.dart'; +import 'package:in_app_purchase/in_app_purchase.dart'; +import 'package:invoiceninja_flutter/constants.dart'; +import 'package:invoiceninja_flutter/data/web_client.dart'; +import 'package:invoiceninja_flutter/redux/app/app_actions.dart'; +import 'package:invoiceninja_flutter/redux/app/app_state.dart'; +import 'package:invoiceninja_flutter/ui/app/dialogs/alert_dialog.dart'; +import 'package:invoiceninja_flutter/ui/app/dialogs/error_dialog.dart'; +import 'package:invoiceninja_flutter/ui/app/loading_indicator.dart'; +import 'package:flutter/material.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; +import 'package:invoiceninja_flutter/utils/platforms.dart'; + +class UpgradeDialog extends StatefulWidget { + @override + _UpgradeDialogState createState() => _UpgradeDialogState(); +} + +class _UpgradeDialogState extends State { + StreamSubscription> _subscription; + List products; + + Future redeemPurchase(PurchaseDetails purchase) async { + print('redeemPurchase: ${purchase.purchaseID}'); + if (purchase.error != null) { + return null; + } + + final store = StoreProvider.of(context); + final company = store.state.selectedCompany; + final webClient = WebClient(); + final data = { + 'order_id': purchase.purchaseID, + 'product_id': purchase.productID, + 'timestamp': purchase.transactionDate, + }; + + final String response = + await webClient.post('/api/v1/upgrade', company.token, json.encode(data)); + + print('response: $response'); + + if (response == 'success') { + showDialog( + context: context, + builder: (BuildContext context) { + return MessageDialog(response, onDismiss: () { + store.dispatch(RefreshData( + platform: getPlatform(context), + )); + }); + }); + } + } + + @override + void initState() { + final Stream purchaseUpdates = + InAppPurchaseConnection.instance.purchaseUpdatedStream; + + InAppPurchaseConnection.instance + .queryPastPurchases() + .then((response) async { + for (PurchaseDetails purchase in response.pastPurchases) { + await redeemPurchase(purchase); + if (Platform.isIOS) { + InAppPurchaseConnection.instance.completePurchase(purchase); + } + } + }); + + _subscription = purchaseUpdates.listen((dynamic purchases) { + (purchases as List).forEach((purchase) async { + await redeemPurchase(purchase); + }); + }, onDone: () { + _subscription.cancel(); + _subscription = null; + }, onError: (dynamic error) { + showDialog( + context: context, + builder: (BuildContext context) { + return ErrorDialog(error); + }); + }); + + initStore(); + + super.initState(); + } + + void initStore() async { + final bool available = await InAppPurchaseConnection.instance.isAvailable(); + + if (!available) { + showDialog( + context: context, + builder: (BuildContext context) { + return ErrorDialog('Store is not available'); + }); + return; + } + + final productIds = Set.from(kProductPlans); + final ProductDetailsResponse response = + await InAppPurchaseConnection.instance.queryProductDetails(productIds); + + setState(() { + products = response.productDetails; + }); + } + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } + + void upgrade(BuildContext context, ProductDetails productDetails) { + final store = StoreProvider.of(context); + final company = store.state.selectedCompany; + + InAppPurchaseConnection.instance.buyNonConsumable( + purchaseParam: PurchaseParam( + productDetails: productDetails, + applicationUserName: company.companyKey, + sandboxTesting: false, + )); + } + + @override + Widget build(BuildContext context) { + final localization = AppLocalization.of(context); + + if (products == null) { + return LoadingIndicator(height: 50); + } + + return SimpleDialog( + title: Text(localization.annualSubscription), + contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 15), + children: products.reversed + .map((productDetails) => ListTile( + title: Text(productDetails.title), + subtitle: Text(productDetails.description), + trailing: + Text(productDetails.price, style: TextStyle(fontSize: 18)), + onTap: () => upgrade(context, productDetails), + )) + .toList(), + ); + } +}