· 6 years ago · Apr 12, 2020, 11:16 AM
1import 'dart:async';
2import 'dart:io';
3import 'package:find_the_treasure/models/user_model.dart';
4import 'package:find_the_treasure/services/database.dart';
5import 'package:find_the_treasure/widgets_common/buy_diamond_key_button.dart';
6import 'package:find_the_treasure/widgets_common/platform_alert_dialog.dart';
7import 'package:find_the_treasure/widgets_common/quests/diamondAndKeyContainer.dart';
8import 'package:flutter/material.dart';
9import 'package:in_app_purchase/in_app_purchase.dart';
10import 'package:provider/provider.dart';
11
12// App Store and Google Play consumable IDS
13const String _diamond50 = 'diamond_50';
14const String _diamond150 = 'diamond_150';
15const String _diamond300 = 'diamond_300';
16const String _diamond500 = 'diamond_500';
17
18class ShopScreen extends StatefulWidget {
19 @override
20 _ShopScreenState createState() => _ShopScreenState();
21}
22
23class _ShopScreenState extends State<ShopScreen> {
24 // IAP Plugin Interface
25 final InAppPurchaseConnection _iap = InAppPurchaseConnection.instance;
26 // Updates to purchases
27 StreamSubscription<List<PurchaseDetails>> _subscription;
28
29 // Products for sale
30 List<ProductDetails> _products = [];
31 // Past purchases
32 List<PurchaseDetails> _purchases = [];
33 // Is the API available on the device
34 bool _isAvailable = false;
35 // Diasble button if purchase pending
36 bool _isPurchasePending = false;
37
38 // Consumable credits the user can buy
39 int _diamonds = 0;
40 int _keys = 0;
41
42 @override
43 void initState() {
44 _initialise();
45 super.initState();
46 }
47
48 @override
49 void dispose() {
50 _subscription.cancel();
51 super.dispose();
52 }
53
54 void _initialise() async {
55 // Check availilbility of In App Purchases
56 _isAvailable = await _iap.isAvailable();
57
58 if (_isAvailable) {
59 List<Future> futures = [_getProducts(), _getPastPurchases()];
60 await Future.wait(futures);
61 _verifyPurchase();
62
63 // Listen to new purchases
64 _subscription = _iap.purchaseUpdatedStream.listen((data) => setState(() {
65 _purchases.addAll(data);
66
67 _verifyPurchase();
68 }));
69 }
70 }
71
72 // Get all products available for sale
73 Future<void> _getProducts() async {
74 Set<String> ids =
75 Set.from([_diamond50, _diamond150, _diamond300, _diamond500]);
76 ProductDetailsResponse response = await _iap.queryProductDetails(ids);
77 setState(() {
78 _products = response.productDetails;
79 _products.sort((a, b) => a.title.length.compareTo(b.title.length));
80 });
81 }
82
83 // Gets past purchases (May not need)
84 Future<void> _getPastPurchases() async {
85 QueryPurchaseDetailsResponse response = await _iap.queryPastPurchases();
86
87 for (PurchaseDetails purchase in response.pastPurchases) {
88 if (Platform.isIOS) {
89 InAppPurchaseConnection.instance.completePurchase(purchase);
90 }
91 else InAppPurchaseConnection.instance.consumePurchase(purchase);
92 }
93
94 setState(() {
95 _purchases = response.pastPurchases;
96 });
97 }
98
99 // Returns purchase of specific product ID
100 PurchaseDetails _hasPurchased(String productID) {
101
102 return _purchases.firstWhere(
103 (purchase) => purchase.productID == productID,
104 orElse: () => null,);
105
106 }
107
108
109
110
111
112
113
114
115 void _verifyPurchase() async {
116 DatabaseService _databaseService =
117 Provider.of<DatabaseService>(context, listen: false);
118 UserData _userData = Provider.of<UserData>(context, listen: false);
119
120 PurchaseDetails _purchase = _hasPurchased(_purchases.last.productID);
121 if (_purchase != null && _purchase.status == PurchaseStatus.purchased) {
122 _isPurchasePending = true;
123 _getCorrect(_purchase);
124
125 final _updateUserData = UserData(
126 displayName: _userData.displayName,
127 email: _userData.email,
128 photoURL: _userData.photoURL,
129 uid: _userData.uid,
130 userDiamondCount: _userData.userDiamondCount + _diamonds,
131 userKeyCount: _userData.userKeyCount + _keys);
132
133 final _didSelectOK = await PlatformAlertDialog(
134 title: 'Jackpot!',
135 content:
136 'I\'ll add the loot to ye treasure chest. Happy adventures.',
137 image: Image.asset('images/ic_thnx.png'),
138 defaultActionText: 'Add Loot')
139 .show(context);
140 if (_didSelectOK) {
141 _databaseService.updateUserDiamondAndKey(userData: _updateUserData);
142 _isPurchasePending = false;
143 }
144 } else if (_purchase != null && _purchase.status == PurchaseStatus.pending) {
145 _isPurchasePending = true;
146 PlatformAlertDialog(
147 title: 'Purchase Pending',
148 content:
149 'Your order is being processed, you\'ll recieve an order update very soon.',
150 image: Image.asset('images/ic_credit_card.png'),
151 defaultActionText: 'OK')
152 .show(context);
153 } else if (_purchase != null && _purchase.status == PurchaseStatus.error) {
154 _isPurchasePending = true;
155 PlatformAlertDialog(
156 title: 'Shiver Me Timbers!',
157 content:
158 'There has been an error whilst processing your payment. Please try again.',
159 image: Image.asset('images/ic_owl_wrong.png'),
160 defaultActionText: 'OK')
161 .show(context);
162 }
163
164 }
165
166 void _getCorrect(PurchaseDetails purchase) {
167 switch (purchase.productID) {
168 case _diamond50:
169 _diamonds = 50;
170 _keys = 1;
171 break;
172 case _diamond150:
173 _diamonds = 150;
174 _keys = 2;
175 break;
176 case _diamond300:
177 _diamonds = 300;
178 _keys = 3;
179 break;
180 case _diamond500:
181 _diamonds = 500;
182 _keys = 5;
183 break;
184 }
185 }
186
187 void _buyProduct(ProductDetails productDetails) {
188 final PurchaseParam purchaseParam =
189 PurchaseParam(productDetails: productDetails);
190 _iap.buyConsumable(
191 purchaseParam: purchaseParam,
192 );
193 }
194
195 @override
196 Widget build(BuildContext context) {
197 return SafeArea(
198 child: Scaffold(
199 appBar: AppBar(
200 backgroundColor: Colors.brown,
201 iconTheme: IconThemeData(color: Colors.black87),
202 actions: <Widget>[
203 Consumer<UserData>(
204 builder: (_, _userData, __) => DiamondAndKeyContainer(
205 mainAxisAlignment: MainAxisAlignment.spaceEvenly,
206 numberOfDiamonds: _userData.userDiamondCount,
207 numberOfKeys: _userData.userKeyCount,
208 color: Colors.white,
209 ),
210 ),
211 SizedBox(
212 width: 20,
213 )
214 ],
215 ),
216 body: Container(
217 height: MediaQuery.of(context).size.height,
218 width: MediaQuery.of(context).size.width,
219 decoration: BoxDecoration(
220 image: DecorationImage(
221 image: AssetImage(
222 "images/background_shop.png",
223 ),
224 fit: BoxFit.cover,
225 ),
226 ),
227 child: _isAvailable
228 ? Column(mainAxisAlignment: MainAxisAlignment.start, children: [
229 // Image.asset(),
230 SizedBox(
231 height: 10,
232 ),
233 IconButton(
234 icon: Icon(
235 Icons.help_outline,
236 color: Colors.brown.shade300,
237 size: 40,
238 ),
239 tooltip: 'Store questions',
240 onPressed: () async {
241 final _didSelectOK = await PlatformAlertDialog(
242 title: 'Welcome to The Shop',
243 content:
244 'Stock ye treasure chest with diamonds and keys. Use \'em to unlock quests and ye can also trade \'em for hints!',
245 image: Image.asset('images/ic_thnx.png'),
246 defaultActionText: 'OK')
247 .show(context);
248 if (_didSelectOK) {}
249 },
250 ),
251 SizedBox(
252 height: 10,
253 ),
254 for (var prod in _products)
255 BuyDiamondOrKeyButton(
256 numberOfDiamonds: numberOfDiamonds(prod.price),
257 diamondCost: prod.price,
258 bonusKey: numberOfKeys(prod.price),
259 onPressed: () {
260 _buyProduct(prod);
261 _hasPurchased(prod.id);
262
263 } ,
264 isPending: _isPurchasePending,
265 ),
266
267 // BuyTreasureChest()
268 ])
269 : Center(
270 child: Container(
271 color: Colors.white.withOpacity(0.5),
272 height: MediaQuery.of(context).size.height / 3,
273 width: double.infinity,
274 margin: EdgeInsets.symmetric(horizontal: 15),
275 child: Column(
276 mainAxisAlignment: MainAxisAlignment.center,
277 children: <Widget>[
278 Text('Loading Store...'),
279 SizedBox(
280 height: 20,
281 ),
282 Image.asset(
283 'images/compass.gif',
284 height: 200,
285 )
286 ],
287 ),
288 ))),
289 ),
290 );
291 }
292
293 String numberOfDiamonds(String productPrice) {
294 String _numberOfDiamonds;
295 switch (productPrice) {
296 case '\$4.99':
297 _numberOfDiamonds = '50';
298 break;
299 case '\$9.99':
300 _numberOfDiamonds = '150';
301 break;
302 case '\$19.99':
303 _numberOfDiamonds = '300';
304 break;
305 case '\$39.99':
306 _numberOfDiamonds = '500';
307 break;
308 default:
309 _numberOfDiamonds = '50';
310 }
311 return _numberOfDiamonds;
312 }
313
314 String numberOfKeys(String productPrice) {
315 String _numberOfKeys;
316 switch (productPrice) {
317 case '\$4.99':
318 _numberOfKeys = '1';
319 break;
320 case '\$9.99':
321 _numberOfKeys = '2';
322 break;
323 case '\$19.99':
324 _numberOfKeys = '3';
325 break;
326 case '\$39.99':
327 _numberOfKeys = '5';
328 break;
329 default:
330 _numberOfKeys = 'err';
331 }
332 return _numberOfKeys;
333 }
334}
335
336class BuyTreasureChest extends StatelessWidget {
337 const BuyTreasureChest({
338 Key key,
339 }) : super(key: key);
340
341 @override
342 Widget build(BuildContext context) {
343 return Container(
344 padding: EdgeInsets.symmetric(vertical: 10),
345 width: MediaQuery.of(context).size.width * 0.9,
346 child: Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
347 Column(
348 children: <Widget>[
349 Image.asset(
350 'images/chest.png',
351 height: 40,
352 ),
353 ],
354 ),
355 Row(
356 children: <Widget>[
357 Text('? x', style: TextStyle(color: Colors.white, fontSize: 25)),
358 SizedBox(width: 8),
359 Image.asset(
360 'images/2.0x/ic_diamond.png',
361 height: 20,
362 ),
363 ],
364 ),
365 Row(
366 children: <Widget>[
367 Text('? x', style: TextStyle(color: Colors.white, fontSize: 25)),
368 Image.asset(
369 'images/skull_key_outline.png',
370 height: 30,
371 ),
372 ],
373 ),
374 Container(
375 padding: EdgeInsets.symmetric(vertical: 5, horizontal: 24),
376 decoration: BoxDecoration(
377 border: Border.all(width: 1, color: Colors.grey),
378 borderRadius: BorderRadius.circular(5),
379 color: Colors.grey.shade800),
380 child: Text(
381 '\$9.99',
382 textAlign: TextAlign.center,
383 style: TextStyle(color: Colors.white, fontSize: 15),
384 ),
385 ),
386 ]),
387 decoration: BoxDecoration(
388 color: Colors.grey.shade800,
389 border: Border.all(color: Colors.amberAccent, width: 2),
390 borderRadius: BorderRadius.circular(35)),
391 );
392 }
393}