diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 00000000..9cad7b21 --- /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 57be19c0..c3e8e704 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1,3 +1,5 @@ +*.env + # Miscellaneous *.class *.log diff --git a/frontend/ios/Podfile.lock b/frontend/ios/Podfile.lock index 334a3470..21a85848 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/ios/Runner.xcodeproj/project.pbxproj b/frontend/ios/Runner.xcodeproj/project.pbxproj index 50fd984c..177178cc 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 a8191529..ae61fda2 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/l10n.yaml b/frontend/l10n.yaml index f3781cab..ca687774 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/common/shared_functions.dart b/frontend/lib/common/shared_functions.dart index e5169940..1f7b6b25 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/constants/assets.dart b/frontend/lib/constants/assets.dart new file mode 100644 index 00000000..5bcb5701 --- /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/constants/constants.dart b/frontend/lib/constants/constants.dart index 15640624..be66b301 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 a6e24c66..00000000 --- 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/data_sources/in_app_purchase_data_source.dart b/frontend/lib/data_sources/in_app_purchase_data_source.dart new file mode 100644 index 00000000..3798ed38 --- /dev/null +++ b/frontend/lib/data_sources/in_app_purchase_data_source.dart @@ -0,0 +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 new file mode 100644 index 00000000..d3ee34ff --- /dev/null +++ b/frontend/lib/data_sources/revenuecat_data_source.dart @@ -0,0 +1,23 @@ +import 'package:frontend/data_sources/in_app_purchase_data_source.dart'; +import 'package:purchases_flutter/purchases_flutter.dart'; + +class RevenueCatDataSource implements IInAppPurchaseDataSource { + @override + Future buyProduct(String productId) async { + final product = (await Purchases.getProducts([productId])).first; + final customerInfo = await Purchases.purchaseStoreProduct(product); + final isPro = customerInfo.entitlements.active[productId] != null; + return isPro; + } + + @override + Future checkStatus(String productId) async { + final customerInfo = await Purchases.getCustomerInfo(); + return customerInfo.entitlements.active.containsKey(productId); + } + + @override + Future restorePurchases() async { + Purchases.restorePurchases(); + } +} diff --git a/frontend/lib/l10n/en.arb b/frontend/lib/l10n/en.arb index 317a3194..6b32384e 100644 --- a/frontend/lib/l10n/en.arb +++ b/frontend/lib/l10n/en.arb @@ -103,5 +103,10 @@ "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 $", + "purchaseError": "Could not buy the product. Try again later", + "restorePurchases": "Restore Purchases", + "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 dd3b5fd6..5bc46e54 100644 --- a/frontend/lib/l10n/ru.arb +++ b/frontend/lib/l10n/ru.arb @@ -103,5 +103,10 @@ "moveBackModal": "Возможность вернуть\nигровое поле на ход назад", "threatsModal": "Отображение опасности\nвзятия вашей фигуры", "hintsModal": "Отображение подсказок\nвозможных ходов", - "description": "Описание" -} + "description": "Описание", + "price": "199 ₽", + "purchaseError": "Ошибка покупки товара. Попробуйте позже", + "restorePurchases": "Восстановить покупки", + "restorePurchasesError": "Ошибка восстановления покупок. Попробуйте позже", + "restorePurchasesCompleted" : "Восстановление покупок прошло успешно. Проверьте ваш текущий статус" +} \ No newline at end of file diff --git a/frontend/lib/main.dart b/frontend/lib/main.dart index 36ee863b..19229c8f 100644 --- a/frontend/lib/main.dart +++ b/frontend/lib/main.dart @@ -1,13 +1,30 @@ +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"; 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 { + await dotenv.load(fileName: ".env"); + WidgetsFlutterBinding.ensureInitialized(); + if (Platform.isIOS) { + await configureRevenueCat(); + } runApp(const MyApp()); } +Future configureRevenueCat() async { + Purchases.setLogLevel(LogLevel.debug); + final apiKey = dotenv.env['REVENUE_CAT_API_KEY']; + Purchases.configure(PurchasesConfiguration(apiKey!)); +} + class MyApp extends StatelessWidget { const MyApp({super.key}); @@ -23,7 +40,12 @@ class MyApp extends StatelessWidget { create: (context) => ThemeProvider(), ), ChangeNotifierProvider( - create: (context) => ProVersionProvider(), + create: (context) => ProVersionProvider( + repository: InAppPurchaseRepository( + //TODO: choose data source based off of a platform + dataSource: RevenueCatDataSource(), + ), + ), ) ], child: Builder(builder: (context) { diff --git a/frontend/lib/providers/pro_version_errors.dart b/frontend/lib/providers/pro_version_errors.dart new file mode 100644 index 00000000..eabb16a6 --- /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 173e0912..4acb3754 100644 --- a/frontend/lib/providers/pro_version_provider.dart +++ b/frontend/lib/providers/pro_version_provider.dart @@ -1,20 +1,69 @@ import 'package:flutter/material.dart'; +import 'package:frontend/providers/pro_version_errors.dart'; +import 'package:frontend/providers/pro_version_states.dart'; +import 'package:frontend/repositories/in_app_purchase_repository.dart'; class ProVersionProvider extends ChangeNotifier { - bool _isProStatus = false; + //The key of a product should be the same as an entitlement name attached to it + static const _proVersionKey = 'chessknock_pro_version'; - void _setProStatus(bool status) { - _isProStatus = status; - notifyListeners(); + late ProVersionState _state; + + final InAppPurchaseRepository _repository; + + ProVersionProvider({required InAppPurchaseRepository repository}) + : _repository = repository { + _state = const InitialProVersionState(); + _init(); } - void upgradeToPro() { - _setProStatus(true); + void _init() async { + final isPro = await _repository.checkStatus(_proVersionKey); + _setState(InitialProVersionState(isProStatus: isPro)); + } + + Future upgradeToPro() async { + try { + await _repository.buyProduct(_proVersionKey); + final isPro = await _repository.checkStatus(_proVersionKey); + if (isPro) _setState(const SuccessfulPurchaseState()); + } catch (_) { + _setState(ErrorProVersionState( + error: ProVersionError.purchaseError, + isProStatus: state.isProStatus, + )); + } } void downgradeFromPro() { - _setProStatus(false); + _setState(const InitialProVersionState()); + } + + Future restorePurchases() async { + try { + await _repository.restorePurchases(); + final isPro = await _repository.checkStatus(_proVersionKey); + _setState(SuccessfulRestorePurchasesState(isProStatus: isPro)); + } catch (_) { + _setState(ErrorProVersionState( + error: ProVersionError.restoreError, + isProStatus: state.isProStatus, + )); + } + } + + void _setState(ProVersionState state) { + _state = state; + notifyListeners(); } - bool get isPro => _isProStatus; + ProVersionState get state => _state; + + bool get isPro => state.isProStatus; + ProVersionError? get error { + if (state is ErrorProVersionState) { + return (state as ErrorProVersionState).error; + } + return null; + } } diff --git a/frontend/lib/providers/pro_version_states.dart b/frontend/lib/providers/pro_version_states.dart new file mode 100644 index 00000000..d3802f00 --- /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 new file mode 100644 index 00000000..b6f56052 --- /dev/null +++ b/frontend/lib/repositories/in_app_purchase_repository.dart @@ -0,0 +1,21 @@ +import 'package:frontend/data_sources/in_app_purchase_data_source.dart'; + +class InAppPurchaseRepository { + final IInAppPurchaseDataSource _dataSource; + + InAppPurchaseRepository({ + required IInAppPurchaseDataSource dataSource, + }) : _dataSource = dataSource; + + Future buyProduct(String productId) { + return _dataSource.buyProduct(productId); + } + + Future checkStatus(String productId) { + return _dataSource.checkStatus(productId); + } + + Future restorePurchases() async { + _dataSource.restorePurchases(); + } +} diff --git a/frontend/lib/views/components/button_to_guide.dart b/frontend/lib/views/components/button_to_guide.dart index 312b2eb0..82698f1a 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/components.dart b/frontend/lib/views/components/components.dart index 05e17aee..05258009 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 00000000..2819d2e8 --- /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/components/loading_widget.dart b/frontend/lib/views/components/loading_widget.dart index cba71bd8..4ea04663 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 64fa7d95..04d5c7da 100644 --- a/frontend/lib/views/components/pro_functions_tooltip.dart +++ b/frontend/lib/views/components/pro_functions_tooltip.dart @@ -67,7 +67,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/back_arrow_button.dart b/frontend/lib/views/game_view/components/back_arrow_button.dart index d494cf67..81bb9104 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 1745f2c4..a419c6ca 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 b21d76e4..9d8ec907 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 076b00a9..00768997 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 1bdc8a61..583fb037 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 @@ -12,7 +13,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, @@ -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 60127165..10f2bbf1 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 { @@ -10,7 +11,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, @@ -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/components/shared_functions.dart b/frontend/lib/views/game_view/components/shared_functions.dart index af675ae9..45529d10 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 823f70a7..f783f8d1 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/game_view/game_page_view.dart b/frontend/lib/views/game_view/game_page_view.dart index 8c58ca64..baea9230 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'; @@ -35,7 +36,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( @@ -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 5fe5212c..526e0378 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/guide_piece_carousel.dart b/frontend/lib/views/guide_view/components/guide_piece_carousel.dart index 5545c959..67e3c22c 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/components/points_indicator.dart b/frontend/lib/views/guide_view/components/points_indicator.dart index c46ab4f5..7d053031 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/guide_view/guide_chose_view.dart b/frontend/lib/views/guide_view/guide_chose_view.dart index b611f2ca..6a2c5c6b 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( @@ -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/guide_view/guide_piece_view.dart b/frontend/lib/views/guide_view/guide_piece_view.dart index d36c0977..037805cc 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/custom_switch.dart b/frontend/lib/views/menu_view/components/custom_switch.dart index 4dc44d23..8b176b01 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/menu_app_bar.dart b/frontend/lib/views/menu_view/components/menu_app_bar.dart index 0e048c39..78133f5e 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,9 @@ 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'; import '../../../exports.dart'; class MenuAppBar extends StatelessWidget { @@ -15,18 +18,22 @@ 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; + provider.addListener(() { + _onListenProvider(context); + }); return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ isPro ? Row( children: [ - CustomSwitch(provider), + CustomSwitch( + Provider.of(context, listen: false)), const SizedBox(width: 16), - ProStatusIndicator(onTap: () => context.push(promoScreenRoute)), + const ProStatusIndicator(), ], ) : UpgradeToProButton(onTap: () => context.push(promoScreenRoute)), @@ -39,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/menu_view/components/pro_status_indicator.dart b/frontend/lib/views/menu_view/components/pro_status_indicator.dart index c44c6ccc..7b34d511 100644 --- a/frontend/lib/views/menu_view/components/pro_status_indicator.dart +++ b/frontend/lib/views/menu_view/components/pro_status_indicator.dart @@ -1,20 +1,22 @@ +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({ super.key, - required this.onTap, }); - final VoidCallback onTap; - @override Widget build(BuildContext context) { return GestureDetector( - onTap: onTap, + onTap: () => + (Platform.isIOS) ? context.push(RouteLocations.promoScreen) : null, child: Row( children: [ ShaderMask( @@ -23,7 +25,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), )), @@ -33,7 +35,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 69e81324..b36e024b 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'; @@ -12,7 +13,7 @@ class UpgradeToProButton extends StatelessWidget { }); final VoidCallback onTap; - final int? price; + final String? price; @override Widget build(BuildContext context) { @@ -30,53 +31,41 @@ class UpgradeToProButton extends StatelessWidget { right: 12, ); final double iconSize = isExtended ? 29 : 24; - final l10n = AppLocalizations.of(context)!; - return GestureDetector( + final l10n = AppLocalizations.of(context); + 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/images/icons/pro_sparkles.svg', - colorFilter: const ColorFilter.mode( - ColorsConst.neutralColor0, BlendMode.srcIn), - ), - if (price != null) ...[ - Text( - '$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 82b918f1..a06457b3 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/phone_menu_layout.dart b/frontend/lib/views/menu_view/phone_menu_layout.dart index 5be2516a..42c00792 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 4c00b114..891aee7b 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: MenuPageStringConst.localButton, + buttonText: AppLocalizations.of(context).startGame, settingsScreenRoute: RouteLocations.settingsScreen, ), ), 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 b413cafd..07c4e6c1 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 7439b89d..f0cbe4a6 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 b6164e5d..a89efac3 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 - }; + final l10n = AppLocalizations.of(context); 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 83003a3f..401b6b4b 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 bb345667..16a8a417 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), @@ -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], ); }, 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 00000000..6864eebc --- /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 d11ae818..96edbc1a 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/promo_cards_section.dart b/frontend/lib/views/promo_view/components/promo_cards_section.dart index 146a105b..477cd44c 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'; @@ -11,7 +12,7 @@ class PromoCardsSection extends StatelessWidget { @override Widget build(BuildContext context) { - final l10n = AppLocalizations.of(context)!; + final l10n = AppLocalizations.of(context); return Column( children: [ PromoFeatureCard( @@ -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 c03852a3..d3674daa 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: () { @@ -28,7 +29,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/components/restore_purchases_button.dart b/frontend/lib/views/promo_view/components/restore_purchases_button.dart new file mode 100644 index 00000000..d6ce2803 --- /dev/null +++ b/frontend/lib/views/promo_view/components/restore_purchases_button.dart @@ -0,0 +1,43 @@ +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'; + +class RestorePurchasesButton extends StatelessWidget { + const RestorePurchasesButton({super.key}); + + @override + Widget build(BuildContext context) { + final provider = context.read(); + provider.addListener(() { + _onListenProvider(context); + }); + + return CustomButton( + gradient: GradientConsts.grey, + onTap: provider.restorePurchases, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Text( + AppLocalizations.of(context).restorePurchases, + style: + TextStyles.header1.copyWith(color: ColorsConst.neutralColor0), + ), + )); + } + + 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); + } + } +} 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 30f477bf..00000000 --- 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 457746b7..00000000 --- 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 d7c8e605..703b1651 100644 --- a/frontend/lib/views/promo_view/promo_page_view.dart +++ b/frontend/lib/views/promo_view/promo_page_view.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; import 'package:frontend/exports.dart'; -import 'package:go_router/go_router.dart'; -import 'package:provider/provider.dart'; class PromoPageView extends StatelessWidget { const PromoPageView({super.key}); @@ -15,38 +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: context.read().isPro - ? null - : PromoPageConstants.proVersionPrice, - ), - ), - ) + PromoScreenHeader(), + PromoCardsSection(), + BottomButtonsSection(), ], ), )), ); } - - void _onDownGradeFromPro(BuildContext context) { - context.read().downgradeFromPro(); - context.read().resetTheme(); - } } diff --git a/frontend/lib/views/promo_view/promo_view.dart b/frontend/lib/views/promo_view/promo_view.dart index c52e298f..8be1cb02 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'; 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 6ed2754a..ade06f16 100644 --- a/frontend/lib/views/setting_view/components/app_bar_settings.dart +++ b/frontend/lib/views/setting_view/components/app_bar_settings.dart @@ -1,5 +1,6 @@ import "package:flutter/material.dart"; import "package:frontend/common/shared_functions.dart"; +import "package:frontend/constants/assets.dart"; import "package:go_router/go_router.dart"; import "../../../exports.dart"; @@ -16,7 +17,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: isTablet ? 50 : 40, onTap: () { 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 d00502a4..6e85bbaa 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/chose_time_carousel.dart b/frontend/lib/views/setting_view/components/chose_time_carousel.dart index 31d28fbd..a79fa11c 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"; @@ -56,7 +57,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: () { @@ -91,7 +92,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/color_chose_button.dart b/frontend/lib/views/setting_view/components/color_chose_button.dart index 7967863c..b40b6d59 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 e27c4ec8..a1719ddf 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_row.dart b/frontend/lib/views/setting_view/components/settings_row.dart index dc7ec97a..8879e6de 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/common/shared_functions.dart"; import "package:frontend/exports.dart"; import "package:provider/provider.dart"; @@ -51,7 +52,7 @@ class _SettingsRowState extends State { isPro: isPro, modalHeader: widget.modalHeader, child: SvgPicture.asset( - "assets/images/icons/question_icon.svg", + Assets.questionIcon, colorFilter: ColorFilter.mode( isPro ? scheme.tertiaryContainer 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 5a9d4f75..0f4eabe7 100644 --- a/frontend/lib/views/setting_view/components/settings_rows_section.dart +++ b/frontend/lib/views/setting_view/components/settings_rows_section.dart @@ -12,7 +12,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/constants/game_setting_const.dart b/frontend/lib/views/setting_view/constants/game_setting_const.dart index b36bead9..c2d90d5b 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 561b6329..188a5e9c 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 5d95758d..be914e16 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 171db46c..41d356b2 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"; diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock index dc616a49..decbc298 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: @@ -165,6 +173,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: @@ -201,10 +217,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 +229,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 +273,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 +313,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: @@ -301,6 +341,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 @@ -391,10 +439,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 +483,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: diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index 4ab46ddb..5f3104f0 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -27,6 +27,8 @@ dependencies: super_tooltip: ^2.0.9 flutter_localizations: sdk: flutter + purchases_flutter: ^5.8.0 + flutter_dotenv: ^5.2.1 dev_dependencies: flutter_test: @@ -51,6 +53,7 @@ flutter: - assets/images/small_pieces/ - assets/images/icons/ - assets/images/guide_boards/ + - .env fonts: - family: Roboto