qobiljon-archives / globens-mobile

Globens Flutter Client Application (iOS + Android)
3 stars 0 forks source link

google in app purchases screen #14

Closed ilyosbekkk closed 3 years ago

ilyosbekkk commented 3 years ago

import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; import 'package:globens_flutter_client/utils/utils.dart'; import 'package:in_app_purchase/in_app_purchase.dart'; import 'consumable store.dart';

const bool _kAutoConsume = true;

const String _kConsumableId = 'consumable'; const List _kProductIds = ['globens_2020', 'globens_subscription'];

class ProductList extends StatefulWidget { @override _ProductListState createState() => _ProductListState(); }

class _ProductListState extends State { final InAppPurchaseConnection _connection = InAppPurchaseConnection.instance; StreamSubscription<List> _subscription; List _notFoundIds = []; List _products = []; List _purchases = []; List _consumables = []; bool _isAvailable = false; bool _purchasePending = false; bool _loading = true; List stack = []; String _queryProductError;

@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 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) { if (_queryProductError == null) { stack.add( ListView( children: [ _buildConnectionCheckTile(), _buildProductList(), _buildConsumableBox(), ], ), ); } else { stack.add(Center( child: Text(_queryProductError), )); } if (_purchasePending) { stack.add( Stack( children: [ Opacity( opacity: 0.3, child: const ModalBarrier(dismissible: false, color: Colors.grey), ), Center( child: CircularProgressIndicator(), ), ], ), ); } return Scaffold( appBar: AppBar( title: Text("Products"), leading: backButton(_onBackButtonPressed, context), ), body: SafeArea( child: 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 children = [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')); List productList = []; if (_notFoundIds.isNotEmpty) { 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 verify the purchase data.
Map<String, PurchaseDetails> purchases =
    Map.fromEntries(_purchases.map((PurchaseDetails purchase) {
  if (purchase.pendingCompletePurchase) {
    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')); final List 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: [ consumableHeader, Divider(), GridView.count( crossAxisCount: 5, children: tokens, shrinkWrap: true, padding: EdgeInsets.all(16.0), ) ])); }

Future consume(String id) async { await ConsumableStore.consume(id); final List 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 consumables = await ConsumableStore.load(); setState(() { _purchasePending = false; _consumables = consumables; }); } else { setState(() { _purchases.add(purchaseDetails); _purchasePending = false; }); } }

void handleError(IAPError error) { setState(() { _purchasePending = false; }); }

Future _verifyPurchase(PurchaseDetails purchaseDetails) { // IMPORTANT!! Always verify a purchase before delivering the product. // For the purpose of an example, we directly return true. return Future.value(true); }

void _handleInvalidPurchase(PurchaseDetails purchaseDetails) { // handle invalid purchase here if _verifyPurchase` failed. }

void _listenToPurchaseUpdated(List 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); return; } } if (Platform.isAndroid) { if (!_kAutoConsume && purchaseDetails.productID == _kConsumableId) { await InAppPurchaseConnection.instance .consumePurchase(purchaseDetails); } } if (purchaseDetails.pendingCompletePurchase) { await InAppPurchaseConnection.instance .completePurchase(purchaseDetails); } } }); }

void _onBackButtonPressed(BuildContext context) { Navigator.of(context).pop(); } }