Merge pull request #87 from flixcoo/enhance/86-rework-import--and-export-button

Reworked SettingsView
This commit is contained in:
2025-07-09 19:01:54 +02:00
committed by GitHub
21 changed files with 401 additions and 318 deletions

View File

@@ -83,8 +83,9 @@
"point_limit_subtitle": "... hier ist Schluss", "point_limit_subtitle": "... hier ist Schluss",
"reset_to_default": "Auf Standard zurücksetzen", "reset_to_default": "Auf Standard zurücksetzen",
"game_data": "Spieldaten", "game_data": "Spieldaten",
"import_data": "Daten importieren", "import_data": "Spieldaten importieren",
"export_data": "Daten exportieren", "export_data": "Spieldaten exportieren",
"app": "App",
"import_success_title": "Import erfolgreich", "import_success_title": "Import erfolgreich",
"import_success_message":"Die Spieldaten wurden erfolgreich importiert.", "import_success_message":"Die Spieldaten wurden erfolgreich importiert.",
@@ -97,11 +98,13 @@
"export_error_title": "Fehler", "export_error_title": "Fehler",
"export_error_message": "Datei konnte nicht exportiert werden", "export_error_message": "Datei konnte nicht exportiert werden",
"error_found": "Fehler gefunden?", "error_found": "Fehler gefunden?",
"create_issue": "Issue erstellen", "create_issue": "Issue erstellen",
"wiki": "Wiki",
"app_version": "App-Version", "app_version": "App-Version",
"build": "Build", "build": "Build-Nr.",
"load_version": "Lade Version...", "loading": "Lädt...",
"about_text": "Hey :) Danke, dass du als eine:r der ersten User meiner ersten eigenen App dabei bist! Ich hab sehr viel Arbeit in dieses Projekt gesteckt und auch, wenn ich (hoffentlich) an vieles Gedacht hab, wird auf jeden Fall noch nicht alles 100% funktionieren. Solltest du also irgendwelche Fehler entdecken oder Feedback zum Design oder der Benutzerfreundlichkeit haben, teile Sie mir gern über die Testflight App oder auf den dir bekannten Wegen mit. Danke! " "about_text": "Hey :) Danke, dass du als eine:r der ersten User meiner ersten eigenen App dabei bist! Ich hab sehr viel Arbeit in dieses Projekt gesteckt und auch, wenn ich (hoffentlich) an vieles Gedacht hab, wird auf jeden Fall noch nicht alles 100% funktionieren. Solltest du also irgendwelche Fehler entdecken oder Feedback zum Design oder der Benutzerfreundlichkeit haben, teile Sie mir gern über die Testflight App oder auf den dir bekannten Wegen mit. Danke! "
} }

View File

@@ -71,6 +71,10 @@
"export_game": "Export Game", "export_game": "Export Game",
"game_process": "Spielverlauf", "game_process": "Spielverlauf",
"id_error_title": "ID Error",
"id_error_message": "The game has not yet been assigned an ID. If you want to delete the game, please do so via the main menu. All newly created games have an ID.",
"end_game_title": "End the game?",
"end_game_message": "Do you want to end the game? The game gets marked as finished and cannot be continued.",
"settings": "Settings", "settings": "Settings",
"cabo_penalty": "Cabo Penalty", "cabo_penalty": "Cabo Penalty",
@@ -81,10 +85,7 @@
"game_data": "Game Data", "game_data": "Game Data",
"import_data": "Import Data", "import_data": "Import Data",
"export_data": "Export Data", "export_data": "Export Data",
"id_error_title": "ID Error", "app": "App",
"id_error_message": "The game has not yet been assigned an ID. If you want to delete the game, please do so via the main menu. All newly created games have an ID.",
"end_game_title": "End the game?",
"end_game_message": "Do you want to end the game? The game gets marked as finished and cannot be continued.",
"import_success_title": "Import successful", "import_success_title": "Import successful",
"import_success_message":"The game data has been successfully imported.", "import_success_message":"The game data has been successfully imported.",
@@ -97,11 +98,13 @@
"export_error_title": "Export failed", "export_error_title": "Export failed",
"export_error_message": "Could not export file", "export_error_message": "Could not export file",
"error_found": "Found a bug?", "error_found": "Found a bug?",
"create_issue": "Create Issue", "create_issue": "Create Issue",
"wiki": "Wiki",
"app_version": "App Version", "app_version": "App Version",
"load_version": "Loading version...", "loading": "Loading...",
"build": "Build", "build": "Build No.",
"about_text": "Hey :) Thanks for being one of the first users of my app! Ive put a lot of work into this project, and even though I tried to think of everything, it might not work perfectly just yet. So if you discover any bugs or have feedback on the design or usability, please let me know via the TestFlight app or by sending me a message or email. Thank you very much!" "about_text": "Hey :) Thanks for being one of the first users of my app! Ive put a lot of work into this project, and even though I tried to think of everything, it might not work perfectly just yet. So if you discover any bugs or have feedback on the design or usability, please let me know via the TestFlight app or by sending me a message or email. Thank you very much!"
} }

View File

@@ -467,15 +467,21 @@ abstract class AppLocalizations {
/// No description provided for @import_data. /// No description provided for @import_data.
/// ///
/// In de, this message translates to: /// In de, this message translates to:
/// **'Daten importieren'** /// **'Spieldaten importieren'**
String get import_data; String get import_data;
/// No description provided for @export_data. /// No description provided for @export_data.
/// ///
/// In de, this message translates to: /// In de, this message translates to:
/// **'Daten exportieren'** /// **'Spieldaten exportieren'**
String get export_data; String get export_data;
/// No description provided for @app.
///
/// In de, this message translates to:
/// **'App'**
String get app;
/// No description provided for @import_success_title. /// No description provided for @import_success_title.
/// ///
/// In de, this message translates to: /// In de, this message translates to:
@@ -548,6 +554,12 @@ abstract class AppLocalizations {
/// **'Issue erstellen'** /// **'Issue erstellen'**
String get create_issue; String get create_issue;
/// No description provided for @wiki.
///
/// In de, this message translates to:
/// **'Wiki'**
String get wiki;
/// No description provided for @app_version. /// No description provided for @app_version.
/// ///
/// In de, this message translates to: /// In de, this message translates to:
@@ -557,14 +569,14 @@ abstract class AppLocalizations {
/// No description provided for @build. /// No description provided for @build.
/// ///
/// In de, this message translates to: /// In de, this message translates to:
/// **'Build'** /// **'Build-Nr.'**
String get build; String get build;
/// No description provided for @load_version. /// No description provided for @loading.
/// ///
/// In de, this message translates to: /// In de, this message translates to:
/// **'Lade Version...'** /// **'Lädt...'**
String get load_version; String get loading;
/// No description provided for @about_text. /// No description provided for @about_text.
/// ///

View File

@@ -203,10 +203,13 @@ class AppLocalizationsDe extends AppLocalizations {
String get game_data => 'Spieldaten'; String get game_data => 'Spieldaten';
@override @override
String get import_data => 'Daten importieren'; String get import_data => 'Spieldaten importieren';
@override @override
String get export_data => 'Daten exportieren'; String get export_data => 'Spieldaten exportieren';
@override
String get app => 'App';
@override @override
String get import_success_title => 'Import erfolgreich'; String get import_success_title => 'Import erfolgreich';
@@ -247,14 +250,17 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get create_issue => 'Issue erstellen'; String get create_issue => 'Issue erstellen';
@override
String get wiki => 'Wiki';
@override @override
String get app_version => 'App-Version'; String get app_version => 'App-Version';
@override @override
String get build => 'Build'; String get build => 'Build-Nr.';
@override @override
String get load_version => 'Lade Version...'; String get loading => 'Lädt...';
@override @override
String get about_text => String get about_text =>

View File

@@ -205,6 +205,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get export_data => 'Export Data'; String get export_data => 'Export Data';
@override
String get app => 'App';
@override @override
String get import_success_title => 'Import successful'; String get import_success_title => 'Import successful';
@@ -244,14 +247,17 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get create_issue => 'Create Issue'; String get create_issue => 'Create Issue';
@override
String get wiki => 'Wiki';
@override @override
String get app_version => 'App Version'; String get app_version => 'App Version';
@override @override
String get build => 'Build'; String get build => 'Build No.';
@override @override
String get load_version => 'Loading version...'; String get loading => 'Loading...';
@override @override
String get about_text => String get about_text =>

View File

@@ -1,8 +1,9 @@
import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/l10n/app_localizations.dart';
import 'package:cabo_counter/presentation/views/tab_view.dart';
import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/config_service.dart';
import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart';
import 'package:cabo_counter/services/version_service.dart';
import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:cabo_counter/utility/custom_theme.dart';
import 'package:cabo_counter/views/tab_view.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@@ -13,6 +14,7 @@ Future<void> main() async {
await ConfigService.initConfig(); await ConfigService.initConfig();
ConfigService.pointLimit = await ConfigService.getPointLimit(); ConfigService.pointLimit = await ConfigService.getPointLimit();
ConfigService.caboPenalty = await ConfigService.getCaboPenalty(); ConfigService.caboPenalty = await ConfigService.getCaboPenalty();
await VersionService.init();
runApp(const App()); runApp(const App());
} }

View File

@@ -1,11 +1,11 @@
import 'package:cabo_counter/data/game_manager.dart'; import 'package:cabo_counter/data/game_manager.dart';
import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/data/game_session.dart';
import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/l10n/app_localizations.dart';
import 'package:cabo_counter/presentation/views/create_game_view.dart';
import 'package:cabo_counter/presentation/views/graph_view.dart';
import 'package:cabo_counter/presentation/views/round_view.dart';
import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart';
import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:cabo_counter/utility/custom_theme.dart';
import 'package:cabo_counter/views/create_game_view.dart';
import 'package:cabo_counter/views/graph_view.dart';
import 'package:cabo_counter/views/round_view.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View File

@@ -1,10 +1,10 @@
import 'package:cabo_counter/data/game_manager.dart'; import 'package:cabo_counter/data/game_manager.dart';
import 'package:cabo_counter/data/game_session.dart'; import 'package:cabo_counter/data/game_session.dart';
import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/l10n/app_localizations.dart';
import 'package:cabo_counter/presentation/views/active_game_view.dart';
import 'package:cabo_counter/presentation/views/mode_selection_view.dart';
import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/config_service.dart';
import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:cabo_counter/utility/custom_theme.dart';
import 'package:cabo_counter/views/active_game_view.dart';
import 'package:cabo_counter/views/mode_selection_view.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
enum CreateStatus { enum CreateStatus {

View File

@@ -1,11 +1,11 @@
import 'package:cabo_counter/data/game_manager.dart'; import 'package:cabo_counter/data/game_manager.dart';
import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/l10n/app_localizations.dart';
import 'package:cabo_counter/presentation/views/active_game_view.dart';
import 'package:cabo_counter/presentation/views/create_game_view.dart';
import 'package:cabo_counter/presentation/views/settings_view.dart';
import 'package:cabo_counter/services/config_service.dart'; import 'package:cabo_counter/services/config_service.dart';
import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart';
import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:cabo_counter/utility/custom_theme.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';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View File

@@ -0,0 +1,230 @@
import 'package:cabo_counter/l10n/app_localizations.dart';
import 'package:cabo_counter/presentation/widgets/custom_form_row.dart';
import 'package:cabo_counter/presentation/widgets/stepper.dart';
import 'package:cabo_counter/services/config_service.dart';
import 'package:cabo_counter/services/local_storage_service.dart';
import 'package:cabo_counter/services/version_service.dart';
import 'package:cabo_counter/utility/custom_theme.dart';
import 'package:flutter/cupertino.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:url_launcher/url_launcher.dart';
class SettingsView extends StatefulWidget {
const SettingsView({super.key});
@override
State<SettingsView> createState() => _SettingsViewState();
}
class _SettingsViewState extends State<SettingsView> {
UniqueKey _stepperKey1 = UniqueKey();
UniqueKey _stepperKey2 = UniqueKey();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(AppLocalizations.of(context).settings),
),
child: SafeArea(
child: Stack(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Text(
AppLocalizations.of(context).points,
style: CustomTheme.rowTitle,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(10, 15, 10, 10),
child: CupertinoFormSection.insetGrouped(
backgroundColor: CustomTheme.backgroundColor,
margin: EdgeInsets.zero,
children: [
CustomFormRow(
prefixText: 'Cabo-Strafe',
prefixIcon: CupertinoIcons.bolt_fill,
suffixWidget: Stepper(
key: _stepperKey1,
initialValue: ConfigService.caboPenalty,
minValue: 0,
maxValue: 50,
step: 1,
onChanged: (newCaboPenalty) {
setState(() {
ConfigService.setCaboPenalty(newCaboPenalty);
ConfigService.caboPenalty = newCaboPenalty;
});
},
),
),
CustomFormRow(
prefixText: 'Punkte-Limit',
prefixIcon: FontAwesomeIcons.bullseye,
suffixWidget: Stepper(
key: _stepperKey2,
initialValue: ConfigService.pointLimit,
minValue: 30,
maxValue: 1000,
step: 10,
onChanged: (newPointLimit) {
setState(() {
ConfigService.setPointLimit(newPointLimit);
ConfigService.pointLimit = newPointLimit;
});
},
),
),
CustomFormRow(
prefixText:
AppLocalizations.of(context).reset_to_default,
prefixIcon: CupertinoIcons.arrow_counterclockwise,
onPressed: () {
ConfigService.resetConfig();
setState(() {
_stepperKey1 = UniqueKey();
_stepperKey2 = UniqueKey();
print('Config reset to default');
});
},
)
])),
Padding(
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Text(
AppLocalizations.of(context).game_data,
style: CustomTheme.rowTitle,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(10, 15, 10, 10),
child: CupertinoFormSection.insetGrouped(
backgroundColor: CustomTheme.backgroundColor,
margin: EdgeInsets.zero,
children: [
CustomFormRow(
prefixText: AppLocalizations.of(context).import_data,
prefixIcon: CupertinoIcons.square_arrow_down,
onPressed: () async {
final status =
await LocalStorageService.importJsonFile();
showFeedbackDialog(status);
},
suffixWidget: const CupertinoListTileChevron(),
),
CustomFormRow(
prefixText: AppLocalizations.of(context).export_data,
prefixIcon: CupertinoIcons.square_arrow_up,
onPressed: () => LocalStorageService.exportGameData(),
suffixWidget: const CupertinoListTileChevron(),
),
])),
Padding(
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Text(
AppLocalizations.of(context).app,
style: CustomTheme.rowTitle,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(10, 15, 10, 0),
child: CupertinoFormSection.insetGrouped(
backgroundColor: CustomTheme.backgroundColor,
margin: EdgeInsets.zero,
children: [
CustomFormRow(
prefixText: AppLocalizations.of(context).create_issue,
prefixIcon: FontAwesomeIcons.github,
onPressed: () => launchUrl(Uri.parse(
'https://github.com/flixcoo/Cabo-Counter/issues')),
suffixWidget: const CupertinoListTileChevron(),
),
CustomFormRow(
prefixText: AppLocalizations.of(context).wiki,
prefixIcon: CupertinoIcons.book,
onPressed: () => launchUrl(Uri.parse(
'https://github.com/flixcoo/Cabo-Counter/wiki')),
suffixWidget: const CupertinoListTileChevron(),
),
CustomFormRow(
prefixText:
AppLocalizations.of(context).app_version,
prefixIcon: CupertinoIcons.tag,
onPressed: null,
suffixWidget: Text(VersionService.getVersion(),
style: TextStyle(
color: CustomTheme.primaryColor,
))),
CustomFormRow(
prefixText: AppLocalizations.of(context).build,
prefixIcon: CupertinoIcons.number,
onPressed: null,
suffixWidget: Text(VersionService.getBuildNumber(),
style: TextStyle(
color: CustomTheme.primaryColor,
))),
])),
],
),
],
)),
);
}
void showFeedbackDialog(ImportStatus status) {
if (status == ImportStatus.canceled) return;
final (title, message) = _getDialogContent(status);
showCupertinoDialog(
context: context,
builder: (context) {
return CupertinoAlertDialog(
title: Text(title),
content: Text(message),
actions: [
CupertinoDialogAction(
child: Text(AppLocalizations.of(context).ok),
onPressed: () => Navigator.pop(context),
),
],
);
});
}
(String, String) _getDialogContent(ImportStatus status) {
switch (status) {
case ImportStatus.success:
return (
AppLocalizations.of(context).import_success_title,
AppLocalizations.of(context).import_success_message
);
case ImportStatus.validationError:
return (
AppLocalizations.of(context).import_validation_error_title,
AppLocalizations.of(context).import_validation_error_message
);
case ImportStatus.formatError:
return (
AppLocalizations.of(context).import_format_error_title,
AppLocalizations.of(context).import_format_error_message
);
case ImportStatus.genericError:
return (
AppLocalizations.of(context).import_generic_error_title,
AppLocalizations.of(context).import_generic_error_message
);
case ImportStatus.canceled:
return ('', '');
}
}
}

View File

@@ -1,7 +1,7 @@
import 'package:cabo_counter/l10n/app_localizations.dart'; import 'package:cabo_counter/l10n/app_localizations.dart';
import 'package:cabo_counter/presentation/views/information_view.dart';
import 'package:cabo_counter/presentation/views/main_menu_view.dart';
import 'package:cabo_counter/utility/custom_theme.dart'; import 'package:cabo_counter/utility/custom_theme.dart';
import 'package:cabo_counter/views/information_view.dart';
import 'package:cabo_counter/views/main_menu_view.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
class TabView extends StatefulWidget { class TabView extends StatefulWidget {

View File

@@ -0,0 +1,53 @@
import 'package:cabo_counter/presentation/widgets/stepper.dart';
import 'package:cabo_counter/utility/custom_theme.dart';
import 'package:flutter/cupertino.dart';
class CustomFormRow extends StatefulWidget {
final String prefixText;
final IconData prefixIcon;
final Widget? suffixWidget;
final void Function()? onPressed;
const CustomFormRow({
super.key,
required this.prefixText,
required this.prefixIcon,
this.onPressed,
this.suffixWidget,
});
@override
State<CustomFormRow> createState() => _CustomFormRowState();
}
class _CustomFormRowState extends State<CustomFormRow> {
late Widget suffixWidget;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
suffixWidget = widget.suffixWidget ?? const SizedBox.shrink();
return CupertinoButton(
padding: EdgeInsets.zero,
onPressed: widget.onPressed,
child: CupertinoFormRow(
prefix: Row(
children: [
Icon(
widget.prefixIcon,
color: CustomTheme.primaryColor,
),
const SizedBox(width: 10),
Text(widget.prefixText),
],
),
padding: suffixWidget is Stepper
? const EdgeInsets.fromLTRB(15, 0, 0, 0)
: const EdgeInsets.symmetric(vertical: 10, horizontal: 15),
child: suffixWidget,
),
);
}
}

View File

@@ -1,3 +1,4 @@
import 'package:cabo_counter/utility/custom_theme.dart';
import 'package:flutter/cupertino.dart'; // Für iOS-Style import 'package:flutter/cupertino.dart'; // Für iOS-Style
class Stepper extends StatefulWidget { class Stepper extends StatefulWidget {
@@ -34,18 +35,20 @@ class _StepperState extends State<Stepper> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
CupertinoButton( CupertinoButton(
padding: const EdgeInsets.all(8), padding: EdgeInsets.zero,
onPressed: _decrement, onPressed: _decrement,
child: const Icon(CupertinoIcons.minus), child: const Icon(CupertinoIcons.minus),
), ),
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0), padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Text('$_value', style: const TextStyle(fontSize: 18)), child: Text('$_value',
style: TextStyle(fontSize: 18, color: CustomTheme.white)),
), ),
CupertinoButton( CupertinoButton(
padding: const EdgeInsets.all(8), padding: EdgeInsets.zero,
onPressed: _increment, onPressed: _increment,
child: const Icon(CupertinoIcons.add), child: const Icon(CupertinoIcons.add),
), ),

View File

@@ -21,7 +21,7 @@ class LocalStorageService {
static const String _fileName = 'game_data.json'; static const String _fileName = 'game_data.json';
/// Writes the game session list to a JSON file and returns it as string. /// Writes the game session list to a JSON file and returns it as string.
static String getGameDataAsJsonFile() { static String _getGameDataAsJsonFile() {
final jsonFile = final jsonFile =
gameManager.gameList.map((session) => session.toJson()).toList(); gameManager.gameList.map((session) => session.toJson()).toList();
return json.encode(jsonFile); return json.encode(jsonFile);
@@ -39,7 +39,7 @@ class LocalStorageService {
print('[local_storage_service.dart] Versuche, Daten zu speichern...'); print('[local_storage_service.dart] Versuche, Daten zu speichern...');
try { try {
final file = await _getFilePath(); final file = await _getFilePath();
final jsonFile = getGameDataAsJsonFile(); final jsonFile = _getGameDataAsJsonFile();
await file.writeAsString(jsonFile); await file.writeAsString(jsonFile);
print( print(
'[local_storage_service.dart] Die Spieldaten wurden zwischengespeichert.'); '[local_storage_service.dart] Die Spieldaten wurden zwischengespeichert.');
@@ -70,7 +70,7 @@ class LocalStorageService {
return false; return false;
} }
if (!await validateJsonSchema(jsonString, true)) { if (!await _validateJsonSchema(jsonString, true)) {
print( print(
'[local_storage_service.dart] Die Datei konnte nicht validiert werden'); '[local_storage_service.dart] Die Datei konnte nicht validiert werden');
gameManager.gameList = []; gameManager.gameList = [];
@@ -105,7 +105,7 @@ class LocalStorageService {
/// Opens the file picker to export game data as a JSON file. /// Opens the file picker to export game data as a JSON file.
/// This method will export the given [jsonString] as a JSON file. It opens /// This method will export the given [jsonString] as a JSON file. It opens
/// the file picker with the choosen [fileName]. /// the file picker with the choosen [fileName].
static Future<bool> exportJsonData( static Future<bool> _exportJsonData(
String jsonString, String jsonString,
String fileName, String fileName,
) async { ) async {
@@ -133,16 +133,16 @@ class LocalStorageService {
/// Opens the file picker to export all game sessions as a JSON file. /// Opens the file picker to export all game sessions as a JSON file.
static Future<bool> exportGameData() async { static Future<bool> exportGameData() async {
String jsonString = getGameDataAsJsonFile(); String jsonString = _getGameDataAsJsonFile();
String fileName = 'cabo_counter-game_data'; String fileName = 'cabo_counter-game_data';
return exportJsonData(jsonString, fileName); return _exportJsonData(jsonString, fileName);
} }
/// Opens the file picker to save a single game session as a JSON file. /// Opens the file picker to save a single game session as a JSON file.
static Future<bool> exportSingleGameSession(GameSession session) async { static Future<bool> exportSingleGameSession(GameSession session) async {
String jsonString = json.encode(session.toJson()); String jsonString = json.encode(session.toJson());
String fileName = 'cabo_counter-game_${session.id.substring(0, 7)}'; String fileName = 'cabo_counter-game_${session.id.substring(0, 7)}';
return exportJsonData(jsonString, fileName); return _exportJsonData(jsonString, fileName);
} }
/// Opens the file picker to import a JSON file and loads the game data from it. /// Opens the file picker to import a JSON file and loads the game data from it.
@@ -162,7 +162,7 @@ class LocalStorageService {
try { try {
final jsonString = await _readFileContent(path.files.single); final jsonString = await _readFileContent(path.files.single);
if (await validateJsonSchema(jsonString, true)) { if (await _validateJsonSchema(jsonString, true)) {
// Checks if the JSON String is in the gameList format // Checks if the JSON String is in the gameList format
final jsonData = json.decode(jsonString) as List<dynamic>; final jsonData = json.decode(jsonString) as List<dynamic>;
@@ -172,12 +172,12 @@ class LocalStorageService {
.toList(); .toList();
for (GameSession s in importedList) { for (GameSession s in importedList) {
importSession(s); _importSession(s);
} }
} else if (await validateJsonSchema(jsonString, false)) { } else if (await _validateJsonSchema(jsonString, false)) {
// Checks if the JSON String is in the single game format // Checks if the JSON String is in the single game format
final jsonData = json.decode(jsonString) as Map<String, dynamic>; final jsonData = json.decode(jsonString) as Map<String, dynamic>;
importSession(GameSession.fromJson(jsonData)); _importSession(GameSession.fromJson(jsonData));
} else { } else {
return ImportStatus.validationError; return ImportStatus.validationError;
} }
@@ -198,7 +198,7 @@ class LocalStorageService {
} }
/// Imports a single game session into the gameList. /// Imports a single game session into the gameList.
static Future<void> importSession(GameSession session) async { static Future<void> _importSession(GameSession session) async {
if (gameManager.gameExistsInGameList(session.id)) { if (gameManager.gameExistsInGameList(session.id)) {
print( print(
'[local_storage_service.dart] Die Session mit der ID ${session.id} existiert bereits. Sie wird überschrieben.'); '[local_storage_service.dart] Die Session mit der ID ${session.id} existiert bereits. Sie wird überschrieben.');
@@ -221,7 +221,7 @@ class LocalStorageService {
/// This method checks if the provided [jsonString] is valid against the /// This method checks if the provided [jsonString] is valid against the
/// JSON schema. It takes a boolean [isGameList] to determine /// JSON schema. It takes a boolean [isGameList] to determine
/// which schema to use (game list or single game). /// which schema to use (game list or single game).
static Future<bool> validateJsonSchema( static Future<bool> _validateJsonSchema(
String jsonString, bool isGameList) async { String jsonString, bool isGameList) async {
final String schemaString; final String schemaString;

View File

@@ -0,0 +1,32 @@
import 'package:cabo_counter/utility/globals.dart';
import 'package:package_info_plus/package_info_plus.dart';
class VersionService {
static String _version = '-.-.-';
static String _buildNumber = '-';
static Future<void> init() async {
var packageInfo = await PackageInfo.fromPlatform();
_version = packageInfo.version;
_buildNumber = packageInfo.buildNumber;
}
static String getVersionNumber() {
return _version;
}
static String getVersion() {
if (_version == '-.-.-') {
return getVersionNumber();
}
return '${Globals.appDevPhase} $_version';
}
static String getBuildNumber() {
return _buildNumber;
}
static String getVersionWithBuild() {
return '$_version ($_buildNumber)';
}
}

View File

@@ -1,267 +0,0 @@
import 'package:cabo_counter/l10n/app_localizations.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/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});
@override
State<SettingsView> createState() => _SettingsViewState();
}
class _SettingsViewState extends State<SettingsView> {
UniqueKey _stepperKey1 = UniqueKey();
UniqueKey _stepperKey2 = UniqueKey();
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text(AppLocalizations.of(context).settings),
),
child: SafeArea(
child: Stack(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Text(
AppLocalizations.of(context).points,
style: CustomTheme.rowTitle,
),
),
Padding(
padding: const EdgeInsets.fromLTRB(15, 10, 10, 0),
child: CupertinoListTile(
padding: EdgeInsets.zero,
title: Text(AppLocalizations.of(context).cabo_penalty),
subtitle: Text(
AppLocalizations.of(context).cabo_penalty_subtitle),
trailing: Stepper(
key: _stepperKey1,
initialValue: ConfigService.caboPenalty,
minValue: 0,
maxValue: 50,
step: 1,
onChanged: (newCaboPenalty) {
setState(() {
ConfigService.setCaboPenalty(newCaboPenalty);
ConfigService.caboPenalty = newCaboPenalty;
});
},
),
)),
Padding(
padding: const EdgeInsets.fromLTRB(15, 10, 10, 0),
child: CupertinoListTile(
padding: EdgeInsets.zero,
title: Text(AppLocalizations.of(context).point_limit),
subtitle:
Text(AppLocalizations.of(context).point_limit_subtitle),
trailing: Stepper(
key: _stepperKey2,
initialValue: ConfigService.pointLimit,
minValue: 30,
maxValue: 1000,
step: 10,
onChanged: (newPointLimit) {
setState(() {
ConfigService.setPointLimit(newPointLimit);
ConfigService.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:
Text(AppLocalizations.of(context).reset_to_default),
),
)),
Padding(
padding: const EdgeInsets.fromLTRB(10, 10, 0, 0),
child: Text(
AppLocalizations.of(context).game_data,
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(
AppLocalizations.of(context).import_data,
style:
TextStyle(color: CustomTheme.backgroundColor),
),
onPressed: () async {
final success =
await LocalStorageService.importJsonFile();
showFeedbackDialog(success);
}),
const SizedBox(
width: 20,
),
CupertinoButton(
color: CustomTheme.primaryColor,
sizeStyle: CupertinoButtonSize.medium,
child: Text(
AppLocalizations.of(context).export_data,
style:
TextStyle(color: CustomTheme.backgroundColor),
),
onPressed: () async {
final success =
await LocalStorageService.exportGameData();
if (!success && context.mounted) {
showCupertinoDialog(
context: context,
builder: (context) => CupertinoAlertDialog(
title: Text(AppLocalizations.of(context)
.export_error_title),
content: Text(AppLocalizations.of(context)
.export_error_message),
actions: [
CupertinoDialogAction(
child:
Text(AppLocalizations.of(context).ok),
onPressed: () => Navigator.pop(context),
),
],
),
);
}
},
),
],
)),
)
],
),
Positioned(
bottom: 30,
left: 0,
right: 0,
child: Column(
children: [
Center(
child: Text(AppLocalizations.of(context).error_found),
),
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: Text(AppLocalizations.of(context).create_issue),
),
),
),
FutureBuilder<PackageInfo>(
future: _getPackageInfo(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(
'${Globals.appDevPhase} ${snapshot.data!.version} '
'(${AppLocalizations.of(context).build} ${snapshot.data!.buildNumber})',
textAlign: TextAlign.center,
);
} else if (snapshot.hasError) {
return Text(
'${AppLocalizations.of(context).app_version} -.-.- (${AppLocalizations.of(context).build} -)',
textAlign: TextAlign.center,
);
}
return Text(
AppLocalizations.of(context).load_version,
textAlign: TextAlign.center,
);
},
)
],
)),
],
)),
);
}
Future<PackageInfo> _getPackageInfo() async {
return await PackageInfo.fromPlatform();
}
void showFeedbackDialog(ImportStatus status) {
if (status == ImportStatus.canceled) return;
final (title, message) = _getDialogContent(status);
showCupertinoDialog(
context: context,
builder: (context) {
return CupertinoAlertDialog(
title: Text(title),
content: Text(message),
actions: [
CupertinoDialogAction(
child: Text(AppLocalizations.of(context).ok),
onPressed: () => Navigator.pop(context),
),
],
);
});
}
(String, String) _getDialogContent(ImportStatus status) {
switch (status) {
case ImportStatus.success:
return (
AppLocalizations.of(context).import_success_title,
AppLocalizations.of(context).import_success_message
);
case ImportStatus.validationError:
return (
AppLocalizations.of(context).import_validation_error_title,
AppLocalizations.of(context).import_validation_error_message
);
case ImportStatus.formatError:
return (
AppLocalizations.of(context).import_format_error_title,
AppLocalizations.of(context).import_format_error_message
);
case ImportStatus.genericError:
return (
AppLocalizations.of(context).import_generic_error_title,
AppLocalizations.of(context).import_generic_error_message
);
case ImportStatus.canceled:
return ('', '');
}
}
}

View File

@@ -2,7 +2,7 @@ name: cabo_counter
description: "Mobile app for the card game Cabo" description: "Mobile app for the card game Cabo"
publish_to: 'none' publish_to: 'none'
version: 0.3.9+331 version: 0.4.0+382
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4