Implement basic IAP support for Android

It's very ugly, but it works.
This commit is contained in:
Vishesh Handa
2020-04-10 12:57:58 +02:00
parent 37909c84f8
commit d724dfb8ad
7 changed files with 85 additions and 51 deletions

View File

@ -10,7 +10,7 @@ buildscript {
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:3.2.1' classpath 'com.android.tools.build:gradle:3.3.2'
classpath 'com.google.gms:google-services:3.2.1' classpath 'com.google.gms:google-services:3.2.1'
classpath 'io.fabric.tools:gradle:1.+' classpath 'io.fabric.tools:gradle:1.+'
} }

View File

@ -1,4 +1,4 @@
class Features { class Features {
static bool perFolderConfig = false; static bool perFolderConfig = false;
static bool purchaseProModeAvailable = false; static bool purchaseProModeAvailable = true;
} }

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gitjournal/core/flattened_notes_folder.dart'; import 'package:gitjournal/core/flattened_notes_folder.dart';
import 'package:gitjournal/iap.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:gitjournal/core/notes_folder_fs.dart'; import 'package:gitjournal/core/notes_folder_fs.dart';
@ -17,6 +18,9 @@ class _HomeScreenState extends State<HomeScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
InAppPurchases.confirmProPurchase();
Future.delayed(Duration.zero, () { Future.delayed(Duration.zero, () {
if (!mounted) return; if (!mounted) return;
final rootFolder = Provider.of<NotesFolderFS>(context); final rootFolder = Provider.of<NotesFolderFS>(context);

View File

@ -2,9 +2,11 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gitjournal/analytics.dart'; import 'package:gitjournal/analytics.dart';
import 'package:gitjournal/.env.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_inapp_purchase/flutter_inapp_purchase.dart'; import 'package:gitjournal/settings.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
class PurchaseScreen extends StatefulWidget { class PurchaseScreen extends StatefulWidget {
@override @override
@ -12,13 +14,7 @@ class PurchaseScreen extends StatefulWidget {
} }
class _PurchaseScreenState extends State<PurchaseScreen> { class _PurchaseScreenState extends State<PurchaseScreen> {
String _platformVersion = 'Unknown'; Offerings _offerings;
var _skus = ['sku_monthly_min'];
List<IAPItem> _iapItems = [];
StreamSubscription _purchaseUpdatedSubscription;
StreamSubscription _purchaseErrorSubscription;
StreamSubscription _conectionSubscription;
@override @override
void initState() { void initState() {
@ -27,36 +23,18 @@ class _PurchaseScreenState extends State<PurchaseScreen> {
} }
Future<void> initPlatformState() async { Future<void> initPlatformState() async {
// prepare Purchases.setDebugLogsEnabled(true);
var result = await FlutterInappPurchase.instance.initConnection; await Purchases.setup(environment['revenueCat']);
print('result: $result');
Offerings offerings = await Purchases.getOfferings();
// If the widget was removed from the tree while the asynchronous platform // If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling // message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance. // setState to update our non-existent appearance.
if (!mounted) return; if (!mounted) return;
try { setState(() {
_iapItems = await FlutterInappPurchase.instance.getSubscriptions(_skus); _offerings = offerings;
setState(() {});
print("IAP ITEMS $_iapItems");
} catch (err) {
print('getSubscriptions error: $err');
}
_conectionSubscription =
FlutterInappPurchase.connectionUpdated.listen((connected) {
print('connected: $connected');
});
_purchaseUpdatedSubscription =
FlutterInappPurchase.purchaseUpdated.listen((productItem) {
print('purchase-updated: $productItem');
});
_purchaseErrorSubscription =
FlutterInappPurchase.purchaseError.listen((purchaseError) {
print('purchase-error: $purchaseError');
}); });
} }
@ -65,10 +43,11 @@ class _PurchaseScreenState extends State<PurchaseScreen> {
var theme = Theme.of(context); var theme = Theme.of(context);
var textTheme = theme.textTheme; var textTheme = theme.textTheme;
if (_iapItems.isEmpty) { if (_offerings == null) {
return const PurchaseLoadingScreen(); return const PurchaseLoadingScreen();
} }
var iap = _iapItems[0]; var offering = _offerings.current;
var monthly = offering.monthly;
// FIXME: This screen needs to be made way way more beautiful // FIXME: This screen needs to be made way way more beautiful
// It's an extrememly important screen // It's an extrememly important screen
@ -77,12 +56,7 @@ class _PurchaseScreenState extends State<PurchaseScreen> {
children: <Widget>[ children: <Widget>[
Text('Pro Version', style: textTheme.display2), Text('Pro Version', style: textTheme.display2),
Text('Support GitJournal by going Pro', style: textTheme.subhead), Text('Support GitJournal by going Pro', style: textTheme.subhead),
RaisedButton( PurchaseButton(monthly),
child: Text('Subscribe for ${iap.localizedPrice} / month'),
onPressed: () {
FlutterInappPurchase.instance.requestSubscription(_skus[0]);
},
),
], ],
mainAxisAlignment: MainAxisAlignment.spaceAround, mainAxisAlignment: MainAxisAlignment.spaceAround,
); );
@ -104,6 +78,62 @@ class _PurchaseScreenState extends State<PurchaseScreen> {
} }
} }
class PurchaseButton extends StatelessWidget {
final Package package;
PurchaseButton(this.package);
@override
Widget build(BuildContext context) {
return RaisedButton(
child: Text('Subscribe for ${package.product.priceString} / month'),
onPressed: () async {
try {
var purchaserInfo = await Purchases.purchasePackage(package);
var isPro = purchaserInfo.entitlements.all["pro"].isActive;
if (isPro) {
Settings.instance.proMode = true;
Settings.instance.save();
// vHanda FIXME: Show some screen to indicate bought purchase?
Navigator.of(context).pop();
return;
}
} on PlatformException catch (e) {
var errorCode = PurchasesErrorHelper.getErrorCode(e);
var errorContent = "";
switch (errorCode) {
case PurchasesErrorCode.purchaseCancelledError:
errorContent = "User cancelled";
break;
case PurchasesErrorCode.purchaseNotAllowedError:
errorContent = "User not allowed to purchase";
break;
default:
errorContent = errorCode.toString();
break;
}
var dialog = AlertDialog(
title: const Text("Purchase Failed"),
content: Text(errorContent),
actions: <Widget>[
FlatButton(
child: const Text("OK"),
onPressed: () => Navigator.of(context).pop(),
),
],
);
await showDialog(context: context, builder: (context) => dialog);
}
return null;
},
);
}
}
class PurchaseLoadingScreen extends StatelessWidget { class PurchaseLoadingScreen extends StatelessWidget {
const PurchaseLoadingScreen({Key key}) : super(key: key); const PurchaseLoadingScreen({Key key}) : super(key: key);

View File

@ -80,7 +80,7 @@ class AppDrawer extends StatelessWidget {
); );
}, },
), ),
if (Features.purchaseProModeAvailable && !!Settings.instance.proMode) if (Features.purchaseProModeAvailable && !Settings.instance.proMode)
divider, divider,
_buildDrawerTile( _buildDrawerTile(
context, context,

View File

@ -207,13 +207,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.1+1" version: "2.2.1+1"
flutter_inapp_purchase:
dependency: "direct main"
description:
name: flutter_inapp_purchase
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.4"
flutter_launcher_icons: flutter_launcher_icons:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -537,6 +530,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.4.2" version: "1.4.2"
purchases_flutter:
dependency: "direct main"
description:
name: purchases_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
quiver: quiver:
dependency: transitive dependency: transitive
description: description:

View File

@ -41,7 +41,7 @@ dependencies:
font_awesome_flutter: ^8.7.0 font_awesome_flutter: ^8.7.0
sentry: ">=3.0.0 <4.0.0" sentry: ">=3.0.0 <4.0.0"
equatable: ^1.1.0 equatable: ^1.1.0
flutter_inapp_purchase: ^2.1.4 purchases_flutter: ^1.1.0
dev_dependencies: dev_dependencies:
flutter_launcher_icons: "^0.7.2" flutter_launcher_icons: "^0.7.2"