diff --git a/lib/data/game_session.dart b/lib/data/game_session.dart index 1523469..850a9f8 100644 --- a/lib/data/game_session.dart +++ b/lib/data/game_session.dart @@ -11,20 +11,25 @@ import 'package:cabo_counter/data/round.dart'; /// [isGameFinished] is a boolean indicating if the game has ended yet. /// [winner] is the name of the player who won the game. class GameSession { - final DateTime createdAt = DateTime.now(); + final DateTime createdAt; final String gameTitle; - final bool isPointsLimitEnabled; final List players; - late List playerScores; - List roundList = []; - int roundNumber = 1; + final int pointLimit; + final int caboPenalty; + final bool isPointsLimitEnabled; bool isGameFinished = false; String winner = ''; + int roundNumber = 1; + late List playerScores; + List roundList = []; GameSession({ + required this.createdAt, required this.gameTitle, - required this.isPointsLimitEnabled, required this.players, + required this.pointLimit, + required this.caboPenalty, + required this.isPointsLimitEnabled, }) { playerScores = List.filled(players.length, 0); } @@ -32,33 +37,37 @@ class GameSession { @override toString() { return ('GameSession: [createdAt: $createdAt, gameTitle: $gameTitle, ' - 'isPointsLimitEnabled: $isPointsLimitEnabled, players: $players, ' - 'playerScores: $playerScores, roundList: $roundList, ' - 'roundNumber: $roundNumber, isGameFinished: $isGameFinished, ' - 'winner: $winner]'); + 'isPointsLimitEnabled: $isPointsLimitEnabled, pointLimit: $pointLimit, caboPenalty: $caboPenalty,' + ' players: $players, playerScores: $playerScores, roundList: $roundList, winner: $winner]'); } /// Converts the GameSession object to a JSON map. Map toJson() => { + 'createdAt': createdAt.toIso8601String(), 'gameTitle': gameTitle, - 'gameHasPointLimit': isPointsLimitEnabled, 'players': players, - 'playerScores': playerScores, - 'roundNumber': roundNumber, + 'pointLimit': pointLimit, + 'caboPenalty': caboPenalty, + 'isPointsLimitEnabled': isPointsLimitEnabled, 'isGameFinished': isGameFinished, 'winner': winner, + 'roundNumber': roundNumber, + 'playerScores': playerScores, 'roundList': roundList.map((e) => e.toJson()).toList() }; /// Creates a GameSession object from a JSON map. GameSession.fromJson(Map json) - : gameTitle = json['gameTitle'], - isPointsLimitEnabled = json['gameHasPointLimit'], + : createdAt = DateTime.parse(json['createdAt']), + gameTitle = json['gameTitle'], players = List.from(json['players']), - playerScores = List.from(json['playerScores']), - roundNumber = json['roundNumber'], + pointLimit = json['pointLimit'], + caboPenalty = json['caboPenalty'], + isPointsLimitEnabled = json['gameHasPointLimit'], isGameFinished = json['isGameFinished'], winner = json['winner'], + roundNumber = json['roundNumber'], + playerScores = List.from(json['playerScores']), roundList = (json['roundList'] as List).map((e) => Round.fromJson(e)).toList(); @@ -76,7 +85,7 @@ class GameSession { void applyKamikaze(int roundNum, int kamikazePlayerIndex) { List roundScores = List.generate(players.length, (_) => 0); List scoreUpdates = List.generate(players.length, (_) => 0); - for (int i = 0; i < roundScores.length; i++) { + for (int i = 0; i < scoreUpdates.length; i++) { if (i != kamikazePlayerIndex) { scoreUpdates[i] += 50; } @@ -213,13 +222,13 @@ class GameSession { /// It then checks if any player has exceeded 100 points. If so, it sets /// isGameFinished to true and calls the _setWinner() method to determine /// the winner. - void updatePoints() { + Future updatePoints() async { _sumPoints(); if (isPointsLimitEnabled) { _checkHundredPointsReached(); for (int i = 0; i < playerScores.length; i++) { - if (playerScores[i] > 100) { + if (playerScores[i] > pointLimit) { isGameFinished = true; print('${players[i]} hat die 100 Punkte ueberschritten, ' 'deswegen wurde das Spiel beendet'); @@ -245,7 +254,7 @@ class GameSession { /// the corresponding round update. void _checkHundredPointsReached() { for (int i = 0; i < players.length; i++) { - if (playerScores[i] == 100) { + if (playerScores[i] == pointLimit) { print('${players[i]} hat genau 100 Punkte erreicht und bekommt ' 'deswegen 50 Punkte abgezogen'); roundList[roundNumber - 1].scoreUpdates[i] -= 50; diff --git a/lib/main.dart b/lib/main.dart index 953723d..edd7d3c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,47 +1,15 @@ -import 'package:cabo_counter/data/game_session.dart'; +import 'package:cabo_counter/services/config_service.dart'; +import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:cabo_counter/utility/globals.dart'; -import 'package:cabo_counter/utility/local_storage_service.dart'; import 'package:cabo_counter/views/tab_view.dart'; import 'package:flutter/cupertino.dart'; -void main() { - /// FIXME Just for Debugging - /// Fills the game list with some test data. - Globals.addGameSession(GameSession( - gameTitle: 'Spiel am 27.02.2025', - players: ['Clara', 'Tobias', 'Yannik', 'Lena', 'Lekaia'], - isPointsLimitEnabled: true)); - Globals.addGameSession(GameSession( - gameTitle: 'Freundschaftsrunde', - players: ['Felix', 'Jonas', 'Nils'], - isPointsLimitEnabled: false)); - Globals.addGameSession(GameSession( - gameTitle: 'Familienabend', - players: ['Mama', 'Papa', 'Lisa'], - isPointsLimitEnabled: true, - )); - Globals.addGameSession(GameSession( - gameTitle: 'Turnier 1. Runde', - players: ['Tim', 'Max', 'Sophie', 'Lena'], - isPointsLimitEnabled: false)); - Globals.addGameSession(GameSession( - gameTitle: '2 Namen max length', - players: ['Heinrich', 'Johannes'], - isPointsLimitEnabled: true)); - Globals.addGameSession(GameSession( - gameTitle: '3 Namen max length', - players: ['Benjamin', 'Stefanie', 'Wolfgang'], - isPointsLimitEnabled: false)); - Globals.addGameSession(GameSession( - gameTitle: '4 Namen max length', - players: ['Leonhard', 'Mathilde', 'Bernhard', 'Gerlinde'], - isPointsLimitEnabled: true)); - Globals.addGameSession(GameSession( - gameTitle: '5 Namen max length', - players: ['Hartmuth', 'Elisabet', 'Rosalind', 'Theresia', 'Karoline'], - isPointsLimitEnabled: false)); - +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await ConfigService.initConfig(); + Globals.pointLimit = await ConfigService.getPointLimit(); + Globals.caboPenalty = await ConfigService.getCaboPenalty(); runApp(const App()); } diff --git a/lib/services/config_service.dart b/lib/services/config_service.dart new file mode 100644 index 0000000..1c8275a --- /dev/null +++ b/lib/services/config_service.dart @@ -0,0 +1,57 @@ +import 'package:cabo_counter/utility/globals.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +/// This class handles the configuration settings for the app. +/// It uses SharedPreferences to store and retrieve the personal configuration of the app. +/// Currently it provides methods to initialize, get, and set the point limit and cabo penalty. +class ConfigService { + static const String _keyPointLimit = 'pointLimit'; + static const String _keyCaboPenalty = 'caboPenalty'; + static const int _defaultPointLimit = 100; // Default Value + static const int _defaultCaboPenalty = 5; // Default Value + + static Future initConfig() async { + final prefs = await SharedPreferences.getInstance(); + + // Default values only set if they are not already set + prefs.setInt( + _keyPointLimit, prefs.getInt(_keyPointLimit) ?? _defaultPointLimit); + prefs.setInt( + _keyCaboPenalty, prefs.getInt(_keyCaboPenalty) ?? _defaultCaboPenalty); + } + + /// Getter for the point limit. + static Future getPointLimit() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getInt(_keyPointLimit) ?? _defaultPointLimit; + } + + /// Setter for the point limit. + /// [newPointLimit] is the new point limit to be set. + static Future setPointLimit(int newPointLimit) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setInt(_keyPointLimit, newPointLimit); + } + + /// Getter for the cabo penalty. + static Future getCaboPenalty() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getInt(_keyCaboPenalty) ?? _defaultCaboPenalty; + } + + /// Setter for the cabo penalty. + /// [newCaboPenalty] is the new cabo penalty to be set. + static Future setCaboPenalty(int newCaboPenalty) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setInt(_keyCaboPenalty, newCaboPenalty); + } + + /// Resets the configuration to default values. + static Future resetConfig() async { + Globals.pointLimit = _defaultPointLimit; + Globals.caboPenalty = _defaultCaboPenalty; + final prefs = await SharedPreferences.getInstance(); + await prefs.setInt(_keyPointLimit, _defaultPointLimit); + await prefs.setInt(_keyCaboPenalty, _defaultCaboPenalty); + } +} diff --git a/lib/utility/local_storage_service.dart b/lib/services/local_storage_service.dart similarity index 100% rename from lib/utility/local_storage_service.dart rename to lib/services/local_storage_service.dart diff --git a/lib/utility/custom_theme.dart b/lib/utility/custom_theme.dart index 225d452..77a2f5b 100644 --- a/lib/utility/custom_theme.dart +++ b/lib/utility/custom_theme.dart @@ -16,7 +16,7 @@ class CustomTheme { fontSize: 16, ); - static TextStyle createGameTitle = TextStyle( + static TextStyle rowTitle = TextStyle( fontSize: 20, color: primaryColor, fontWeight: FontWeight.bold, @@ -27,10 +27,4 @@ class CustomTheme { color: white, fontWeight: FontWeight.bold, ); - - static TextStyle roundPlayers = TextStyle( - fontSize: 20, - color: white, - fontWeight: FontWeight.bold, - ); } diff --git a/lib/utility/globals.dart b/lib/utility/globals.dart index 62e64bd..89d8559 100644 --- a/lib/utility/globals.dart +++ b/lib/utility/globals.dart @@ -8,4 +8,10 @@ class Globals { gameList.add(session); gameList.sort((a, b) => b.createdAt.compareTo(a.createdAt)); } + + static int pointLimit = 100; + + static int caboPenalty = 5; + + static String appDevPhase = 'Alpha'; } diff --git a/lib/views/active_game_view.dart b/lib/views/active_game_view.dart index 659f2a8..1baabe5 100644 --- a/lib/views/active_game_view.dart +++ b/lib/views/active_game_view.dart @@ -28,7 +28,7 @@ class _ActiveGameViewState extends State { padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), child: Text( 'Spieler:innen', - style: CustomTheme.createGameTitle, + style: CustomTheme.rowTitle, ), ), ListView.builder( @@ -61,7 +61,7 @@ class _ActiveGameViewState extends State { padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), child: Text( 'Runden', - style: CustomTheme.createGameTitle, + style: CustomTheme.rowTitle, ), ), ListView.builder( diff --git a/lib/views/create_game_view.dart b/lib/views/create_game_view.dart index 4c3e49f..9150a22 100644 --- a/lib/views/create_game_view.dart +++ b/lib/views/create_game_view.dart @@ -1,7 +1,7 @@ import 'package:cabo_counter/data/game_session.dart'; +import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:cabo_counter/utility/globals.dart'; -import 'package:cabo_counter/utility/local_storage_service.dart'; import 'package:cabo_counter/views/active_game_view.dart'; import 'package:cabo_counter/views/mode_selection_view.dart'; import 'package:flutter/cupertino.dart'; @@ -44,11 +44,11 @@ class _CreateGameState extends State { padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), child: Text( 'Spiel', - style: CustomTheme.createGameTitle, + style: CustomTheme.rowTitle, ), ), Padding( - padding: const EdgeInsets.fromLTRB(10, 10, 10, 0), + padding: const EdgeInsets.fromLTRB(15, 10, 10, 0), child: CupertinoTextField( decoration: const BoxDecoration(), maxLength: 16, @@ -60,7 +60,7 @@ class _CreateGameState extends State { ), // Spielmodus-Auswahl mit Chevron Padding( - padding: const EdgeInsets.fromLTRB(10, 10, 10, 0), + padding: const EdgeInsets.fromLTRB(15, 10, 10, 0), child: CupertinoTextField( decoration: const BoxDecoration(), readOnly: true, @@ -77,15 +77,15 @@ class _CreateGameState extends State { ], ), onTap: () async { - // Öffne das Modus-Auswahlmenü final selected = await Navigator.push( context, CupertinoPageRoute( - builder: (context) => const ModeSelectionMenu(), + builder: (context) => ModeSelectionMenu( + pointLimit: Globals.pointLimit, + ), ), ); - // Aktualisiere den ausgewählten Modus if (selected != null) { setState(() { selectedMode = selected; @@ -98,7 +98,7 @@ class _CreateGameState extends State { padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), child: Text( 'Spieler:innen', - style: CustomTheme.createGameTitle, + style: CustomTheme.rowTitle, ), ), Expanded( @@ -282,17 +282,24 @@ class _CreateGameState extends State { players.add(controller.text); } GameSession gameSession = GameSession( + createdAt: DateTime.now(), gameTitle: _gameTitleTextController.text, players: players, + pointLimit: Globals.pointLimit, + caboPenalty: Globals.caboPenalty, isPointsLimitEnabled: selectedMode!, ); Globals.addGameSession(gameSession); LocalStorageService.saveGameSessions(); - Navigator.pushReplacement( - context, - CupertinoPageRoute( - builder: (context) => - ActiveGameView(gameSession: gameSession))); + if (context.mounted) { + Navigator.pushReplacement( + context, + CupertinoPageRoute( + builder: (context) => + ActiveGameView(gameSession: gameSession))); + } else { + print('Context is not mounted'); + } }, ), ), diff --git a/lib/views/information_view.dart b/lib/views/information_view.dart index cac3a61..72b76d1 100644 --- a/lib/views/information_view.dart +++ b/lib/views/information_view.dart @@ -1,4 +1,3 @@ -import 'package:cabo_counter/utility/local_storage_service.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; @@ -30,7 +29,7 @@ class InformationView extends StatelessWidget { ), Padding( padding: - const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + const EdgeInsets.symmetric(horizontal: 20, vertical: 30), child: SizedBox( height: 200, child: Image.asset('assets/cabo-counter-logo_rounded.png'), @@ -51,7 +50,7 @@ class InformationView extends StatelessWidget { softWrap: true, )), const SizedBox( - height: 15, + height: 30, ), const Text( '\u00A9 Felix Kirchner', @@ -74,53 +73,6 @@ class InformationView extends StatelessWidget { icon: const Icon(FontAwesomeIcons.github)), ], ), - const SizedBox( - height: 10, - ), - CupertinoButton( - sizeStyle: CupertinoButtonSize.medium, - child: const Text('Spieldaten exportieren'), - onPressed: () async { - final success = await LocalStorageService.exportJsonFile(); - if (!success && context.mounted) { - showCupertinoDialog( - context: context, - builder: (context) => CupertinoAlertDialog( - title: const Text('Fehler'), - content: - const Text('Datei konnte nicht exportiert werden.'), - actions: [ - CupertinoDialogAction( - child: const Text('OK'), - onPressed: () => Navigator.pop(context), - ), - ], - ), - ); - } - }, - ), - CupertinoButton( - sizeStyle: CupertinoButtonSize.medium, - child: const Text('Spieldaten importieren'), - onPressed: () async { - final success = await LocalStorageService.importJsonFile(); - if (!success && context.mounted) { - showCupertinoDialog( - context: context, - builder: (context) => CupertinoAlertDialog( - title: const Text('Fehler'), - content: const Text( - 'Datei konnte nicht importiert werden.'), - actions: [ - CupertinoDialogAction( - child: const Text('OK'), - onPressed: () => Navigator.pop(context), - ), - ], - )); - } - }), ], ))); } diff --git a/lib/views/main_menu_view.dart b/lib/views/main_menu_view.dart index 2b5afd1..e5a1039 100644 --- a/lib/views/main_menu_view.dart +++ b/lib/views/main_menu_view.dart @@ -1,6 +1,6 @@ +import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:cabo_counter/utility/globals.dart'; -import 'package:cabo_counter/utility/local_storage_service.dart'; import 'package:cabo_counter/views/active_game_view.dart'; import 'package:cabo_counter/views/create_game_view.dart'; import 'package:cabo_counter/views/settings_view.dart'; @@ -16,17 +16,23 @@ class MainMenuView extends StatefulWidget { } class _MainMenuViewState extends State { + bool _isLoading = true; + @override initState() { super.initState(); LocalStorageService.loadGameSessions().then((_) { - setState(() {}); + setState(() { + _isLoading = false; + }); }); } @override Widget build(BuildContext context) { + print('MainMenuView build'); LocalStorageService.loadGameSessions(); + return CupertinoPageScaffold( resizeToAvoidBottomInset: false, navigationBar: CupertinoNavigationBar( @@ -57,74 +63,76 @@ class _MainMenuViewState extends State { ), child: CupertinoPageScaffold( child: SafeArea( - child: Globals.gameList.isEmpty - ? Column( - mainAxisAlignment: - MainAxisAlignment.center, // Oben ausrichten - children: [ - const SizedBox(height: 30), // Abstand von oben - Center( - child: GestureDetector( - onTap: () => setState(() {}), - child: Icon( - CupertinoIcons.plus, - size: 60, - color: CustomTheme.primaryColor, - ), - )), - const SizedBox(height: 10), // Abstand von oben - const Padding( - padding: EdgeInsets.symmetric(horizontal: 70), - child: Text( - 'Ganz schön leer hier...\nFüge über den Button oben rechts eine neue Runde hinzu.', - textAlign: TextAlign.center, - style: TextStyle(fontSize: 16), - ), - ), - ], - ) - : ListView.builder( - itemCount: Globals.gameList.length, - itemBuilder: (context, index) { - final session = Globals.gameList[index]; - return Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0), - child: CupertinoListTile( - title: Text(session.gameTitle), - subtitle: session.isGameFinished == true - ? Text( - '\u{1F947} ${session.winner}', - style: const TextStyle(fontSize: 14), - ) - : Text( - 'Modus: ${_translateGameMode(session.isPointsLimitEnabled)}', - style: const TextStyle(fontSize: 14), - ), - trailing: Row( - children: [ - Text('${session.roundNumber}'), - const SizedBox(width: 3), - const Icon(CupertinoIcons - .arrow_2_circlepath_circle_fill), - const SizedBox(width: 15), - Text('${session.players.length}'), - const SizedBox(width: 3), - const Icon(CupertinoIcons.person_2_fill), - ], + child: _isLoading + ? const Center(child: CupertinoActivityIndicator()) + : Globals.gameList.isEmpty + ? Column( + mainAxisAlignment: + MainAxisAlignment.center, // Oben ausrichten + children: [ + const SizedBox(height: 30), // Abstand von oben + Center( + child: GestureDetector( + onTap: () => setState(() {}), + child: Icon( + CupertinoIcons.plus, + size: 60, + color: CustomTheme.primaryColor, ), - onTap: () async { - //ignore: unused_local_variable - final val = await Navigator.push( - context, - CupertinoPageRoute( - builder: (context) => ActiveGameView( - gameSession: Globals.gameList[index]), + )), + const SizedBox(height: 10), // Abstand von oben + const Padding( + padding: EdgeInsets.symmetric(horizontal: 70), + child: Text( + 'Ganz schön leer hier...\nFüge über den Button oben rechts eine neue Runde hinzu.', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16), + ), + ), + ], + ) + : ListView.builder( + itemCount: Globals.gameList.length, + itemBuilder: (context, index) { + final session = Globals.gameList[index]; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: CupertinoListTile( + title: Text(session.gameTitle), + subtitle: session.isGameFinished == true + ? Text( + '\u{1F947} ${session.winner}', + style: const TextStyle(fontSize: 14), + ) + : Text( + 'Modus: ${_translateGameMode(session.isPointsLimitEnabled)}', + style: const TextStyle(fontSize: 14), + ), + trailing: Row( + children: [ + Text('${session.roundNumber}'), + const SizedBox(width: 3), + const Icon(CupertinoIcons + .arrow_2_circlepath_circle_fill), + const SizedBox(width: 15), + Text('${session.players.length}'), + const SizedBox(width: 3), + const Icon(CupertinoIcons.person_2_fill), + ], ), - ); - setState(() {}); - }, - )); - }), + onTap: () async { + //ignore: unused_local_variable + final val = await Navigator.push( + context, + CupertinoPageRoute( + builder: (context) => ActiveGameView( + gameSession: Globals.gameList[index]), + ), + ); + setState(() {}); + }, + )); + }), ), ), ); diff --git a/lib/views/mode_selection_view.dart b/lib/views/mode_selection_view.dart index 5dd8010..f4edf37 100644 --- a/lib/views/mode_selection_view.dart +++ b/lib/views/mode_selection_view.dart @@ -2,7 +2,8 @@ import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:flutter/cupertino.dart'; class ModeSelectionMenu extends StatelessWidget { - const ModeSelectionMenu({super.key}); + final int pointLimit; + const ModeSelectionMenu({super.key, required this.pointLimit}); @override Widget build(BuildContext context) { @@ -15,9 +16,9 @@ class ModeSelectionMenu extends StatelessWidget { Padding( padding: const EdgeInsets.fromLTRB(0, 16, 0, 0), child: CupertinoListTile( - title: Text('101 Punkte', style: CustomTheme.modeTitle), - subtitle: const Text( - 'Es wird solange gespielt, bis einer Spieler mehr als 100 Punkte erreicht', + title: Text('$pointLimit Punkte', style: CustomTheme.modeTitle), + subtitle: Text( + 'Es wird solange gespielt, bis einer Spieler mehr als $pointLimit Punkte erreicht', style: CustomTheme.modeDescription, maxLines: 3, ), diff --git a/lib/views/round_view.dart b/lib/views/round_view.dart index f22b8c1..48bc4b9 100644 --- a/lib/views/round_view.dart +++ b/lib/views/round_view.dart @@ -1,6 +1,6 @@ import 'package:cabo_counter/data/game_session.dart'; +import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/utility/custom_theme.dart'; -import 'package:cabo_counter/utility/local_storage_service.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; diff --git a/lib/views/settings_view.dart b/lib/views/settings_view.dart index 90cbd2b..8f0dcf4 100644 --- a/lib/views/settings_view.dart +++ b/lib/views/settings_view.dart @@ -1,5 +1,11 @@ +import 'package:cabo_counter/services/config_service.dart'; +import 'package:cabo_counter/services/local_storage_service.dart'; +import 'package:cabo_counter/utility/custom_theme.dart'; +import 'package:cabo_counter/utility/globals.dart'; +import 'package:cabo_counter/widgets/stepper.dart'; import 'package:flutter/cupertino.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:url_launcher/url_launcher.dart'; class SettingsView extends StatefulWidget { const SettingsView({super.key}); @@ -9,6 +15,13 @@ class SettingsView extends StatefulWidget { } class _SettingsViewState extends State { + UniqueKey _stepperKey1 = UniqueKey(); + UniqueKey _stepperKey2 = UniqueKey(); + @override + void initState() { + super.initState(); + } + @override Widget build(BuildContext context) { return CupertinoPageScaffold( @@ -18,41 +31,193 @@ class _SettingsViewState extends State { child: SafeArea( child: Stack( children: [ - const Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, + Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Center( - child: Icon( - CupertinoIcons.settings, - size: 100, - )), + Padding( + padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), + child: Text( + 'Punkte', + style: CustomTheme.rowTitle, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(15, 10, 10, 0), + child: CupertinoListTile( + padding: EdgeInsets.zero, + title: const Text('Cabo-Strafe'), + subtitle: const Text('... für falsches Cabo sagen'), + trailing: Stepper( + key: _stepperKey1, + initialValue: Globals.caboPenalty, + minValue: 0, + maxValue: 50, + step: 1, + onChanged: (newCaboPenalty) { + setState(() { + ConfigService.setCaboPenalty(newCaboPenalty); + Globals.caboPenalty = newCaboPenalty; + }); + }, + ), + )), + Padding( + padding: const EdgeInsets.fromLTRB(15, 10, 10, 0), + child: CupertinoListTile( + padding: EdgeInsets.zero, + title: const Text('Punkte-Limit'), + subtitle: const Text('... hier ist Schluss'), + trailing: Stepper( + key: _stepperKey2, + initialValue: Globals.pointLimit, + minValue: 30, + maxValue: 1000, + step: 10, + onChanged: (newPointLimit) { + setState(() { + ConfigService.setPointLimit(newPointLimit); + Globals.pointLimit = newPointLimit; + }); + }, + ), + )), + Padding( + padding: const EdgeInsets.fromLTRB(0, 10, 0, 0), + child: Center( + heightFactor: 0.9, + child: CupertinoButton( + padding: EdgeInsets.zero, + onPressed: () => setState(() { + ConfigService.resetConfig(); + _stepperKey1 = UniqueKey(); + _stepperKey2 = UniqueKey(); + }), + child: const Text('Standard zurücksetzten'), + ), + )), + Padding( + padding: const EdgeInsets.fromLTRB(10, 10, 0, 0), + child: Text( + 'Spieldaten', + style: CustomTheme.rowTitle, + ), + ), + Padding( + padding: const EdgeInsets.only(top: 30), + child: Center( + heightFactor: 1, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CupertinoButton( + color: CustomTheme.primaryColor, + sizeStyle: CupertinoButtonSize.medium, + child: Text( + 'Daten exportieren', + style: + TextStyle(color: CustomTheme.backgroundColor), + ), + onPressed: () async { + print('Export pressed'); + final success = + await LocalStorageService.exportJsonFile(); + if (!success && context.mounted) { + showCupertinoDialog( + context: context, + builder: (context) => CupertinoAlertDialog( + title: const Text('Fehler'), + content: const Text( + 'Datei konnte nicht exportiert werden.'), + actions: [ + CupertinoDialogAction( + child: const Text('OK'), + onPressed: () => Navigator.pop(context), + ), + ], + ), + ); + } + }, + ), + const SizedBox( + width: 20, + ), + CupertinoButton( + color: CustomTheme.primaryColor, + sizeStyle: CupertinoButtonSize.medium, + child: Text( + 'Daten importieren', + style: + TextStyle(color: CustomTheme.backgroundColor), + ), + onPressed: () async { + print('Import pressed'); + final success = + await LocalStorageService.importJsonFile(); + if (!success && context.mounted) { + showCupertinoDialog( + context: context, + builder: (context) => CupertinoAlertDialog( + title: const Text('Fehler'), + content: const Text( + 'Datei konnte nicht importiert werden.'), + actions: [ + CupertinoDialogAction( + child: const Text('OK'), + onPressed: () => + Navigator.pop(context), + ), + ], + )); + } + }), + ], + )), + ) ], ), Positioned( bottom: 30, left: 0, right: 0, - child: FutureBuilder( - future: _getPackageInfo(), - builder: (context, snapshot) { - if (snapshot.hasData) { - return Text( - 'Alpha ${snapshot.data!.version} ' - '(Build ${snapshot.data!.buildNumber})', - textAlign: TextAlign.center, - ); - } else if (snapshot.hasError) { - return const Text( - 'App-Version -.-.- (Build -)', - textAlign: TextAlign.center, - ); - } - return const Text( - 'Lade Version...', - textAlign: TextAlign.center, - ); - }, + child: Column( + children: [ + const Center( + child: Text('Fehler gefunden?'), + ), + Padding( + padding: const EdgeInsets.fromLTRB(0, 0, 0, 30), + child: Center( + child: CupertinoButton( + onPressed: () => launchUrl(Uri.parse( + 'https://github.com/flixcoo/Cabo-Counter/issues')), + child: const Text('Issue erstellen'), + ), + ), + ), + FutureBuilder( + future: _getPackageInfo(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return Text( + '${Globals.appDevPhase} ${snapshot.data!.version} ' + '(Build ${snapshot.data!.buildNumber})', + textAlign: TextAlign.center, + ); + } else if (snapshot.hasError) { + return const Text( + 'App-Version -.-.- (Build -)', + textAlign: TextAlign.center, + ); + } + return const Text( + 'Lade Version...', + textAlign: TextAlign.center, + ); + }, + ) + ], )), ], )), diff --git a/lib/widgets/stepper.dart b/lib/widgets/stepper.dart new file mode 100644 index 0000000..879235e --- /dev/null +++ b/lib/widgets/stepper.dart @@ -0,0 +1,73 @@ +import 'package:flutter/cupertino.dart'; // Für iOS-Style + +class Stepper extends StatefulWidget { + final int minValue; + final int maxValue; + final int? initialValue; + final int step; + final ValueChanged onChanged; + const Stepper({ + super.key, + required this.minValue, + required this.maxValue, + required this.step, + required this.onChanged, + this.initialValue, + }); + + @override + // ignore: library_private_types_in_public_api + _StepperState createState() => _StepperState(); +} + +class _StepperState extends State { + late int _value; + + @override + void initState() { + super.initState(); + final start = widget.initialValue ?? widget.minValue; + _value = start.clamp(widget.minValue, widget.maxValue); + } + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + CupertinoButton( + padding: const EdgeInsets.all(8), + onPressed: _decrement, + child: const Icon(CupertinoIcons.minus), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: Text('$_value', style: const TextStyle(fontSize: 18)), + ), + CupertinoButton( + padding: const EdgeInsets.all(8), + onPressed: _increment, + child: const Icon(CupertinoIcons.add), + ), + ], + ); + } + + void _increment() { + if (_value + widget.step <= widget.maxValue) { + setState(() { + _value += widget.step; + widget.onChanged.call(_value); + }); + } + } + + void _decrement() { + if (_value - widget.step >= widget.minValue) { + setState(() { + _value -= widget.step; + widget.onChanged.call(_value); + }); + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 6141b6e..2941c3d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: cabo_counter description: "Mobile app for the card game Cabo" publish_to: 'none' -version: 0.1.5+116 +version: 0.1.6-alpha+138 environment: sdk: ^3.5.4 @@ -20,6 +20,7 @@ dependencies: typed_data: ^1.3.2 url_launcher: any json_schema: ^5.2.1 + shared_preferences: ^2.5.3 dev_dependencies: flutter_test: