From 5172f8e4d4eb9346270be3025e6b745fb8f33e9b Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Thu, 29 May 2025 14:46:59 +0600 Subject: [PATCH 01/20] =?UTF-8?q?build:=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=BA=D0=BE=D0=BD=D1=84=D0=B8=D0=B3=D1=83=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20ios=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82?= =?UTF-8?q?=D0=B0=20=D0=B2=20=D1=81=D0=B2=D1=8F=D0=B7=D0=B8=20=D1=81=20?= =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=D0=BC=20?= =?UTF-8?q?=D0=B0=D0=BA=D0=BA=D0=B0=D1=83=D0=BD=D1=82=D0=B0=20appstore=20c?= =?UTF-8?q?onnect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/ios/Runner.xcodeproj/project.pbxproj | 18 +++--- frontend/ios/Runner/Info.plist | 4 +- frontend/pubspec.lock | 56 +++++++++++++++---- 3 files changed, 55 insertions(+), 23 deletions(-) diff --git a/frontend/ios/Runner.xcodeproj/project.pbxproj b/frontend/ios/Runner.xcodeproj/project.pbxproj index 50fd984..177178c 100644 --- a/frontend/ios/Runner.xcodeproj/project.pbxproj +++ b/frontend/ios/Runner.xcodeproj/project.pbxproj @@ -473,8 +473,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 4; - DEVELOPMENT_TEAM = JVX7U43WRX; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = HC3NQ49D3T; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ChessKnock; @@ -484,7 +484,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = band.effective.chessknock; + PRODUCT_BUNDLE_IDENTIFIER = dev.effective.chessknock; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -664,8 +664,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 4; - DEVELOPMENT_TEAM = JVX7U43WRX; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = HC3NQ49D3T; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ChessKnock; @@ -675,7 +675,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = band.effective.chessknock; + PRODUCT_BUNDLE_IDENTIFIER = dev.effective.chessknock; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -695,8 +695,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 4; - DEVELOPMENT_TEAM = JVX7U43WRX; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = HC3NQ49D3T; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = ChessKnock; @@ -706,7 +706,7 @@ "@executable_path/Frameworks", ); MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = band.effective.chessknock; + PRODUCT_BUNDLE_IDENTIFIER = dev.effective.chessknock; PRODUCT_NAME = "$(TARGET_NAME)"; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; diff --git a/frontend/ios/Runner/Info.plist b/frontend/ios/Runner/Info.plist index a819152..ae61fda 100644 --- a/frontend/ios/Runner/Info.plist +++ b/frontend/ios/Runner/Info.plist @@ -24,6 +24,8 @@ ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) + ITSAppUsesNonExemptEncryption + LSRequiresIPhoneOS UIApplicationSupportsIndirectInputEvents @@ -38,7 +40,5 @@ UIInterfaceOrientationPortrait - ITSAppUsesNonExemptEncryption - diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock index dc616a4..cb52dc7 100644 --- a/frontend/pubspec.lock +++ b/frontend/pubspec.lock @@ -201,10 +201,10 @@ packages: dependency: "direct main" description: name: intl - sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf url: "https://pub.dev" source: hosted - version: "0.18.1" + version: "0.19.0" json_annotation: dependency: transitive description: @@ -213,6 +213,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.9.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + url: "https://pub.dev" + source: hosted + version: "10.0.4" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + url: "https://pub.dev" + source: hosted + version: "3.0.3" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: @@ -233,26 +257,26 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.12.0" nested: dependency: transitive description: @@ -273,10 +297,10 @@ packages: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.8.3" + version: "1.9.0" path_parsing: dependency: transitive description: @@ -391,10 +415,10 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" url: "https://pub.dev" source: hosted - version: "0.6.1" + version: "0.7.0" typed_data: dependency: transitive description: @@ -435,6 +459,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + url: "https://pub.dev" + source: hosted + version: "14.2.1" web: dependency: transitive description: From 45846213004ad3b2b69e9acfa6cc36ed2e7a5aae Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Thu, 29 May 2025 18:39:27 +0600 Subject: [PATCH 02/20] =?UTF-8?q?feat:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D1=82=D0=B5=D1=81=D1=82=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20=D0=BB?= =?UTF-8?q?=D0=BE=D0=B3=D0=B8=D0=BA=D0=B8=20=D0=BF=D0=BE=D0=BA=D1=83=D0=BF?= =?UTF-8?q?=D0=BA=D0=B8=20=D0=BF=D1=80=D0=BE=20=D0=B2=D0=B5=D1=80=D1=81?= =?UTF-8?q?=D0=B8=D0=B8=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D0=B0=D0=BF?= =?UTF-8?q?=D0=BF=D1=81=D1=82=D0=BE=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/ios/Podfile.lock | 17 +++++++++++++ .../in_app_purchase_data_source.dart | 4 +++ .../data_sources/revenuecat_data_source.dart | 19 ++++++++++++++ frontend/lib/main.dart | 23 +++++++++++++++-- .../lib/providers/pro_version_provider.dart | 25 +++++++++++++++++-- .../in_app_purchase_repository.dart | 19 ++++++++++++++ frontend/pubspec.lock | 16 ++++++++++++ frontend/pubspec.yaml | 1 + 8 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 frontend/lib/data_sources/in_app_purchase_data_source.dart create mode 100644 frontend/lib/data_sources/revenuecat_data_source.dart create mode 100644 frontend/lib/repositories/in_app_purchase_repository.dart diff --git a/frontend/ios/Podfile.lock b/frontend/ios/Podfile.lock index 334a347..21a8584 100644 --- a/frontend/ios/Podfile.lock +++ b/frontend/ios/Podfile.lock @@ -1,21 +1,38 @@ PODS: - Flutter (1.0.0) + - purchases_flutter (5.8.2): + - Flutter + - PurchasesHybridCommon (= 6.3.2) + - PurchasesHybridCommon (6.3.2): + - RevenueCat (= 4.26.1) + - RevenueCat (4.26.1) - sqflite (0.0.3): - Flutter - FlutterMacOS DEPENDENCIES: - Flutter (from `Flutter`) + - purchases_flutter (from `.symlinks/plugins/purchases_flutter/ios`) - sqflite (from `.symlinks/plugins/sqflite/darwin`) +SPEC REPOS: + trunk: + - PurchasesHybridCommon + - RevenueCat + EXTERNAL SOURCES: Flutter: :path: Flutter + purchases_flutter: + :path: ".symlinks/plugins/purchases_flutter/ios" sqflite: :path: ".symlinks/plugins/sqflite/darwin" SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 + purchases_flutter: 32697fa7b2aceb2af2dde25826c5f8b74021fcd1 + PurchasesHybridCommon: 98af59169dd9c418eac0725c43f02db92a643b79 + RevenueCat: 4e8899a69fd57180ef166237d1eb670023be05de sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 diff --git a/frontend/lib/data_sources/in_app_purchase_data_source.dart b/frontend/lib/data_sources/in_app_purchase_data_source.dart new file mode 100644 index 0000000..3cbbf5c --- /dev/null +++ b/frontend/lib/data_sources/in_app_purchase_data_source.dart @@ -0,0 +1,4 @@ +abstract interface class IInAppPurchaseDataSource { + Future buyProduct(String productId); + Future checkStatus(String productId); +} diff --git a/frontend/lib/data_sources/revenuecat_data_source.dart b/frontend/lib/data_sources/revenuecat_data_source.dart new file mode 100644 index 0000000..503b3d9 --- /dev/null +++ b/frontend/lib/data_sources/revenuecat_data_source.dart @@ -0,0 +1,19 @@ +import 'package:frontend/data_sources/in_app_purchase_data_source.dart'; +import 'package:purchases_flutter/purchases_flutter.dart'; + +class RevenueCatDataSource implements IInAppPurchaseDataSource { + @override + Future buyProduct(String productId) async { + final product = (await Purchases.getProducts([productId])).first; + return (await Purchases.purchaseStoreProduct(product)) + .entitlements + .active[productId] != + null; + } + + @override + Future checkStatus(String productId) async { + final customerInfo = await Purchases.getCustomerInfo(); + return customerInfo.entitlements.active.containsKey(productId); + } +} diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index 36ee863..a607a46 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -1,13 +1,28 @@ +import "dart:io"; + +import "package:frontend/data_sources/revenuecat_data_source.dart"; import "package:frontend/exports.dart"; import "package:flutter/material.dart"; import "package:flutter/services.dart"; +import "package:frontend/repositories/in_app_purchase_repository.dart"; import "package:provider/provider.dart"; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import "package:purchases_flutter/purchases_flutter.dart"; -void main() { +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + if (Platform.isIOS) { + await configureRevenueCat(); + } runApp(const MyApp()); } +Future configureRevenueCat() async { + Purchases.setLogLevel(LogLevel.debug); + Purchases.configure( + PurchasesConfiguration('appl_XXuSbFegqvobEdAwmWZnhIlglOX')); +} + class MyApp extends StatelessWidget { const MyApp({super.key}); @@ -23,7 +38,11 @@ class MyApp extends StatelessWidget { create: (context) => ThemeProvider(), ), ChangeNotifierProvider( - create: (context) => ProVersionProvider(), + create: (context) => ProVersionProvider( + repository: InAppPurchaseRepository( + dataSource: RevenueCatDataSource(), + ), + ), ) ], child: Builder(builder: (context) { diff --git a/frontend/lib/providers/pro_version_provider.dart b/frontend/lib/providers/pro_version_provider.dart index 173e091..4cf7a91 100644 --- a/frontend/lib/providers/pro_version_provider.dart +++ b/frontend/lib/providers/pro_version_provider.dart @@ -1,15 +1,36 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:frontend/repositories/in_app_purchase_repository.dart'; +import 'package:purchases_flutter/purchases_flutter.dart'; class ProVersionProvider extends ChangeNotifier { bool _isProStatus = false; + final InAppPurchaseRepository repository; + + ProVersionProvider({required this.repository}) { + _init(); + } + + void _init() async { + if (Platform.isIOS) { + Purchases.addCustomerInfoUpdateListener((info) { + info.entitlements.active.containsKey('chessknock_pro_version') + ? _setProStatus(true) + : _setProStatus(false); + }); + _setProStatus(await repository.checkStatus('chessknock_pro_version')); + } + } + void _setProStatus(bool status) { _isProStatus = status; notifyListeners(); } - void upgradeToPro() { - _setProStatus(true); + void upgradeToPro() async { + _setProStatus(await repository.buyProduct('chessknock_pro_version')); } void downgradeFromPro() { diff --git a/frontend/lib/repositories/in_app_purchase_repository.dart b/frontend/lib/repositories/in_app_purchase_repository.dart new file mode 100644 index 0000000..8acbd24 --- /dev/null +++ b/frontend/lib/repositories/in_app_purchase_repository.dart @@ -0,0 +1,19 @@ +import 'package:frontend/data_sources/in_app_purchase_data_source.dart'; + +class InAppPurchaseRepository { + final IInAppPurchaseDataSource dataSource; + + InAppPurchaseRepository({required this.dataSource}); + + Future buyProduct(String productId) async { + try { + return dataSource.buyProduct(productId); + } catch (_) { + rethrow; + } + } + + Future checkStatus(String productId) { + return dataSource.checkStatus(productId); + } +} diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock index cb52dc7..361d8c3 100644 --- a/frontend/pubspec.lock +++ b/frontend/pubspec.lock @@ -165,6 +165,14 @@ packages: description: flutter source: sdk version: "0.0.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 + url: "https://pub.dev" + source: hosted + version: "2.4.4" go_router: dependency: "direct main" description: @@ -325,6 +333,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.2" + purchases_flutter: + dependency: "direct main" + description: + name: purchases_flutter + sha256: ccf446dc474dab5e07fcb359845928c1f7e7cb269223f058d42f1254fa25bcdf + url: "https://pub.dev" + source: hosted + version: "5.8.2" sky_engine: dependency: transitive description: flutter diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index 4ab46dd..202728b 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -27,6 +27,7 @@ dependencies: super_tooltip: ^2.0.9 flutter_localizations: sdk: flutter + purchases_flutter: ^5.8.0 dev_dependencies: flutter_test: From 12f85bbc858e8b95e84d16204a768585fa56e084 Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Thu, 29 May 2025 19:33:02 +0600 Subject: [PATCH 03/20] =?UTF-8?q?fix:=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=B2=D0=BE=D0=B7=D0=BC=D0=BE=D0=B6=D0=BD?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B8=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8F=20=D1=81=D0=BE=20=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D1=82=D1=83=D1=81=D0=BE=D0=BC=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=BF=D0=B0=D0=B4=D0=B0=D1=82=D1=8C=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=20=D1=81=20=D0=BE=D1=82?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=BE=D0=B9=20=D0=BF=D0=BE=D0=BA=D1=83=D0=BF?= =?UTF-8?q?=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu_view/components/menu_app_bar.dart | 2 +- .../components/pro_status_indicator.dart | 48 ++++++++----------- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/frontend/lib/views/menu_view/components/menu_app_bar.dart b/frontend/lib/views/menu_view/components/menu_app_bar.dart index 0e048c3..9b54f12 100644 --- a/frontend/lib/views/menu_view/components/menu_app_bar.dart +++ b/frontend/lib/views/menu_view/components/menu_app_bar.dart @@ -26,7 +26,7 @@ class MenuAppBar extends StatelessWidget { children: [ CustomSwitch(provider), const SizedBox(width: 16), - ProStatusIndicator(onTap: () => context.push(promoScreenRoute)), + const ProStatusIndicator(), ], ) : UpgradeToProButton(onTap: () => context.push(promoScreenRoute)), diff --git a/frontend/lib/views/menu_view/components/pro_status_indicator.dart b/frontend/lib/views/menu_view/components/pro_status_indicator.dart index c44c6cc..48d4e32 100644 --- a/frontend/lib/views/menu_view/components/pro_status_indicator.dart +++ b/frontend/lib/views/menu_view/components/pro_status_indicator.dart @@ -6,37 +6,31 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class ProStatusIndicator extends StatelessWidget { const ProStatusIndicator({ super.key, - required this.onTap, }); - final VoidCallback onTap; - @override Widget build(BuildContext context) { - return GestureDetector( - onTap: onTap, - child: Row( - children: [ - ShaderMask( - shaderCallback: (Rect bounds) { - return GradientConsts.orange.createShader( - Rect.fromLTWH(0, 0, bounds.width, bounds.height)); - }, - child: Text( - AppLocalizations.of(context)!.pro, - style: - TextStyles.body1.copyWith(color: ColorsConst.neutralColor0), - )), - ShaderMask( - shaderCallback: (Rect bounds) { - return GradientConsts.orange.createShader( - Rect.fromLTWH(0, 0, bounds.width, bounds.height)); - }, - child: SvgPicture.asset( - 'assets/images/icons/pro_sparkles.svg', - )), - ], - ), + return Row( + children: [ + ShaderMask( + shaderCallback: (Rect bounds) { + return GradientConsts.orange.createShader( + Rect.fromLTWH(0, 0, bounds.width, bounds.height)); + }, + child: Text( + AppLocalizations.of(context)!.pro, + style: + TextStyles.body1.copyWith(color: ColorsConst.neutralColor0), + )), + ShaderMask( + shaderCallback: (Rect bounds) { + return GradientConsts.orange.createShader( + Rect.fromLTWH(0, 0, bounds.width, bounds.height)); + }, + child: SvgPicture.asset( + 'assets/images/icons/pro_sparkles.svg', + )), + ], ); } } From 0c4e4d4a9a31546c75bbc0d12927e72614dccdd5 Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Thu, 29 May 2025 23:12:26 +0600 Subject: [PATCH 04/20] =?UTF-8?q?refactor:=20=D0=B2=D1=8B=D0=BD=D0=B5?= =?UTF-8?q?=D1=81=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BD=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D0=BD=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/lib/main.dart | 5 +++-- frontend/lib/providers/pro_version_provider.dart | 13 ++++++++----- .../repositories/in_app_purchase_repository.dart | 6 +----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index a607a46..04405ff 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -17,10 +17,11 @@ void main() async { runApp(const MyApp()); } +const _revenueCatApiKey = 'appl_XXuSbFegqvobEdAwmWZnhIlglOX'; + Future configureRevenueCat() async { Purchases.setLogLevel(LogLevel.debug); - Purchases.configure( - PurchasesConfiguration('appl_XXuSbFegqvobEdAwmWZnhIlglOX')); + Purchases.configure(PurchasesConfiguration(_revenueCatApiKey)); } class MyApp extends StatelessWidget { diff --git a/frontend/lib/providers/pro_version_provider.dart b/frontend/lib/providers/pro_version_provider.dart index 4cf7a91..3e05dae 100644 --- a/frontend/lib/providers/pro_version_provider.dart +++ b/frontend/lib/providers/pro_version_provider.dart @@ -4,23 +4,26 @@ import 'package:flutter/material.dart'; import 'package:frontend/repositories/in_app_purchase_repository.dart'; import 'package:purchases_flutter/purchases_flutter.dart'; +const _proVersionKey = 'chessknock_pro_version'; + class ProVersionProvider extends ChangeNotifier { bool _isProStatus = false; - final InAppPurchaseRepository repository; + final InAppPurchaseRepository _repository; - ProVersionProvider({required this.repository}) { + ProVersionProvider({required InAppPurchaseRepository repository}) + : _repository = repository { _init(); } void _init() async { if (Platform.isIOS) { Purchases.addCustomerInfoUpdateListener((info) { - info.entitlements.active.containsKey('chessknock_pro_version') + info.entitlements.active.containsKey(_proVersionKey) ? _setProStatus(true) : _setProStatus(false); }); - _setProStatus(await repository.checkStatus('chessknock_pro_version')); + _setProStatus(await _repository.checkStatus(_proVersionKey)); } } @@ -30,7 +33,7 @@ class ProVersionProvider extends ChangeNotifier { } void upgradeToPro() async { - _setProStatus(await repository.buyProduct('chessknock_pro_version')); + _setProStatus(await _repository.buyProduct(_proVersionKey)); } void downgradeFromPro() { diff --git a/frontend/lib/repositories/in_app_purchase_repository.dart b/frontend/lib/repositories/in_app_purchase_repository.dart index 8acbd24..8a7135d 100644 --- a/frontend/lib/repositories/in_app_purchase_repository.dart +++ b/frontend/lib/repositories/in_app_purchase_repository.dart @@ -6,11 +6,7 @@ class InAppPurchaseRepository { InAppPurchaseRepository({required this.dataSource}); Future buyProduct(String productId) async { - try { - return dataSource.buyProduct(productId); - } catch (_) { - rethrow; - } + return dataSource.buyProduct(productId); } Future checkStatus(String productId) { From 384b69427c15a4be1465ce2fac553e3e6eed5ed3 Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Fri, 30 May 2025 15:56:09 +0600 Subject: [PATCH 05/20] =?UTF-8?q?fix:=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=86=D0=B5=D0=BD=D1=8B=20=D0=BF?= =?UTF-8?q?=D1=80=D0=BE=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D0=B8=20=D0=B2=20?= =?UTF-8?q?=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8E=20+=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA=20?= =?UTF-8?q?=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/lib/constants/constants.dart | 1 - frontend/lib/constants/string_constants.dart | 7 ------- frontend/lib/l10n/en.arb | 3 ++- frontend/lib/l10n/ru.arb | 3 ++- .../lib/views/guide_view/guide_chose_view.dart | 2 +- .../components/upgrade_to_pro_button.dart | 4 ++-- .../constants/menu_page_string_const.dart | 3 --- .../views/menu_view/tablet_menu_layout.dart | 2 +- .../views/promo_view/constants/constants.dart | 1 - .../constants/promo_page_constants.dart | 14 -------------- .../lib/views/promo_view/promo_page_view.dart | 18 ++++++++---------- frontend/lib/views/promo_view/promo_view.dart | 1 - 12 files changed, 16 insertions(+), 43 deletions(-) delete mode 100644 frontend/lib/constants/string_constants.dart delete mode 100644 frontend/lib/views/promo_view/constants/constants.dart delete mode 100644 frontend/lib/views/promo_view/constants/promo_page_constants.dart diff --git a/frontend/lib/constants/constants.dart b/frontend/lib/constants/constants.dart index 1564062..be66b30 100644 --- a/frontend/lib/constants/constants.dart +++ b/frontend/lib/constants/constants.dart @@ -1,4 +1,3 @@ export "colors.dart"; export "text_styles.dart"; -export "string_constants.dart"; export 'gradient_consts.dart'; diff --git a/frontend/lib/constants/string_constants.dart b/frontend/lib/constants/string_constants.dart deleted file mode 100644 index a6e24c6..0000000 --- a/frontend/lib/constants/string_constants.dart +++ /dev/null @@ -1,7 +0,0 @@ -sealed class StringConstants { - static String proVersionAvailability = - 'Эта функция доступна \nтолько в Pro версии'; - static String partyHistory = 'История ваших партий'; - static String becomePro = 'Стать Pro'; - static String downgradeFromPro = 'Отменить статус Pro'; -} diff --git a/frontend/lib/l10n/en.arb b/frontend/lib/l10n/en.arb index 317a319..2a67350 100644 --- a/frontend/lib/l10n/en.arb +++ b/frontend/lib/l10n/en.arb @@ -103,5 +103,6 @@ "moveBackModal": "Ability to return\nthe game board one move back", "threatsModal": "Display of danger\nof your piece being captured", "hintsModal": "Display of hints\nfor possible moves", - "description": "Description" + "description": "Description", + "price": "1.99 $" } diff --git a/frontend/lib/l10n/ru.arb b/frontend/lib/l10n/ru.arb index dd3b5fd..497b2e7 100644 --- a/frontend/lib/l10n/ru.arb +++ b/frontend/lib/l10n/ru.arb @@ -103,5 +103,6 @@ "moveBackModal": "Возможность вернуть\nигровое поле на ход назад", "threatsModal": "Отображение опасности\nвзятия вашей фигуры", "hintsModal": "Отображение подсказок\nвозможных ходов", - "description": "Описание" + "description": "Описание", + "price": "199 ₽" } diff --git a/frontend/lib/views/guide_view/guide_chose_view.dart b/frontend/lib/views/guide_view/guide_chose_view.dart index b611f2c..be7b98d 100644 --- a/frontend/lib/views/guide_view/guide_chose_view.dart +++ b/frontend/lib/views/guide_view/guide_chose_view.dart @@ -102,7 +102,7 @@ class GuideChoseView extends StatelessWidget { ) : ProFunctionsTooltip( isPro: isPro, - modalHeader: StringConstants.partyHistory, + modalHeader: l10n.partyHistoryPage, child: GuideChosePieceButton( iconName: null, label: l10n.partyHistoryPage, diff --git a/frontend/lib/views/menu_view/components/upgrade_to_pro_button.dart b/frontend/lib/views/menu_view/components/upgrade_to_pro_button.dart index 69e8132..7c5a788 100644 --- a/frontend/lib/views/menu_view/components/upgrade_to_pro_button.dart +++ b/frontend/lib/views/menu_view/components/upgrade_to_pro_button.dart @@ -12,7 +12,7 @@ class UpgradeToProButton extends StatelessWidget { }); final VoidCallback onTap; - final int? price; + final String? price; @override Widget build(BuildContext context) { @@ -71,7 +71,7 @@ class UpgradeToProButton extends StatelessWidget { ), if (price != null) ...[ Text( - '$price ₽', + price!, style: style, ) ] diff --git a/frontend/lib/views/menu_view/constants/menu_page_string_const.dart b/frontend/lib/views/menu_view/constants/menu_page_string_const.dart index 82b918f..a06457b 100644 --- a/frontend/lib/views/menu_view/constants/menu_page_string_const.dart +++ b/frontend/lib/views/menu_view/constants/menu_page_string_const.dart @@ -1,6 +1,3 @@ class MenuPageStringConst { - static String slogan = "Побеждать\nв шахматах — \nпобеждать\nв жизни"; - static String sloganWide = "Побеждать в шахматах — \nпобеждать в жизни"; - static String localButton = "Начать игру"; static String pathToIcon = "assets/images/icons/"; } diff --git a/frontend/lib/views/menu_view/tablet_menu_layout.dart b/frontend/lib/views/menu_view/tablet_menu_layout.dart index 4c00b11..a0f0c74 100644 --- a/frontend/lib/views/menu_view/tablet_menu_layout.dart +++ b/frontend/lib/views/menu_view/tablet_menu_layout.dart @@ -77,7 +77,7 @@ class TabletMenuView extends StatelessWidget { child: MenuButton( gameModel: gameModel, height: height * 0.24, - buttonText: MenuPageStringConst.localButton, + buttonText: AppLocalizations.of(context)!.startGame, settingsScreenRoute: RouteLocations.settingsScreen, ), ), diff --git a/frontend/lib/views/promo_view/constants/constants.dart b/frontend/lib/views/promo_view/constants/constants.dart deleted file mode 100644 index 30f477b..0000000 --- a/frontend/lib/views/promo_view/constants/constants.dart +++ /dev/null @@ -1 +0,0 @@ -export 'promo_page_constants.dart'; diff --git a/frontend/lib/views/promo_view/constants/promo_page_constants.dart b/frontend/lib/views/promo_view/constants/promo_page_constants.dart deleted file mode 100644 index 457746b..0000000 --- a/frontend/lib/views/promo_view/constants/promo_page_constants.dart +++ /dev/null @@ -1,14 +0,0 @@ -sealed class PromoPageConstants { - static const themeSwitchHeader = 'Смена темы приложения'; - static const themeSwitchDescription = - 'Настраивай приложение \nпод себя в один клик'; - static const personalModeHeader = 'Персональный режим'; - static const personalModeDescription = - 'Настраивай параметры \nи собирай свой режим игры'; - static const threatsDescription = 'Угрозы \nвашим фигурам'; - static const hintsDesctiption = 'Подсказки \nво время игры'; - static const gamesHistoryHeader = 'История партий'; - static const gamesHistoryDescription = 'Смотри историю \nсыгранных партий'; - static const proVersion = 'Pro-версия'; - static const proVersionPrice = 120; -} diff --git a/frontend/lib/views/promo_view/promo_page_view.dart b/frontend/lib/views/promo_view/promo_page_view.dart index d7c8e60..70ef158 100644 --- a/frontend/lib/views/promo_view/promo_page_view.dart +++ b/frontend/lib/views/promo_view/promo_page_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:frontend/exports.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class PromoPageView extends StatelessWidget { const PromoPageView({super.key}); @@ -27,16 +28,13 @@ class PromoPageView extends StatelessWidget { maxHeight: MediaQuery.sizeOf(context).height * 0.07, ), child: UpgradeToProButton( - onTap: () { - context.read().isPro - ? _onDownGradeFromPro(context) - : context.read().upgradeToPro(); - context.pop(); - }, - price: context.read().isPro - ? null - : PromoPageConstants.proVersionPrice, - ), + onTap: () { + context.read().isPro + ? _onDownGradeFromPro(context) + : context.read().upgradeToPro(); + context.pop(); + }, + price: AppLocalizations.of(context)?.price), ), ) ], diff --git a/frontend/lib/views/promo_view/promo_view.dart b/frontend/lib/views/promo_view/promo_view.dart index c52e298..8be1cb0 100644 --- a/frontend/lib/views/promo_view/promo_view.dart +++ b/frontend/lib/views/promo_view/promo_view.dart @@ -1,3 +1,2 @@ export 'components/components.dart'; export 'promo_page_view.dart'; -export 'constants/constants.dart'; From ad3d1f5978d000fc745fa46ed466f2bb4ce73687 Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Fri, 30 May 2025 15:58:27 +0600 Subject: [PATCH 06/20] =?UTF-8?q?refactor:=20=D1=83=D0=B4=D0=B0=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BD=D0=B5=D0=BD=D1=83=D0=B6=D0=BD?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D0=B0=20?= =?UTF-8?q?=D0=B2=20=D0=B8=D0=BD=D0=B8=D1=86=D0=B8=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B0=D0=B9?= =?UTF-8?q?=D0=B4=D0=B5=D1=80=D0=B0=20=D0=BF=D1=80=D0=BE=20=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=D1=81=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/lib/providers/pro_version_provider.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/lib/providers/pro_version_provider.dart b/frontend/lib/providers/pro_version_provider.dart index 3e05dae..647106b 100644 --- a/frontend/lib/providers/pro_version_provider.dart +++ b/frontend/lib/providers/pro_version_provider.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:frontend/repositories/in_app_purchase_repository.dart'; import 'package:purchases_flutter/purchases_flutter.dart'; +//The key of a product should be the same as an entitlement name attached to it const _proVersionKey = 'chessknock_pro_version'; class ProVersionProvider extends ChangeNotifier { @@ -23,7 +24,6 @@ class ProVersionProvider extends ChangeNotifier { ? _setProStatus(true) : _setProStatus(false); }); - _setProStatus(await _repository.checkStatus(_proVersionKey)); } } From c324429d9f10349da2156504a1008c1e8c3eddca Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Sun, 1 Jun 2025 16:13:00 +0600 Subject: [PATCH 07/20] =?UTF-8?q?style:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC?= =?UTF-8?q?=D0=B5=D1=82=D1=80=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D0=B8=D1=81?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20non-nullable=20=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/l10n.yaml | 1 + .../lib/views/game_view/components/back_arrow_button.dart | 2 +- .../views/game_view/components/game_info_and_controls.dart | 3 ++- .../components/game_info_and_controls/game_status.dart | 2 +- .../game_info_and_controls/restart_exit_buttons.dart | 2 +- .../game_view/components/name_with_advantage_widget.dart | 2 +- .../lib/views/game_view/components/piece_choose_window.dart | 2 +- frontend/lib/views/game_view/game_page_view.dart | 2 +- .../lib/views/guide_view/components/guide_piece_carousel.dart | 2 +- frontend/lib/views/guide_view/guide_chose_view.dart | 2 +- frontend/lib/views/guide_view/guide_piece_view.dart | 2 +- .../lib/views/menu_view/components/pro_status_indicator.dart | 2 +- .../lib/views/menu_view/components/upgrade_to_pro_button.dart | 2 +- frontend/lib/views/menu_view/phone_menu_layout.dart | 2 +- frontend/lib/views/menu_view/tablet_menu_layout.dart | 4 ++-- .../party_history_view/components/one_party_view_widget.dart | 2 +- .../lib/views/party_history_view/party_history_main_view.dart | 2 +- .../lib/views/promo_view/components/promo_cards_section.dart | 2 +- .../lib/views/promo_view/components/promo_screen_header.dart | 2 +- frontend/lib/views/promo_view/promo_page_view.dart | 2 +- .../lib/views/setting_view/components/chose_color_widget.dart | 2 +- .../lib/views/setting_view/components/color_chose_button.dart | 2 +- .../lib/views/setting_view/components/set_time_section.dart | 2 +- .../views/setting_view/components/settings_rows_section.dart | 2 +- frontend/lib/views/setting_view/game_settings_view.dart | 2 +- 25 files changed, 27 insertions(+), 25 deletions(-) diff --git a/frontend/l10n.yaml b/frontend/l10n.yaml index f3781ca..ca68777 100644 --- a/frontend/l10n.yaml +++ b/frontend/l10n.yaml @@ -1,3 +1,4 @@ arb-dir: lib/l10n template-arb-file: en.arb output-localization-file: app_localizations.dart +nullable-getter: false diff --git a/frontend/lib/views/game_view/components/back_arrow_button.dart b/frontend/lib/views/game_view/components/back_arrow_button.dart index d494cf6..81bb910 100644 --- a/frontend/lib/views/game_view/components/back_arrow_button.dart +++ b/frontend/lib/views/game_view/components/back_arrow_button.dart @@ -11,7 +11,7 @@ class BackArrowButton extends StatelessWidget { @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); return SizedBox( height: MediaQuery.sizeOf(context).height * 0.04, child: Stack( diff --git a/frontend/lib/views/game_view/components/game_info_and_controls.dart b/frontend/lib/views/game_view/components/game_info_and_controls.dart index 1745f2c..a419c6c 100644 --- a/frontend/lib/views/game_view/components/game_info_and_controls.dart +++ b/frontend/lib/views/game_view/components/game_info_and_controls.dart @@ -29,7 +29,8 @@ class GameInfoAndControls extends StatelessWidget { gameModel, ) : ProFunctionsTooltip( - modalHeader: ModalStrings.moveBackModalText(AppLocalizations.of(context)!), + modalHeader: ModalStrings.moveBackModalText( + AppLocalizations.of(context)), isPro: isPro, child: UndoRedoButtons( gameModel, diff --git a/frontend/lib/views/game_view/components/game_info_and_controls/game_status.dart b/frontend/lib/views/game_view/components/game_info_and_controls/game_status.dart index b21d76e..9d8ec90 100644 --- a/frontend/lib/views/game_view/components/game_info_and_controls/game_status.dart +++ b/frontend/lib/views/game_view/components/game_info_and_controls/game_status.dart @@ -9,7 +9,7 @@ class GameStatus extends StatelessWidget { @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); return Consumer( builder: (context, gameModel, child) => Row( mainAxisAlignment: MainAxisAlignment.center, diff --git a/frontend/lib/views/game_view/components/game_info_and_controls/restart_exit_buttons.dart b/frontend/lib/views/game_view/components/game_info_and_controls/restart_exit_buttons.dart index 076b00a..0076899 100644 --- a/frontend/lib/views/game_view/components/game_info_and_controls/restart_exit_buttons.dart +++ b/frontend/lib/views/game_view/components/game_info_and_controls/restart_exit_buttons.dart @@ -13,7 +13,7 @@ class RestartExitButtons extends StatelessWidget { @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); return Row( children: [ Expanded( diff --git a/frontend/lib/views/game_view/components/name_with_advantage_widget.dart b/frontend/lib/views/game_view/components/name_with_advantage_widget.dart index 1bdc8a6..d6b4975 100644 --- a/frontend/lib/views/game_view/components/name_with_advantage_widget.dart +++ b/frontend/lib/views/game_view/components/name_with_advantage_widget.dart @@ -12,7 +12,7 @@ class NameWithAdvantageForPlayer extends StatelessWidget { @override Widget build(BuildContext context) { - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, diff --git a/frontend/lib/views/game_view/components/piece_choose_window.dart b/frontend/lib/views/game_view/components/piece_choose_window.dart index 6012716..3ba6a8f 100644 --- a/frontend/lib/views/game_view/components/piece_choose_window.dart +++ b/frontend/lib/views/game_view/components/piece_choose_window.dart @@ -10,7 +10,7 @@ class PieceChooseWindow extends StatelessWidget { @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); return Container( width: 245, height: 275, diff --git a/frontend/lib/views/game_view/game_page_view.dart b/frontend/lib/views/game_view/game_page_view.dart index 8c58ca6..33c3908 100644 --- a/frontend/lib/views/game_view/game_page_view.dart +++ b/frontend/lib/views/game_view/game_page_view.dart @@ -35,7 +35,7 @@ class _GameViewState extends State { final scheme = Theme.of(context).colorScheme; final deviceWidth = MediaQuery.of(context).size.width; final deviceHeight = MediaQuery.of(context).size.height; - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); return isLoading ? const LoadingWidget() : Scaffold( diff --git a/frontend/lib/views/guide_view/components/guide_piece_carousel.dart b/frontend/lib/views/guide_view/components/guide_piece_carousel.dart index 5545c95..67e3c22 100644 --- a/frontend/lib/views/guide_view/components/guide_piece_carousel.dart +++ b/frontend/lib/views/guide_view/components/guide_piece_carousel.dart @@ -17,7 +17,7 @@ class GuidePieceCarousel extends StatelessWidget { @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); return Column( children: [ diff --git a/frontend/lib/views/guide_view/guide_chose_view.dart b/frontend/lib/views/guide_view/guide_chose_view.dart index be7b98d..6a2c5c6 100644 --- a/frontend/lib/views/guide_view/guide_chose_view.dart +++ b/frontend/lib/views/guide_view/guide_chose_view.dart @@ -34,7 +34,7 @@ class GuideChoseView extends StatelessWidget { @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); return Scaffold( backgroundColor: scheme.background, body: SafeArea( diff --git a/frontend/lib/views/guide_view/guide_piece_view.dart b/frontend/lib/views/guide_view/guide_piece_view.dart index d36c097..037805c 100644 --- a/frontend/lib/views/guide_view/guide_piece_view.dart +++ b/frontend/lib/views/guide_view/guide_piece_view.dart @@ -67,7 +67,7 @@ class _GuideViewState extends State { iconName: GuideStrings.appbarIcon, iconColor: scheme.onTertiary, bottomMargin: 32, - header: AppLocalizations.of(context)!.guideHeader, + header: AppLocalizations.of(context).guideHeader, ), Expanded( child: GuidePieceCarousel( diff --git a/frontend/lib/views/menu_view/components/pro_status_indicator.dart b/frontend/lib/views/menu_view/components/pro_status_indicator.dart index 48d4e32..0d391a6 100644 --- a/frontend/lib/views/menu_view/components/pro_status_indicator.dart +++ b/frontend/lib/views/menu_view/components/pro_status_indicator.dart @@ -18,7 +18,7 @@ class ProStatusIndicator extends StatelessWidget { Rect.fromLTWH(0, 0, bounds.width, bounds.height)); }, child: Text( - AppLocalizations.of(context)!.pro, + AppLocalizations.of(context).pro, style: TextStyles.body1.copyWith(color: ColorsConst.neutralColor0), )), diff --git a/frontend/lib/views/menu_view/components/upgrade_to_pro_button.dart b/frontend/lib/views/menu_view/components/upgrade_to_pro_button.dart index 7c5a788..2d1d613 100644 --- a/frontend/lib/views/menu_view/components/upgrade_to_pro_button.dart +++ b/frontend/lib/views/menu_view/components/upgrade_to_pro_button.dart @@ -30,7 +30,7 @@ class UpgradeToProButton extends StatelessWidget { right: 12, ); final double iconSize = isExtended ? 29 : 24; - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); return GestureDetector( onTap: onTap, child: DecoratedBox( diff --git a/frontend/lib/views/menu_view/phone_menu_layout.dart b/frontend/lib/views/menu_view/phone_menu_layout.dart index 5be2516..42c0079 100644 --- a/frontend/lib/views/menu_view/phone_menu_layout.dart +++ b/frontend/lib/views/menu_view/phone_menu_layout.dart @@ -20,7 +20,7 @@ class PhoneMenuView extends StatelessWidget { final width = MediaQuery.of(context).size.width; final height = MediaQuery.of(context).size.height; final colorScheme = Theme.of(context).colorScheme; - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); return ConstrainedBox( constraints: BoxConstraints(minWidth: width, minHeight: height), diff --git a/frontend/lib/views/menu_view/tablet_menu_layout.dart b/frontend/lib/views/menu_view/tablet_menu_layout.dart index a0f0c74..891aee7 100644 --- a/frontend/lib/views/menu_view/tablet_menu_layout.dart +++ b/frontend/lib/views/menu_view/tablet_menu_layout.dart @@ -20,7 +20,7 @@ class TabletMenuView extends StatelessWidget { final width = MediaQuery.of(context).size.width; final height = MediaQuery.of(context).size.height; final colorScheme = Theme.of(context).colorScheme; - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); return ConstrainedBox( constraints: BoxConstraints(minWidth: width, minHeight: height), @@ -77,7 +77,7 @@ class TabletMenuView extends StatelessWidget { child: MenuButton( gameModel: gameModel, height: height * 0.24, - buttonText: AppLocalizations.of(context)!.startGame, + buttonText: AppLocalizations.of(context).startGame, settingsScreenRoute: RouteLocations.settingsScreen, ), ), diff --git a/frontend/lib/views/party_history_view/components/one_party_view_widget.dart b/frontend/lib/views/party_history_view/components/one_party_view_widget.dart index b6164e5..0a5b1bf 100644 --- a/frontend/lib/views/party_history_view/components/one_party_view_widget.dart +++ b/frontend/lib/views/party_history_view/components/one_party_view_widget.dart @@ -13,7 +13,7 @@ class OnePartyViewWidget extends StatelessWidget { @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); Map computerListOfColorsIcons = { l10n.victory: scheme.onSecondaryContainer, l10n.defeat: scheme.primary, diff --git a/frontend/lib/views/party_history_view/party_history_main_view.dart b/frontend/lib/views/party_history_view/party_history_main_view.dart index bb34566..01a73c2 100644 --- a/frontend/lib/views/party_history_view/party_history_main_view.dart +++ b/frontend/lib/views/party_history_view/party_history_main_view.dart @@ -57,7 +57,7 @@ class _PartyHistoryMainViewState extends State { iconName: PartyHistoryConst.appbarMainIcon, iconColor: scheme.onTertiary, bottomMargin: 32, - header: AppLocalizations.of(context)!.gameHistory, + header: AppLocalizations.of(context).gameHistory, ), Container( margin: const EdgeInsets.symmetric(horizontal: 24), diff --git a/frontend/lib/views/promo_view/components/promo_cards_section.dart b/frontend/lib/views/promo_view/components/promo_cards_section.dart index 146a105..ebc194d 100644 --- a/frontend/lib/views/promo_view/components/promo_cards_section.dart +++ b/frontend/lib/views/promo_view/components/promo_cards_section.dart @@ -11,7 +11,7 @@ class PromoCardsSection extends StatelessWidget { @override Widget build(BuildContext context) { - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); return Column( children: [ PromoFeatureCard( diff --git a/frontend/lib/views/promo_view/components/promo_screen_header.dart b/frontend/lib/views/promo_view/components/promo_screen_header.dart index c03852a..69ed10b 100644 --- a/frontend/lib/views/promo_view/components/promo_screen_header.dart +++ b/frontend/lib/views/promo_view/components/promo_screen_header.dart @@ -28,7 +28,7 @@ class PromoScreenHeader extends StatelessWidget { Center( child: FittedBox( child: Text( - AppLocalizations.of(context)!.proVersion, + AppLocalizations.of(context).proVersion, style: TextStyles.header2 .copyWith(color: scheme.primary, height: 1.3), ), diff --git a/frontend/lib/views/promo_view/promo_page_view.dart b/frontend/lib/views/promo_view/promo_page_view.dart index 70ef158..5d5e9a4 100644 --- a/frontend/lib/views/promo_view/promo_page_view.dart +++ b/frontend/lib/views/promo_view/promo_page_view.dart @@ -34,7 +34,7 @@ class PromoPageView extends StatelessWidget { : context.read().upgradeToPro(); context.pop(); }, - price: AppLocalizations.of(context)?.price), + price: AppLocalizations.of(context).price), ), ) ], diff --git a/frontend/lib/views/setting_view/components/chose_color_widget.dart b/frontend/lib/views/setting_view/components/chose_color_widget.dart index d00502a..6e85bba 100644 --- a/frontend/lib/views/setting_view/components/chose_color_widget.dart +++ b/frontend/lib/views/setting_view/components/chose_color_widget.dart @@ -17,7 +17,7 @@ class ChoseColorWidget extends StatelessWidget { return Column( children: [ TextHeading( - text: AppLocalizations.of(context)!.pieceColor, + text: AppLocalizations.of(context).pieceColor, topMargin: 32, bottomMargin: 16, ), diff --git a/frontend/lib/views/setting_view/components/color_chose_button.dart b/frontend/lib/views/setting_view/components/color_chose_button.dart index 7967863..b40b6d5 100644 --- a/frontend/lib/views/setting_view/components/color_chose_button.dart +++ b/frontend/lib/views/setting_view/components/color_chose_button.dart @@ -18,7 +18,7 @@ class ColorChoseButton extends StatelessWidget { @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); Map icon = { Player.player2: "black.svg", Player.player1: "white.svg", diff --git a/frontend/lib/views/setting_view/components/set_time_section.dart b/frontend/lib/views/setting_view/components/set_time_section.dart index e27c4ec..a1719dd 100644 --- a/frontend/lib/views/setting_view/components/set_time_section.dart +++ b/frontend/lib/views/setting_view/components/set_time_section.dart @@ -17,7 +17,7 @@ class SetTimeSection extends StatelessWidget { @override Widget build(BuildContext context) { - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); return Column( children: [ ChoseTimeCarousel( diff --git a/frontend/lib/views/setting_view/components/settings_rows_section.dart b/frontend/lib/views/setting_view/components/settings_rows_section.dart index 1e75714..ec9e990 100644 --- a/frontend/lib/views/setting_view/components/settings_rows_section.dart +++ b/frontend/lib/views/setting_view/components/settings_rows_section.dart @@ -21,7 +21,7 @@ class SettingsRowsSection extends StatelessWidget { @override Widget build(BuildContext context) { - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/frontend/lib/views/setting_view/game_settings_view.dart b/frontend/lib/views/setting_view/game_settings_view.dart index 4ec8b3f..e8466e1 100644 --- a/frontend/lib/views/setting_view/game_settings_view.dart +++ b/frontend/lib/views/setting_view/game_settings_view.dart @@ -157,7 +157,7 @@ class _GameSettingsViewState extends State @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); return isLoading ? const LoadingWidget() : DefaultTabController( From 92475a8d596f6444f57624bf9a8187b65ec46110 Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Sun, 1 Jun 2025 16:23:36 +0600 Subject: [PATCH 08/20] =?UTF-8?q?style:=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D0=BE=D0=B9=20=D1=81=20=D0=B0=D0=BF=D0=B8=20=D0=BA?= =?UTF-8?q?=D0=BB=D1=8E=D1=87=D0=BE=D0=BC=20=D0=B2=20=D0=BA=D0=BB=D0=B0?= =?UTF-8?q?=D1=81=D1=81,=20=D0=B3=D0=B4=D0=B5=20=D0=BE=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D1=83=D0=B5=D1=82?= =?UTF-8?q?=D1=81=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/lib/providers/pro_version_provider.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/lib/providers/pro_version_provider.dart b/frontend/lib/providers/pro_version_provider.dart index 647106b..01891ad 100644 --- a/frontend/lib/providers/pro_version_provider.dart +++ b/frontend/lib/providers/pro_version_provider.dart @@ -4,10 +4,10 @@ import 'package:flutter/material.dart'; import 'package:frontend/repositories/in_app_purchase_repository.dart'; import 'package:purchases_flutter/purchases_flutter.dart'; -//The key of a product should be the same as an entitlement name attached to it -const _proVersionKey = 'chessknock_pro_version'; - class ProVersionProvider extends ChangeNotifier { + //The key of a product should be the same as an entitlement name attached to it + static const _proVersionKey = 'chessknock_pro_version'; + bool _isProStatus = false; final InAppPurchaseRepository _repository; From e8d8362445c59c616dd8cf13971f5d133bbf4917 Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Sun, 1 Jun 2025 16:40:01 +0600 Subject: [PATCH 09/20] =?UTF-8?q?refactor:=20=D0=B2=D1=8B=D0=BD=D0=B5?= =?UTF-8?q?=D1=81=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B0=D1=81=D1=81=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=B2=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C?= =?UTF-8?q?=D0=BD=D1=8B=D0=B5=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/lib/constants/assets.dart | 28 +++++++++++++++++++ .../lib/views/components/button_to_guide.dart | 3 +- .../lib/views/components/loading_widget.dart | 3 +- .../components/pro_functions_tooltip.dart | 2 +- .../name_with_advantage_widget.dart | 3 +- .../components/piece_choose_window.dart | 9 +++--- .../lib/views/game_view/game_page_view.dart | 3 +- .../components/guide_chose_piece_button.dart | 3 +- .../components/points_indicator.dart | 3 +- .../menu_view/components/custom_switch.dart | 7 ++--- .../components/pro_status_indicator.dart | 3 +- .../components/upgrade_to_pro_button.dart | 3 +- .../components/promo_cards_section.dart | 13 +++++---- .../components/promo_screen_header.dart | 3 +- .../components/app_bar_settings.dart | 3 +- .../components/chose_time_carousel.dart | 5 ++-- .../setting_view/components/settings_row.dart | 3 +- 17 files changed, 69 insertions(+), 28 deletions(-) create mode 100644 frontend/lib/constants/assets.dart diff --git a/frontend/lib/constants/assets.dart b/frontend/lib/constants/assets.dart new file mode 100644 index 0000000..5bcb570 --- /dev/null +++ b/frontend/lib/constants/assets.dart @@ -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'; +} diff --git a/frontend/lib/views/components/button_to_guide.dart b/frontend/lib/views/components/button_to_guide.dart index 312b2eb..82698f1 100644 --- a/frontend/lib/views/components/button_to_guide.dart +++ b/frontend/lib/views/components/button_to_guide.dart @@ -1,5 +1,6 @@ import "package:flutter/material.dart"; import "package:flutter_svg/svg.dart"; +import "package:frontend/constants/assets.dart"; class ButtonToGuide extends StatelessWidget { const ButtonToGuide( @@ -29,7 +30,7 @@ class ButtonToGuide extends StatelessWidget { constraints: const BoxConstraints(), color: backGroundColor, icon: SvgPicture.asset( - "assets/images/icons/handbook.svg", + Assets.handbook, ), ), ); diff --git a/frontend/lib/views/components/loading_widget.dart b/frontend/lib/views/components/loading_widget.dart index cba71bd..4ea0466 100644 --- a/frontend/lib/views/components/loading_widget.dart +++ b/frontend/lib/views/components/loading_widget.dart @@ -1,5 +1,6 @@ import "package:flutter/material.dart"; import "package:flutter_svg/flutter_svg.dart"; +import "package:frontend/constants/assets.dart"; import "package:frontend/exports.dart"; class LoadingWidget extends StatelessWidget { @@ -16,7 +17,7 @@ class LoadingWidget extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ SvgPicture.asset( - "assets/images/icons/loading.svg", + Assets.loading, colorFilter: ColorFilter.mode(scheme.primary, BlendMode.srcIn), ), const SizedBox( diff --git a/frontend/lib/views/components/pro_functions_tooltip.dart b/frontend/lib/views/components/pro_functions_tooltip.dart index fef1d50..0cb49f8 100644 --- a/frontend/lib/views/components/pro_functions_tooltip.dart +++ b/frontend/lib/views/components/pro_functions_tooltip.dart @@ -55,7 +55,7 @@ class _ProFunctionsTooltipState extends State { Padding( padding: const EdgeInsets.symmetric(vertical: 12), child: Text( - AppLocalizations.of(context)!.proFeatureDescription, + AppLocalizations.of(context).proFeatureDescription, style: TextStyles.caption1.copyWith( color: ColorsConst.neutralColor100, height: 1.3, diff --git a/frontend/lib/views/game_view/components/name_with_advantage_widget.dart b/frontend/lib/views/game_view/components/name_with_advantage_widget.dart index d6b4975..583fb03 100644 --- a/frontend/lib/views/game_view/components/name_with_advantage_widget.dart +++ b/frontend/lib/views/game_view/components/name_with_advantage_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:frontend/constants/assets.dart'; import '../../../exports.dart'; // ignore: must_be_immutable @@ -33,7 +34,7 @@ class NameWithAdvantageForPlayer extends StatelessWidget { gameModel.advantageForPlayer(player)) ...[ Row( children: [ - SvgPicture.asset('assets/images/icons/advantage.svg'), + SvgPicture.asset(Assets.advantage), Text( "+${gameModel.advantageForPlayer(oppositePlayer(player)) - gameModel.advantageForPlayer(player)}", textAlign: TextAlign.center, diff --git a/frontend/lib/views/game_view/components/piece_choose_window.dart b/frontend/lib/views/game_view/components/piece_choose_window.dart index 3ba6a8f..10f2bbf 100644 --- a/frontend/lib/views/game_view/components/piece_choose_window.dart +++ b/frontend/lib/views/game_view/components/piece_choose_window.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:frontend/constants/assets.dart'; import '../../../exports.dart'; class PieceChooseWindow extends StatelessWidget { @@ -42,7 +43,7 @@ class PieceChooseWindow extends StatelessWidget { children: [ IconButton( icon: SvgPicture.asset( - 'assets/images/pieces/bishop.svg', + AssetsPieces.bishop, width: 55, height: 55, colorFilter: @@ -55,7 +56,7 @@ class PieceChooseWindow extends StatelessWidget { ), IconButton( icon: SvgPicture.asset( - 'assets/images/pieces/rook.svg', + AssetsPieces.rook, width: 55, height: 55, colorFilter: @@ -74,7 +75,7 @@ class PieceChooseWindow extends StatelessWidget { children: [ IconButton( icon: SvgPicture.asset( - 'assets/images/pieces/knight.svg', + AssetsPieces.knight, width: 55, height: 55, colorFilter: @@ -87,7 +88,7 @@ class PieceChooseWindow extends StatelessWidget { ), IconButton( icon: SvgPicture.asset( - 'assets/images/pieces/queen.svg', + AssetsPieces.queen, width: 55, height: 55, colorFilter: diff --git a/frontend/lib/views/game_view/game_page_view.dart b/frontend/lib/views/game_view/game_page_view.dart index 33c3908..baea923 100644 --- a/frontend/lib/views/game_view/game_page_view.dart +++ b/frontend/lib/views/game_view/game_page_view.dart @@ -1,5 +1,6 @@ import "package:flutter/material.dart"; import "package:flutter_svg/flutter_svg.dart"; +import "package:frontend/constants/assets.dart"; import "package:go_router/go_router.dart"; import "package:provider/provider.dart"; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -80,7 +81,7 @@ class _GameViewState extends State { Align( alignment: Alignment.topCenter, child: SvgPicture.asset( - "assets/images/board.svg", + Assets.board, width: deviceWidth, height: deviceWidth * LogicConsts.boardRatio, diff --git a/frontend/lib/views/guide_view/components/guide_chose_piece_button.dart b/frontend/lib/views/guide_view/components/guide_chose_piece_button.dart index 5fe5212..526e037 100644 --- a/frontend/lib/views/guide_view/components/guide_chose_piece_button.dart +++ b/frontend/lib/views/guide_view/components/guide_chose_piece_button.dart @@ -1,5 +1,6 @@ import "package:flutter/material.dart"; import "package:flutter_svg/flutter_svg.dart"; +import "package:frontend/constants/assets.dart"; import "package:provider/provider.dart"; import "../../../exports.dart"; @@ -75,7 +76,7 @@ class GuideChosePieceButton extends StatelessWidget { ], ), SvgPicture.asset( - "assets/images/icons/back_arrow_icon.svg", + Assets.backArrowIcon, colorFilter: ColorFilter.mode(iconArrowColor, BlendMode.srcIn), ), ], diff --git a/frontend/lib/views/guide_view/components/points_indicator.dart b/frontend/lib/views/guide_view/components/points_indicator.dart index c46ab4f..7d05303 100644 --- a/frontend/lib/views/guide_view/components/points_indicator.dart +++ b/frontend/lib/views/guide_view/components/points_indicator.dart @@ -1,5 +1,6 @@ import "package:flutter/material.dart"; import "package:flutter_svg/svg.dart"; +import "package:frontend/constants/assets.dart"; class PointsIndicator extends StatelessWidget { const PointsIndicator( @@ -16,7 +17,7 @@ class PointsIndicator extends StatelessWidget { return Row( children: [ SvgPicture.asset( - "assets/images/icons/point_light.svg", + Assets.pointLight, width: 10, height: 10, colorFilter: ColorFilter.mode( diff --git a/frontend/lib/views/menu_view/components/custom_switch.dart b/frontend/lib/views/menu_view/components/custom_switch.dart index 4dc44d2..8b176b0 100644 --- a/frontend/lib/views/menu_view/components/custom_switch.dart +++ b/frontend/lib/views/menu_view/components/custom_switch.dart @@ -1,5 +1,6 @@ import "package:flutter/material.dart"; import "package:flutter_svg/flutter_svg.dart"; +import "package:frontend/constants/assets.dart"; import "package:frontend/exports.dart"; class CustomSwitch extends StatefulWidget { @@ -11,9 +12,6 @@ class CustomSwitch extends StatefulWidget { } class _CustomSwitchState extends State { - final activeIcon = "assets/images/icons/dark_switch.svg"; - final inactiveIcon = "assets/images/icons/light_switch.svg"; - @override Widget build(BuildContext context) { bool isToggle = widget.provider.isDarkMode; @@ -36,7 +34,8 @@ class _CustomSwitchState extends State { height: 34, decoration: const BoxDecoration( shape: BoxShape.circle, color: Colors.white), - child: SvgPicture.asset(isToggle ? activeIcon : inactiveIcon), + child: SvgPicture.asset( + isToggle ? Assets.darkSwitch : Assets.lightSwitch), ), ), ), diff --git a/frontend/lib/views/menu_view/components/pro_status_indicator.dart b/frontend/lib/views/menu_view/components/pro_status_indicator.dart index 0d391a6..27de15a 100644 --- a/frontend/lib/views/menu_view/components/pro_status_indicator.dart +++ b/frontend/lib/views/menu_view/components/pro_status_indicator.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:frontend/constants/assets.dart'; import 'package:frontend/exports.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -28,7 +29,7 @@ class ProStatusIndicator extends StatelessWidget { Rect.fromLTWH(0, 0, bounds.width, bounds.height)); }, child: SvgPicture.asset( - 'assets/images/icons/pro_sparkles.svg', + Assets.proSparkles, )), ], ); diff --git a/frontend/lib/views/menu_view/components/upgrade_to_pro_button.dart b/frontend/lib/views/menu_view/components/upgrade_to_pro_button.dart index 2d1d613..ee7c2e4 100644 --- a/frontend/lib/views/menu_view/components/upgrade_to_pro_button.dart +++ b/frontend/lib/views/menu_view/components/upgrade_to_pro_button.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:frontend/constants/assets.dart'; import 'package:frontend/exports.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -65,7 +66,7 @@ class UpgradeToProButton extends StatelessWidget { fit: BoxFit.fill, height: iconSize, width: iconSize, - 'assets/images/icons/pro_sparkles.svg', + Assets.proSparkles, colorFilter: const ColorFilter.mode( ColorsConst.neutralColor0, BlendMode.srcIn), ), diff --git a/frontend/lib/views/promo_view/components/promo_cards_section.dart b/frontend/lib/views/promo_view/components/promo_cards_section.dart index ebc194d..477cd44 100644 --- a/frontend/lib/views/promo_view/components/promo_cards_section.dart +++ b/frontend/lib/views/promo_view/components/promo_cards_section.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:frontend/constants/assets.dart'; import 'package:frontend/exports.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -23,9 +24,9 @@ class PromoCardsSection extends StatelessWidget { columnAlignment: CrossAxisAlignment.end, backgroundImages: [ SvgPicture.asset( - 'assets/images/icons/sun.svg', + Assets.sun, ), - SvgPicture.asset('assets/images/icons/moon.svg') + SvgPicture.asset(Assets.moon) ], ), const SizedBox(height: 10), @@ -36,7 +37,7 @@ class PromoCardsSection extends StatelessWidget { columnAlignment: CrossAxisAlignment.start, backgroundImages: [ SvgPicture.asset( - 'assets/images/icons/chess_piece.svg', + Assets.chessPiece, ) ], ), @@ -50,7 +51,7 @@ class PromoCardsSection extends StatelessWidget { gradient: GradientConsts.orange, backgroundImages: [ SvgPicture.asset( - 'assets/images/icons/explosion.svg', + Assets.explosion, ) ], )), @@ -62,7 +63,7 @@ class PromoCardsSection extends StatelessWidget { gradient: GradientConsts.orange, backgroundImages: [ SvgPicture.asset( - 'assets/images/icons/lamp.svg', + Assets.lamp, ) ], )) @@ -76,7 +77,7 @@ class PromoCardsSection extends StatelessWidget { gradient: GradientConsts.lightGrey, backgroundImages: [ SvgPicture.asset( - 'assets/images/icons/book.svg', + Assets.book, ) ], ), diff --git a/frontend/lib/views/promo_view/components/promo_screen_header.dart b/frontend/lib/views/promo_view/components/promo_screen_header.dart index 69ed10b..d3674da 100644 --- a/frontend/lib/views/promo_view/components/promo_screen_header.dart +++ b/frontend/lib/views/promo_view/components/promo_screen_header.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:frontend/constants/assets.dart'; import 'package:frontend/exports.dart'; import 'package:go_router/go_router.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -18,7 +19,7 @@ class PromoScreenHeader extends StatelessWidget { alignment: AlignmentDirectional.centerStart, children: [ CustomIconButton( - iconName: "assets/images/icons/left_big_arrow_icon.svg", + iconName: Assets.leftBigArrowIcon, color: scheme.onTertiary, iconSize: 40, onTap: () { diff --git a/frontend/lib/views/setting_view/components/app_bar_settings.dart b/frontend/lib/views/setting_view/components/app_bar_settings.dart index bed9a94..f111f20 100644 --- a/frontend/lib/views/setting_view/components/app_bar_settings.dart +++ b/frontend/lib/views/setting_view/components/app_bar_settings.dart @@ -1,4 +1,5 @@ import "package:flutter/material.dart"; +import "package:frontend/constants/assets.dart"; import "package:go_router/go_router.dart"; import "../../../exports.dart"; @@ -14,7 +15,7 @@ class AppBarSettings extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ CustomIconButton( - iconName: "assets/images/icons/left_big_arrow_icon.svg", + iconName: Assets.leftBigArrowIcon, color: scheme.onTertiary, iconSize: 40, onTap: () { diff --git a/frontend/lib/views/setting_view/components/chose_time_carousel.dart b/frontend/lib/views/setting_view/components/chose_time_carousel.dart index b65b5f3..59ce39b 100644 --- a/frontend/lib/views/setting_view/components/chose_time_carousel.dart +++ b/frontend/lib/views/setting_view/components/chose_time_carousel.dart @@ -1,4 +1,5 @@ import "package:flutter/material.dart"; +import "package:frontend/constants/assets.dart"; import "package:provider/provider.dart"; import "package:wheel_chooser/wheel_chooser.dart"; @@ -55,7 +56,7 @@ class _ChoseTimeCarouselState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ CustomIconButton( - iconName: "assets/images/icons/left_big_arrow_icon.svg", + iconName: Assets.leftBigArrowIcon, color: scheme.onTertiary, iconSize: 30, onTap: () { @@ -90,7 +91,7 @@ class _ChoseTimeCarouselState extends State { ), ), CustomIconButton( - iconName: "assets/images/icons/right_big_arrow_icon.svg", + iconName: Assets.rightBigArrowIcon, color: scheme.onTertiary, iconSize: 30, onTap: () { diff --git a/frontend/lib/views/setting_view/components/settings_row.dart b/frontend/lib/views/setting_view/components/settings_row.dart index 4ecdaaf..5d4dbf5 100644 --- a/frontend/lib/views/setting_view/components/settings_row.dart +++ b/frontend/lib/views/setting_view/components/settings_row.dart @@ -1,5 +1,6 @@ import "package:flutter/material.dart"; import "package:flutter_svg/flutter_svg.dart"; +import "package:frontend/constants/assets.dart"; import "package:frontend/exports.dart"; import "package:provider/provider.dart"; @@ -45,7 +46,7 @@ class SettingsRow extends StatelessWidget { isPro: isPro, modalHeader: modalHeader, child: SvgPicture.asset( - "assets/images/icons/question_icon.svg", + Assets.questionIcon, colorFilter: ColorFilter.mode( isPro ? scheme.tertiaryContainer From fc0cb7b73630dae54017d121c738af4299d863d0 Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Sun, 1 Jun 2025 17:28:45 +0600 Subject: [PATCH 10/20] =?UTF-8?q?fix:=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D1=8F=20listener'a=20=D0=BD=D0=B0=20=D0=BE?= =?UTF-8?q?=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D1=81=D0=BA=D0=BE=D0=B9=20=D0=B8=D0=BD=D1=84=D0=BE=D1=80?= =?UTF-8?q?=D0=BC=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B2=20=D0=B4=D0=B0=D1=82?= =?UTF-8?q?=D0=B0=20=D1=81=D0=BE=D1=80=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in_app_purchase_data_source.dart | 1 + .../data_sources/revenuecat_data_source.dart | 20 +++++++++++++++++++ .../lib/providers/pro_version_provider.dart | 11 +--------- .../in_app_purchase_repository.dart | 4 ++++ 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/frontend/lib/data_sources/in_app_purchase_data_source.dart b/frontend/lib/data_sources/in_app_purchase_data_source.dart index 3cbbf5c..deababe 100644 --- a/frontend/lib/data_sources/in_app_purchase_data_source.dart +++ b/frontend/lib/data_sources/in_app_purchase_data_source.dart @@ -1,4 +1,5 @@ abstract interface class IInAppPurchaseDataSource { Future buyProduct(String productId); Future checkStatus(String productId); + void init(Function onInit); } diff --git a/frontend/lib/data_sources/revenuecat_data_source.dart b/frontend/lib/data_sources/revenuecat_data_source.dart index 503b3d9..aaf4e12 100644 --- a/frontend/lib/data_sources/revenuecat_data_source.dart +++ b/frontend/lib/data_sources/revenuecat_data_source.dart @@ -1,7 +1,11 @@ +import 'dart:io'; + import 'package:frontend/data_sources/in_app_purchase_data_source.dart'; import 'package:purchases_flutter/purchases_flutter.dart'; class RevenueCatDataSource implements IInAppPurchaseDataSource { + //The key of a product should be the same as an entitlement name attached to it + static const _proVersionKey = 'chessknock_pro_version'; @override Future buyProduct(String productId) async { final product = (await Purchases.getProducts([productId])).first; @@ -16,4 +20,20 @@ class RevenueCatDataSource implements IInAppPurchaseDataSource { final customerInfo = await Purchases.getCustomerInfo(); return customerInfo.entitlements.active.containsKey(productId); } + + @override + void init(Function function) { + try { + final callback = function as void Function(bool); + if (Platform.isIOS) { + Purchases.addCustomerInfoUpdateListener((info) { + info.entitlements.active.containsKey(_proVersionKey) + ? callback(true) + : callback(false); + }); + } + } catch (e) { + throw Exception('function type should be void Function(bool)'); + } + } } diff --git a/frontend/lib/providers/pro_version_provider.dart b/frontend/lib/providers/pro_version_provider.dart index 01891ad..dc8de6a 100644 --- a/frontend/lib/providers/pro_version_provider.dart +++ b/frontend/lib/providers/pro_version_provider.dart @@ -1,8 +1,5 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:frontend/repositories/in_app_purchase_repository.dart'; -import 'package:purchases_flutter/purchases_flutter.dart'; class ProVersionProvider extends ChangeNotifier { //The key of a product should be the same as an entitlement name attached to it @@ -18,13 +15,7 @@ class ProVersionProvider extends ChangeNotifier { } void _init() async { - if (Platform.isIOS) { - Purchases.addCustomerInfoUpdateListener((info) { - info.entitlements.active.containsKey(_proVersionKey) - ? _setProStatus(true) - : _setProStatus(false); - }); - } + _repository.init(_setProStatus); } void _setProStatus(bool status) { diff --git a/frontend/lib/repositories/in_app_purchase_repository.dart b/frontend/lib/repositories/in_app_purchase_repository.dart index 8a7135d..c7faa8e 100644 --- a/frontend/lib/repositories/in_app_purchase_repository.dart +++ b/frontend/lib/repositories/in_app_purchase_repository.dart @@ -12,4 +12,8 @@ class InAppPurchaseRepository { Future checkStatus(String productId) { return dataSource.checkStatus(productId); } + + void init(Function onInit) { + dataSource.init(onInit); + } } From 4218d0441cfbb760f92ca5f032ff84194c846c73 Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Sun, 1 Jun 2025 17:59:08 +0600 Subject: [PATCH 11/20] =?UTF-8?q?fix:=20=D1=83=D0=BB=D1=83=D1=87=D1=88?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=87=D0=B8=D1=82=D0=B0=D0=B1=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D0=BE=D1=81=D1=82=D0=B8=20=D0=BC=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B4=D0=BE=D0=B2=20+=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=BA=D0=B8=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=BA=D1=83=D0=BF=D0=BA=D0=B8=20=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data_sources/revenuecat_data_source.dart | 7 ++--- .../lib/providers/pro_version_provider.dart | 15 +++++++++- .../in_app_purchase_repository.dart | 2 +- .../lib/views/promo_view/promo_page_view.dart | 30 ++++++++++++++----- 4 files changed, 40 insertions(+), 14 deletions(-) diff --git a/frontend/lib/data_sources/revenuecat_data_source.dart b/frontend/lib/data_sources/revenuecat_data_source.dart index aaf4e12..baf91c7 100644 --- a/frontend/lib/data_sources/revenuecat_data_source.dart +++ b/frontend/lib/data_sources/revenuecat_data_source.dart @@ -9,10 +9,9 @@ class RevenueCatDataSource implements IInAppPurchaseDataSource { @override Future buyProduct(String productId) async { final product = (await Purchases.getProducts([productId])).first; - return (await Purchases.purchaseStoreProduct(product)) - .entitlements - .active[productId] != - null; + final customerInfo = await Purchases.purchaseStoreProduct(product); + final isPro = customerInfo.entitlements.active[productId] != null; + return isPro; } @override diff --git a/frontend/lib/providers/pro_version_provider.dart b/frontend/lib/providers/pro_version_provider.dart index dc8de6a..1aa9996 100644 --- a/frontend/lib/providers/pro_version_provider.dart +++ b/frontend/lib/providers/pro_version_provider.dart @@ -6,6 +6,7 @@ class ProVersionProvider extends ChangeNotifier { static const _proVersionKey = 'chessknock_pro_version'; bool _isProStatus = false; + bool _isPurchaseError = false; final InAppPurchaseRepository _repository; @@ -23,8 +24,19 @@ class ProVersionProvider extends ChangeNotifier { notifyListeners(); } + void _setPurchaseError(bool isError) { + _isPurchaseError = isError; + notifyListeners(); + } + void upgradeToPro() async { - _setProStatus(await _repository.buyProduct(_proVersionKey)); + try { + final isPro = await _repository.buyProduct(_proVersionKey); + _setProStatus(isPro); + _setPurchaseError(false); + } catch (_) { + _setPurchaseError(true); + } } void downgradeFromPro() { @@ -32,4 +44,5 @@ class ProVersionProvider extends ChangeNotifier { } bool get isPro => _isProStatus; + bool get isPurchaseError => _isPurchaseError; } diff --git a/frontend/lib/repositories/in_app_purchase_repository.dart b/frontend/lib/repositories/in_app_purchase_repository.dart index c7faa8e..d896570 100644 --- a/frontend/lib/repositories/in_app_purchase_repository.dart +++ b/frontend/lib/repositories/in_app_purchase_repository.dart @@ -5,7 +5,7 @@ class InAppPurchaseRepository { InAppPurchaseRepository({required this.dataSource}); - Future buyProduct(String productId) async { + Future buyProduct(String productId) { return dataSource.buyProduct(productId); } diff --git a/frontend/lib/views/promo_view/promo_page_view.dart b/frontend/lib/views/promo_view/promo_page_view.dart index 5d5e9a4..5a85a25 100644 --- a/frontend/lib/views/promo_view/promo_page_view.dart +++ b/frontend/lib/views/promo_view/promo_page_view.dart @@ -27,14 +27,28 @@ class PromoPageView extends StatelessWidget { constraints: BoxConstraints( maxHeight: MediaQuery.sizeOf(context).height * 0.07, ), - child: UpgradeToProButton( - onTap: () { - context.read().isPro - ? _onDownGradeFromPro(context) - : context.read().upgradeToPro(); - context.pop(); - }, - price: AppLocalizations.of(context).price), + child: Consumer( + builder: (BuildContext context, notifier, Widget? child) { + if (notifier.isPurchaseError) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'Could not buy the product. Try again later'), + duration: Duration(seconds: 2), + ), + ); + } + return child!; + }, + child: UpgradeToProButton( + onTap: () { + context.read().isPro + ? _onDownGradeFromPro(context) + : context.read().upgradeToPro(); + context.pop(); + }, + price: AppLocalizations.of(context).price), + ), ), ) ], From ac28497f08a0e85d254df643829bc892e93c2366 Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Sun, 1 Jun 2025 19:12:10 +0600 Subject: [PATCH 12/20] =?UTF-8?q?feat:=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=BA=D0=B8=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=BF=D0=BE=D0=BA=D1=83=D0=BF=D0=BA=D0=B8=20=D1=82=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=80=D0=B0=20=D0=BD=D0=B0=20ui?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/lib/l10n/en.arb | 5 +++-- frontend/lib/l10n/ru.arb | 5 +++-- .../lib/providers/pro_version_provider.dart | 4 +++- .../menu_view/components/menu_app_bar.dart | 22 ++++++++++++++++--- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/frontend/lib/l10n/en.arb b/frontend/lib/l10n/en.arb index 2a67350..e1e58c3 100644 --- a/frontend/lib/l10n/en.arb +++ b/frontend/lib/l10n/en.arb @@ -104,5 +104,6 @@ "threatsModal": "Display of danger\nof your piece being captured", "hintsModal": "Display of hints\nfor possible moves", "description": "Description", - "price": "1.99 $" -} + "price": "1.99 $", + "purchaseError": "Could not buy the product. Try again later" +} \ No newline at end of file diff --git a/frontend/lib/l10n/ru.arb b/frontend/lib/l10n/ru.arb index 497b2e7..a6d6c56 100644 --- a/frontend/lib/l10n/ru.arb +++ b/frontend/lib/l10n/ru.arb @@ -104,5 +104,6 @@ "threatsModal": "Отображение опасности\nвзятия вашей фигуры", "hintsModal": "Отображение подсказок\nвозможных ходов", "description": "Описание", - "price": "199 ₽" -} + "price": "199 ₽", + "purchaseError": "Ошибка покупки товара. Попробуйте позже" +} \ No newline at end of file diff --git a/frontend/lib/providers/pro_version_provider.dart b/frontend/lib/providers/pro_version_provider.dart index 1aa9996..3fe11bb 100644 --- a/frontend/lib/providers/pro_version_provider.dart +++ b/frontend/lib/providers/pro_version_provider.dart @@ -29,13 +29,15 @@ class ProVersionProvider extends ChangeNotifier { notifyListeners(); } - void upgradeToPro() async { + Future upgradeToPro() async { try { final isPro = await _repository.buyProduct(_proVersionKey); _setProStatus(isPro); _setPurchaseError(false); } catch (_) { _setPurchaseError(true); + await Future.delayed(const Duration(milliseconds: 100)); + _setPurchaseError(false); } } diff --git a/frontend/lib/views/menu_view/components/menu_app_bar.dart b/frontend/lib/views/menu_view/components/menu_app_bar.dart index 9b54f12..080384a 100644 --- a/frontend/lib/views/menu_view/components/menu_app_bar.dart +++ b/frontend/lib/views/menu_view/components/menu_app_bar.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../../../exports.dart'; class MenuAppBar extends StatelessWidget { @@ -15,16 +16,31 @@ class MenuAppBar extends StatelessWidget { @override Widget build(BuildContext context) { - final provider = Provider.of(context, listen: false); final scheme = Theme.of(context).colorScheme; - final isPro = context.watch().isPro; + final provider = context.watch(); + final isPro = provider.isPro; + if (provider.isPurchaseError) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: Colors.black, + content: Text( + AppLocalizations.of(context).purchaseError, + style: TextStyles.caption2, + ), + duration: const Duration(seconds: 2), + ), + ); + }); + } return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ isPro ? Row( children: [ - CustomSwitch(provider), + CustomSwitch( + Provider.of(context, listen: false)), const SizedBox(width: 16), const ProStatusIndicator(), ], From baad32bae5cb7462db8d2e532b1ee076c48795f3 Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Sun, 1 Jun 2025 19:22:56 +0600 Subject: [PATCH 13/20] =?UTF-8?q?fix:=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BB=D0=B8=D1=88=D0=BD=D0=B5=D0=B3=D0=BE?= =?UTF-8?q?=20consumer'=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/views/promo_view/promo_page_view.dart | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/frontend/lib/views/promo_view/promo_page_view.dart b/frontend/lib/views/promo_view/promo_page_view.dart index 5a85a25..5d5e9a4 100644 --- a/frontend/lib/views/promo_view/promo_page_view.dart +++ b/frontend/lib/views/promo_view/promo_page_view.dart @@ -27,28 +27,14 @@ class PromoPageView extends StatelessWidget { constraints: BoxConstraints( maxHeight: MediaQuery.sizeOf(context).height * 0.07, ), - child: Consumer( - builder: (BuildContext context, notifier, Widget? child) { - if (notifier.isPurchaseError) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text( - 'Could not buy the product. Try again later'), - duration: Duration(seconds: 2), - ), - ); - } - return child!; - }, - child: UpgradeToProButton( - onTap: () { - context.read().isPro - ? _onDownGradeFromPro(context) - : context.read().upgradeToPro(); - context.pop(); - }, - price: AppLocalizations.of(context).price), - ), + child: UpgradeToProButton( + onTap: () { + context.read().isPro + ? _onDownGradeFromPro(context) + : context.read().upgradeToPro(); + context.pop(); + }, + price: AppLocalizations.of(context).price), ), ) ], From 5a9452279ede565ac7a3747e55ad1857d731ac4e Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Mon, 2 Jun 2025 15:03:04 +0600 Subject: [PATCH 14/20] =?UTF-8?q?fix:=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B8?= =?UTF-8?q?=20=D0=B2=D0=B7=D0=B0=D0=B8=D0=BC=D0=BE=D0=B4=D0=B5=D0=B9=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D0=B8=D1=8F=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D0=B5?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lib/data_sources/in_app_purchase_data_source.dart | 4 +++- frontend/lib/data_sources/revenuecat_data_source.dart | 8 ++++---- frontend/lib/providers/pro_version_provider.dart | 6 ++++-- .../lib/repositories/in_app_purchase_repository.dart | 10 ++++++++-- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/frontend/lib/data_sources/in_app_purchase_data_source.dart b/frontend/lib/data_sources/in_app_purchase_data_source.dart index deababe..d222351 100644 --- a/frontend/lib/data_sources/in_app_purchase_data_source.dart +++ b/frontend/lib/data_sources/in_app_purchase_data_source.dart @@ -1,5 +1,7 @@ +import 'package:flutter/material.dart'; + abstract interface class IInAppPurchaseDataSource { Future buyProduct(String productId); Future checkStatus(String productId); - void init(Function onInit); + void init(ValueNotifier notifier); } diff --git a/frontend/lib/data_sources/revenuecat_data_source.dart b/frontend/lib/data_sources/revenuecat_data_source.dart index baf91c7..6c89b6b 100644 --- a/frontend/lib/data_sources/revenuecat_data_source.dart +++ b/frontend/lib/data_sources/revenuecat_data_source.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:flutter/material.dart'; import 'package:frontend/data_sources/in_app_purchase_data_source.dart'; import 'package:purchases_flutter/purchases_flutter.dart'; @@ -21,14 +22,13 @@ class RevenueCatDataSource implements IInAppPurchaseDataSource { } @override - void init(Function function) { + void init(ValueNotifier notifier) { try { - final callback = function as void Function(bool); if (Platform.isIOS) { Purchases.addCustomerInfoUpdateListener((info) { info.entitlements.active.containsKey(_proVersionKey) - ? callback(true) - : callback(false); + ? notifier.value = true + : notifier.value = false; }); } } catch (e) { diff --git a/frontend/lib/providers/pro_version_provider.dart b/frontend/lib/providers/pro_version_provider.dart index 3fe11bb..0cd346f 100644 --- a/frontend/lib/providers/pro_version_provider.dart +++ b/frontend/lib/providers/pro_version_provider.dart @@ -16,7 +16,8 @@ class ProVersionProvider extends ChangeNotifier { } void _init() async { - _repository.init(_setProStatus); + _repository.init(); + _setProStatus(_repository.getStatus()); } void _setProStatus(bool status) { @@ -31,7 +32,8 @@ class ProVersionProvider extends ChangeNotifier { Future upgradeToPro() async { try { - final isPro = await _repository.buyProduct(_proVersionKey); + await _repository.buyProduct(_proVersionKey); + final isPro = _repository.getStatus(); _setProStatus(isPro); _setPurchaseError(false); } catch (_) { diff --git a/frontend/lib/repositories/in_app_purchase_repository.dart b/frontend/lib/repositories/in_app_purchase_repository.dart index d896570..bc0843f 100644 --- a/frontend/lib/repositories/in_app_purchase_repository.dart +++ b/frontend/lib/repositories/in_app_purchase_repository.dart @@ -1,7 +1,9 @@ +import 'package:flutter/material.dart'; import 'package:frontend/data_sources/in_app_purchase_data_source.dart'; class InAppPurchaseRepository { final IInAppPurchaseDataSource dataSource; + final ValueNotifier _proStatusNotifier = ValueNotifier(false); InAppPurchaseRepository({required this.dataSource}); @@ -13,7 +15,11 @@ class InAppPurchaseRepository { return dataSource.checkStatus(productId); } - void init(Function onInit) { - dataSource.init(onInit); + void init() { + dataSource.init(_proStatusNotifier); + } + + bool getStatus() { + return _proStatusNotifier.value; } } From 26e1f19fc2e66a67ffe0f1dafb6d0027d653a14b Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Mon, 2 Jun 2025 15:35:49 +0600 Subject: [PATCH 15/20] =?UTF-8?q?fix:=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D0=BF=D0=BE=D1=81=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=20=D0=B8=D0=BD=D1=8A=D0=B5=D0=BA=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8=D0=BC=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=B5=D0=B9=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B5=D0=BF=D0=BE?= =?UTF-8?q?=D0=B7=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data_sources/revenuecat_data_source.dart | 14 +++++-------- frontend/lib/main.dart | 2 ++ .../lib/providers/pro_version_provider.dart | 1 - .../in_app_purchase_repository.dart | 20 ++++++++++++------- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/frontend/lib/data_sources/revenuecat_data_source.dart b/frontend/lib/data_sources/revenuecat_data_source.dart index 6c89b6b..096d078 100644 --- a/frontend/lib/data_sources/revenuecat_data_source.dart +++ b/frontend/lib/data_sources/revenuecat_data_source.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:frontend/data_sources/in_app_purchase_data_source.dart'; import 'package:purchases_flutter/purchases_flutter.dart'; @@ -24,13 +22,11 @@ class RevenueCatDataSource implements IInAppPurchaseDataSource { @override void init(ValueNotifier notifier) { try { - if (Platform.isIOS) { - Purchases.addCustomerInfoUpdateListener((info) { - info.entitlements.active.containsKey(_proVersionKey) - ? notifier.value = true - : notifier.value = false; - }); - } + Purchases.addCustomerInfoUpdateListener((info) { + info.entitlements.active.containsKey(_proVersionKey) + ? notifier.value = true + : notifier.value = false; + }); } catch (e) { throw Exception('function type should be void Function(bool)'); } diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index 04405ff..2182809 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -41,7 +41,9 @@ class MyApp extends StatelessWidget { ChangeNotifierProvider( create: (context) => ProVersionProvider( repository: InAppPurchaseRepository( + //TODO: choose data source based off of a platform dataSource: RevenueCatDataSource(), + proStatusNotifier: ValueNotifier(false), ), ), ) diff --git a/frontend/lib/providers/pro_version_provider.dart b/frontend/lib/providers/pro_version_provider.dart index 0cd346f..9cd4a62 100644 --- a/frontend/lib/providers/pro_version_provider.dart +++ b/frontend/lib/providers/pro_version_provider.dart @@ -16,7 +16,6 @@ class ProVersionProvider extends ChangeNotifier { } void _init() async { - _repository.init(); _setProStatus(_repository.getStatus()); } diff --git a/frontend/lib/repositories/in_app_purchase_repository.dart b/frontend/lib/repositories/in_app_purchase_repository.dart index bc0843f..5d8f522 100644 --- a/frontend/lib/repositories/in_app_purchase_repository.dart +++ b/frontend/lib/repositories/in_app_purchase_repository.dart @@ -2,21 +2,27 @@ import 'package:flutter/material.dart'; import 'package:frontend/data_sources/in_app_purchase_data_source.dart'; class InAppPurchaseRepository { - final IInAppPurchaseDataSource dataSource; - final ValueNotifier _proStatusNotifier = ValueNotifier(false); + final IInAppPurchaseDataSource _dataSource; + final ValueNotifier _proStatusNotifier; - InAppPurchaseRepository({required this.dataSource}); + InAppPurchaseRepository( + {required IInAppPurchaseDataSource dataSource, + required ValueNotifier proStatusNotifier}) + : _dataSource = dataSource, + _proStatusNotifier = proStatusNotifier { + _init(); + } Future buyProduct(String productId) { - return dataSource.buyProduct(productId); + return _dataSource.buyProduct(productId); } Future checkStatus(String productId) { - return dataSource.checkStatus(productId); + return _dataSource.checkStatus(productId); } - void init() { - dataSource.init(_proStatusNotifier); + void _init() { + _dataSource.init(_proStatusNotifier); } bool getStatus() { From 6c882f70c93252ad3ba39971a96800be41e2e5cf Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Mon, 2 Jun 2025 16:44:46 +0600 Subject: [PATCH 16/20] =?UTF-8?q?fix:=20=D0=BE=D1=82=D0=BA=D0=B0=D0=B7=20?= =?UTF-8?q?=D0=BE=D1=82=20=D1=80=D0=B5=D0=B0=D0=BA=D1=82=D0=B8=D0=B2=D0=BD?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20=D1=81=D0=BF=D0=BE=D0=B1=D0=BE=D1=81=D0=B0?= =?UTF-8?q?=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D1=81=D1=82=D0=B0=D1=82=D1=83=D1=81=D0=B0=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=B2=20=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D1=83=20=D0=B1=D0=BE?= =?UTF-8?q?=D0=BB=D0=B5=D0=B5=20=D0=BF=D1=80=D0=BE=D1=81=D1=82=D0=BE=D0=B9?= =?UTF-8?q?=20=D1=80=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8?= =?UTF-8?q?=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BA=D0=B8=20=D0=B2=D0=B7=D0=B0?= =?UTF-8?q?=D0=B8=D0=BC=D0=BE=D0=B4=D0=B5=D0=B9=D1=81=D1=82=D0=B2=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=20+=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=B0=D0=BF=D0=B8=20=D0=BA=D0=BB=D1=8E=D1=87?= =?UTF-8?q?=D0=B0=20=D0=B2=20.env=20=D1=84=D0=B0=D0=B9=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/.env.example | 1 + frontend/.gitignore | 2 ++ .../in_app_purchase_data_source.dart | 3 --- .../data_sources/revenuecat_data_source.dart | 16 --------------- frontend/lib/main.dart | 8 ++++---- .../lib/providers/pro_version_provider.dart | 5 +++-- .../in_app_purchase_repository.dart | 20 +++---------------- frontend/pubspec.lock | 8 ++++++++ frontend/pubspec.yaml | 2 ++ 9 files changed, 23 insertions(+), 42 deletions(-) create mode 100644 frontend/.env.example diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..9cad7b2 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1 @@ +REVENUE_CAT_API_KEY= \ No newline at end of file diff --git a/frontend/.gitignore b/frontend/.gitignore index 57be19c..c3e8e70 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1,3 +1,5 @@ +*.env + # Miscellaneous *.class *.log diff --git a/frontend/lib/data_sources/in_app_purchase_data_source.dart b/frontend/lib/data_sources/in_app_purchase_data_source.dart index d222351..3cbbf5c 100644 --- a/frontend/lib/data_sources/in_app_purchase_data_source.dart +++ b/frontend/lib/data_sources/in_app_purchase_data_source.dart @@ -1,7 +1,4 @@ -import 'package:flutter/material.dart'; - abstract interface class IInAppPurchaseDataSource { Future buyProduct(String productId); Future checkStatus(String productId); - void init(ValueNotifier notifier); } diff --git a/frontend/lib/data_sources/revenuecat_data_source.dart b/frontend/lib/data_sources/revenuecat_data_source.dart index 096d078..6f8d9a3 100644 --- a/frontend/lib/data_sources/revenuecat_data_source.dart +++ b/frontend/lib/data_sources/revenuecat_data_source.dart @@ -1,10 +1,7 @@ -import 'package:flutter/material.dart'; import 'package:frontend/data_sources/in_app_purchase_data_source.dart'; import 'package:purchases_flutter/purchases_flutter.dart'; class RevenueCatDataSource implements IInAppPurchaseDataSource { - //The key of a product should be the same as an entitlement name attached to it - static const _proVersionKey = 'chessknock_pro_version'; @override Future buyProduct(String productId) async { final product = (await Purchases.getProducts([productId])).first; @@ -18,17 +15,4 @@ class RevenueCatDataSource implements IInAppPurchaseDataSource { final customerInfo = await Purchases.getCustomerInfo(); return customerInfo.entitlements.active.containsKey(productId); } - - @override - void init(ValueNotifier notifier) { - try { - Purchases.addCustomerInfoUpdateListener((info) { - info.entitlements.active.containsKey(_proVersionKey) - ? notifier.value = true - : notifier.value = false; - }); - } catch (e) { - throw Exception('function type should be void Function(bool)'); - } - } } diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index 2182809..19229c8 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -1,5 +1,6 @@ import "dart:io"; +import "package:flutter_dotenv/flutter_dotenv.dart"; import "package:frontend/data_sources/revenuecat_data_source.dart"; import "package:frontend/exports.dart"; import "package:flutter/material.dart"; @@ -10,6 +11,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import "package:purchases_flutter/purchases_flutter.dart"; void main() async { + await dotenv.load(fileName: ".env"); WidgetsFlutterBinding.ensureInitialized(); if (Platform.isIOS) { await configureRevenueCat(); @@ -17,11 +19,10 @@ void main() async { runApp(const MyApp()); } -const _revenueCatApiKey = 'appl_XXuSbFegqvobEdAwmWZnhIlglOX'; - Future configureRevenueCat() async { Purchases.setLogLevel(LogLevel.debug); - Purchases.configure(PurchasesConfiguration(_revenueCatApiKey)); + final apiKey = dotenv.env['REVENUE_CAT_API_KEY']; + Purchases.configure(PurchasesConfiguration(apiKey!)); } class MyApp extends StatelessWidget { @@ -43,7 +44,6 @@ class MyApp extends StatelessWidget { repository: InAppPurchaseRepository( //TODO: choose data source based off of a platform dataSource: RevenueCatDataSource(), - proStatusNotifier: ValueNotifier(false), ), ), ) diff --git a/frontend/lib/providers/pro_version_provider.dart b/frontend/lib/providers/pro_version_provider.dart index 9cd4a62..1c83a57 100644 --- a/frontend/lib/providers/pro_version_provider.dart +++ b/frontend/lib/providers/pro_version_provider.dart @@ -16,7 +16,8 @@ class ProVersionProvider extends ChangeNotifier { } void _init() async { - _setProStatus(_repository.getStatus()); + final isPro = await _repository.checkStatus(_proVersionKey); + _setProStatus(isPro); } void _setProStatus(bool status) { @@ -32,7 +33,7 @@ class ProVersionProvider extends ChangeNotifier { Future upgradeToPro() async { try { await _repository.buyProduct(_proVersionKey); - final isPro = _repository.getStatus(); + final isPro = await _repository.checkStatus(_proVersionKey); _setProStatus(isPro); _setPurchaseError(false); } catch (_) { diff --git a/frontend/lib/repositories/in_app_purchase_repository.dart b/frontend/lib/repositories/in_app_purchase_repository.dart index 5d8f522..1e6c2d4 100644 --- a/frontend/lib/repositories/in_app_purchase_repository.dart +++ b/frontend/lib/repositories/in_app_purchase_repository.dart @@ -1,17 +1,11 @@ -import 'package:flutter/material.dart'; import 'package:frontend/data_sources/in_app_purchase_data_source.dart'; class InAppPurchaseRepository { final IInAppPurchaseDataSource _dataSource; - final ValueNotifier _proStatusNotifier; - InAppPurchaseRepository( - {required IInAppPurchaseDataSource dataSource, - required ValueNotifier proStatusNotifier}) - : _dataSource = dataSource, - _proStatusNotifier = proStatusNotifier { - _init(); - } + InAppPurchaseRepository({ + required IInAppPurchaseDataSource dataSource, + }) : _dataSource = dataSource; Future buyProduct(String productId) { return _dataSource.buyProduct(productId); @@ -20,12 +14,4 @@ class InAppPurchaseRepository { Future checkStatus(String productId) { return _dataSource.checkStatus(productId); } - - void _init() { - _dataSource.init(_proStatusNotifier); - } - - bool getStatus() { - return _proStatusNotifier.value; - } } diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock index 361d8c3..decbc29 100644 --- a/frontend/pubspec.lock +++ b/frontend/pubspec.lock @@ -126,6 +126,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b + url: "https://pub.dev" + source: hosted + version: "5.2.1" flutter_launcher_icons: dependency: "direct dev" description: diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index 202728b..5f3104f 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -28,6 +28,7 @@ dependencies: flutter_localizations: sdk: flutter purchases_flutter: ^5.8.0 + flutter_dotenv: ^5.2.1 dev_dependencies: flutter_test: @@ -52,6 +53,7 @@ flutter: - assets/images/small_pieces/ - assets/images/icons/ - assets/images/guide_boards/ + - .env fonts: - family: Roboto From 30bd6290bd75941120efa002662f69a5c495dcf1 Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Tue, 3 Jun 2025 13:26:54 +0600 Subject: [PATCH 17/20] =?UTF-8?q?fix:=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BD=D1=81=D1=82=D0=B0=D0=BD?= =?UTF-8?q?=D1=82=D0=BD=D1=8B=D1=85=20=D1=81=D1=82=D1=80=D0=BE=D0=BA,=20?= =?UTF-8?q?=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=B5=20=D0=B4=D0=BE=D0=BB?= =?UTF-8?q?=D0=B6=D0=BD=D1=8B=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=D1=81=D1=8F=20=D1=87=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B7=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=BB=D0=BE?= =?UTF-8?q?=D0=BA=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../constants/game_setting_const.dart | 26 +------------------ .../setting_view/game_settings_mobile.dart | 13 +++++----- .../setting_view/game_settings_tablet.dart | 13 +++++----- .../setting_view/game_settings_view.dart | 1 + 4 files changed, 15 insertions(+), 38 deletions(-) diff --git a/frontend/lib/views/setting_view/constants/game_setting_const.dart b/frontend/lib/views/setting_view/constants/game_setting_const.dart index b36bead..c2d90d5 100644 --- a/frontend/lib/views/setting_view/constants/game_setting_const.dart +++ b/frontend/lib/views/setting_view/constants/game_setting_const.dart @@ -1,30 +1,6 @@ class GameSettingConsts { - static String appBarLabel = "Параметры"; - static String gameModeText = "Режим игры"; - static String colorPiecesText = "Цвет фигур"; - static String timeText = "Время"; - static String gameSettingsHeader = "Настройки партии"; - static String choseDiffModalHeader = "Уровень сложности бота"; - static String personalLevelDifficultyText = "Сложность бота"; - static String additionalSettingsText = "Дополнительно"; - static String startGameText = "Начать партию"; - static String gameWithComputerText = "С компьютером"; - static String gameWithHumanText = "С другом"; - static String gameWithTimeText = "С часами"; - static String gameWithoutTimeText = "Без часов"; - static String minutesSubtitle = "Минут на партию"; - static String secondsSubtitle = "Добавление секунд на ход"; - static String moveBackText = "Возврат ходов"; - static String threatsText = "Угрозы"; - static String hintsText = "Подсказки"; - static String easyDescription = "Подсказки и возврат хода"; - static String mediumDescription = "Только возврат хода"; - static String hardDescription = "Никакой помощи"; - static String personalityDescription = "Настрой под себя"; static String longDashSymbol = "—"; - static String whiteColorChose = "Белые"; - static String blackColorChose = "Чёрные"; - static String randomColorChose = "Случайный цвет"; + static int countOfDifficultyLevels = 3; static List listOfDurations = [ diff --git a/frontend/lib/views/setting_view/game_settings_mobile.dart b/frontend/lib/views/setting_view/game_settings_mobile.dart index 561b632..188a5e9 100644 --- a/frontend/lib/views/setting_view/game_settings_mobile.dart +++ b/frontend/lib/views/setting_view/game_settings_mobile.dart @@ -9,6 +9,8 @@ class GameSettingsMobile extends StatelessWidget { Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; final gameSettingsProvider = context.read(); + final l10n = AppLocalizations.of(context); + return DefaultTabController( length: 2, child: Scaffold( @@ -41,14 +43,13 @@ class GameSettingsMobile extends StatelessWidget { builder: (_, values, __) => Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - AppBarSettings( - label: GameSettingConsts.appBarLabel), + AppBarSettings(label: l10n.settings), CustomTabBar( initialIndex: values.withoutTime ? 0 : 1, - header: GameSettingConsts.timeText, + header: l10n.time, subTitles: [ - GameSettingConsts.gameWithoutTimeText, - GameSettingConsts.gameWithTimeText, + l10n.withoutTimer, + l10n.withTimer, ], isSettingsPage: true, onTap: (dynamic index) => @@ -88,7 +89,7 @@ class GameSettingsMobile extends StatelessWidget { right: 23, ), child: NextPageButton( - text: GameSettingConsts.startGameText, + text: l10n.startGameButton, textColor: ColorsConst.primaryColor0, buttonColor: scheme.secondaryContainer, isClickable: true, diff --git a/frontend/lib/views/setting_view/game_settings_tablet.dart b/frontend/lib/views/setting_view/game_settings_tablet.dart index 5d95758..be914e1 100644 --- a/frontend/lib/views/setting_view/game_settings_tablet.dart +++ b/frontend/lib/views/setting_view/game_settings_tablet.dart @@ -8,7 +8,7 @@ class GameSettingsTablet extends StatelessWidget { Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; final gameSettingsProvider = context.read(); - + final l10n = AppLocalizations.of(context); return DefaultTabController( length: 2, child: Scaffold( @@ -40,14 +40,13 @@ class GameSettingsTablet extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - AppBarSettings( - label: GameSettingConsts.appBarLabel), + AppBarSettings(label: l10n.settings), CustomTabBar( initialIndex: values.withoutTime ? 0 : 1, - header: GameSettingConsts.timeText, + header: l10n.time, subTitles: [ - GameSettingConsts.gameWithoutTimeText, - GameSettingConsts.gameWithTimeText, + l10n.withoutTimer, + l10n.withTimer, ], isSettingsPage: true, onTap: (dynamic index) => @@ -90,7 +89,7 @@ class GameSettingsTablet extends StatelessWidget { right: 23, ), child: NextPageButton( - text: GameSettingConsts.startGameText, + text: l10n.startGameButton, textColor: ColorsConst.primaryColor0, buttonColor: scheme.secondaryContainer, isClickable: true, diff --git a/frontend/lib/views/setting_view/game_settings_view.dart b/frontend/lib/views/setting_view/game_settings_view.dart index 171db46..41d356b 100644 --- a/frontend/lib/views/setting_view/game_settings_view.dart +++ b/frontend/lib/views/setting_view/game_settings_view.dart @@ -1,6 +1,7 @@ import "package:flutter/material.dart"; import "package:frontend/common/shared_functions.dart"; import "package:frontend/views/setting_view/providers/game_settings_provider.dart"; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import "package:provider/provider.dart"; From 502be15b5041c568f95db29fadf89486d81b916f Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Thu, 5 Jun 2025 16:19:14 +0600 Subject: [PATCH 18/20] =?UTF-8?q?fix:=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1?= =?UTF-8?q?=D0=BA=D0=B8,=20=D0=BF=D1=80=D0=B8=20=D0=BA=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=BE=D0=B9=20=D0=B2=20=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=83=D1=8E=20=D0=B1=D0=B4=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D1=8B=D0=B2=D0=B0=D0=BB=D0=B8=D1=81=D1=8C=20=D0=BB?= =?UTF-8?q?=D0=BE=D0=BA=D0=B0=D0=BB=D0=B8=D0=B7=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BD=D0=BD=D1=8B=D0=B5=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/shared_functions.dart | 16 ++-- .../game_view/constants/game_page_const.dart | 17 ---- .../components/info_bar_item.dart | 29 ++----- .../components/info_party_bar.dart | 77 ++++++++----------- .../components/one_party_view_widget.dart | 49 ++---------- .../constants/party_history_const.dart | 12 --- .../party_history_main_view.dart | 2 - 7 files changed, 53 insertions(+), 149 deletions(-) diff --git a/frontend/lib/views/game_view/components/shared_functions.dart b/frontend/lib/views/game_view/components/shared_functions.dart index af675ae..45529d1 100644 --- a/frontend/lib/views/game_view/components/shared_functions.dart +++ b/frontend/lib/views/game_view/components/shared_functions.dart @@ -3,28 +3,30 @@ import 'package:sqflite/sqflite.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../../../exports.dart'; -String getResultForHistory(GameModel gameModel, AppLocalizations l10n) { +enum GameResult { draw, blackVictory, whiteVictory } + +String getResultForHistory(GameModel gameModel) { if (gameModel.gameOver) { if (gameModel.stalemate || gameModel.draw) { - return l10n.draw; + return GameResult.draw.name; } else { if (gameModel.turn == Player.player1) { - return l10n.blackVictory; + return GameResult.blackVictory.name; } else { - return l10n.whiteVictory; + return GameResult.whiteVictory.name; } } } else { - return l10n.draw; + return GameResult.draw.name; } } List getPartyData(GameModel gameModel, AppLocalizations l10n) { - String enemy = gameModel.playerCount == 1 ? l10n.computer : l10n.friend; + String enemy = l10n.friend; String formattedDate = DateFormat("dd.MM.yyyy").format(DateTime.now()); String formattedTime = DateFormat.Hm().format(DateTime.now()); String durationGame = _formatDuration(gameModel.durationOfGame); - String result = getResultForHistory(gameModel, l10n); + String result = getResultForHistory(gameModel); String color = gameModel.playerSide == Player.player1 ? l10n.whitePieces : l10n.blackPieces; diff --git a/frontend/lib/views/game_view/constants/game_page_const.dart b/frontend/lib/views/game_view/constants/game_page_const.dart index 823f70a..f783f8d 100644 --- a/frontend/lib/views/game_view/constants/game_page_const.dart +++ b/frontend/lib/views/game_view/constants/game_page_const.dart @@ -7,21 +7,4 @@ class GamePageConst { static const startPos = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"; static List listOfColumns = ["h", "g", "f", "e", "d", "c", "b", "a"]; - static const gameStatusEnemyMove = "Ход противника "; - static const gameStatusOurMove = "Ваш ход"; - static const gameStatusWhiteMove = "Ход белых"; - static const gameStatusBlackMove = "Ход чёрных"; - static const gameStatusStalemate = "Пат (Ничья)"; - static const gameStatusDraw = "Ничья"; - static const gameStatusBlackWin = "Выиграли чёрные"; - static const gameStatusWhiteWin = "Выиграли белые"; - static const gameEndText = "В главное меню"; - static const continueGameText = "Продолжить игру"; - static const gameRestartText = "Новая игра"; - static const gameGiveUpText = "Сдаться"; - static const gameBackModalHeader = "Сдаться?"; - static const gameResultWin = "Победа"; - static const gameResultLose = "Поражение"; - static const gameResultWinBlack = "Победа чёрных"; - static const gameResultWinWhite = "Победа белых"; } diff --git a/frontend/lib/views/party_history_view/components/info_bar_item.dart b/frontend/lib/views/party_history_view/components/info_bar_item.dart index b413caf..07c4e6c 100644 --- a/frontend/lib/views/party_history_view/components/info_bar_item.dart +++ b/frontend/lib/views/party_history_view/components/info_bar_item.dart @@ -3,45 +3,28 @@ import 'package:flutter_svg/svg.dart'; import 'package:frontend/exports.dart'; class InfoBarItem extends StatelessWidget { - const InfoBarItem({super.key, required this.index, required this.isComputer}); + const InfoBarItem({super.key, required this.text, required this.iconColor}); - final int index; - final bool isComputer; + final String text; + final Color iconColor; @override Widget build(BuildContext context) { - final scheme = Theme.of(context).colorScheme; - List computerColors = [ - scheme.onSecondaryContainer, - scheme.primary, - ColorsConst.secondaryColor100 - ]; - - List friendColors = [ - scheme.primaryContainer, - scheme.onSecondary, - scheme.onSurface - ]; - return Row( children: [ SvgPicture.asset( PartyHistoryConst.infoPartyIconName, height: 24, width: 24, - colorFilter: ColorFilter.mode( - isComputer ? computerColors[index] : friendColors[index], - BlendMode.srcIn), + colorFilter: ColorFilter.mode(iconColor, BlendMode.srcIn), ), const SizedBox( width: 4, ), Text( - isComputer - ? PartyHistoryConst.gameResults[index] - : PartyHistoryConst.friendGameResults[index], + text, style: TextStyles.caption2.copyWith( - color: scheme.primary, + color: Theme.of(context).colorScheme.primary, height: 0.09, ), ), diff --git a/frontend/lib/views/party_history_view/components/info_party_bar.dart b/frontend/lib/views/party_history_view/components/info_party_bar.dart index 7439b89..f0cbe4a 100644 --- a/frontend/lib/views/party_history_view/components/info_party_bar.dart +++ b/frontend/lib/views/party_history_view/components/info_party_bar.dart @@ -1,20 +1,19 @@ import 'package:flutter/material.dart'; import 'package:frontend/exports.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class InfoPartyBar extends StatelessWidget { const InfoPartyBar({ super.key, required this.height, - required this.isComputer, }); final double height; - final bool isComputer; @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; - + final l10n = AppLocalizations.of(context); return Container( height: height, width: double.infinity, @@ -23,49 +22,33 @@ class InfoPartyBar extends StatelessWidget { decoration: BoxDecoration( color: scheme.onSurfaceVariant, borderRadius: BorderRadius.circular(16)), - child: isComputer - ? Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: List.generate(PartyHistoryConst.gameResults.length, - (index) { - return Row( - children: [ - InfoBarItem( - index: index, - isComputer: isComputer, - ), - ], - ); - }), - ) - : Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - InfoBarItem( - index: 0, - isComputer: isComputer, - ), - const SizedBox(), - InfoBarItem( - index: 1, - isComputer: isComputer, - ) - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - InfoBarItem( - index: 2, - isComputer: isComputer, - ), - ], - ) - ], - )); + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + InfoBarItem( + text: l10n.whiteVictory, + iconColor: scheme.primaryContainer, + ), + const SizedBox(), + InfoBarItem( + text: l10n.blackVictory, + iconColor: scheme.onSecondary, + ) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InfoBarItem( + text: l10n.draw, + iconColor: scheme.onSurface, + ), + ], + ) + ], + )); } } diff --git a/frontend/lib/views/party_history_view/components/one_party_view_widget.dart b/frontend/lib/views/party_history_view/components/one_party_view_widget.dart index 0a5b1bf..a89efac 100644 --- a/frontend/lib/views/party_history_view/components/one_party_view_widget.dart +++ b/frontend/lib/views/party_history_view/components/one_party_view_widget.dart @@ -4,26 +4,19 @@ import 'package:frontend/exports.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class OnePartyViewWidget extends StatelessWidget { - const OnePartyViewWidget( - {super.key, required this.partyData, required this.isComputer}); + const OnePartyViewWidget({super.key, required this.partyData}); final Map partyData; - final bool isComputer; @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; final l10n = AppLocalizations.of(context); - Map computerListOfColorsIcons = { - l10n.victory: scheme.onSecondaryContainer, - l10n.defeat: scheme.primary, - l10n.draw: ColorsConst.secondaryColor100 - }; Map friendListOfColorsIcons = { - l10n.whiteVictory: scheme.primaryContainer, - l10n.blackVictory: scheme.onSecondary, - l10n.draw: scheme.onSurface + GameResult.whiteVictory.name: scheme.primaryContainer, + GameResult.blackVictory.name: scheme.onSecondary, + GameResult.draw.name: scheme.onSurface }; return Container( margin: const EdgeInsets.only(bottom: 12, left: 24, right: 24), @@ -36,9 +29,8 @@ class OnePartyViewWidget extends StatelessWidget { height: 35, width: 35, colorFilter: ColorFilter.mode( - isComputer - ? computerListOfColorsIcons[partyData["result"]]! - : friendListOfColorsIcons[partyData["result"]]!, + friendListOfColorsIcons[partyData["result"]] ?? + ColorsConst.disabledColor, BlendMode.srcIn), ), const SizedBox( @@ -68,9 +60,7 @@ class OnePartyViewWidget extends StatelessWidget { child: Row( children: [ Column( - mainAxisAlignment: isComputer - ? MainAxisAlignment.spaceBetween - : MainAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( @@ -82,23 +72,12 @@ class OnePartyViewWidget extends StatelessWidget { height: 1, ), ), - isComputer - ? Text( - l10n.pieceColor, - style: TextStyles.caption1.copyWith( - color: scheme.error, - height: 1, - ), - ) - : const SizedBox(), ], ), SizedBox( width: 50, child: Column( - mainAxisAlignment: isComputer - ? MainAxisAlignment.spaceBetween - : MainAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ partyData["durationGame"] != "00:00" @@ -110,18 +89,6 @@ class OnePartyViewWidget extends StatelessWidget { ), ) : const SizedBox(), - isComputer - ? Container( - width: 12, - height: 12, - margin: const EdgeInsets.only(right: 4), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(50), - color: partyData["color"] == l10n.whitePieces - ? scheme.inverseSurface - : ColorsConst.neutralColor100), - ) - : const SizedBox() ], ), ) diff --git a/frontend/lib/views/party_history_view/constants/party_history_const.dart b/frontend/lib/views/party_history_view/constants/party_history_const.dart index 83003a3..401b6b4 100644 --- a/frontend/lib/views/party_history_view/constants/party_history_const.dart +++ b/frontend/lib/views/party_history_view/constants/party_history_const.dart @@ -1,22 +1,10 @@ class PartyHistoryConst { - static String partyHistoryHeader = "История партий"; - static String appbarIconName = "assets/images/icons/left_big_arrow_icon.svg"; static String appbarMainIcon = "assets/images/icons/cancel.svg"; static String infoPartyIconName = "assets/images/icons/black.svg"; - static List gameResults = ["Победа", "Поражение", "Ничья"]; - - static List friendGameResults = [ - "Победа белых", - "Победа чёрных", - "Ничья" - ]; - - static List gameEnemies = ["Компьютер", "Друг"]; - static String dbCreateScript = """CREATE TABLE History ( id INTEGER PRIMARY KEY, enemy STRING, diff --git a/frontend/lib/views/party_history_view/party_history_main_view.dart b/frontend/lib/views/party_history_view/party_history_main_view.dart index 01a73c2..16a8a41 100644 --- a/frontend/lib/views/party_history_view/party_history_main_view.dart +++ b/frontend/lib/views/party_history_view/party_history_main_view.dart @@ -65,7 +65,6 @@ class _PartyHistoryMainViewState extends State { children: [ InfoPartyBar( height: 80, - isComputer: false, ), SizedBox(height: 24), ], @@ -76,7 +75,6 @@ class _PartyHistoryMainViewState extends State { itemCount: friendParties.length, itemBuilder: (context, index) { return OnePartyViewWidget( - isComputer: false, partyData: friendParties[index], ); }, From 9e52e4e72eb06f2f0913b896215945a5a3d18959 Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Sun, 8 Jun 2025 21:49:43 +0600 Subject: [PATCH 19/20] =?UTF-8?q?feat=20+=20refactor:=20=D0=B4=D0=BE=D0=B1?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=D0=BE=D0=B7?= =?UTF-8?q?=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=D1=81=D1=82=D0=B8=20=D0=B2=D0=BE?= =?UTF-8?q?=D1=81=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BF=D0=BE=D0=BA=D1=83=D0=BF=D0=BE=D0=BA=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D0=B5=D0=BC=20=D0=B4=D0=BB=D1=8F=20=D1=83=D1=81=D1=82?= =?UTF-8?q?=D1=80=D0=BE=D0=B9=D1=81=D1=82=D0=B2=20=D0=BD=D0=B0=20ios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/lib/common/shared_functions.dart | 14 ++++ .../in_app_purchase_data_source.dart | 1 + .../data_sources/revenuecat_data_source.dart | 10 +++ frontend/lib/l10n/en.arb | 4 +- frontend/lib/l10n/ru.arb | 4 +- .../lib/providers/pro_version_provider.dart | 21 +++++ .../in_app_purchase_repository.dart | 4 + frontend/lib/views/components/components.dart | 1 + .../lib/views/components/custom_button.dart | 31 ++++++++ .../menu_view/components/menu_app_bar.dart | 13 +--- .../components/pro_status_indicator.dart | 49 +++++++----- .../components/upgrade_to_pro_button.dart | 76 ++++++++----------- .../components/bottom_buttons_section.dart | 55 ++++++++++++++ .../promo_view/components/components.dart | 2 + .../components/restore_purchases_button.dart | 31 ++++++++ .../lib/views/promo_view/promo_page_view.dart | 31 +------- 16 files changed, 243 insertions(+), 104 deletions(-) create mode 100644 frontend/lib/views/components/custom_button.dart create mode 100644 frontend/lib/views/promo_view/components/bottom_buttons_section.dart create mode 100644 frontend/lib/views/promo_view/components/restore_purchases_button.dart diff --git a/frontend/lib/common/shared_functions.dart b/frontend/lib/common/shared_functions.dart index e516994..1f7b6b2 100644 --- a/frontend/lib/common/shared_functions.dart +++ b/frontend/lib/common/shared_functions.dart @@ -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), + ), + ); + } } diff --git a/frontend/lib/data_sources/in_app_purchase_data_source.dart b/frontend/lib/data_sources/in_app_purchase_data_source.dart index 3cbbf5c..8ce1793 100644 --- a/frontend/lib/data_sources/in_app_purchase_data_source.dart +++ b/frontend/lib/data_sources/in_app_purchase_data_source.dart @@ -1,4 +1,5 @@ abstract interface class IInAppPurchaseDataSource { Future buyProduct(String productId); Future checkStatus(String productId); + Future restorePurchases(); } diff --git a/frontend/lib/data_sources/revenuecat_data_source.dart b/frontend/lib/data_sources/revenuecat_data_source.dart index 6f8d9a3..424ecfe 100644 --- a/frontend/lib/data_sources/revenuecat_data_source.dart +++ b/frontend/lib/data_sources/revenuecat_data_source.dart @@ -15,4 +15,14 @@ class RevenueCatDataSource implements IInAppPurchaseDataSource { final customerInfo = await Purchases.getCustomerInfo(); return customerInfo.entitlements.active.containsKey(productId); } + + @override + Future restorePurchases() async { + try { + await Purchases.restorePurchases(); + return true; + } catch (_) { + return false; + } + } } diff --git a/frontend/lib/l10n/en.arb b/frontend/lib/l10n/en.arb index e1e58c3..2888ce0 100644 --- a/frontend/lib/l10n/en.arb +++ b/frontend/lib/l10n/en.arb @@ -105,5 +105,7 @@ "hintsModal": "Display of hints\nfor possible moves", "description": "Description", "price": "1.99 $", - "purchaseError": "Could not buy the product. Try again later" + "purchaseError": "Could not buy the product. Try again later", + "restorePurchases": "Restore Purchases", + "restorePurchasesError": "Could not restore purchases. Try again later" } \ No newline at end of file diff --git a/frontend/lib/l10n/ru.arb b/frontend/lib/l10n/ru.arb index a6d6c56..1f97b42 100644 --- a/frontend/lib/l10n/ru.arb +++ b/frontend/lib/l10n/ru.arb @@ -105,5 +105,7 @@ "hintsModal": "Отображение подсказок\nвозможных ходов", "description": "Описание", "price": "199 ₽", - "purchaseError": "Ошибка покупки товара. Попробуйте позже" + "purchaseError": "Ошибка покупки товара. Попробуйте позже", + "restorePurchases": "Восстановить покупки", + "restorePurchasesError": "Ошибка восстановления покупок. Попробуйте позже" } \ No newline at end of file diff --git a/frontend/lib/providers/pro_version_provider.dart b/frontend/lib/providers/pro_version_provider.dart index 1c83a57..b15b292 100644 --- a/frontend/lib/providers/pro_version_provider.dart +++ b/frontend/lib/providers/pro_version_provider.dart @@ -7,6 +7,7 @@ class ProVersionProvider extends ChangeNotifier { bool _isProStatus = false; bool _isPurchaseError = false; + bool _isRestoreError = false; final InAppPurchaseRepository _repository; @@ -21,15 +22,23 @@ class ProVersionProvider extends ChangeNotifier { } void _setProStatus(bool status) { + if (_isProStatus == status) return; _isProStatus = status; notifyListeners(); } void _setPurchaseError(bool isError) { + if (_isPurchaseError == isError) return; _isPurchaseError = isError; notifyListeners(); } + void _setRestoreError(bool isRestoreError) { + if (_isRestoreError == isRestoreError) return; + _isRestoreError = isRestoreError; + notifyListeners(); + } + Future upgradeToPro() async { try { await _repository.buyProduct(_proVersionKey); @@ -47,6 +56,18 @@ class ProVersionProvider extends ChangeNotifier { _setProStatus(false); } + Future restorePurchases() async { + final isRestoreCompleted = await _repository.restorePurchases(); + if (isRestoreCompleted) { + _setRestoreError(false); + } else { + _setRestoreError(true); + await Future.delayed(const Duration(milliseconds: 100)); + _setRestoreError(false); + } + } + bool get isPro => _isProStatus; bool get isPurchaseError => _isPurchaseError; + bool get isRestoreError => _isRestoreError; } diff --git a/frontend/lib/repositories/in_app_purchase_repository.dart b/frontend/lib/repositories/in_app_purchase_repository.dart index 1e6c2d4..2feabb8 100644 --- a/frontend/lib/repositories/in_app_purchase_repository.dart +++ b/frontend/lib/repositories/in_app_purchase_repository.dart @@ -14,4 +14,8 @@ class InAppPurchaseRepository { Future checkStatus(String productId) { return _dataSource.checkStatus(productId); } + + Future restorePurchases() { + return _dataSource.restorePurchases(); + } } diff --git a/frontend/lib/views/components/components.dart b/frontend/lib/views/components/components.dart index 05e17ae..0525800 100644 --- a/frontend/lib/views/components/components.dart +++ b/frontend/lib/views/components/components.dart @@ -4,3 +4,4 @@ export "custom_icon_button.dart"; export "loading_widget.dart"; export "custom_tab_bar.dart"; export 'pro_functions_tooltip.dart'; +export 'custom_button.dart'; diff --git a/frontend/lib/views/components/custom_button.dart b/frontend/lib/views/components/custom_button.dart new file mode 100644 index 0000000..2819d2e --- /dev/null +++ b/frontend/lib/views/components/custom_button.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:frontend/constants/gradient_consts.dart'; + +class CustomButton extends StatelessWidget { + const CustomButton({ + super.key, + required this.onTap, + required this.child, + this.borderRadius = 16, + this.gradient = GradientConsts.orange, + }); + + final VoidCallback onTap; + final Widget child; + final double borderRadius; + final Gradient gradient; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: DecoratedBox( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(borderRadius), + gradient: gradient, + ), + child: Center(child: child), + ), + ); + } +} diff --git a/frontend/lib/views/menu_view/components/menu_app_bar.dart b/frontend/lib/views/menu_view/components/menu_app_bar.dart index 080384a..b4c8168 100644 --- a/frontend/lib/views/menu_view/components/menu_app_bar.dart +++ b/frontend/lib/views/menu_view/components/menu_app_bar.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:frontend/common/shared_functions.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -21,16 +22,8 @@ class MenuAppBar extends StatelessWidget { final isPro = provider.isPro; if (provider.isPurchaseError) { WidgetsBinding.instance.addPostFrameCallback((_) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: Colors.black, - content: Text( - AppLocalizations.of(context).purchaseError, - style: TextStyles.caption2, - ), - duration: const Duration(seconds: 2), - ), - ); + SharedFunctions.showSnackBar( + context, AppLocalizations.of(context).purchaseError); }); } return Row( diff --git a/frontend/lib/views/menu_view/components/pro_status_indicator.dart b/frontend/lib/views/menu_view/components/pro_status_indicator.dart index 27de15a..7b34d51 100644 --- a/frontend/lib/views/menu_view/components/pro_status_indicator.dart +++ b/frontend/lib/views/menu_view/components/pro_status_indicator.dart @@ -1,8 +1,11 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:frontend/constants/assets.dart'; import 'package:frontend/exports.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:go_router/go_router.dart'; class ProStatusIndicator extends StatelessWidget { const ProStatusIndicator({ @@ -11,27 +14,31 @@ class ProStatusIndicator extends StatelessWidget { @override Widget build(BuildContext context) { - return Row( - children: [ - ShaderMask( - shaderCallback: (Rect bounds) { - return GradientConsts.orange.createShader( - Rect.fromLTWH(0, 0, bounds.width, bounds.height)); - }, - child: Text( - AppLocalizations.of(context).pro, - style: - TextStyles.body1.copyWith(color: ColorsConst.neutralColor0), - )), - ShaderMask( - shaderCallback: (Rect bounds) { - return GradientConsts.orange.createShader( - Rect.fromLTWH(0, 0, bounds.width, bounds.height)); - }, - child: SvgPicture.asset( - Assets.proSparkles, - )), - ], + return GestureDetector( + onTap: () => + (Platform.isIOS) ? context.push(RouteLocations.promoScreen) : null, + child: Row( + children: [ + ShaderMask( + shaderCallback: (Rect bounds) { + return GradientConsts.orange.createShader( + Rect.fromLTWH(0, 0, bounds.width, bounds.height)); + }, + child: Text( + AppLocalizations.of(context).pro, + style: + TextStyles.body1.copyWith(color: ColorsConst.neutralColor0), + )), + ShaderMask( + shaderCallback: (Rect bounds) { + return GradientConsts.orange.createShader( + Rect.fromLTWH(0, 0, bounds.width, bounds.height)); + }, + child: SvgPicture.asset( + Assets.proSparkles, + )), + ], + ), ); } } diff --git a/frontend/lib/views/menu_view/components/upgrade_to_pro_button.dart b/frontend/lib/views/menu_view/components/upgrade_to_pro_button.dart index ee7c2e4..b36e024 100644 --- a/frontend/lib/views/menu_view/components/upgrade_to_pro_button.dart +++ b/frontend/lib/views/menu_view/components/upgrade_to_pro_button.dart @@ -32,52 +32,40 @@ class UpgradeToProButton extends StatelessWidget { ); final double iconSize = isExtended ? 29 : 24; final l10n = AppLocalizations.of(context); - return GestureDetector( + return CustomButton( + borderRadius: isExtended ? 16 : 24, onTap: onTap, - child: DecoratedBox( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(isExtended ? 16 : 24), - gradient: const LinearGradient( - colors: [ - Color.fromRGBO(255, 190, 146, 1), - Color.fromRGBO(220, 101, 35, 1) + child: FittedBox( + fit: BoxFit.scaleDown, + child: Padding( + padding: padding, + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Builder(builder: (context) { + return Text( + context.watch().isPro + ? l10n.downgradeFromPro + : l10n.becomePro, + style: style, + ); + }), + SvgPicture.asset( + fit: BoxFit.fill, + height: iconSize, + width: iconSize, + Assets.proSparkles, + colorFilter: const ColorFilter.mode( + ColorsConst.neutralColor0, BlendMode.srcIn), + ), + if (price != null) ...[ + Text( + price!, + style: style, + ) + ] ], - begin: Alignment.topRight, - end: Alignment.bottomLeft, - ), - ), - child: FittedBox( - fit: BoxFit.scaleDown, - child: Padding( - padding: padding, - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Builder(builder: (context) { - return Text( - context.watch().isPro - ? l10n.downgradeFromPro - : l10n.becomePro, - style: style, - ); - }), - SvgPicture.asset( - fit: BoxFit.fill, - height: iconSize, - width: iconSize, - Assets.proSparkles, - colorFilter: const ColorFilter.mode( - ColorsConst.neutralColor0, BlendMode.srcIn), - ), - if (price != null) ...[ - Text( - price!, - style: style, - ) - ] - ], - ), ), ), ), diff --git a/frontend/lib/views/promo_view/components/bottom_buttons_section.dart b/frontend/lib/views/promo_view/components/bottom_buttons_section.dart new file mode 100644 index 0000000..6864eeb --- /dev/null +++ b/frontend/lib/views/promo_view/components/bottom_buttons_section.dart @@ -0,0 +1,55 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:frontend/exports.dart'; +import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class BottomButtonsSection extends StatelessWidget { + const BottomButtonsSection({super.key}); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + if (!context.watch().isPro) ...[ + SizedBox( + width: double.infinity, + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.sizeOf(context).height * 0.07, + ), + child: UpgradeToProButton( + onTap: () { + context.read().isPro + ? _onDownGradeFromPro(context) + : context.read().upgradeToPro(); + context.pop(); + }, + price: AppLocalizations.of(context).price), + ), + ), + SizedBox( + height: MediaQuery.sizeOf(context).height * 0.02, + ), + ], + if (Platform.isIOS) ...[ + SizedBox( + width: double.infinity, + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.sizeOf(context).height * 0.07, + ), + child: const RestorePurchasesButton()), + ) + ] + ], + ); + } + + void _onDownGradeFromPro(BuildContext context) { + context.read().downgradeFromPro(); + context.read().resetTheme(); + } +} diff --git a/frontend/lib/views/promo_view/components/components.dart b/frontend/lib/views/promo_view/components/components.dart index d11ae81..96edbc1 100644 --- a/frontend/lib/views/promo_view/components/components.dart +++ b/frontend/lib/views/promo_view/components/components.dart @@ -1,3 +1,5 @@ export 'promo_feature_card.dart'; export 'promo_screen_header.dart'; export 'promo_cards_section.dart'; +export 'restore_purchases_button.dart'; +export 'bottom_buttons_section.dart'; diff --git a/frontend/lib/views/promo_view/components/restore_purchases_button.dart b/frontend/lib/views/promo_view/components/restore_purchases_button.dart new file mode 100644 index 0000000..57c9faa --- /dev/null +++ b/frontend/lib/views/promo_view/components/restore_purchases_button.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:frontend/common/shared_functions.dart'; +import 'package:frontend/exports.dart'; +import 'package:provider/provider.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class RestorePurchasesButton extends StatelessWidget { + const RestorePurchasesButton({super.key}); + + @override + Widget build(BuildContext context) { + final provider = context.watch(); + if (provider.isRestoreError) { + WidgetsBinding.instance.addPostFrameCallback((_) { + SharedFunctions.showSnackBar( + context, AppLocalizations.of(context).restorePurchasesError); + }); + } + return CustomButton( + gradient: GradientConsts.grey, + onTap: context.read().restorePurchases, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Text( + AppLocalizations.of(context).restorePurchases, + style: + TextStyles.header1.copyWith(color: ColorsConst.neutralColor0), + ), + )); + } +} diff --git a/frontend/lib/views/promo_view/promo_page_view.dart b/frontend/lib/views/promo_view/promo_page_view.dart index 5d5e9a4..703b165 100644 --- a/frontend/lib/views/promo_view/promo_page_view.dart +++ b/frontend/lib/views/promo_view/promo_page_view.dart @@ -1,8 +1,5 @@ import 'package:flutter/material.dart'; import 'package:frontend/exports.dart'; -import 'package:go_router/go_router.dart'; -import 'package:provider/provider.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class PromoPageView extends StatelessWidget { const PromoPageView({super.key}); @@ -16,35 +13,15 @@ class PromoPageView extends StatelessWidget { horizontal: 24, vertical: MediaQuery.sizeOf(context).height * 0.02, ), - child: Column( + child: const Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - const PromoScreenHeader(), - const PromoCardsSection(), - SizedBox( - width: double.infinity, - child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: MediaQuery.sizeOf(context).height * 0.07, - ), - child: UpgradeToProButton( - onTap: () { - context.read().isPro - ? _onDownGradeFromPro(context) - : context.read().upgradeToPro(); - context.pop(); - }, - price: AppLocalizations.of(context).price), - ), - ) + PromoScreenHeader(), + PromoCardsSection(), + BottomButtonsSection(), ], ), )), ); } - - void _onDownGradeFromPro(BuildContext context) { - context.read().downgradeFromPro(); - context.read().resetTheme(); - } } From c468439bf863391f2670d1564409887860cfb4cf Mon Sep 17 00:00:00 2001 From: neekeetuh Date: Mon, 9 Jun 2025 17:09:53 +0600 Subject: [PATCH 20/20] =?UTF-8?q?refactor:=20=D0=B2=D1=8B=D0=BD=D0=B5?= =?UTF-8?q?=D1=81=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D1=85,=20=D1=85=D1=80=D0=B0=D0=BD=D1=8F=D1=89=D0=B8=D1=85?= =?UTF-8?q?=D1=81=D1=8F=20=D0=B2=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B0=D0=B9?= =?UTF-8?q?=D0=B4=D0=B5=D1=80=D0=B5=20=D0=B2=20=D0=BA=D0=BB=D0=B0=D1=81?= =?UTF-8?q?=D1=81=D1=8B=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9=20+=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA?= =?UTF-8?q?=D0=B0=20=D0=B2=D0=BE=D1=81=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BE=D0=BA=D1=83=D0=BF?= =?UTF-8?q?=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../in_app_purchase_data_source.dart | 2 +- .../data_sources/revenuecat_data_source.dart | 9 +-- frontend/lib/l10n/en.arb | 3 +- frontend/lib/l10n/ru.arb | 3 +- .../lib/providers/pro_version_errors.dart | 1 + .../lib/providers/pro_version_provider.dart | 72 +++++++++---------- .../lib/providers/pro_version_states.dart | 27 +++++++ .../in_app_purchase_repository.dart | 4 +- .../menu_view/components/menu_app_bar.dart | 18 +++-- .../components/restore_purchases_button.dart | 28 +++++--- 10 files changed, 103 insertions(+), 64 deletions(-) create mode 100644 frontend/lib/providers/pro_version_errors.dart create mode 100644 frontend/lib/providers/pro_version_states.dart diff --git a/frontend/lib/data_sources/in_app_purchase_data_source.dart b/frontend/lib/data_sources/in_app_purchase_data_source.dart index 8ce1793..3798ed3 100644 --- a/frontend/lib/data_sources/in_app_purchase_data_source.dart +++ b/frontend/lib/data_sources/in_app_purchase_data_source.dart @@ -1,5 +1,5 @@ abstract interface class IInAppPurchaseDataSource { Future buyProduct(String productId); Future checkStatus(String productId); - Future restorePurchases(); + Future restorePurchases(); } diff --git a/frontend/lib/data_sources/revenuecat_data_source.dart b/frontend/lib/data_sources/revenuecat_data_source.dart index 424ecfe..d3ee34f 100644 --- a/frontend/lib/data_sources/revenuecat_data_source.dart +++ b/frontend/lib/data_sources/revenuecat_data_source.dart @@ -17,12 +17,7 @@ class RevenueCatDataSource implements IInAppPurchaseDataSource { } @override - Future restorePurchases() async { - try { - await Purchases.restorePurchases(); - return true; - } catch (_) { - return false; - } + Future restorePurchases() async { + Purchases.restorePurchases(); } } diff --git a/frontend/lib/l10n/en.arb b/frontend/lib/l10n/en.arb index 2888ce0..6b32384 100644 --- a/frontend/lib/l10n/en.arb +++ b/frontend/lib/l10n/en.arb @@ -107,5 +107,6 @@ "price": "1.99 $", "purchaseError": "Could not buy the product. Try again later", "restorePurchases": "Restore Purchases", - "restorePurchasesError": "Could not restore purchases. Try again later" + "restorePurchasesError": "Could not restore purchases. Try again later", + "restorePurchasesCompleted" : "Restore purchases completed. Check your current status" } \ No newline at end of file diff --git a/frontend/lib/l10n/ru.arb b/frontend/lib/l10n/ru.arb index 1f97b42..5bc46e5 100644 --- a/frontend/lib/l10n/ru.arb +++ b/frontend/lib/l10n/ru.arb @@ -107,5 +107,6 @@ "price": "199 ₽", "purchaseError": "Ошибка покупки товара. Попробуйте позже", "restorePurchases": "Восстановить покупки", - "restorePurchasesError": "Ошибка восстановления покупок. Попробуйте позже" + "restorePurchasesError": "Ошибка восстановления покупок. Попробуйте позже", + "restorePurchasesCompleted" : "Восстановление покупок прошло успешно. Проверьте ваш текущий статус" } \ No newline at end of file diff --git a/frontend/lib/providers/pro_version_errors.dart b/frontend/lib/providers/pro_version_errors.dart new file mode 100644 index 0000000..eabb16a --- /dev/null +++ b/frontend/lib/providers/pro_version_errors.dart @@ -0,0 +1 @@ +enum ProVersionError { purchaseError, restoreError } diff --git a/frontend/lib/providers/pro_version_provider.dart b/frontend/lib/providers/pro_version_provider.dart index b15b292..4acb375 100644 --- a/frontend/lib/providers/pro_version_provider.dart +++ b/frontend/lib/providers/pro_version_provider.dart @@ -1,73 +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 { //The key of a product should be the same as an entitlement name attached to it static const _proVersionKey = 'chessknock_pro_version'; - bool _isProStatus = false; - bool _isPurchaseError = false; - bool _isRestoreError = false; + late ProVersionState _state; final InAppPurchaseRepository _repository; ProVersionProvider({required InAppPurchaseRepository repository}) : _repository = repository { + _state = const InitialProVersionState(); _init(); } void _init() async { final isPro = await _repository.checkStatus(_proVersionKey); - _setProStatus(isPro); - } - - void _setProStatus(bool status) { - if (_isProStatus == status) return; - _isProStatus = status; - notifyListeners(); - } - - void _setPurchaseError(bool isError) { - if (_isPurchaseError == isError) return; - _isPurchaseError = isError; - notifyListeners(); - } - - void _setRestoreError(bool isRestoreError) { - if (_isRestoreError == isRestoreError) return; - _isRestoreError = isRestoreError; - notifyListeners(); + _setState(InitialProVersionState(isProStatus: isPro)); } Future upgradeToPro() async { try { await _repository.buyProduct(_proVersionKey); final isPro = await _repository.checkStatus(_proVersionKey); - _setProStatus(isPro); - _setPurchaseError(false); + if (isPro) _setState(const SuccessfulPurchaseState()); } catch (_) { - _setPurchaseError(true); - await Future.delayed(const Duration(milliseconds: 100)); - _setPurchaseError(false); + _setState(ErrorProVersionState( + error: ProVersionError.purchaseError, + isProStatus: state.isProStatus, + )); } } void downgradeFromPro() { - _setProStatus(false); + _setState(const InitialProVersionState()); } Future restorePurchases() async { - final isRestoreCompleted = await _repository.restorePurchases(); - if (isRestoreCompleted) { - _setRestoreError(false); - } else { - _setRestoreError(true); - await Future.delayed(const Duration(milliseconds: 100)); - _setRestoreError(false); + try { + await _repository.restorePurchases(); + final isPro = await _repository.checkStatus(_proVersionKey); + _setState(SuccessfulRestorePurchasesState(isProStatus: isPro)); + } catch (_) { + _setState(ErrorProVersionState( + error: ProVersionError.restoreError, + isProStatus: state.isProStatus, + )); } } - bool get isPro => _isProStatus; - bool get isPurchaseError => _isPurchaseError; - bool get isRestoreError => _isRestoreError; + void _setState(ProVersionState state) { + _state = state; + notifyListeners(); + } + + ProVersionState get state => _state; + + bool get isPro => state.isProStatus; + ProVersionError? get error { + if (state is ErrorProVersionState) { + return (state as ErrorProVersionState).error; + } + return null; + } } diff --git a/frontend/lib/providers/pro_version_states.dart b/frontend/lib/providers/pro_version_states.dart new file mode 100644 index 0000000..d3802f0 --- /dev/null +++ b/frontend/lib/providers/pro_version_states.dart @@ -0,0 +1,27 @@ +import 'package:frontend/providers/pro_version_errors.dart'; + +sealed class ProVersionState { + final bool isProStatus; + + const ProVersionState({ + required this.isProStatus, + }); +} + +class InitialProVersionState extends ProVersionState { + const InitialProVersionState({super.isProStatus = false}); +} + +class ErrorProVersionState extends ProVersionState { + ErrorProVersionState({required this.error, required super.isProStatus}); + + final ProVersionError error; +} + +class SuccessfulRestorePurchasesState extends ProVersionState { + SuccessfulRestorePurchasesState({required super.isProStatus}); +} + +class SuccessfulPurchaseState extends ProVersionState { + const SuccessfulPurchaseState({super.isProStatus = true}); +} diff --git a/frontend/lib/repositories/in_app_purchase_repository.dart b/frontend/lib/repositories/in_app_purchase_repository.dart index 2feabb8..b6f5605 100644 --- a/frontend/lib/repositories/in_app_purchase_repository.dart +++ b/frontend/lib/repositories/in_app_purchase_repository.dart @@ -15,7 +15,7 @@ class InAppPurchaseRepository { return _dataSource.checkStatus(productId); } - Future restorePurchases() { - return _dataSource.restorePurchases(); + Future restorePurchases() async { + _dataSource.restorePurchases(); } } diff --git a/frontend/lib/views/menu_view/components/menu_app_bar.dart b/frontend/lib/views/menu_view/components/menu_app_bar.dart index b4c8168..78133f5 100644 --- a/frontend/lib/views/menu_view/components/menu_app_bar.dart +++ b/frontend/lib/views/menu_view/components/menu_app_bar.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:frontend/common/shared_functions.dart'; +import 'package:frontend/providers/pro_version_errors.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -20,12 +21,9 @@ class MenuAppBar extends StatelessWidget { final scheme = Theme.of(context).colorScheme; final provider = context.watch(); final isPro = provider.isPro; - if (provider.isPurchaseError) { - WidgetsBinding.instance.addPostFrameCallback((_) { - SharedFunctions.showSnackBar( - context, AppLocalizations.of(context).purchaseError); - }); - } + provider.addListener(() { + _onListenProvider(context); + }); return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -48,4 +46,12 @@ class MenuAppBar extends StatelessWidget { ], ); } + + void _onListenProvider(BuildContext context) { + final provider = context.read(); + if (provider.error == ProVersionError.purchaseError) { + SharedFunctions.showSnackBar( + context, AppLocalizations.of(context).purchaseError); + } + } } diff --git a/frontend/lib/views/promo_view/components/restore_purchases_button.dart b/frontend/lib/views/promo_view/components/restore_purchases_button.dart index 57c9faa..d6ce280 100644 --- a/frontend/lib/views/promo_view/components/restore_purchases_button.dart +++ b/frontend/lib/views/promo_view/components/restore_purchases_button.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:frontend/common/shared_functions.dart'; import 'package:frontend/exports.dart'; +import 'package:frontend/providers/pro_version_errors.dart'; +import 'package:frontend/providers/pro_version_states.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -9,16 +11,14 @@ class RestorePurchasesButton extends StatelessWidget { @override Widget build(BuildContext context) { - final provider = context.watch(); - if (provider.isRestoreError) { - WidgetsBinding.instance.addPostFrameCallback((_) { - SharedFunctions.showSnackBar( - context, AppLocalizations.of(context).restorePurchasesError); - }); - } + final provider = context.read(); + provider.addListener(() { + _onListenProvider(context); + }); + return CustomButton( gradient: GradientConsts.grey, - onTap: context.read().restorePurchases, + onTap: provider.restorePurchases, child: Padding( padding: const EdgeInsets.symmetric(vertical: 16), child: Text( @@ -28,4 +28,16 @@ class RestorePurchasesButton extends StatelessWidget { ), )); } + + void _onListenProvider(BuildContext context) { + final provider = context.read(); + if (provider.error == ProVersionError.restoreError) { + SharedFunctions.showSnackBar( + context, AppLocalizations.of(context).restorePurchasesError); + } + if (provider.state is SuccessfulRestorePurchasesState) { + SharedFunctions.showSnackBar( + context, AppLocalizations.of(context).restorePurchasesCompleted); + } + } }