Skip to content
Snippets Groups Projects
Commit 8c90c184 authored by Benoît Harrault's avatar Benoît Harrault
Browse files

Create base empty application

parent 59f62a10
No related branches found
No related tags found
1 merge request!1Resolve "Create base empty application"
Pipeline #5912 passed
Showing
with 1048 additions and 0 deletions
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:awale/models/game/game.dart';
import 'package:awale/models/settings/settings_game.dart';
import 'package:awale/models/settings/settings_global.dart';
import 'package:awale/utils/tools.dart';
part 'game_state.dart';
class GameCubit extends HydratedCubit<GameState> {
GameCubit()
: super(GameState(
currentGame: Game.createNull(),
));
void updateState(Game game) {
emit(GameState(
currentGame: game,
));
}
void refresh() {
final Game game = Game(
// Settings
gameSettings: state.currentGame.gameSettings,
globalSettings: state.currentGame.globalSettings,
// State
isRunning: state.currentGame.isRunning,
isStarted: state.currentGame.isStarted,
isFinished: state.currentGame.isFinished,
animationInProgress: state.currentGame.animationInProgress,
boardAnimated: state.currentGame.boardAnimated,
// Base data
board: state.currentGame.board,
// Game data
scores: state.currentGame.scores,
);
// game.dump();
updateState(game);
}
void startNewGame({
required GameSettings gameSettings,
required GlobalSettings globalSettings,
}) {
final Game newGame = Game.createNew(
// Settings
gameSettings: gameSettings,
globalSettings: globalSettings,
);
newGame.dump();
updateState(newGame);
refresh();
}
void quitGame() {
state.currentGame.isRunning = false;
refresh();
}
void resumeSavedGame() {
state.currentGame.isRunning = true;
refresh();
}
void deleteSavedGame() {
state.currentGame.isRunning = false;
state.currentGame.isFinished = true;
refresh();
}
// FIXME: should be removed
void doSomething() {
printlog('shoud do something!');
refresh();
}
// FIXME: should be removed
void logSomething([dynamic yolo]) {
printlog('logSomething: $yolo');
refresh();
}
@override
GameState? fromJson(Map<String, dynamic> json) {
final Game currentGame = json['currentGame'] as Game;
return GameState(
currentGame: currentGame,
);
}
@override
Map<String, dynamic>? toJson(GameState state) {
return <String, dynamic>{
'currentGame': state.currentGame.toJson(),
};
}
}
part of 'game_cubit.dart';
@immutable
class GameState extends Equatable {
const GameState({
required this.currentGame,
});
final Game currentGame;
@override
List<dynamic> get props => <dynamic>[
currentGame,
];
}
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:awale/config/menu.dart';
class NavCubit extends HydratedCubit<int> {
NavCubit() : super(0);
void updateIndex(int index) {
if (Menu.isIndexAllowed(index)) {
emit(index);
} else {
goToGamePage();
}
}
void goToGamePage() {
emit(Menu.indexGame);
}
void goToSettingsPage() {
emit(Menu.indexSettings);
}
void goToAboutPage() {
emit(Menu.indexAbout);
}
@override
int fromJson(Map<String, dynamic> json) {
return Menu.indexGame;
}
@override
Map<String, dynamic>? toJson(int state) {
return <String, int>{'pageIndex': state};
}
}
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:awale/config/default_game_settings.dart';
import 'package:awale/models/settings/settings_game.dart';
part 'settings_game_state.dart';
class GameSettingsCubit extends HydratedCubit<GameSettingsState> {
GameSettingsCubit() : super(GameSettingsState(settings: GameSettings.createDefault()));
void setValues({
String? gameMode,
}) {
emit(
GameSettingsState(
settings: GameSettings(
gameMode: gameMode ?? state.settings.gameMode,
),
),
);
}
String getParameterValue(String code) {
switch (code) {
case DefaultGameSettings.parameterCodeGameMode:
return GameSettings.getGameModeValueFromUnsafe(state.settings.gameMode);
}
return '';
}
void setParameterValue(String code, String value) {
final String gameMode = code == DefaultGameSettings.parameterCodeGameMode
? value
: getParameterValue(DefaultGameSettings.parameterCodeGameMode);
setValues(
gameMode: gameMode,
);
}
@override
GameSettingsState? fromJson(Map<String, dynamic> json) {
final String gameMode = json[DefaultGameSettings.parameterCodeGameMode] as String;
return GameSettingsState(
settings: GameSettings(
gameMode: gameMode,
),
);
}
@override
Map<String, dynamic>? toJson(GameSettingsState state) {
return <String, dynamic>{
DefaultGameSettings.parameterCodeGameMode: state.settings.gameMode,
};
}
}
part of 'settings_game_cubit.dart';
@immutable
class GameSettingsState extends Equatable {
const GameSettingsState({
required this.settings,
});
final GameSettings settings;
@override
List<dynamic> get props => <dynamic>[
settings,
];
}
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:awale/config/default_global_settings.dart';
import 'package:awale/models/settings/settings_global.dart';
part 'settings_global_state.dart';
class GlobalSettingsCubit extends HydratedCubit<GlobalSettingsState> {
GlobalSettingsCubit() : super(GlobalSettingsState(settings: GlobalSettings.createDefault()));
void setValues({
String? skin,
}) {
emit(
GlobalSettingsState(
settings: GlobalSettings(
skin: skin ?? state.settings.skin,
),
),
);
}
String getParameterValue(String code) {
switch (code) {
case DefaultGlobalSettings.parameterCodeSkin:
return GlobalSettings.getSkinValueFromUnsafe(state.settings.skin);
}
return '';
}
void setParameterValue(String code, String value) {
final String skin = (code == DefaultGlobalSettings.parameterCodeSkin)
? value
: getParameterValue(DefaultGlobalSettings.parameterCodeSkin);
setValues(
skin: skin,
);
}
@override
GlobalSettingsState? fromJson(Map<String, dynamic> json) {
final String skin = json[DefaultGlobalSettings.parameterCodeSkin] as String;
return GlobalSettingsState(
settings: GlobalSettings(
skin: skin,
),
);
}
@override
Map<String, dynamic>? toJson(GlobalSettingsState state) {
return <String, dynamic>{
DefaultGlobalSettings.parameterCodeSkin: state.settings.skin,
};
}
}
part of 'settings_global_cubit.dart';
@immutable
class GlobalSettingsState extends Equatable {
const GlobalSettingsState({
required this.settings,
});
final GlobalSettings settings;
@override
List<dynamic> get props => <dynamic>[
settings,
];
}
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
part 'theme_state.dart';
class ThemeCubit extends HydratedCubit<ThemeModeState> {
ThemeCubit() : super(const ThemeModeState());
void getTheme(ThemeModeState state) {
emit(state);
}
@override
ThemeModeState? fromJson(Map<String, dynamic> json) {
switch (json['themeMode']) {
case 'ThemeMode.dark':
return const ThemeModeState(themeMode: ThemeMode.dark);
case 'ThemeMode.light':
return const ThemeModeState(themeMode: ThemeMode.light);
case 'ThemeMode.system':
default:
return const ThemeModeState(themeMode: ThemeMode.system);
}
}
@override
Map<String, String>? toJson(ThemeModeState state) {
return <String, String>{'themeMode': state.themeMode.toString()};
}
}
part of 'theme_cubit.dart';
@immutable
class ThemeModeState extends Equatable {
const ThemeModeState({
this.themeMode,
});
final ThemeMode? themeMode;
@override
List<Object?> get props => <Object?>[
themeMode,
];
}
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive/hive.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:path_provider/path_provider.dart';
import 'package:awale/config/default_global_settings.dart';
import 'package:awale/config/theme.dart';
import 'package:awale/cubit/game_cubit.dart';
import 'package:awale/cubit/nav_cubit.dart';
import 'package:awale/cubit/settings_game_cubit.dart';
import 'package:awale/cubit/settings_global_cubit.dart';
import 'package:awale/cubit/theme_cubit.dart';
import 'package:awale/ui/skeleton.dart';
void main() async {
// Initialize packages
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
final Directory tmpDir = await getTemporaryDirectory();
Hive.init(tmpDir.toString());
HydratedBloc.storage = await HydratedStorage.build(
storageDirectory: tmpDir,
);
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((value) => runApp(EasyLocalization(
path: 'assets/translations',
supportedLocales: const <Locale>[
Locale('en'),
Locale('fr'),
],
fallbackLocale: const Locale('en'),
useFallbackTranslations: true,
child: const MyApp(),
)));
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
final List<String> assets = getImagesAssets();
for (String asset in assets) {
precacheImage(AssetImage(asset), context);
}
return MultiBlocProvider(
providers: [
BlocProvider<NavCubit>(create: (context) => NavCubit()),
BlocProvider<ThemeCubit>(create: (context) => ThemeCubit()),
BlocProvider<GameCubit>(create: (context) => GameCubit()),
BlocProvider<GlobalSettingsCubit>(create: (context) => GlobalSettingsCubit()),
BlocProvider<GameSettingsCubit>(create: (context) => GameSettingsCubit()),
],
child: BlocBuilder<ThemeCubit, ThemeModeState>(
builder: (BuildContext context, ThemeModeState state) {
return MaterialApp(
title: 'Awale',
home: const SkeletonScreen(),
// Theme stuff
theme: lightTheme,
darkTheme: darkTheme,
themeMode: state.themeMode,
// Localization stuff
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
debugShowCheckedModeBanner: false,
);
},
),
);
}
List<String> getImagesAssets() {
final List<String> assets = [];
const List<String> gameImages = [
'button_back',
'button_delete_saved_game',
'button_resume_game',
'button_start',
'game_fail',
'game_win',
'placeholder',
];
for (String image in gameImages) {
assets.add('assets/ui/$image.png');
}
final List<String> skinImages = [
'house',
'seed',
];
for (String skin in DefaultGlobalSettings.allowedSkinValues) {
for (String image in skinImages) {
assets.add('assets/skins/${skin}_$image.png');
}
}
return assets;
}
}
class Category {
final String key;
final String text;
final String emoji;
const Category({
required this.key,
required this.text,
required this.emoji,
});
@override
String toString() {
return '$Category(${toJson()})';
}
Map<String, dynamic> toJson() {
return {
'key': key,
'text': text,
'emoji': emoji,
};
}
}
class Letter {
final String key;
final String text;
const Letter({
required this.key,
required this.text,
});
@override
String toString() {
return '$Letter(${toJson()})';
}
Map<String, dynamic> toJson() {
return {
'key': key,
'text': text,
};
}
}
import 'package:awale/utils/tools.dart';
typedef BoardCells = List<int>;
class Board {
Board({
required this.cells,
});
BoardCells cells = const [];
factory Board.createNull() {
return Board(
cells: [],
);
}
factory Board.createNew({
required BoardCells cells,
}) {
return Board(
cells: cells,
);
}
void dump() {
printlog('');
printlog('$Board:');
printlog(' cells: $cells');
printlog('');
}
@override
String toString() {
return '$Board(${toJson()})';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
'cells': cells,
};
}
}
import 'package:awale/models/game/board.dart';
import 'package:awale/models/settings/settings_game.dart';
import 'package:awale/models/settings/settings_global.dart';
import 'package:awale/utils/tools.dart';
typedef MovingTile = String;
typedef Move = Board;
typedef Player = String;
typedef ConflictsCount = List<List<int>>;
typedef AnimatedBoard = List<List<bool>>;
typedef AnimatedBoardSequence = List<AnimatedBoard>;
typedef Word = String;
class Game {
Game({
// Settings
required this.gameSettings,
required this.globalSettings,
// State
this.isRunning = false,
this.isStarted = false,
this.isFinished = false,
this.animationInProgress = false,
this.boardAnimated = const [],
// Base data
required this.board,
// Game data
required this.scores,
});
// Settings
final GameSettings gameSettings;
final GlobalSettings globalSettings;
// State
bool isRunning;
bool isStarted;
bool isFinished;
bool animationInProgress;
AnimatedBoard boardAnimated;
// Base data
final Board board;
// Game data
List<int> scores;
factory Game.createNull() {
return Game(
// Settings
gameSettings: GameSettings.createDefault(),
globalSettings: GlobalSettings.createDefault(),
// Base data
board: Board.createNull(),
// Game data
scores: [0, 0],
);
}
factory Game.createNew({
GameSettings? gameSettings,
GlobalSettings? globalSettings,
}) {
final GameSettings newGameSettings = gameSettings ?? GameSettings.createDefault();
final GlobalSettings newGlobalSettings = globalSettings ?? GlobalSettings.createDefault();
const int boardSize = 12;
final BoardCells cells = [];
for (int index = 0; index < boardSize; index++) {
cells.add(4);
}
return Game(
// Settings
gameSettings: newGameSettings,
globalSettings: newGlobalSettings,
// State
isRunning: true,
boardAnimated: [],
// Base data
board: Board.createNew(cells: cells),
// Game data
scores: [0, 0],
);
}
bool get canBeResumed => isStarted && !isFinished;
void dump() {
printlog('');
printlog('## Current game dump:');
printlog('');
printlog('$Game:');
printlog(' Settings');
gameSettings.dump();
globalSettings.dump();
printlog(' State');
printlog(' isRunning: $isRunning');
printlog(' isStarted: $isStarted');
printlog(' isFinished: $isFinished');
printlog(' animationInProgress: $animationInProgress');
printlog('board:');
board.dump();
printlog(' Game data');
printlog(' scores: $scores');
printlog('');
}
@override
String toString() {
return '$Game(${toJson()})';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
// Settings
'gameSettings': gameSettings.toJson(),
'globalSettings': globalSettings.toJson(),
// State
'isRunning': isRunning,
'isStarted': isStarted,
'isFinished': isFinished,
'animationInProgress': animationInProgress,
'boardAnimated': boardAnimated,
// Base data
'board': board.toJson(),
// Game data
'scores': scores,
};
}
}
import 'package:awale/config/default_game_settings.dart';
import 'package:awale/utils/tools.dart';
class GameSettings {
final String gameMode;
GameSettings({
required this.gameMode,
});
static String getGameModeValueFromUnsafe(String gameMode) {
if (DefaultGameSettings.allowedGameModeValues.contains(gameMode)) {
return gameMode;
}
return DefaultGameSettings.defaultGameModeValue;
}
factory GameSettings.createDefault() {
return GameSettings(
gameMode: DefaultGameSettings.defaultGameModeValue,
);
}
void dump() {
printlog('$GameSettings:');
printlog(' ${DefaultGameSettings.parameterCodeGameMode}: $gameMode');
printlog('');
}
@override
String toString() {
return '$GameSettings(${toJson()})';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
DefaultGameSettings.parameterCodeGameMode: gameMode,
};
}
}
import 'package:awale/config/default_global_settings.dart';
import 'package:awale/utils/tools.dart';
class GlobalSettings {
String skin;
GlobalSettings({
required this.skin,
});
static String getSkinValueFromUnsafe(String skin) {
if (DefaultGlobalSettings.allowedSkinValues.contains(skin)) {
return skin;
}
return DefaultGlobalSettings.defaultSkinValue;
}
factory GlobalSettings.createDefault() {
return GlobalSettings(
skin: DefaultGlobalSettings.defaultSkinValue,
);
}
void dump() {
printlog('$GlobalSettings:');
printlog(' ${DefaultGlobalSettings.parameterCodeSkin}: $skin');
printlog('');
}
@override
String toString() {
return '$GlobalSettings(${toJson()})';
}
Map<String, dynamic>? toJson() {
return <String, dynamic>{
DefaultGlobalSettings.parameterCodeSkin: skin,
};
}
}
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
class AppHeader extends StatelessWidget {
const AppHeader({super.key, required this.text});
final String text;
@override
Widget build(BuildContext context) {
return Text(
tr(text),
textAlign: TextAlign.start,
style: Theme.of(context).textTheme.headlineMedium!.apply(fontWeightDelta: 2),
);
}
}
class AppTitle extends StatelessWidget {
const AppTitle({super.key, required this.text});
final String text;
@override
Widget build(BuildContext context) {
return Text(
tr(text),
textAlign: TextAlign.start,
style: Theme.of(context).textTheme.titleLarge!.apply(fontWeightDelta: 2),
);
}
}
import 'package:flutter/material.dart';
import 'package:awale/utils/color_extensions.dart';
class OutlinedText extends StatelessWidget {
const OutlinedText({
super.key,
required this.text,
required this.fontSize,
required this.textColor,
this.outlineColor,
});
final String text;
final double fontSize;
final Color textColor;
final Color? outlineColor;
@override
Widget build(BuildContext context) {
final double delta = fontSize / 30;
return Text(
text,
style: TextStyle(
inherit: true,
fontSize: fontSize,
fontWeight: FontWeight.w600,
color: textColor,
shadows: [
Shadow(
offset: Offset(-delta, -delta),
color: outlineColor ?? textColor.darken(),
),
Shadow(
offset: Offset(delta, -delta),
color: outlineColor ?? textColor.darken(),
),
Shadow(
offset: Offset(delta, delta),
color: outlineColor ?? textColor.darken(),
),
Shadow(
offset: Offset(-delta, delta),
color: outlineColor ?? textColor.darken(),
),
],
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:awale/cubit/game_cubit.dart';
import 'package:awale/models/game/game.dart';
import 'package:awale/ui/widgets/game/game_board.dart';
import 'package:awale/ui/widgets/game/game_bottom.dart';
import 'package:awale/ui/widgets/game/game_end.dart';
import 'package:awale/ui/widgets/game/game_top.dart';
class GameLayout extends StatelessWidget {
const GameLayout({super.key});
@override
Widget build(BuildContext context) {
return BlocBuilder<GameCubit, GameState>(
builder: (BuildContext context, GameState gameState) {
final Game currentGame = gameState.currentGame;
return Container(
alignment: AlignmentDirectional.topCenter,
padding: const EdgeInsets.all(4),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const GameTopWidget(),
const SizedBox(height: 8),
const GameBoardWidget(),
const SizedBox(height: 8),
const GameBottomWidget(),
const Expanded(child: SizedBox.shrink()),
currentGame.isFinished ? const GameEndWidget() : const SizedBox.shrink(),
],
),
);
},
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:awale/config/default_game_settings.dart';
import 'package:awale/config/default_global_settings.dart';
import 'package:awale/cubit/settings_game_cubit.dart';
import 'package:awale/cubit/settings_global_cubit.dart';
import 'package:awale/ui/parameters/parameter_painter.dart';
import 'package:awale/ui/widgets/actions/button_delete_saved_game.dart';
import 'package:awale/ui/widgets/actions/button_game_start_new.dart';
import 'package:awale/ui/widgets/actions/button_resume_saved_game.dart';
import 'package:awale/ui/parameters/parameter_image.dart';
class ParametersLayout extends StatelessWidget {
const ParametersLayout({super.key, required this.canResume});
final bool canResume;
final double separatorHeight = 8.0;
@override
Widget build(BuildContext context) {
final List<Widget> lines = [];
// Game settings
for (String code in DefaultGameSettings.availableParameters) {
lines.add(Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: buildParametersLine(
code: code,
isGlobal: false,
),
));
lines.add(SizedBox(height: separatorHeight));
}
lines.add(SizedBox(height: separatorHeight));
if (canResume == false) {
// Start new game
lines.add(const Expanded(
child: StartNewGameButton(),
));
} else {
// Resume game
lines.add(const Expanded(
child: ResumeSavedGameButton(),
));
// Delete saved game
lines.add(SizedBox.square(
dimension: MediaQuery.of(context).size.width / 4,
child: const DeleteSavedGameButton(),
));
}
lines.add(SizedBox(height: separatorHeight));
// Global settings
for (String code in DefaultGlobalSettings.availableParameters) {
lines.add(Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: buildParametersLine(
code: code,
isGlobal: true,
),
));
lines.add(SizedBox(height: separatorHeight));
}
return Column(
children: lines,
);
}
List<Widget> buildParametersLine({
required String code,
required bool isGlobal,
}) {
final List<Widget> parameterButtons = [];
final List<String> availableValues = isGlobal
? DefaultGlobalSettings.getAvailableValues(code)
: DefaultGameSettings.getAvailableValues(code);
if (availableValues.length <= 1) {
return [];
}
for (String value in availableValues) {
final Widget parameterButton = BlocBuilder<GameSettingsCubit, GameSettingsState>(
builder: (BuildContext context, GameSettingsState gameSettingsState) {
return BlocBuilder<GlobalSettingsCubit, GlobalSettingsState>(
builder: (BuildContext context, GlobalSettingsState globalSettingsState) {
final GameSettingsCubit gameSettingsCubit =
BlocProvider.of<GameSettingsCubit>(context);
final GlobalSettingsCubit globalSettingsCubit =
BlocProvider.of<GlobalSettingsCubit>(context);
final String currentValue = isGlobal
? globalSettingsCubit.getParameterValue(code)
: gameSettingsCubit.getParameterValue(code);
final bool isActive = (value == currentValue);
final double displayWidth = MediaQuery.of(context).size.width;
final double itemWidth = displayWidth / availableValues.length - 26;
final bool displayedWithAssets =
DefaultGlobalSettings.displayedWithAssets.contains(code) ||
DefaultGameSettings.displayedWithAssets.contains(code);
return TextButton(
child: Container(
child: displayedWithAssets
? SizedBox.square(
dimension: itemWidth,
child: ParameterImage(
code: code,
value: value,
isSelected: isActive,
),
)
: CustomPaint(
size: Size(itemWidth, itemWidth),
willChange: false,
painter: ParameterPainter(
code: code,
value: value,
isSelected: isActive,
gameSettings: gameSettingsState.settings,
globalSettings: globalSettingsState.settings,
),
isComplex: true,
),
),
onPressed: () {
isGlobal
? globalSettingsCubit.setParameterValue(code, value)
: gameSettingsCubit.setParameterValue(code, value);
},
);
},
);
},
);
parameterButtons.add(parameterButton);
}
return parameterButtons;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment