-
Notifications
You must be signed in to change notification settings - Fork 0
Добавление функции оплаты и подготовка к релизу в AppStore #77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5172f8e
4584621
12f85bb
0c4e4d4
384b694
ad3d1f5
c324429
92475a8
e8d8362
fc0cb7b
4218d04
ac28497
baad32b
5a94522
26e1f19
6c882f7
30ba166
30bd629
502be15
9e52e4e
c468439
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| REVENUE_CAT_API_KEY= |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,5 @@ | ||
| *.env | ||
|
|
||
| # Miscellaneous | ||
| *.class | ||
| *.log | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,4 @@ | ||
| arb-dir: lib/l10n | ||
| template-arb-file: en.arb | ||
| output-localization-file: app_localizations.dart | ||
| nullable-getter: false |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,22 @@ | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:frontend/constants/text_styles.dart'; | ||
|
|
||
| sealed class SharedFunctions { | ||
| static bool isTablet(BuildContext context) { | ||
| final shortestSide = MediaQuery.of(context).size.shortestSide; | ||
| return shortestSide >= 640; | ||
| } | ||
|
|
||
| static void showSnackBar(BuildContext context, String text) { | ||
| ScaffoldMessenger.of(context).showSnackBar( | ||
| SnackBar( | ||
| backgroundColor: Colors.black, | ||
| content: Text( | ||
| text, | ||
| style: TextStyles.caption2, | ||
| ), | ||
| duration: const Duration(seconds: 2), | ||
| ), | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| sealed class Assets { | ||
| static const handbook = "assets/images/icons/handbook.svg"; | ||
| static const loading = "assets/images/icons/loading.svg"; | ||
| static const board = "assets/images/board.svg"; | ||
| static const advantage = 'assets/images/icons/advantage.svg'; | ||
| static const backArrowIcon = "assets/images/icons/back_arrow_icon.svg"; | ||
| static const pointLight = "assets/images/icons/point_light.svg"; | ||
| static const darkSwitch = "assets/images/icons/dark_switch.svg"; | ||
| static const lightSwitch = "assets/images/icons/light_switch.svg"; | ||
| static const proSparkles = 'assets/images/icons/pro_sparkles.svg'; | ||
| static const sun = 'assets/images/icons/sun.svg'; | ||
| static const moon = 'assets/images/icons/moon.svg'; | ||
| static const explosion = 'assets/images/icons/explosion.svg'; | ||
| static const lamp = 'assets/images/icons/lamp.svg'; | ||
| static const book = 'assets/images/icons/book.svg'; | ||
| static const chessPiece = 'assets/images/icons/chess_piece.svg'; | ||
| static const leftBigArrowIcon = "assets/images/icons/left_big_arrow_icon.svg"; | ||
| static const rightBigArrowIcon = | ||
| "assets/images/icons/right_big_arrow_icon.svg"; | ||
| static const questionIcon = "assets/images/icons/question_icon.svg"; | ||
| } | ||
|
|
||
| sealed class AssetsPieces { | ||
| static const bishop = 'assets/images/pieces/bishop.svg'; | ||
| static const rook = 'assets/images/pieces/rook.svg'; | ||
| static const knight = 'assets/images/pieces/knight.svg'; | ||
| static const queen = 'assets/images/pieces/queen.svg'; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,3 @@ | ||
| export "colors.dart"; | ||
| export "text_styles.dart"; | ||
| export "string_constants.dart"; | ||
| export 'gradient_consts.dart'; |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| abstract interface class IInAppPurchaseDataSource { | ||
| Future<bool> buyProduct(String productId); | ||
| Future<bool> checkStatus(String productId); | ||
| Future<void> restorePurchases(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| import 'package:frontend/data_sources/in_app_purchase_data_source.dart'; | ||
| import 'package:purchases_flutter/purchases_flutter.dart'; | ||
|
|
||
| class RevenueCatDataSource implements IInAppPurchaseDataSource { | ||
| @override | ||
| Future<bool> buyProduct(String productId) async { | ||
| final product = (await Purchases.getProducts([productId])).first; | ||
| final customerInfo = await Purchases.purchaseStoreProduct(product); | ||
| final isPro = customerInfo.entitlements.active[productId] != null; | ||
| return isPro; | ||
| } | ||
|
|
||
| @override | ||
| Future<bool> checkStatus(String productId) async { | ||
| final customerInfo = await Purchases.getCustomerInfo(); | ||
| return customerInfo.entitlements.active.containsKey(productId); | ||
| } | ||
|
|
||
| @override | ||
| Future<void> restorePurchases() async { | ||
| Purchases.restorePurchases(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| enum ProVersionError { purchaseError, restoreError } |
neekeetuh marked this conversation as resolved.
Show resolved
Hide resolved
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,20 +1,69 @@ | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:frontend/providers/pro_version_errors.dart'; | ||
| import 'package:frontend/providers/pro_version_states.dart'; | ||
| import 'package:frontend/repositories/in_app_purchase_repository.dart'; | ||
|
|
||
| class ProVersionProvider extends ChangeNotifier { | ||
| bool _isProStatus = false; | ||
| //The key of a product should be the same as an entitlement name attached to it | ||
| static const _proVersionKey = 'chessknock_pro_version'; | ||
|
|
||
| void _setProStatus(bool status) { | ||
| _isProStatus = status; | ||
| notifyListeners(); | ||
| late ProVersionState _state; | ||
|
|
||
| final InAppPurchaseRepository _repository; | ||
|
|
||
| ProVersionProvider({required InAppPurchaseRepository repository}) | ||
| : _repository = repository { | ||
| _state = const InitialProVersionState(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. У тебя и здесь происходит задание переменной, и в init. Потенциально нельзя здесь не трогать тогда _state? Или ты так ты пытаешься обезопасить от вызова, когда init еще не отработал?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Да, потому что init асинхронный и стейт может из-за этого поздно инициализироваться, когда уже будет отрисован ui (тестил на айпаде) |
||
| _init(); | ||
| } | ||
|
|
||
| void upgradeToPro() { | ||
| _setProStatus(true); | ||
| void _init() async { | ||
| final isPro = await _repository.checkStatus(_proVersionKey); | ||
| _setState(InitialProVersionState(isProStatus: isPro)); | ||
| } | ||
|
|
||
| Future<void> upgradeToPro() async { | ||
| try { | ||
| await _repository.buyProduct(_proVersionKey); | ||
| final isPro = await _repository.checkStatus(_proVersionKey); | ||
| if (isPro) _setState(const SuccessfulPurchaseState()); | ||
| } catch (_) { | ||
| _setState(ErrorProVersionState( | ||
| error: ProVersionError.purchaseError, | ||
| isProStatus: state.isProStatus, | ||
| )); | ||
| } | ||
| } | ||
|
|
||
| void downgradeFromPro() { | ||
| _setProStatus(false); | ||
| _setState(const InitialProVersionState()); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. В данном случае, когда есть у нас восстановление покупок это будто бы тоже самое, что downgrade. Только если не подразумевается другая логика в будущем для этого метода. Точно ли он у тебя до сих пор где-то используется или только для дебага?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Да, в текущей версии под ios в этом методе смысла нет, это скорее на будущее оставил, если понадобится функционал отмены покупки/возврата средств по нажатию на кнопку в приложении (если сбп будет подключаться, например) |
||
| } | ||
|
|
||
| Future<void> restorePurchases() async { | ||
| try { | ||
| await _repository.restorePurchases(); | ||
| final isPro = await _repository.checkStatus(_proVersionKey); | ||
| _setState(SuccessfulRestorePurchasesState(isProStatus: isPro)); | ||
| } catch (_) { | ||
| _setState(ErrorProVersionState( | ||
| error: ProVersionError.restoreError, | ||
| isProStatus: state.isProStatus, | ||
| )); | ||
| } | ||
| } | ||
|
|
||
| void _setState(ProVersionState state) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Честно, хотелось бы любое другое название, но не _setState. Возникают сразу же ассоциации с виджетовским методом
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Понимаю, но блин, это буквально сеттер стейта), да и область видимости у него только внутри провайдера, так что наверное сложно будет запутаться. Хотя ладно, придумаю новое название |
||
| _state = state; | ||
| notifyListeners(); | ||
| } | ||
|
|
||
| bool get isPro => _isProStatus; | ||
| ProVersionState get state => _state; | ||
|
|
||
| bool get isPro => state.isProStatus; | ||
| ProVersionError? get error { | ||
| if (state is ErrorProVersionState) { | ||
| return (state as ErrorProVersionState).error; | ||
| } | ||
| return null; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Эта операция у тебя всегда будет успешной? Что если я выключу интернет прямо во время запроса?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Это обрабатывается уже на уровне провайдера. Если будет какое-то исключение, то установится состояние ошибки, и в связи с этим появится снекбар с сообщением об ошибке