Created
July 29, 2019 09:52
-
-
Save mestartlearncode/1ca0bdf7d25a10ecfeb54b0c380478de to your computer and use it in GitHub Desktop.
Mostly taken from https://github.com/flutter/plugins/blob/master/packages/in_app_purchase/example/lib/main.dart, i just change class name
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'package:flutter/material.dart'; | |
import 'package:pigment/pigment.dart'; | |
import 'package:prj035apps/src/blocs/auth/forgot_password_bloc.dart'; | |
import 'package:prj035apps/src/resources/index.dart'; | |
import 'package:prj035apps/src/translations.dart'; | |
import 'package:prj035apps/src/ui/widgets/custom_textfield.dart'; | |
import 'package:prj035apps/src/ui/widgets/loading.dart'; | |
import 'package:prj035apps/src/blocs/analytics/analytics_firebase.dart'; | |
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | |
import 'package:superellipse_shape/superellipse_shape.dart'; | |
import 'package:prj035apps/src/blocs/admob/admob_bloc.dart'; | |
import 'package:firebase_admob/firebase_admob.dart'; | |
import 'dart:async'; | |
import 'dart:io'; | |
import 'package:in_app_purchase/in_app_purchase.dart'; | |
import 'consumable_store.dart'; | |
const bool kAutoConsume = true; | |
const String _kConsumableId = 'consumable'; | |
const List<String> _kProductIds = <String>[ | |
_kConsumableId, | |
'upgrade', | |
'subscription' | |
]; | |
class PermanentSubscriberScreen extends StatefulWidget { | |
@override | |
_PermanentSubscriberScreenState createState() => _PermanentSubscriberScreenState(); | |
} | |
class _PermanentSubscriberScreenState extends State<PermanentSubscriberScreen>{ | |
final InAppPurchaseConnection _connection = InAppPurchaseConnection.instance; | |
StreamSubscription<List<PurchaseDetails>> _subscription; | |
List<String> _notFoundIds = []; | |
List<ProductDetails> _products = []; | |
List<PurchaseDetails> _purchases = []; | |
List<String> _consumables = []; | |
bool _isAvailable = false; | |
bool _purchasePending = false; | |
bool _loading = true; | |
String _queryProductError = null; | |
@override | |
void initState() { | |
Stream purchaseUpdated = | |
InAppPurchaseConnection.instance.purchaseUpdatedStream; | |
_subscription = purchaseUpdated.listen((purchaseDetailsList) { | |
_listenToPurchaseUpdated(purchaseDetailsList); | |
}, onDone: () { | |
_subscription.cancel(); | |
}, onError: (error) { | |
// handle error here. | |
}); | |
initStoreInfo(); | |
super.initState(); | |
} | |
Future<void> initStoreInfo() async { | |
final bool isAvailable = await _connection.isAvailable(); | |
if (!isAvailable) { | |
setState(() { | |
_isAvailable = isAvailable; | |
_products = []; | |
_purchases = []; | |
_notFoundIds = []; | |
_consumables = []; | |
_purchasePending = false; | |
_loading = false; | |
}); | |
return; | |
} | |
ProductDetailsResponse productDetailResponse = | |
await _connection.queryProductDetails(_kProductIds.toSet()); | |
if (productDetailResponse.error != null) { | |
setState(() { | |
_queryProductError = productDetailResponse.error.message; | |
_isAvailable = isAvailable; | |
_products = productDetailResponse.productDetails; | |
_purchases = []; | |
_notFoundIds = productDetailResponse.notFoundIDs; | |
_consumables = []; | |
_purchasePending = false; | |
_loading = false; | |
}); | |
return; | |
} | |
if (productDetailResponse.productDetails.isEmpty) { | |
setState(() { | |
_queryProductError = null; | |
_isAvailable = isAvailable; | |
_products = productDetailResponse.productDetails; | |
_purchases = []; | |
_notFoundIds = productDetailResponse.notFoundIDs; | |
_consumables = []; | |
_purchasePending = false; | |
_loading = false; | |
}); | |
return; | |
} | |
final QueryPurchaseDetailsResponse purchaseResponse = | |
await _connection.queryPastPurchases(); | |
if (purchaseResponse.error != null) { | |
// handle query past purchase error.. | |
} | |
final List<PurchaseDetails> verifiedPurchases = []; | |
for (PurchaseDetails purchase in purchaseResponse.pastPurchases) { | |
if (await _verifyPurchase(purchase)) { | |
verifiedPurchases.add(purchase); | |
} | |
} | |
List<String> consumables = await ConsumableStore.load(); | |
setState(() { | |
_isAvailable = isAvailable; | |
_products = productDetailResponse.productDetails; | |
_purchases = verifiedPurchases; | |
_notFoundIds = productDetailResponse.notFoundIDs; | |
_consumables = consumables; | |
_purchasePending = false; | |
_loading = false; | |
}); | |
} | |
@override | |
void dispose() { | |
_subscription.cancel(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) { | |
List<Widget> stack = []; | |
if (_queryProductError == null) { | |
stack.add( | |
ListView( | |
children: [ | |
_buildConnectionCheckTile(), | |
_buildProductList(), | |
_buildConsumableBox(), | |
], | |
), | |
); | |
} else { | |
stack.add(Center( | |
child: Text(_queryProductError), | |
)); | |
} | |
if (_purchasePending) { | |
stack.add( | |
Stack( | |
children: [ | |
new Opacity( | |
opacity: 0.3, | |
child: const ModalBarrier(dismissible: false, color: Colors.grey), | |
), | |
new Center( | |
child: new CircularProgressIndicator(), | |
), | |
], | |
), | |
); | |
} | |
return MaterialApp( | |
home: Scaffold( | |
appBar: AppBar( | |
title: const Text('IAP Example'), | |
), | |
body: Stack( | |
children: stack, | |
), | |
), | |
); | |
} | |
Card _buildConnectionCheckTile() { | |
if (_loading) { | |
return Card(child: ListTile(title: const Text('Trying to connect...'))); | |
} | |
final Widget storeHeader = ListTile( | |
leading: Icon(_isAvailable ? Icons.check : Icons.block, | |
color: _isAvailable ? Colors.green : ThemeData.light().errorColor), | |
title: Text( | |
'The store is ' + (_isAvailable ? 'available' : 'unavailable') + '.'), | |
); | |
final List<Widget> children = <Widget>[storeHeader]; | |
if (!_isAvailable) { | |
children.addAll([ | |
Divider(), | |
ListTile( | |
title: Text('Not connected', | |
style: TextStyle(color: ThemeData.light().errorColor)), | |
subtitle: const Text( | |
'Unable to connect to the payments processor. Has this app been configured correctly? See the example README for instructions.'), | |
), | |
]); | |
} | |
return Card(child: Column(children: children)); | |
} | |
Card _buildProductList() { | |
if (_loading) { | |
return Card( | |
child: (ListTile( | |
leading: CircularProgressIndicator(), | |
title: Text('Fetching products...')))); | |
} | |
if (!_isAvailable) { | |
return Card(); | |
} | |
final ListTile productHeader = ListTile( | |
title: Text('Products for Sale', | |
style: Theme.of(context).textTheme.headline)); | |
List<ListTile> productList = <ListTile>[]; | |
if (!_notFoundIds.isEmpty) { | |
productList.add(ListTile( | |
title: Text('[${_notFoundIds.join(", ")}] not found', | |
style: TextStyle(color: ThemeData.light().errorColor)), | |
subtitle: Text( | |
'This app needs special configuration to run. Please see example/README.md for instructions.'))); | |
} | |
// This loading previous purchases code is just a demo. Please do not use this as it is. | |
// In your app you should always verify the purchase data using the `verificationData` inside the [PurchaseDetails] object before trusting it. | |
// We recommend that you use your own server to verity the purchase data. | |
Map<String, PurchaseDetails> purchases = | |
Map.fromEntries(_purchases.map((PurchaseDetails purchase) { | |
if (Platform.isIOS) { | |
InAppPurchaseConnection.instance.completePurchase(purchase); | |
} | |
return MapEntry<String, PurchaseDetails>(purchase.productID, purchase); | |
})); | |
productList.addAll(_products.map( | |
(ProductDetails productDetails) { | |
PurchaseDetails previousPurchase = purchases[productDetails.id]; | |
return ListTile( | |
title: Text( | |
productDetails.title, | |
), | |
subtitle: Text( | |
productDetails.description, | |
), | |
trailing: previousPurchase != null | |
? Icon(Icons.check) | |
: FlatButton( | |
child: Text(productDetails.price), | |
color: Colors.green[800], | |
textColor: Colors.white, | |
onPressed: () { | |
PurchaseParam purchaseParam = PurchaseParam( | |
productDetails: productDetails, | |
applicationUserName: null, | |
sandboxTesting: true); | |
if (productDetails.id == _kConsumableId) { | |
_connection.buyConsumable( | |
purchaseParam: purchaseParam, | |
autoConsume: kAutoConsume || Platform.isIOS); | |
} else { | |
_connection.buyNonConsumable( | |
purchaseParam: purchaseParam); | |
} | |
}, | |
)); | |
}, | |
)); | |
return Card( | |
child: | |
Column(children: <Widget>[productHeader, Divider()] + productList)); | |
} | |
Card _buildConsumableBox() { | |
if (_loading) { | |
return Card( | |
child: (ListTile( | |
leading: CircularProgressIndicator(), | |
title: Text('Fetching consumables...')))); | |
} | |
if (!_isAvailable || _notFoundIds.contains(_kConsumableId)) { | |
return Card(); | |
} | |
final ListTile consumableHeader = ListTile( | |
title: Text('Purchased consumables', | |
style: Theme.of(context).textTheme.headline)); | |
final List<Widget> tokens = _consumables.map((String id) { | |
return GridTile( | |
child: IconButton( | |
icon: Icon( | |
Icons.stars, | |
size: 42.0, | |
color: Colors.orange, | |
), | |
splashColor: Colors.yellowAccent, | |
onPressed: () => consume(id), | |
), | |
); | |
}).toList(); | |
return Card( | |
child: Column(children: <Widget>[ | |
consumableHeader, | |
Divider(), | |
GridView.count( | |
crossAxisCount: 5, | |
children: tokens, | |
shrinkWrap: true, | |
padding: EdgeInsets.all(16.0), | |
) | |
])); | |
} | |
Future<void> consume(String id) async { | |
await ConsumableStore.consume(id); | |
final List<String> consumables = await ConsumableStore.load(); | |
setState(() { | |
_consumables = consumables; | |
}); | |
} | |
void showPendingUI() { | |
setState(() { | |
_purchasePending = true; | |
}); | |
} | |
void deliverProduct(PurchaseDetails purchaseDetails) async { | |
// IMPORTANT!! Always verify a purchase purchase details before delivering the product. | |
if (purchaseDetails.productID == _kConsumableId) { | |
await ConsumableStore.save(purchaseDetails.purchaseID); | |
List<String> consumables = await ConsumableStore.load(); | |
setState(() { | |
_purchasePending = false; | |
_consumables = consumables; | |
}); | |
} else { | |
setState(() { | |
_purchases.add(purchaseDetails); | |
_purchasePending = false; | |
}); | |
} | |
} | |
void handleError(IAPError error) { | |
setState(() { | |
_purchasePending = false; | |
}); | |
} | |
Future<bool> _verifyPurchase(PurchaseDetails purchaseDetails) { | |
// IMPORTANT!! Always verify a purchase before delivering the product. | |
// For the purpose of an example, we directly return true. | |
return Future<bool>.value(true); | |
} | |
void _handleInvalidPurchase(PurchaseDetails purchaseDetails) { | |
// handle invalid purchase here if _verifyPurchase` failed. | |
} | |
static ListTile buildListCard(ListTile innerTile) => | |
ListTile(title: Card(child: innerTile)); | |
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) { | |
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async { | |
if (purchaseDetails.status == PurchaseStatus.pending) { | |
showPendingUI(); | |
} else { | |
if (purchaseDetails.status == PurchaseStatus.error) { | |
handleError(purchaseDetails.error); | |
} else if (purchaseDetails.status == PurchaseStatus.purchased) { | |
bool valid = await _verifyPurchase(purchaseDetails); | |
if (valid) { | |
deliverProduct(purchaseDetails); | |
} else { | |
_handleInvalidPurchase(purchaseDetails); | |
} | |
} | |
if (Platform.isIOS) { | |
InAppPurchaseConnection.instance.completePurchase(purchaseDetails); | |
} else if (Platform.isAndroid) { | |
if (!kAutoConsume && purchaseDetails.productID == _kConsumableId) { | |
InAppPurchaseConnection.instance.consumePurchase(purchaseDetails); | |
} | |
} | |
} | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment