diff --git a/android/gradle.properties b/android/gradle.properties index f0be9fb67d6fe0b36ce90df03ff2f3f1551d738c..d9abd55731010fe508f39321892e8002f10e79ef 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,5 +1,5 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true -app.versionName=0.0.11 -app.versionCode=11 +app.versionName=0.0.12 +app.versionCode=12 diff --git a/assets/translations/en.json b/assets/translations/en.json index 435402a46f0aaaede5093f8f4ce57dc90aa9400f..a170877f82723392a7e4c0b6e9d731166207600d 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -1,6 +1,11 @@ { "app_name": "Twister", + "left_hand": "left hand", + "right_hand": "right hand", + "left_foot": "left foot", + "right_foot": "right foot", + "bottom_nav_game": "Game", "bottom_nav_settings": "Settings", diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 45fb89746475f43c800ee5a590983273327fd2bc..05bbe109fdb1c472c5c7387a974e3e8ed48d9bb3 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -1,6 +1,11 @@ { "app_name": "Twister", + "left_hand": "main gauche", + "right_hand": "main droite", + "left_foot": "pied gauche", + "right_foot": "pied droit", + "bottom_nav_game": "Jeu", "bottom_nav_settings": "Réglages", diff --git a/fastlane/metadata/android/en-US/changelogs/12.txt b/fastlane/metadata/android/en-US/changelogs/12.txt new file mode 100644 index 0000000000000000000000000000000000000000..c4767c31cd60be21ad424c3e0b8357a7a045e229 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/12.txt @@ -0,0 +1 @@ +Add minimal playable game. diff --git a/fastlane/metadata/android/fr-FR/changelogs/12.txt b/fastlane/metadata/android/fr-FR/changelogs/12.txt new file mode 100644 index 0000000000000000000000000000000000000000..90a1f5eea419b25b945a8fe6bdf3f55355220ec0 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/12.txt @@ -0,0 +1 @@ +Création du jeu minimal mais jouable. diff --git a/lib/cubit/game_cubit.dart b/lib/cubit/game_cubit.dart new file mode 100644 index 0000000000000000000000000000000000000000..ddc7353f6e2f25cd92448993e0c26e599e1c4efb --- /dev/null +++ b/lib/cubit/game_cubit.dart @@ -0,0 +1,39 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:hydrated_bloc/hydrated_bloc.dart'; + +import 'package:twister/models/move.dart'; + +part 'game_state.dart'; + +class GameCubit extends HydratedCubit<GameState> { + GameCubit() : super(const GameState()); + + Move getMove() { + return state.move ?? Move.createNull(); + } + + void setValues({ + Move? move, + }) { + emit(GameState( + move: move ?? state.move, + )); + } + + @override + GameState? fromJson(Map<String, dynamic> json) { + Move move = json['move'] as Move; + + return GameState( + move: move, + ); + } + + @override + Map<String, dynamic>? toJson(GameState state) { + return <String, dynamic>{ + 'move': state.move?.toJson(), + }; + } +} diff --git a/lib/cubit/game_state.dart b/lib/cubit/game_state.dart new file mode 100644 index 0000000000000000000000000000000000000000..f859fd5aebc6f5c645398506e55de63a5a5f461f --- /dev/null +++ b/lib/cubit/game_state.dart @@ -0,0 +1,19 @@ +part of 'game_cubit.dart'; + +@immutable +class GameState extends Equatable { + const GameState({ + this.move, + }); + + final Move? move; + + @override + List<dynamic> get props => <dynamic>[ + move, + ]; + + Map<String, dynamic> get values => <String, dynamic>{ + 'move': move, + }; +} diff --git a/lib/cubit/settings_state.dart b/lib/cubit/settings_state.dart index 381163143e113aad95ce32baff1ae66f3a5c3338..6048256f247aeb898ba1b3b10ca2daae3468a7c9 100644 --- a/lib/cubit/settings_state.dart +++ b/lib/cubit/settings_state.dart @@ -14,6 +14,6 @@ class SettingsState extends Equatable { ]; Map<String, dynamic> get values => <String, dynamic>{ - 'discoveriesDaysCount': timerValue, + 'timerValue': timerValue, }; } diff --git a/lib/main.dart b/lib/main.dart index d30fd3e910b5cf65647fc12c6a7a2d1dd03788a1..04f395244869b5cf92e405317da2b66c8d30f2c9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'package:path_provider/path_provider.dart'; import 'package:twister/config/theme.dart'; import 'package:twister/cubit/bottom_nav_cubit.dart'; +import 'package:twister/cubit/game_cubit.dart'; import 'package:twister/cubit/settings_cubit.dart'; import 'package:twister/ui/skeleton.dart'; @@ -44,6 +45,7 @@ class MyApp extends StatelessWidget { return MultiBlocProvider( providers: [ BlocProvider<BottomNavCubit>(create: (context) => BottomNavCubit()), + BlocProvider<GameCubit>(create: (context) => GameCubit()), BlocProvider<SettingsCubit>(create: (context) => SettingsCubit()), ], child: MaterialApp( diff --git a/lib/models/move.dart b/lib/models/move.dart new file mode 100644 index 0000000000000000000000000000000000000000..b415b2bdfb04738bba994bb87cd11b0d02733ccd --- /dev/null +++ b/lib/models/move.dart @@ -0,0 +1,37 @@ +import 'package:twister/models/twister_color.dart'; +import 'package:twister/models/twister_member.dart'; + +class Move { + final TwisterColor? color; + final TwisterMember? member; + + Move({ + required this.color, + required this.member, + }); + + factory Move.createNull() { + return Move( + color: null, + member: null, + ); + } + + factory Move.pickRandom() { + return Move( + color: TwisterColor.pickRandom(), + member: TwisterMember.pickRandom(), + ); + } + + String toString() { + return 'Move(' + this.toJson().toString() + ')'; + } + + Map<String, dynamic>? toJson() { + return <String, dynamic>{ + 'color': this.color.toString(), + 'member': this.member.toString(), + }; + } +} diff --git a/lib/models/twister_color.dart b/lib/models/twister_color.dart new file mode 100644 index 0000000000000000000000000000000000000000..eb8c21b75948a70a05674f591874800fc35d3b9d --- /dev/null +++ b/lib/models/twister_color.dart @@ -0,0 +1,25 @@ +import 'dart:math'; + +enum TwisterAllowedColors { + blue, + green, + red, + yellow, +} + +class TwisterColor { + final TwisterAllowedColors value; + + TwisterColor({ + required this.value, + }); + + factory TwisterColor.pickRandom() { + int random = Random().nextInt(TwisterAllowedColors.values.length); + return TwisterColor(value: TwisterAllowedColors.values[random]); + } + + String toString() { + return this.value.toString(); + } +} diff --git a/lib/models/twister_member.dart b/lib/models/twister_member.dart new file mode 100644 index 0000000000000000000000000000000000000000..875f7d54f46d86d721a6d0adfd72891e48f92055 --- /dev/null +++ b/lib/models/twister_member.dart @@ -0,0 +1,25 @@ +import 'dart:math'; + +enum TwisterAllowedMembers { + leftHand, + rightHand, + leftFoot, + rightFoot, +} + +class TwisterMember { + final TwisterAllowedMembers value; + + TwisterMember({ + required this.value, + }); + + factory TwisterMember.pickRandom() { + int random = Random().nextInt(TwisterAllowedMembers.values.length); + return TwisterMember(value: TwisterAllowedMembers.values[random]); + } + + String toString() { + return this.value.toString(); + } +} diff --git a/lib/ui/screens/home.dart b/lib/ui/screens/home.dart index d5ba4dfcd554d16c88f2c1a3664f2c5cd689629b..dbda13984896851dcd736e7ef46271fbc9b4e552 100644 --- a/lib/ui/screens/home.dart +++ b/lib/ui/screens/home.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:twister/ui/widgets/game.dart'; import 'package:unicons/unicons.dart'; class ScreenHome extends StatelessWidget { @@ -16,7 +17,7 @@ class ScreenHome extends StatelessWidget { physics: const BouncingScrollPhysics(), children: <Widget>[ const SizedBox(height: 8), - const Text('HOME'), + const Game(), const SizedBox(height: 36), ], ), diff --git a/lib/ui/skeleton.dart b/lib/ui/skeleton.dart index ca33599bd3323d162f8c492a9d8baa42f0639623..a9fae72ca0ce1decd464afdb569ff88bd3f5ddac 100644 --- a/lib/ui/skeleton.dart +++ b/lib/ui/skeleton.dart @@ -18,7 +18,7 @@ class _SkeletonScreenState extends State<SkeletonScreen> { @override Widget build(BuildContext context) { List<Widget> pageNavigation = <Widget>[ - const ScreenHome(), + ScreenHome(), const ScreenSettings(), ]; @@ -28,8 +28,9 @@ class _SkeletonScreenState extends State<SkeletonScreen> { body: BlocBuilder<BottomNavCubit, int>( builder: (BuildContext context, int state) { return AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: pageNavigation.elementAt(state)); + duration: const Duration(milliseconds: 300), + child: pageNavigation.elementAt(state), + ); }, ), backgroundColor: Theme.of(context).colorScheme.background, diff --git a/lib/ui/widgets/game.dart b/lib/ui/widgets/game.dart new file mode 100644 index 0000000000000000000000000000000000000000..1d4a181bcac3e6869b190fa06eaf37ef4c54a9ce --- /dev/null +++ b/lib/ui/widgets/game.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:unicons/unicons.dart'; + +import 'package:twister/cubit/game_cubit.dart'; +import 'package:twister/models/move.dart'; +import 'package:twister/ui/widgets/show_move.dart'; + +class Game extends StatefulWidget { + const Game({super.key}); + + @override + State<Game> createState() => _GameState(); +} + +class _GameState extends State<Game> { + Move move = Move.createNull(); + + Widget pickNewMove() { + return BlocBuilder<GameCubit, GameState>(builder: (BuildContext context, GameState state) { + return TextButton( + onPressed: () { + BlocProvider.of<GameCubit>(context).setValues( + move: Move.pickRandom(), + ); + }, + child: Icon(UniconsSolid.refresh), + ); + }); + } + + @override + Widget build(BuildContext context) { + return BlocBuilder<GameCubit, GameState>( + builder: (context, gameState) { + return Column( + children: [ + ShowMove(move: gameState.move ?? Move.createNull()), + pickNewMove(), + ], + ); + }, + ); + } +} diff --git a/lib/ui/widgets/show_move.dart b/lib/ui/widgets/show_move.dart new file mode 100644 index 0000000000000000000000000000000000000000..adead4b605b53ab8619ac84183ae01cd79df1342 --- /dev/null +++ b/lib/ui/widgets/show_move.dart @@ -0,0 +1,69 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +import 'package:twister/models/move.dart'; +import 'package:twister/models/twister_color.dart'; +import 'package:twister/models/twister_member.dart'; +import 'package:twister/utils/color_extensions.dart'; + +class ShowMove extends StatelessWidget { + const ShowMove({super.key, required this.move}); + + final Move move; + + Color getColor(Move move) { + switch (move.color?.value) { + case TwisterAllowedColors.blue: + return Colors.blue; + case TwisterAllowedColors.green: + return Colors.green; + case TwisterAllowedColors.red: + return Colors.red; + case TwisterAllowedColors.yellow: + return Colors.yellow; + default: + return Colors.grey; + } + } + + Widget getWidget(Move move) { + TextStyle style = TextStyle( + color: Colors.black, + fontSize: 30, + fontWeight: FontWeight.bold, + ); + + switch (move.member?.value) { + case TwisterAllowedMembers.leftHand: + return Text(tr('left_hand'), style: style); + case TwisterAllowedMembers.rightHand: + return Text(tr('right_hand'), style: style); + case TwisterAllowedMembers.leftFoot: + return Text(tr('left_foot'), style: style); + case TwisterAllowedMembers.rightFoot: + return Text(tr('right_foot'), style: style); + default: + return Text('?', style: style); + } + } + + @override + Widget build(BuildContext context) { + Color color = getColor(move); + + return Container( + child: Padding( + padding: EdgeInsets.all(20), + child: getWidget(move), + ), + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.all(Radius.circular(50)), + border: Border.all( + color: color.darken(20), + width: 10, + ), + ), + ); + } +} diff --git a/lib/utils/color_extensions.dart b/lib/utils/color_extensions.dart new file mode 100644 index 0000000000000000000000000000000000000000..4e55e338f0d3ed98b233d1ef887b7b3e17e29d97 --- /dev/null +++ b/lib/utils/color_extensions.dart @@ -0,0 +1,33 @@ +import 'dart:ui'; + +extension ColorExtension on Color { + Color darken([int percent = 40]) { + assert(1 <= percent && percent <= 100); + final value = 1 - percent / 100; + return Color.fromARGB( + alpha, + (red * value).round(), + (green * value).round(), + (blue * value).round(), + ); + } + + Color lighten([int percent = 40]) { + assert(1 <= percent && percent <= 100); + final value = percent / 100; + return Color.fromARGB( + alpha, + (red + ((255 - red) * value)).round(), + (green + ((255 - green) * value)).round(), + (blue + ((255 - blue) * value)).round(), + ); + } + + Color avg(Color other) { + final red = (this.red + other.red) ~/ 2; + final green = (this.green + other.green) ~/ 2; + final blue = (this.blue + other.blue) ~/ 2; + final alpha = (this.alpha + other.alpha) ~/ 2; + return Color.fromARGB(alpha, red, green, blue); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index ac2c2480d0731192995c89dcfde7f84dc605def9..1a0109022d6dbcf267795175a8494ac2f1aadce8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ description: twister game companion publish_to: 'none' -version: 0.0.11+11 +version: 0.0.12+12 environment: sdk: '^3.0.0'