mirror of
https://github.com/GitJournal/GitJournal.git
synced 2025-06-30 03:19:11 +08:00
Implement basic IAP support for Android
It's very ugly, but it works.
This commit is contained in:
@ -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.+'
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
class Features {
|
class Features {
|
||||||
static bool perFolderConfig = false;
|
static bool perFolderConfig = false;
|
||||||
static bool purchaseProModeAvailable = false;
|
static bool purchaseProModeAvailable = true;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
14
pubspec.lock
14
pubspec.lock
@ -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:
|
||||||
|
@ -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"
|
||||||
|
Reference in New Issue
Block a user