From a8e33a39ff07018cdde90d234b243afab3e75360 Mon Sep 17 00:00:00 2001 From: Hillel Coren Date: Mon, 22 Feb 2021 21:48:35 +0200 Subject: [PATCH] Revert changes --- lib/ui/app/upgrade_dialog.dart | 244 ++++++++++++++++++++++++++++++++- lib/ui/auth/login_vm.dart | 57 +++++++- pubspec.lock | 83 +++++++++++ pubspec.yaml | 19 ++- 4 files changed, 399 insertions(+), 4 deletions(-) diff --git a/lib/ui/app/upgrade_dialog.dart b/lib/ui/app/upgrade_dialog.dart index cd2e12414..5be069d5a 100644 --- a/lib/ui/app/upgrade_dialog.dart +++ b/lib/ui/app/upgrade_dialog.dart @@ -1,4 +1,20 @@ +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/buttons/elevated_button.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/formatting.dart'; +import 'package:invoiceninja_flutter/utils/localization.dart'; +import 'package:url_launcher/url_launcher.dart'; class UpgradeDialog extends StatefulWidget { @override @@ -6,8 +22,234 @@ class UpgradeDialog extends StatefulWidget { } class _UpgradeDialogState extends State { + StreamSubscription> _subscription; + List _products; + List _purchases; + bool _showPastPurchases = false; + + Future loadPurchases() async { + InAppPurchaseConnection.instance + .queryPastPurchases() + .then((response) async { + if (response.pastPurchases != null && response.pastPurchases.isNotEmpty) { + setState(() { + _purchases = response.pastPurchases; + }); + } + }); + } + + Future redeemPurchase(PurchaseDetails purchase) async { + if (purchase.error != null || purchase.purchaseID == null) { + return null; + } + + //Navigator.pop(context); + + final localization = AppLocalization.of(context); + final store = StoreProvider.of(context); + final state = store.state; + final webClient = WebClient(); + final data = { + 'order_id': purchase.purchaseID, + 'product_id': purchase.productID, + 'timestamp': (int.parse(purchase.transactionDate) / 1000).floor(), + }; + + try { + final dynamic response = await webClient.post( + '$kAppProductionUrl/api/v1/upgrade', state.credentials.token, + data: json.encode(data)); + final String message = response['message']; + + if (message == 'success') { + showDialog( + context: context, + builder: (BuildContext context) { + return MessageDialog(localization.thankYouForYourPurchase, + onDismiss: () { + store.dispatch(RefreshData()); + }); + }); + + if (Platform.isIOS) { + InAppPurchaseConnection.instance.completePurchase(purchase); + } + } else { + showDialog( + context: context, + builder: (BuildContext context) { + return ErrorDialog(message); + }); + } + } catch (error) { + showDialog( + context: context, + builder: (BuildContext context) { + return ErrorDialog(error); + }); + } + } + + @override + void initState() { + super.initState(); + + final Stream purchaseUpdates = + InAppPurchaseConnection.instance.purchaseUpdatedStream; + + _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(); + } + + 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); + + await loadPurchases(); + + 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.company; + + InAppPurchaseConnection.instance.buyNonConsumable( + purchaseParam: PurchaseParam( + productDetails: productDetails, + applicationUserName: company.companyKey, + sandboxTesting: false, + )); + } + + String convertPlanToString(String plan) { + switch (plan) { + case kProductPlanPro: + return 'Pro - 1 User'; + case kProductPlanEnterprise2: + return 'Enterprise - 2 Users'; + case kProductPlanEnterprise5: + return 'Enterprise - 5 Users'; + case kProductPlanEnterprise10: + return 'Enterprise - 10 Users'; + case kProductPlanEnterprise20: + return 'Enterprise - 20 Users'; + default: + return ''; + } + } + @override Widget build(BuildContext context) { - return Container(); + final localization = AppLocalization.of(context); + + if (_products == null) { + return LoadingIndicator(height: 50); + } + + _products.sort((product1, product2) => + parseDouble(product1.price) > parseDouble(product2.price) ? 1 : -1); + + return SimpleDialog( + title: Column( + children: [ + Text(localization.annualSubscription), + if (Platform.isIOS) + Padding( + padding: const EdgeInsets.only(top: 8, bottom: 4), + child: Text( + 'Payment will be charged to iTunes Account at confirmation of purchase. Subscription automatically renews unless auto-renew is turned off at least 24-hours before the end of the current period. Account will be charged for renewal within 24-hours prior to the end of the current period, and identify the cost of the renewal. Subscriptions may be managed by the user and auto-renewal may be turned off by going to the user\'s Account Settings after purchase.', + style: TextStyle(fontSize: 12, color: Colors.grey), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + TextButton( + child: Text('Terms', style: TextStyle(fontSize: 12)), + onPressed: () => launch(kTermsOfServiceURL), + ), + TextButton( + child: Text('Privacy', style: TextStyle(fontSize: 12)), + onPressed: () => launch(kPrivacyPolicyURL), + ), + ], + ) + ], + ), + contentPadding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16), + children: [ + if (_showPastPurchases) + ..._purchases.map((purchase) => ListTile( + title: Text(purchase.purchaseID), + subtitle: Text(formatDate( + convertTimestampToDateString( + (int.parse(purchase.transactionDate) / 1000).floor()), + context)), + onTap: () => redeemPurchase(purchase), + )), + if (_purchases != null) + AppButton( + label: _showPastPurchases + ? localization.back + : localization.pastPurchases, + onPressed: () { + setState(() { + _showPastPurchases = !_showPastPurchases; + + if (_showPastPurchases) { + loadPurchases(); + } + }); + }, + ), + if (!_showPastPurchases) + ..._products + .map((productDetails) => ListTile( + title: Text(productDetails.title ?? + convertPlanToString(productDetails.id)), + subtitle: Text(productDetails.description ?? ''), + trailing: Text(productDetails.price ?? '', + style: TextStyle(fontSize: 18)), + onTap: () => upgrade(context, productDetails), + )) + .toList() + ], + ); } } diff --git a/lib/ui/auth/login_vm.dart b/lib/ui/auth/login_vm.dart index 62704cdda..50a77682f 100644 --- a/lib/ui/auth/login_vm.dart +++ b/lib/ui/auth/login_vm.dart @@ -14,6 +14,7 @@ import 'package:invoiceninja_flutter/redux/app/app_state.dart'; import 'package:invoiceninja_flutter/redux/auth/auth_actions.dart'; import 'package:invoiceninja_flutter/ui/auth/login_view.dart'; import 'package:invoiceninja_flutter/redux/auth/auth_state.dart'; +import 'package:google_sign_in/google_sign_in.dart'; class LoginScreen extends StatelessWidget { const LoginScreen({Key key}) : super(key: key); @@ -81,6 +82,15 @@ class LoginVM { final Function(BuildContext, Completer completer) onGoogleSignUpPressed; static LoginVM fromStore(Store store) { + final GoogleSignIn _googleSignIn = GoogleSignIn( + scopes: [ + 'email', + 'openid', + 'profile', + 'https://www.googleapis.com/auth/gmail.send', + ], + ); + void _handleLogin({BuildContext context, bool isSignUp = false}) { final layout = calculateLayout(context); @@ -110,9 +120,52 @@ class LoginVM { @required String url, @required String secret, @required String oneTimePassword, - }) async {}, + }) async { + try { + final account = await _googleSignIn.signIn(); + + if (account != null) { + account.authentication.then((GoogleSignInAuthentication value) { + store.dispatch(OAuthLoginRequest( + completer: completer, + idToken: value.idToken, + accessToken: value.accessToken, + serverAuthCode: value.serverAuthCode, + url: formatApiUrl(url.trim()), + secret: secret.trim(), + platform: getPlatform(context), + oneTimePassword: oneTimePassword, + )); + completer.future.then((_) => _handleLogin(context: context)); + }); + } + } catch (error) { + completer.completeError(error); + print(error); + } + }, onGoogleSignUpPressed: - (BuildContext context, Completer completer) async {}, + (BuildContext context, Completer completer) async { + try { + final account = await _googleSignIn.grantOfflineAccess(); + + if (account != null) { + account.authentication.then((GoogleSignInAuthentication value) { + store.dispatch(OAuthSignUpRequest( + completer: completer, + idToken: value.idToken, + accessToken: value.accessToken, + serverAuthCode: value.serverAuthCode, + )); + completer.future.then( + (_) => _handleLogin(context: context, isSignUp: true)); + }); + } + } catch (error) { + completer.completeError(error); + print(error); + } + }, onSignUpPressed: ( BuildContext context, Completer completer, { diff --git a/pubspec.lock b/pubspec.lock index d5855b1cd..54cea541c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -288,6 +288,55 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.6" + firebase: + dependency: transitive + description: + name: firebase + url: "https://pub.dartlang.org" + source: hosted + version: "7.3.3" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + url: "https://pub.dartlang.org" + source: hosted + version: "0.15.2" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.8" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.3+1" + firebase_core: + dependency: transitive + description: + name: firebase_core + url: "https://pub.dartlang.org" + source: hosted + version: "0.4.5" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.1+2" fixnum: dependency: transitive description: @@ -403,6 +452,33 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0" + google_sign_in: + dependency: "direct main" + description: + path: "packages/google_sign_in/google_sign_in" + ref: master + resolved-ref: b9b7e1eabc2da8e669ea1b452d310ecce454741e + url: "https://github.com/invoiceninja/plugins.git" + source: git + version: "4.5.4" + google_sign_in_platform_interface: + dependency: "direct overridden" + description: + path: "packages/google_sign_in/google_sign_in_platform_interface" + ref: HEAD + resolved-ref: b9b7e1eabc2da8e669ea1b452d310ecce454741e + url: "https://github.com/invoiceninja/plugins.git" + source: git + version: "1.1.2" + google_sign_in_web: + dependency: "direct main" + description: + path: "packages/google_sign_in/google_sign_in_web" + ref: master + resolved-ref: b9b7e1eabc2da8e669ea1b452d310ecce454741e + url: "https://github.com/invoiceninja/plugins.git" + source: git + version: "0.9.2" graphs: dependency: transitive description: @@ -466,6 +542,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.6" + in_app_purchase: + dependency: "direct main" + description: + name: in_app_purchase + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.5+1" intl: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 27569ae98..7a58bacd4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -61,10 +61,27 @@ dependencies: extended_image: 1.3.1-dev #TODO remove file_picker: ^2.1.1 draggable_scrollbar: ^0.0.4 + in_app_purchase: ^0.3.1+1 + firebase_auth: 0.15.2 #https://github.com/FirebaseExtended/flutterfire/issues/2433#issuecomment-622438185 + google_sign_in: + git: + url: https://github.com/invoiceninja/plugins.git + path: packages/google_sign_in/google_sign_in + ref: master + google_sign_in_web: + git: + url: https://github.com/invoiceninja/plugins.git + path: packages/google_sign_in/google_sign_in_web + ref: master dependency_overrides: # https://github.com/flutter/flutter/issues/70433#issuecomment-727154345 intl: ^0.17.0-nullsafety.2 + # https://github.com/flutter/flutter/issues/57712#issuecomment-703382420 + google_sign_in_platform_interface: + git: + url: https://github.com/invoiceninja/plugins.git + path: packages/google_sign_in/google_sign_in_platform_interface dev_dependencies: #flutter_driver: # TODO Re-enable @@ -83,4 +100,4 @@ flutter: assets: - assets/images/logo.png - assets/images/google-icon.png - - assets/images/payment_types/ + - assets/images/payment_types/ \ No newline at end of file