From 94b113eb95fe7f84d52161d323d707ac0a5583ab Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 14:07:42 +0100 Subject: [PATCH 001/222] Renamed widget --- .../views/main_menu/create_game/choose_ruleset_view.dart | 4 ++-- ...uleset_list_tile.dart => title_description_list_tile.dart} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename lib/presentation/widgets/tiles/{ruleset_list_tile.dart => title_description_list_tile.dart} (94%) diff --git a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart index b54f56e..647cdc4 100644 --- a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; -import 'package:game_tracker/presentation/widgets/tiles/ruleset_list_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; class ChooseRulesetView extends StatefulWidget { final List<(Ruleset, String, String)> rulesets; @@ -85,7 +85,7 @@ class _ChooseRulesetViewState extends State { padding: const EdgeInsets.only(bottom: 85), itemCount: widget.rulesets.length, itemBuilder: (BuildContext context, int index) { - return RulesetListTile( + return TitleDescriptionListTile( onPressed: () async { setState(() { selectedRulesetIndex = index; diff --git a/lib/presentation/widgets/tiles/ruleset_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart similarity index 94% rename from lib/presentation/widgets/tiles/ruleset_list_tile.dart rename to lib/presentation/widgets/tiles/title_description_list_tile.dart index 13eaf82..5b370d4 100644 --- a/lib/presentation/widgets/tiles/ruleset_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -class RulesetListTile extends StatelessWidget { +class TitleDescriptionListTile extends StatelessWidget { final String title; final String description; final VoidCallback? onPressed; final bool isHighlighted; - const RulesetListTile({ + const TitleDescriptionListTile({ super.key, required this.title, required this.description, From 236a737fd1ffce9d2cd13db86e46435f6e32a8e7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 14:41:50 +0100 Subject: [PATCH 002/222] Implemented choose game view --- lib/core/enums.dart | 18 +++- .../create_game/choose_game_view.dart | 65 +++++++++++++ .../create_game/choose_ruleset_view.dart | 92 ++++--------------- .../create_game/create_game_view.dart | 46 +++++++--- .../tiles/title_description_list_tile.dart | 44 +++++++-- 5 files changed, 167 insertions(+), 98 deletions(-) create mode 100644 lib/presentation/views/main_menu/create_game/choose_game_view.dart diff --git a/lib/core/enums.dart b/lib/core/enums.dart index af1f4a6..68752fb 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -27,5 +27,19 @@ enum ExportResult { success, canceled, unknownException } /// - [Ruleset.singleWinner]: The game is won by a single player /// - [Ruleset.singleLoser]: The game is lost by a single player /// - [Ruleset.mostPoints]: The player with the most points wins. -/// - [Ruleset.lastPoints]: The player with the fewest points wins. -enum Ruleset { singleWinner, singleLoser, mostPoints, lastPoints } +/// - [Ruleset.leastPoints]: The player with the fewest points wins. +enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints } + +/// Translates a [Ruleset] enum value to its corresponding string representation. +String translateRulesetToString(Ruleset ruleset) { + switch (ruleset) { + case Ruleset.singleWinner: + return 'Single Winner'; + case Ruleset.singleLoser: + return 'Single Loser'; + case Ruleset.mostPoints: + return 'Most Points'; + case Ruleset.leastPoints: + return 'Least Points'; + } +} diff --git a/lib/presentation/views/main_menu/create_game/choose_game_view.dart b/lib/presentation/views/main_menu/create_game/choose_game_view.dart new file mode 100644 index 0000000..1c75b32 --- /dev/null +++ b/lib/presentation/views/main_menu/create_game/choose_game_view.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/core/enums.dart'; +import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; + +class ChooseGameView extends StatefulWidget { + final List<(String, String, Ruleset)> games; + final int initialGameIndex; + + const ChooseGameView({ + super.key, + required this.games, + required this.initialGameIndex, + }); + + @override + State createState() => _ChooseGameViewState(); +} + +class _ChooseGameViewState extends State { + late int selectedGameIndex; + + @override + void initState() { + selectedGameIndex = widget.initialGameIndex; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar( + backgroundColor: CustomTheme.backgroundColor, + scrolledUnderElevation: 0, + title: const Text( + 'Choose Game', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + centerTitle: true, + ), + body: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: widget.games.length, + itemBuilder: (BuildContext context, int index) { + return TitleDescriptionListTile( + title: widget.games[index].$1, + description: widget.games[index].$2, + badgeText: translateRulesetToString(widget.games[index].$3), + isHighlighted: selectedGameIndex == index, + onPressed: () async { + setState(() { + selectedGameIndex = index; + }); + Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; + Navigator.of(context).pop(selectedGameIndex); + }); + }, + ); + }, + ), + ); + } +} diff --git a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart index 647cdc4..2e45a7c 100644 --- a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart @@ -6,6 +6,7 @@ import 'package:game_tracker/presentation/widgets/tiles/title_description_list_t class ChooseRulesetView extends StatefulWidget { final List<(Ruleset, String, String)> rulesets; final int initialRulesetIndex; + const ChooseRulesetView({ super.key, required this.rulesets, @@ -41,78 +42,25 @@ class _ChooseRulesetViewState extends State { ), centerTitle: true, ), - body: Column( - children: [ - Container( - color: CustomTheme.backgroundColor, - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - child: TabBar( - padding: const EdgeInsets.symmetric(horizontal: 5), - // Label Settings - labelStyle: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - labelColor: Colors.white, - unselectedLabelStyle: const TextStyle(fontSize: 14), - unselectedLabelColor: Colors.white70, - // Indicator Settings - indicator: CustomTheme.standardBoxDecoration, - indicatorSize: TabBarIndicatorSize.tab, - indicatorWeight: 1, - indicatorPadding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 0, - ), - // Divider Settings - dividerHeight: 0, - tabs: const [ - Tab(text: 'Rulesets'), - Tab(text: 'Gametypes'), - ], - ), - ), - const Divider( - indent: 30, - endIndent: 30, - thickness: 3, - radius: BorderRadius.all(Radius.circular(12)), - ), - Expanded( - child: TabBarView( - children: [ - ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: widget.rulesets.length, - itemBuilder: (BuildContext context, int index) { - return TitleDescriptionListTile( - onPressed: () async { - setState(() { - selectedRulesetIndex = index; - }); - Future.delayed(const Duration(milliseconds: 500), () { - if (!context.mounted) return; - Navigator.of( - context, - ).pop(widget.rulesets[index].$1); - }); - }, - title: widget.rulesets[index].$2, - description: widget.rulesets[index].$3, - isHighlighted: selectedRulesetIndex == index, - ); - }, - ), - const Center( - child: Text( - 'No gametypes available', - style: TextStyle(color: Colors.white70), - ), - ), - ], - ), - ), - ], + body: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: widget.rulesets.length, + itemBuilder: (BuildContext context, int index) { + return TitleDescriptionListTile( + onPressed: () async { + setState(() { + selectedRulesetIndex = index; + }); + Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; + Navigator.of(context).pop(widget.rulesets[index].$1); + }); + }, + title: widget.rulesets[index].$2, + description: widget.rulesets[index].$3, + isHighlighted: selectedRulesetIndex == index, + ); + }, ), ), ); diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 485118b..68883d0 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -5,6 +5,7 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/presentation/views/main_menu/create_game/choose_game_view.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_ruleset_view.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; @@ -53,6 +54,8 @@ class _CreateGameViewState extends State { /// the [ChooseRulesetView] int selectedRulesetIndex = -1; + int selectedGameIndex = -1; + /// The currently selected players List? selectedPlayers; @@ -75,12 +78,17 @@ class _CreateGameViewState extends State { 'Traditional ruleset: the player with the most points wins.', ), ( - Ruleset.lastPoints, + Ruleset.leastPoints, 'Least Points', 'Inverse scoring: the player with the fewest points wins.', ), ]; + List<(String, String, Ruleset)> games = [ + ('Cabo', 'A memory card game', Ruleset.leastPoints), + ('Uno', 'The Classic', Ruleset.singleWinner), + ]; + @override void initState() { super.initState(); @@ -122,6 +130,27 @@ class _CreateGameViewState extends State { }, ), ), + ChooseTile( + title: 'Game', + trailingText: selectedGameIndex == -1 + ? 'None' + : games[selectedGameIndex].$1, + onPressed: () async { + selectedGameIndex = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ChooseGameView( + games: games, + initialGameIndex: selectedGameIndex, + ), + ), + ); + selectedRuleset = games[selectedGameIndex].$3; + selectedRulesetIndex = rulesets.indexWhere( + (r) => r.$1 == selectedRuleset, + ); + setState(() {}); + }, + ), ChooseTile( title: 'Ruleset', trailingText: selectedRuleset == null @@ -139,6 +168,7 @@ class _CreateGameViewState extends State { selectedRulesetIndex = rulesets.indexWhere( (r) => r.$1 == selectedRuleset, ); + selectedGameIndex = -1; setState(() {}); }, ), @@ -207,20 +237,6 @@ class _CreateGameViewState extends State { ); } - /// Translates a [Ruleset] enum value to its corresponding string representation. - String translateRulesetToString(Ruleset ruleset) { - switch (ruleset) { - case Ruleset.singleWinner: - return 'Single Winner'; - case Ruleset.singleLoser: - return 'Single Loser'; - case Ruleset.mostPoints: - return 'Most Points'; - case Ruleset.lastPoints: - return 'Least Points'; - } - } - /// Determines whether the "Create Game" button should be enabled based on /// the current state of the input fields. bool _enableCreateGameButton() { diff --git a/lib/presentation/widgets/tiles/title_description_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart index 5b370d4..ac20b3f 100644 --- a/lib/presentation/widgets/tiles/title_description_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -6,6 +6,8 @@ class TitleDescriptionListTile extends StatelessWidget { final String description; final VoidCallback? onPressed; final bool isHighlighted; + final String? badgeText; + final Color? badgeColor; const TitleDescriptionListTile({ super.key, @@ -13,6 +15,8 @@ class TitleDescriptionListTile extends StatelessWidget { required this.description, this.onPressed, this.isHighlighted = false, + this.badgeText, + this.badgeColor, }); @override @@ -28,20 +32,42 @@ class TitleDescriptionListTile extends StatelessWidget { duration: const Duration(milliseconds: 200), child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, children: [ Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - Flexible( - child: Text( - title, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - ), + Text( + title, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, ), ), + if (badgeText != null) ...[ + const Spacer(), + Container( + margin: const EdgeInsets.only(top: 4), + padding: const EdgeInsets.symmetric( + vertical: 2, + horizontal: 6, + ), + decoration: BoxDecoration( + color: badgeColor ?? CustomTheme.primaryColor, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + badgeText!, + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ), + ], ], ), const SizedBox(height: 5), From 4dbc106e38de013499e282059200e4c29d3bb301 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 23:14:13 +0100 Subject: [PATCH 003/222] Added searchbar --- .../create_game/choose_game_view.dart | 52 ++++++++++++------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_game_view.dart b/lib/presentation/views/main_menu/create_game/choose_game_view.dart index 1c75b32..8d8279e 100644 --- a/lib/presentation/views/main_menu/create_game/choose_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_game_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; +import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; class ChooseGameView extends StatefulWidget { @@ -19,6 +20,7 @@ class ChooseGameView extends StatefulWidget { class _ChooseGameViewState extends State { late int selectedGameIndex; + final TextEditingController searchBarController = TextEditingController(); @override void initState() { @@ -39,26 +41,36 @@ class _ChooseGameViewState extends State { ), centerTitle: true, ), - body: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: widget.games.length, - itemBuilder: (BuildContext context, int index) { - return TitleDescriptionListTile( - title: widget.games[index].$1, - description: widget.games[index].$2, - badgeText: translateRulesetToString(widget.games[index].$3), - isHighlighted: selectedGameIndex == index, - onPressed: () async { - setState(() { - selectedGameIndex = index; - }); - Future.delayed(const Duration(milliseconds: 500), () { - if (!context.mounted) return; - Navigator.of(context).pop(selectedGameIndex); - }); - }, - ); - }, + body: Column( + children: [ + CustomSearchBar( + controller: searchBarController, + hintText: 'Game Name', + ), + const SizedBox(height: 5), + Expanded( + child: ListView.builder( + itemCount: widget.games.length, + itemBuilder: (BuildContext context, int index) { + return TitleDescriptionListTile( + title: widget.games[index].$1, + description: widget.games[index].$2, + badgeText: translateRulesetToString(widget.games[index].$3), + isHighlighted: selectedGameIndex == index, + onPressed: () async { + setState(() { + selectedGameIndex = index; + }); + Future.delayed(const Duration(milliseconds: 500), () { + if (!context.mounted) return; + Navigator.of(context).pop(selectedGameIndex); + }); + }, + ); + }, + ), + ), + ], ), ); } From bcd7bf751b7ff6ecb90c84798cc837598873ba5a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 23:14:29 +0100 Subject: [PATCH 004/222] Added todo & example data --- .../views/main_menu/create_game/create_game_view.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 68883d0..eb5468c 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -61,6 +61,7 @@ class _CreateGameViewState extends State { /// List of available rulesets with their display names and descriptions /// as tuples of (Ruleset, String, String) + /// TODO: Replace when rulesets are implemented List<(Ruleset, String, String)> rulesets = [ ( Ruleset.singleWinner, @@ -84,9 +85,10 @@ class _CreateGameViewState extends State { ), ]; + // TODO: Replace when games are implemented List<(String, String, Ruleset)> games = [ - ('Cabo', 'A memory card game', Ruleset.leastPoints), - ('Uno', 'The Classic', Ruleset.singleWinner), + ('Example Game 1', 'This is a discription', Ruleset.leastPoints), + ('Example Game 2', '', Ruleset.singleWinner), ]; @override From b4ccb567b508e85c6d83f79e12f6efe6a2419106 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 28 Nov 2025 23:15:08 +0100 Subject: [PATCH 005/222] Added conditional description --- .../widgets/tiles/title_description_list_tile.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/presentation/widgets/tiles/title_description_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart index ac20b3f..7bc4221 100644 --- a/lib/presentation/widgets/tiles/title_description_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -32,7 +32,7 @@ class TitleDescriptionListTile extends StatelessWidget { duration: const Duration(milliseconds: 200), child: Column( crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, children: [ Row( mainAxisAlignment: MainAxisAlignment.start, @@ -70,9 +70,11 @@ class TitleDescriptionListTile extends StatelessWidget { ], ], ), - const SizedBox(height: 5), - Text(description, style: const TextStyle(fontSize: 14)), - const SizedBox(height: 2.5), + if (description.isNotEmpty) ...[ + const SizedBox(height: 5), + Text(description, style: const TextStyle(fontSize: 14)), + const SizedBox(height: 2.5), + ], ], ), ), From 6ae7166d344a0fdde863d75103834b7b52c89fea Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 15:18:41 +0100 Subject: [PATCH 006/222] Update state when selecting players and require at least two players to create a group --- lib/presentation/views/main_menu/create_group_view.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/create_group_view.dart index cbaee6d..f20fb4e 100644 --- a/lib/presentation/views/main_menu/create_group_view.dart +++ b/lib/presentation/views/main_menu/create_group_view.dart @@ -66,7 +66,9 @@ class _CreateGroupViewState extends State { Expanded( child: PlayerSelection( onChanged: (value) { - selectedPlayers = [...value]; + setState(() { + selectedPlayers = [...value]; + }); }, ), ), @@ -75,7 +77,8 @@ class _CreateGroupViewState extends State { sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: - (_groupNameController.text.isEmpty || selectedPlayers.isEmpty) + (_groupNameController.text.isEmpty || + (selectedPlayers.length < 2)) ? null : () async { bool success = await db.groupDao.addGroup( From 75b62d0854fb9b6dce79dee74743925ecfabbca9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 6 Dec 2025 16:58:12 +0100 Subject: [PATCH 007/222] Replaced temp navigator --- lib/presentation/views/main_menu/game_history_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 31d1b56..303ea67 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -5,7 +5,7 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/presentation/views/main_menu/create_group_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/create_game/create_game_view.dart'; import 'package:game_tracker/presentation/views/main_menu/game_result_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; @@ -133,7 +133,7 @@ class _GameHistoryViewState extends State { context, MaterialPageRoute( builder: (context) { - return const CreateGroupView(); + return const CreateGameView(); }, ), ); From 10aad4712438188a0d7bb798df2db8b05bf49446 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 6 Dec 2025 17:13:33 +0100 Subject: [PATCH 008/222] Updated choosing mechanism --- .../create_game/choose_game_view.dart | 66 +++++++++++-------- .../create_game/choose_group_view.dart | 21 ++++-- .../create_game/choose_ruleset_view.dart | 20 ++++-- 3 files changed, 67 insertions(+), 40 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_game_view.dart b/lib/presentation/views/main_menu/create_game/choose_game_view.dart index 8d8279e..5b2463b 100644 --- a/lib/presentation/views/main_menu/create_game/choose_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_game_view.dart @@ -35,42 +35,50 @@ class _ChooseGameViewState extends State { appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios), + onPressed: () { + Navigator.of( + context, + ).pop(selectedGameIndex == -1 ? null : selectedGameIndex); + }, + ), title: const Text( 'Choose Game', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), - body: Column( - children: [ - CustomSearchBar( - controller: searchBarController, - hintText: 'Game Name', - ), - const SizedBox(height: 5), - Expanded( - child: ListView.builder( - itemCount: widget.games.length, - itemBuilder: (BuildContext context, int index) { - return TitleDescriptionListTile( - title: widget.games[index].$1, - description: widget.games[index].$2, - badgeText: translateRulesetToString(widget.games[index].$3), - isHighlighted: selectedGameIndex == index, - onPressed: () async { - setState(() { - selectedGameIndex = index; - }); - Future.delayed(const Duration(milliseconds: 500), () { - if (!context.mounted) return; - Navigator.of(context).pop(selectedGameIndex); - }); - }, - ); - }, + body: Container( + decoration: CustomTheme.standardBoxDecoration, + padding: const EdgeInsets.all(10), + child: Column( + children: [ + CustomSearchBar( + controller: searchBarController, + hintText: 'Game Name', ), - ), - ], + const SizedBox(height: 5), + Expanded( + child: ListView.builder( + itemCount: widget.games.length, + itemBuilder: (BuildContext context, int index) { + return TitleDescriptionListTile( + title: widget.games[index].$1, + description: widget.games[index].$2, + badgeText: translateRulesetToString(widget.games[index].$3), + isHighlighted: selectedGameIndex == index, + onPressed: () async { + setState(() { + selectedGameIndex = index; + }); + }, + ); + }, + ), + ), + ], + ), ), ); } diff --git a/lib/presentation/views/main_menu/create_game/choose_group_view.dart b/lib/presentation/views/main_menu/create_game/choose_group_view.dart index c98ce6d..de512bf 100644 --- a/lib/presentation/views/main_menu/create_game/choose_group_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_group_view.dart @@ -33,6 +33,16 @@ class _ChooseGroupViewState extends State { appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios), + onPressed: () { + Navigator.of(context).pop( + selectedGroupIndex == -1 + ? null + : widget.groups[selectedGroupIndex], + ); + }, + ), title: const Text( 'Choose Group', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), @@ -46,12 +56,11 @@ class _ChooseGroupViewState extends State { return GestureDetector( onTap: () { setState(() { - selectedGroupIndex = index; - }); - - Future.delayed(const Duration(milliseconds: 500), () { - if (!context.mounted) return; - Navigator.of(context).pop(widget.groups[index]); + if (selectedGroupIndex == index) { + selectedGroupIndex = -1; + } else { + selectedGroupIndex = index; + } }); }, child: GroupTile( diff --git a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart index 2e45a7c..13a9647 100644 --- a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart @@ -36,6 +36,16 @@ class _ChooseRulesetViewState extends State { appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios), + onPressed: () { + Navigator.of(context).pop( + selectedRulesetIndex == -1 + ? null + : widget.rulesets[selectedRulesetIndex].$1, + ); + }, + ), title: const Text( 'Choose Ruleset', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), @@ -49,11 +59,11 @@ class _ChooseRulesetViewState extends State { return TitleDescriptionListTile( onPressed: () async { setState(() { - selectedRulesetIndex = index; - }); - Future.delayed(const Duration(milliseconds: 500), () { - if (!context.mounted) return; - Navigator.of(context).pop(widget.rulesets[index].$1); + if (selectedRulesetIndex == index) { + selectedRulesetIndex = -1; + } else { + selectedRulesetIndex = index; + } }); }, title: widget.rulesets[index].$2, From 3d12f0c160c09d6fa22d4c87470095be7fff7b34 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 6 Dec 2025 17:23:20 +0100 Subject: [PATCH 009/222] Fixed width problem in choose game view --- .../create_game/choose_game_view.dart | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_game_view.dart b/lib/presentation/views/main_menu/create_game/choose_game_view.dart index 5b2463b..992f02b 100644 --- a/lib/presentation/views/main_menu/create_game/choose_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_game_view.dart @@ -49,36 +49,35 @@ class _ChooseGameViewState extends State { ), centerTitle: true, ), - body: Container( - decoration: CustomTheme.standardBoxDecoration, - padding: const EdgeInsets.all(10), - child: Column( - children: [ - CustomSearchBar( + body: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: CustomSearchBar( controller: searchBarController, hintText: 'Game Name', ), - const SizedBox(height: 5), - Expanded( - child: ListView.builder( - itemCount: widget.games.length, - itemBuilder: (BuildContext context, int index) { - return TitleDescriptionListTile( - title: widget.games[index].$1, - description: widget.games[index].$2, - badgeText: translateRulesetToString(widget.games[index].$3), - isHighlighted: selectedGameIndex == index, - onPressed: () async { - setState(() { - selectedGameIndex = index; - }); - }, - ); - }, - ), + ), + const SizedBox(height: 5), + Expanded( + child: ListView.builder( + itemCount: widget.games.length, + itemBuilder: (BuildContext context, int index) { + return TitleDescriptionListTile( + title: widget.games[index].$1, + description: widget.games[index].$2, + badgeText: translateRulesetToString(widget.games[index].$3), + isHighlighted: selectedGameIndex == index, + onPressed: () async { + setState(() { + selectedGameIndex = index; + }); + }, + ); + }, ), - ], - ), + ), + ], ), ); } From a1a995777bdb288abf679e2b6c77dc0c2404f5c7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 6 Dec 2025 17:23:40 +0100 Subject: [PATCH 010/222] Adjusted margin --- .../views/main_menu/create_game/create_game_view.dart | 4 +--- .../widgets/tiles/title_description_list_tile.dart | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index eb5468c..f83d723 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -123,7 +123,7 @@ class _CreateGameViewState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), child: TextInputField( controller: _gameNameController, hintText: 'Game name', @@ -213,7 +213,6 @@ class _CreateGameViewState extends State { }, ), ), - CustomWidthButton( text: 'Create game', sizeRelativeToWidth: 0.95, @@ -232,7 +231,6 @@ class _CreateGameViewState extends State { } : null, ), - const SizedBox(height: 20), ], ), ), diff --git a/lib/presentation/widgets/tiles/title_description_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart index 7bc4221..2d517f2 100644 --- a/lib/presentation/widgets/tiles/title_description_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -24,7 +24,7 @@ class TitleDescriptionListTile extends StatelessWidget { return GestureDetector( onTap: onPressed, child: AnimatedContainer( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 12), decoration: isHighlighted ? CustomTheme.highlightedBoxDecoration From 6ae0471fa214ab4ee1f900464facf3b1ce2dd6a8 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 6 Dec 2025 17:42:11 +0100 Subject: [PATCH 011/222] Implemented final navigation to GameResultView --- .../create_game/create_game_view.dart | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index f83d723..bbb392b 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -1,3 +1,4 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; @@ -8,6 +9,7 @@ import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_game_view.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/create_game/choose_ruleset_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/game_result_view.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/player_selection.dart'; import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart'; @@ -225,9 +227,20 @@ class _CreateGameViewState extends State { group: selectedGroup!, players: selectedPlayers, ); - // TODO: Replace with navigation to GameResultView() - print('Created game: $game'); - Navigator.pop(context); + final db = Provider.of( + context, + listen: false, + ); + await db.gameDao.addGame(game: game); + if (context.mounted) { + Navigator.pushReplacement( + context, + CupertinoPageRoute( + fullscreenDialog: true, + builder: (context) => GameResultView(game: game), + ), + ); + } } : null, ), From e37a98fdccca68cc20edac23f91c9e6de62df170 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sat, 6 Dec 2025 21:53:24 +0100 Subject: [PATCH 012/222] hart besoffenen fehler gefixt? --- lib/presentation/widgets/text_input/custom_search_bar.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/text_input/custom_search_bar.dart b/lib/presentation/widgets/text_input/custom_search_bar.dart index e3fe976..35c11e1 100644 --- a/lib/presentation/widgets/text_input/custom_search_bar.dart +++ b/lib/presentation/widgets/text_input/custom_search_bar.dart @@ -30,7 +30,7 @@ class CustomSearchBar extends StatelessWidget { constraints: constraints ?? const BoxConstraints(maxHeight: 45, minHeight: 45), hintText: hintText, - onChanged: trailingButtonEnabled ? onChanged : null, + onChanged: onChanged, hintStyle: WidgetStateProperty.all(const TextStyle(fontSize: 16)), leading: const Icon(Icons.search), trailing: [ From 7ecccb13c2f00df72627767327692ceccd6c368d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 13:04:30 +0100 Subject: [PATCH 013/222] Updated skeleton data --- .../views/main_menu/game_history_view.dart | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 31d1b56..a4b8cca 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -27,19 +27,13 @@ class _GameHistoryViewState extends State { late final List skeletonData = List.filled( 4, Game( - name: 'Skeleton Game', + name: 'Skeleton Gamename', group: Group( - name: 'Skeleton Group', - members: [ - Player(name: 'Player 1'), - Player(name: 'Player 2'), - Player(name: 'Player 3'), - Player(name: 'Long Name Player 4'), - Player(name: 'Player 5'), - ], + name: 'Groupname', + members: List.generate(5, (index) => Player(name: 'Player')), ), - winner: Player(name: 'Skeleton Player 1'), - players: [Player(name: 'Skeleton Player 6')], + winner: Player(name: 'Player'), + players: [Player(name: 'Player')], ), ); From d2d0a82c9b2cf3a86f478ad5d838cd196c65c986 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 18:58:49 +0100 Subject: [PATCH 014/222] Implemented deselecting game --- .../main_menu/create_game/choose_game_view.dart | 10 ++++++---- .../main_menu/create_game/create_game_view.dart | 13 ++++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_game_view.dart b/lib/presentation/views/main_menu/create_game/choose_game_view.dart index 992f02b..53a4fcb 100644 --- a/lib/presentation/views/main_menu/create_game/choose_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_game_view.dart @@ -38,9 +38,7 @@ class _ChooseGameViewState extends State { leading: IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () { - Navigator.of( - context, - ).pop(selectedGameIndex == -1 ? null : selectedGameIndex); + Navigator.of(context).pop(selectedGameIndex); }, ), title: const Text( @@ -70,7 +68,11 @@ class _ChooseGameViewState extends State { isHighlighted: selectedGameIndex == index, onPressed: () async { setState(() { - selectedGameIndex = index; + if (selectedGameIndex == index) { + selectedGameIndex = -1; + } else { + selectedGameIndex = index; + } }); }, ); diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index bbb392b..4bf05e7 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -148,11 +148,14 @@ class _CreateGameViewState extends State { ), ), ); - selectedRuleset = games[selectedGameIndex].$3; - selectedRulesetIndex = rulesets.indexWhere( - (r) => r.$1 == selectedRuleset, - ); - setState(() {}); + if (selectedGameIndex != -1) { + setState(() { + selectedRuleset = games[selectedGameIndex].$3; + selectedRulesetIndex = rulesets.indexWhere( + (r) => r.$1 == selectedRuleset, + ); + }); + } }, ), ChooseTile( From f60c11fc08a2d5b912cae2930c5aa2fd66afbc1f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 19:01:19 +0100 Subject: [PATCH 015/222] Added searchbar for choose_group_view --- .../create_game/choose_group_view.dart | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_group_view.dart b/lib/presentation/views/main_menu/create_game/choose_group_view.dart index de512bf..f805d6e 100644 --- a/lib/presentation/views/main_menu/create_game/choose_group_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_group_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; class ChooseGroupView extends StatefulWidget { @@ -19,6 +20,8 @@ class ChooseGroupView extends StatefulWidget { class _ChooseGroupViewState extends State { late int selectedGroupIndex; + final TextEditingController controller = TextEditingController(); + final String hintText = 'Group Name'; @override void initState() { @@ -49,26 +52,36 @@ class _ChooseGroupViewState extends State { ), centerTitle: true, ), - body: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: widget.groups.length, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - setState(() { - if (selectedGroupIndex == index) { - selectedGroupIndex = -1; - } else { - selectedGroupIndex = index; - } - }); - }, - child: GroupTile( - group: widget.groups[index], - isHighlighted: selectedGroupIndex == index, + body: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: CustomSearchBar(controller: controller, hintText: hintText), + ), + Expanded( + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: widget.groups.length, + itemBuilder: (BuildContext context, int index) { + return GestureDetector( + onTap: () { + setState(() { + if (selectedGroupIndex == index) { + selectedGroupIndex = -1; + } else { + selectedGroupIndex = index; + } + }); + }, + child: GroupTile( + group: widget.groups[index], + isHighlighted: selectedGroupIndex == index, + ), + ); + }, ), - ); - }, + ), + ], ), ); } From b4ba4f8d74bcb842bda144f5b90da00b407f43db Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 19:12:43 +0100 Subject: [PATCH 016/222] Implemented search bar in choose_group_view --- .../create_game/choose_group_view.dart | 56 ++++++++++++++----- .../create_game/create_game_view.dart | 8 +-- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_group_view.dart b/lib/presentation/views/main_menu/create_game/choose_group_view.dart index f805d6e..e27adf3 100644 --- a/lib/presentation/views/main_menu/create_game/choose_group_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_group_view.dart @@ -6,12 +6,12 @@ import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; class ChooseGroupView extends StatefulWidget { final List groups; - final int initialGroupIndex; + final String initialGroupId; const ChooseGroupView({ super.key, required this.groups, - required this.initialGroupIndex, + required this.initialGroupId, }); @override @@ -19,13 +19,15 @@ class ChooseGroupView extends StatefulWidget { } class _ChooseGroupViewState extends State { - late int selectedGroupIndex; + late String selectedGroupId; final TextEditingController controller = TextEditingController(); final String hintText = 'Group Name'; + late final List filteredGroups; @override void initState() { - selectedGroupIndex = widget.initialGroupIndex; + selectedGroupId = widget.initialGroupId; + filteredGroups = [...widget.groups]; super.initState(); } @@ -40,9 +42,11 @@ class _ChooseGroupViewState extends State { icon: const Icon(Icons.arrow_back_ios), onPressed: () { Navigator.of(context).pop( - selectedGroupIndex == -1 + selectedGroupId == '' ? null - : widget.groups[selectedGroupIndex], + : widget.groups.firstWhere( + (group) => group.id == selectedGroupId, + ), ); }, ), @@ -56,26 +60,34 @@ class _ChooseGroupViewState extends State { children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 10), - child: CustomSearchBar(controller: controller, hintText: hintText), + child: CustomSearchBar( + controller: controller, + hintText: hintText, + onChanged: (value) { + setState(() { + filterGroups(value); + }); + }, + ), ), Expanded( child: ListView.builder( padding: const EdgeInsets.only(bottom: 85), - itemCount: widget.groups.length, + itemCount: filteredGroups.length, itemBuilder: (BuildContext context, int index) { return GestureDetector( onTap: () { setState(() { - if (selectedGroupIndex == index) { - selectedGroupIndex = -1; + if (selectedGroupId != filteredGroups[index].id) { + selectedGroupId = filteredGroups[index].id; } else { - selectedGroupIndex = index; + selectedGroupId = ''; } }); }, child: GroupTile( - group: widget.groups[index], - isHighlighted: selectedGroupIndex == index, + group: filteredGroups[index], + isHighlighted: selectedGroupId == filteredGroups[index].id, ), ); }, @@ -85,4 +97,22 @@ class _ChooseGroupViewState extends State { ), ); } + + /// Filters the groups based on the search query. + /// TODO: Maybe implement also targetting player names? + void filterGroups(String query) { + setState(() { + if (query.isEmpty) { + filteredGroups.clear(); + filteredGroups.addAll(widget.groups); + } else { + filteredGroups.clear(); + filteredGroups.addAll( + widget.groups.where( + (group) => group.name.toLowerCase().contains(query.toLowerCase()), + ), + ); + } + }); + } } diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 4bf05e7..fdae05c 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -47,7 +47,7 @@ class _CreateGameViewState extends State { /// The index of the currently selected group in [groupsList] to mark it in /// the [ChooseGroupView] - int selectedGroupIndex = -1; + String selectedGroupId = ''; /// The currently selected ruleset Ruleset? selectedRuleset; @@ -189,13 +189,11 @@ class _CreateGameViewState extends State { MaterialPageRoute( builder: (context) => ChooseGroupView( groups: groupsList, - initialGroupIndex: selectedGroupIndex, + initialGroupId: selectedGroupId, ), ), ); - selectedGroupIndex = groupsList.indexWhere( - (g) => g.id == selectedGroup?.id, - ); + selectedGroupId = selectedGroup?.id ?? ''; setState(() {}); }, ), From 6c0cb92e567d6ccb9a42326153c9d64fe658fe7e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 19:15:49 +0100 Subject: [PATCH 017/222] Removed unneccesary title in tupe --- .../main_menu/create_game/choose_ruleset_view.dart | 6 +++--- .../views/main_menu/create_game/create_game_view.dart | 10 +++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart index 13a9647..537f749 100644 --- a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart @@ -4,7 +4,7 @@ import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; class ChooseRulesetView extends StatefulWidget { - final List<(Ruleset, String, String)> rulesets; + final List<(Ruleset, String)> rulesets; final int initialRulesetIndex; const ChooseRulesetView({ @@ -66,8 +66,8 @@ class _ChooseRulesetViewState extends State { } }); }, - title: widget.rulesets[index].$2, - description: widget.rulesets[index].$3, + title: translateRulesetToString(widget.rulesets[index].$1), + description: widget.rulesets[index].$2, isHighlighted: selectedRulesetIndex == index, ); }, diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index fdae05c..0a6a505 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -61,28 +61,24 @@ class _CreateGameViewState extends State { /// The currently selected players List? selectedPlayers; - /// List of available rulesets with their display names and descriptions - /// as tuples of (Ruleset, String, String) + /// List of available rulesets with their descriptions + /// as tuples of (Ruleset, String) /// TODO: Replace when rulesets are implemented - List<(Ruleset, String, String)> rulesets = [ + List<(Ruleset, String)> rulesets = [ ( Ruleset.singleWinner, - 'Single Winner', 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.', ), ( Ruleset.singleLoser, - 'Single Loser', 'Exactly one loser is determined; last place receives the penalty or consequence.', ), ( Ruleset.mostPoints, - 'Most Points', 'Traditional ruleset: the player with the most points wins.', ), ( Ruleset.leastPoints, - 'Least Points', 'Inverse scoring: the player with the fewest points wins.', ), ]; From c214a26c5401e25353b9dacaca497ea86f6e4800 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 19:17:20 +0100 Subject: [PATCH 018/222] Removed onChanged --- .../views/main_menu/create_game/create_game_view.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 0a6a505..2d227d1 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -125,9 +125,6 @@ class _CreateGameViewState extends State { child: TextInputField( controller: _gameNameController, hintText: 'Game name', - onChanged: (value) { - setState(() {}); - }, ), ), ChooseTile( From 45abc79f9515a71e0b63f998b08feb96160f65f3 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 19:17:56 +0100 Subject: [PATCH 019/222] Removed unneccesary db declaration --- .../views/main_menu/create_game/create_game_view.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 2d227d1..4e7c227 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -221,10 +221,6 @@ class _CreateGameViewState extends State { group: selectedGroup!, players: selectedPlayers, ); - final db = Provider.of( - context, - listen: false, - ); await db.gameDao.addGame(game: game); if (context.mounted) { Navigator.pushReplacement( From a1ed17355a2a1581a784985044774ffd9d742d2e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 19:23:38 +0100 Subject: [PATCH 020/222] Minimum of 2 players required to create a game, unless group is selected --- .../views/main_menu/create_game/create_game_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 4e7c227..59e3c0d 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -245,7 +245,7 @@ class _CreateGameViewState extends State { bool _enableCreateGameButton() { return _gameNameController.text.isNotEmpty && (selectedGroup != null || - (selectedPlayers != null && selectedPlayers!.isNotEmpty)) && + (selectedPlayers != null && selectedPlayers!.length > 1)) && selectedRuleset != null; } } From 7bc75d60a72c4a25e05d791e742f400e42951586 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 19:31:11 +0100 Subject: [PATCH 021/222] Fixed text overflow --- .../tiles/title_description_list_tile.dart | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/presentation/widgets/tiles/title_description_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart index 2d517f2..7a138a0 100644 --- a/lib/presentation/widgets/tiles/title_description_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -38,17 +38,23 @@ class TitleDescriptionListTile extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text( - title, - overflow: TextOverflow.ellipsis, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, + SizedBox( + width: 230, + child: Text( + title, + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 18, + ), ), ), if (badgeText != null) ...[ const Spacer(), Container( + constraints: const BoxConstraints(maxWidth: 100), margin: const EdgeInsets.only(top: 4), padding: const EdgeInsets.symmetric( vertical: 2, @@ -60,6 +66,9 @@ class TitleDescriptionListTile extends StatelessWidget { ), child: Text( badgeText!, + overflow: TextOverflow.ellipsis, + maxLines: 1, + softWrap: false, style: const TextStyle( color: Colors.white, fontSize: 12, From f1f3fd7b6ed0f564a2a5aa07b17726fccb266b5f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 22:03:28 +0100 Subject: [PATCH 022/222] Fixed deselecting game bug --- .../views/main_menu/create_game/create_game_view.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 59e3c0d..7a7d904 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -141,14 +141,17 @@ class _CreateGameViewState extends State { ), ), ); - if (selectedGameIndex != -1) { - setState(() { + setState(() { + if (selectedGameIndex != -1) { + print('selectedGameIndex: $selectedGameIndex'); selectedRuleset = games[selectedGameIndex].$3; selectedRulesetIndex = rulesets.indexWhere( (r) => r.$1 == selectedRuleset, ); - }); - } + } else { + selectedRuleset = null; + } + }); }, ), ChooseTile( From 708157df54232352ac41384a8a3d2ffb8651e03d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 22:15:19 +0100 Subject: [PATCH 023/222] Implemented leaving players in the player selection and filtering those who are in the group --- .../create_game/create_game_view.dart | 3 ++- .../widgets/player_selection.dart | 21 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 7a7d904..6643a52 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -196,7 +196,8 @@ class _CreateGameViewState extends State { Expanded( child: PlayerSelection( key: ValueKey(selectedGroup?.id ?? 'no_group'), - initialPlayers: selectedGroup == null + initialSelectedPlayers: selectedPlayers ?? [], + availablePlayers: selectedGroup == null ? playerList : playerList .where( diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 092a613..d48e241 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -11,12 +11,14 @@ import 'package:provider/provider.dart'; class PlayerSelection extends StatefulWidget { final Function(List value) onChanged; - final List initialPlayers; + final List availablePlayers; + final List? initialSelectedPlayers; const PlayerSelection({ super.key, required this.onChanged, - this.initialPlayers = const [], + this.availablePlayers = const [], + this.initialSelectedPlayers, }); @override @@ -51,10 +53,19 @@ class _PlayerSelectionState extends State { suggestedPlayers = skeletonData; _allPlayersFuture.then((loadedPlayers) { setState(() { - if (widget.initialPlayers.isNotEmpty) { - allPlayers = [...widget.initialPlayers]; - suggestedPlayers = [...widget.initialPlayers]; + // If a list of available players is provided, use that list. + if (widget.availablePlayers.isNotEmpty) { + allPlayers = [...widget.availablePlayers]; + suggestedPlayers = [...widget.availablePlayers]; + + if (widget.initialSelectedPlayers != null) { + // Ensures that only players available for selection are pre-selected. + selectedPlayers = widget.initialSelectedPlayers! + .where((p) => widget.availablePlayers.contains(p)) + .toList(); + } } else { + // Otherwise, use the loaded players from the database. loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); allPlayers = [...loadedPlayers]; suggestedPlayers = [...loadedPlayers]; From 7cff48ebc07429c0805cb3a75bbf0c50824b4f93 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 7 Dec 2025 22:23:43 +0100 Subject: [PATCH 024/222] Added empty message --- .../create_game/choose_group_view.dart | 50 +++++++++++-------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_group_view.dart b/lib/presentation/views/main_menu/create_game/choose_group_view.dart index e27adf3..4e81a86 100644 --- a/lib/presentation/views/main_menu/create_game/choose_group_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_group_view.dart @@ -3,6 +3,7 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; class ChooseGroupView extends StatefulWidget { final List groups; @@ -71,26 +72,35 @@ class _ChooseGroupViewState extends State { ), ), Expanded( - child: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: filteredGroups.length, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - setState(() { - if (selectedGroupId != filteredGroups[index].id) { - selectedGroupId = filteredGroups[index].id; - } else { - selectedGroupId = ''; - } - }); - }, - child: GroupTile( - group: filteredGroups[index], - isHighlighted: selectedGroupId == filteredGroups[index].id, - ), - ); - }, + child: Visibility( + visible: filteredGroups.isNotEmpty, + replacement: const TopCenteredMessage( + icon: Icons.info, + title: 'No group', + message: 'There is no group matching your search.', + ), + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: filteredGroups.length, + itemBuilder: (BuildContext context, int index) { + return GestureDetector( + onTap: () { + setState(() { + if (selectedGroupId != filteredGroups[index].id) { + selectedGroupId = filteredGroups[index].id; + } else { + selectedGroupId = ''; + } + }); + }, + child: GroupTile( + group: filteredGroups[index], + isHighlighted: + selectedGroupId == filteredGroups[index].id, + ), + ); + }, + ), ), ), ], From 062c2681bf0216f942ac807fe085e694e55f9012 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 9 Dec 2025 18:10:31 +0100 Subject: [PATCH 025/222] Corrected info message --- .../views/main_menu/create_game/choose_group_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/choose_group_view.dart b/lib/presentation/views/main_menu/create_game/choose_group_view.dart index 4e81a86..c30d6ef 100644 --- a/lib/presentation/views/main_menu/create_game/choose_group_view.dart +++ b/lib/presentation/views/main_menu/create_game/choose_group_view.dart @@ -76,8 +76,8 @@ class _ChooseGroupViewState extends State { visible: filteredGroups.isNotEmpty, replacement: const TopCenteredMessage( icon: Icons.info, - title: 'No group', - message: 'There is no group matching your search.', + title: 'Info', + message: 'There is no group matching your search', ), child: ListView.builder( padding: const EdgeInsets.only(bottom: 85), From 701500c7e2db2d358da6dcdc25b5a84460851f1e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 9 Dec 2025 18:26:05 +0100 Subject: [PATCH 026/222] Fixed state problem with games list --- .../create_game/create_game_view.dart | 8 ++++-- .../views/main_menu/game_history_view.dart | 27 ++++++++++--------- .../views/main_menu/game_result_view.dart | 4 ++- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 6643a52..8b3271c 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -17,7 +17,8 @@ import 'package:game_tracker/presentation/widgets/tiles/choose_tile.dart'; import 'package:provider/provider.dart'; class CreateGameView extends StatefulWidget { - const CreateGameView({super.key}); + final VoidCallback? onWinnerChanged; + const CreateGameView({super.key, this.onWinnerChanged}); @override State createState() => _CreateGameViewState(); @@ -231,7 +232,10 @@ class _CreateGameViewState extends State { context, CupertinoPageRoute( fullscreenDialog: true, - builder: (context) => GameResultView(game: game), + builder: (context) => GameResultView( + game: game, + onWinnerChanged: widget.onWinnerChanged, + ), ), ); } diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/game_history_view.dart index 5d52ac8..46cb1db 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/game_history_view.dart @@ -98,17 +98,16 @@ class _GameHistoryViewState extends State { } return GameHistoryTile( onTap: () async { - await Navigator.push( + Navigator.push( context, CupertinoPageRoute( fullscreenDialog: true, - builder: (context) => - GameResultView(game: games[index]), + builder: (context) => GameResultView( + game: games[index], + onWinnerChanged: refreshGameList, + ), ), ); - setState(() { - _gameListFuture = db.gameDao.getAllGames(); - }); }, game: games[index], ); @@ -123,17 +122,13 @@ class _GameHistoryViewState extends State { text: 'Create Game', sizeRelativeToWidth: 0.90, onPressed: () async { - await Navigator.push( + Navigator.push( context, MaterialPageRoute( - builder: (context) { - return const CreateGameView(); - }, + builder: (context) => + CreateGameView(onWinnerChanged: refreshGameList), ), ); - setState(() { - _gameListFuture = db.gameDao.getAllGames(); - }); }, ), ), @@ -141,4 +136,10 @@ class _GameHistoryViewState extends State { ), ); } + + void refreshGameList() { + setState(() { + _gameListFuture = db.gameDao.getAllGames(); + }); + } } diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/game_result_view.dart index f13553b..6e60410 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/game_result_view.dart @@ -9,8 +9,9 @@ import 'package:provider/provider.dart'; class GameResultView extends StatefulWidget { final Game game; - const GameResultView({super.key, required this.game}); + final VoidCallback? onWinnerChanged; + const GameResultView({super.key, required this.game, this.onWinnerChanged}); @override State createState() => _GameResultViewState(); } @@ -131,6 +132,7 @@ class _GameResultViewState extends State { winnerId: _selectedPlayer!.id, ); } + widget.onWinnerChanged?.call(); } List getAllPlayers(Game game) { From c4094a547e40de2989d713544a01f7a6e52a0d00 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 9 Dec 2025 20:52:14 +0100 Subject: [PATCH 027/222] Fixed button state problem --- .../views/main_menu/create_game/create_game_view.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 8b3271c..b9a8141 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -126,6 +126,11 @@ class _CreateGameViewState extends State { child: TextInputField( controller: _gameNameController, hintText: 'Game name', + onChanged: (value) { + setState(() { + _gameNameController; + }); + }, ), ), ChooseTile( @@ -144,7 +149,6 @@ class _CreateGameViewState extends State { ); setState(() { if (selectedGameIndex != -1) { - print('selectedGameIndex: $selectedGameIndex'); selectedRuleset = games[selectedGameIndex].$3; selectedRulesetIndex = rulesets.indexWhere( (r) => r.$1 == selectedRuleset, @@ -251,9 +255,12 @@ class _CreateGameViewState extends State { /// Determines whether the "Create Game" button should be enabled based on /// the current state of the input fields. bool _enableCreateGameButton() { - return _gameNameController.text.isNotEmpty && + final value = + _gameNameController.text.isNotEmpty && (selectedGroup != null || (selectedPlayers != null && selectedPlayers!.length > 1)) && selectedRuleset != null; + print('button: $value'); + return value; } } From 27ff599a888fa8ae38e10e1ba7fa6969e4748fde Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 9 Dec 2025 20:52:38 +0100 Subject: [PATCH 028/222] Reverted method --- .../views/main_menu/create_game/create_game_view.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index b9a8141..d7e89d6 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -255,12 +255,9 @@ class _CreateGameViewState extends State { /// Determines whether the "Create Game" button should be enabled based on /// the current state of the input fields. bool _enableCreateGameButton() { - final value = - _gameNameController.text.isNotEmpty && + return _gameNameController.text.isNotEmpty && (selectedGroup != null || (selectedPlayers != null && selectedPlayers!.length > 1)) && selectedRuleset != null; - print('button: $value'); - return value; } } From 0d0806dfbb6b90fdb7b580cfd9d36f68e28694c3 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 9 Dec 2025 22:25:45 +0100 Subject: [PATCH 029/222] Implemented listener --- .../views/main_menu/create_game/create_game_view.dart | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index d7e89d6..427ebf2 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -93,6 +93,10 @@ class _CreateGameViewState extends State { @override void initState() { super.initState(); + _gameNameController.addListener(() { + setState(() {}); + }); + db = Provider.of(context, listen: false); _allGroupsFuture = db.groupDao.getAllGroups(); @@ -126,11 +130,6 @@ class _CreateGameViewState extends State { child: TextInputField( controller: _gameNameController, hintText: 'Game name', - onChanged: (value) { - setState(() { - _gameNameController; - }); - }, ), ), ChooseTile( From 5d2fed74ac345a3e360d68021af8aa6a3cebc152 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 9 Dec 2025 23:59:34 +0100 Subject: [PATCH 030/222] Fixed sorting problem --- lib/presentation/widgets/player_selection.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index d48e241..8007d39 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -55,8 +55,9 @@ class _PlayerSelectionState extends State { setState(() { // If a list of available players is provided, use that list. if (widget.availablePlayers.isNotEmpty) { + widget.availablePlayers.sort((a, b) => a.name.compareTo(b.name)); allPlayers = [...widget.availablePlayers]; - suggestedPlayers = [...widget.availablePlayers]; + suggestedPlayers = [...allPlayers]; if (widget.initialSelectedPlayers != null) { // Ensures that only players available for selection are pre-selected. From 23cdddfbd96d495c2d71afc405aa2b34d5dbe2d1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 10 Dec 2025 12:38:39 +0100 Subject: [PATCH 031/222] Fixed problem with player selection --- .../create_game/create_game_view.dart | 43 +++++++++++-------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 427ebf2..b1165b5 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -197,25 +197,30 @@ class _CreateGameViewState extends State { setState(() {}); }, ), - Expanded( - child: PlayerSelection( - key: ValueKey(selectedGroup?.id ?? 'no_group'), - initialSelectedPlayers: selectedPlayers ?? [], - availablePlayers: selectedGroup == null - ? playerList - : playerList - .where( - (p) => !selectedGroup!.members.any( - (m) => m.id == p.id, - ), - ) - .toList(), - onChanged: (value) { - setState(() { - selectedPlayers = value; - }); - }, - ), + FutureBuilder( + future: _allPlayersFuture, + builder: + (BuildContext context, AsyncSnapshot snapshot) => + Expanded( + child: PlayerSelection( + key: ValueKey(selectedGroup?.id ?? 'no_group'), + initialSelectedPlayers: selectedPlayers ?? [], + availablePlayers: selectedGroup == null + ? playerList + : playerList + .where( + (p) => !selectedGroup!.members.any( + (m) => m.id == p.id, + ), + ) + .toList(), + onChanged: (value) { + setState(() { + selectedPlayers = value; + }); + }, + ), + ), ), CustomWidthButton( text: 'Create game', From 93ced81e7e5efdbac0cc2a7fca70cf817339b9f0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 10 Dec 2025 13:55:31 +0100 Subject: [PATCH 032/222] Fixed filtering problem with object reference --- lib/presentation/widgets/player_selection.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 8007d39..e2114b2 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -62,7 +62,11 @@ class _PlayerSelectionState extends State { if (widget.initialSelectedPlayers != null) { // Ensures that only players available for selection are pre-selected. selectedPlayers = widget.initialSelectedPlayers! - .where((p) => widget.availablePlayers.contains(p)) + .where( + (p) => widget.availablePlayers.any( + (available) => available.id == p.id, + ), + ) .toList(); } } else { From e1626225ac57e526b0ade59b792b5e562c564b45 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 10 Dec 2025 13:56:03 +0100 Subject: [PATCH 033/222] Removed future builder --- .../create_game/create_game_view.dart | 43 ++++++++----------- 1 file changed, 19 insertions(+), 24 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index b1165b5..427ebf2 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -197,30 +197,25 @@ class _CreateGameViewState extends State { setState(() {}); }, ), - FutureBuilder( - future: _allPlayersFuture, - builder: - (BuildContext context, AsyncSnapshot snapshot) => - Expanded( - child: PlayerSelection( - key: ValueKey(selectedGroup?.id ?? 'no_group'), - initialSelectedPlayers: selectedPlayers ?? [], - availablePlayers: selectedGroup == null - ? playerList - : playerList - .where( - (p) => !selectedGroup!.members.any( - (m) => m.id == p.id, - ), - ) - .toList(), - onChanged: (value) { - setState(() { - selectedPlayers = value; - }); - }, - ), - ), + Expanded( + child: PlayerSelection( + key: ValueKey(selectedGroup?.id ?? 'no_group'), + initialSelectedPlayers: selectedPlayers ?? [], + availablePlayers: selectedGroup == null + ? playerList + : playerList + .where( + (p) => !selectedGroup!.members.any( + (m) => m.id == p.id, + ), + ) + .toList(), + onChanged: (value) { + setState(() { + selectedPlayers = value; + }); + }, + ), ), CustomWidthButton( text: 'Create game', From 3b3d298ff55118b2bb7df3826d9535eb72c17455 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 10 Dec 2025 14:04:15 +0100 Subject: [PATCH 034/222] Small refactoring --- .../create_game/create_game_view.dart | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 427ebf2..8b6f516 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -43,6 +43,12 @@ class _CreateGameViewState extends State { /// List of all players from the database List playerList = []; + /// List of players filtered based on the selected group + /// If a group is selected, this list contains all players from [playerList] + /// who are not members of the selected group. If no group is selected, + /// this list is identical to [playerList]. + List filteredPlayerList = []; + /// The currently selected group Group? selectedGroup; @@ -57,6 +63,8 @@ class _CreateGameViewState extends State { /// the [ChooseRulesetView] int selectedRulesetIndex = -1; + /// The index of the currently selected game in [games] to mark it in + /// the [ChooseGameView] int selectedGameIndex = -1; /// The currently selected players @@ -105,6 +113,7 @@ class _CreateGameViewState extends State { Future.wait([_allGroupsFuture, _allPlayersFuture]).then((result) async { groupsList = result[0] as List; playerList = result[1] as List; + filteredPlayerList = List.from(playerList); }); } @@ -194,6 +203,15 @@ class _CreateGameViewState extends State { ), ); selectedGroupId = selectedGroup?.id ?? ''; + if (selectedGroup != null) { + filteredPlayerList = playerList + .where( + (p) => !selectedGroup!.members.any((m) => m.id == p.id), + ) + .toList(); + } else { + filteredPlayerList = List.from(playerList); + } setState(() {}); }, ), @@ -201,15 +219,7 @@ class _CreateGameViewState extends State { child: PlayerSelection( key: ValueKey(selectedGroup?.id ?? 'no_group'), initialSelectedPlayers: selectedPlayers ?? [], - availablePlayers: selectedGroup == null - ? playerList - : playerList - .where( - (p) => !selectedGroup!.members.any( - (m) => m.id == p.id, - ), - ) - .toList(), + availablePlayers: filteredPlayerList, onChanged: (value) { setState(() { selectedPlayers = value; @@ -226,7 +236,7 @@ class _CreateGameViewState extends State { Game game = Game( name: _gameNameController.text.trim(), createdAt: DateTime.now(), - group: selectedGroup!, + group: selectedGroup, players: selectedPlayers, ); await db.gameDao.addGame(game: game); From d0059b44a8c62928e03c3691a01081b2c986cba3 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 10 Dec 2025 21:41:37 +0100 Subject: [PATCH 035/222] Moved statement --- .../views/main_menu/create_game/create_game_view.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/create_game/create_game_view.dart index 8b6f516..eef2de6 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/create_game/create_game_view.dart @@ -113,8 +113,9 @@ class _CreateGameViewState extends State { Future.wait([_allGroupsFuture, _allPlayersFuture]).then((result) async { groupsList = result[0] as List; playerList = result[1] as List; - filteredPlayerList = List.from(playerList); }); + + filteredPlayerList = List.from(playerList); } @override From 99cea1e70318a6cf2be25b0ea41a3a8a64f63da1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 11 Dec 2025 20:07:32 +0100 Subject: [PATCH 036/222] Renamed every instance of "game" to "match" --- lib/core/enums.dart | 6 +- lib/data/dao/game_dao.dart | 320 ----- lib/data/dao/game_dao.g.dart | 8 - lib/data/dao/group_dao.dart | 3 +- lib/data/dao/group_dao.g.dart | 3 + lib/data/dao/group_game_dao.dart | 98 -- lib/data/dao/group_game_dao.g.dart | 10 - lib/data/dao/group_match_dao.dart | 98 ++ lib/data/dao/group_match_dao.g.dart | 10 + lib/data/dao/match_dao.dart | 322 +++++ lib/data/dao/match_dao.g.dart | 8 + lib/data/dao/player_game_dao.g.dart | 10 - lib/data/dao/player_group_dao.dart | 3 +- ...er_game_dao.dart => player_match_dao.dart} | 90 +- lib/data/dao/player_match_dao.g.dart | 11 + lib/data/db/database.dart | 24 +- lib/data/db/database.g.dart | 1064 +++++++++-------- lib/data/db/tables/group_game_table.dart | 13 - lib/data/db/tables/group_match_table.dart | 13 + .../{game_table.dart => match_table.dart} | 2 +- lib/data/db/tables/player_game_table.dart | 13 - lib/data/db/tables/player_match_table.dart | 13 + lib/data/dto/{game.dart => match.dart} | 12 +- .../main_menu/custom_navigation_bar.dart | 12 +- .../{ => group_view}/create_group_view.dart | 0 .../{ => group_view}/groups_view.dart | 4 +- .../views/main_menu/home_view.dart | 76 +- .../create_match}/choose_game_view.dart | 0 .../create_match}/choose_group_view.dart | 0 .../create_match}/choose_ruleset_view.dart | 0 .../create_match/create_match_view.dart} | 24 +- .../match_result_view.dart} | 22 +- .../match_view.dart} | 42 +- .../views/main_menu/statistics_view.dart | 78 +- .../widgets/tiles/game_history_tile.dart | 22 +- lib/presentation/widgets/tiles/game_tile.dart | 20 +- .../widgets/tiles/statistics_tile.dart | 6 +- lib/services/data_transfer_service.dart | 19 +- test/db_tests/game_test.dart | 312 ++--- test/db_tests/group_game_test.dart | 91 +- test/db_tests/player_game_test.dart | 102 +- 41 files changed, 1525 insertions(+), 1459 deletions(-) delete mode 100644 lib/data/dao/game_dao.dart delete mode 100644 lib/data/dao/game_dao.g.dart delete mode 100644 lib/data/dao/group_game_dao.dart delete mode 100644 lib/data/dao/group_game_dao.g.dart create mode 100644 lib/data/dao/group_match_dao.dart create mode 100644 lib/data/dao/group_match_dao.g.dart create mode 100644 lib/data/dao/match_dao.dart create mode 100644 lib/data/dao/match_dao.g.dart delete mode 100644 lib/data/dao/player_game_dao.g.dart rename lib/data/dao/{player_game_dao.dart => player_match_dao.dart} (53%) create mode 100644 lib/data/dao/player_match_dao.g.dart delete mode 100644 lib/data/db/tables/group_game_table.dart create mode 100644 lib/data/db/tables/group_match_table.dart rename lib/data/db/tables/{game_table.dart => match_table.dart} (88%) delete mode 100644 lib/data/db/tables/player_game_table.dart create mode 100644 lib/data/db/tables/player_match_table.dart rename lib/data/dto/{game.dart => match.dart} (81%) rename lib/presentation/views/main_menu/{ => group_view}/create_group_view.dart (100%) rename lib/presentation/views/main_menu/{ => group_view}/groups_view.dart (97%) rename lib/presentation/views/main_menu/{create_game => match_view/create_match}/choose_game_view.dart (100%) rename lib/presentation/views/main_menu/{create_game => match_view/create_match}/choose_group_view.dart (100%) rename lib/presentation/views/main_menu/{create_game => match_view/create_match}/choose_ruleset_view.dart (100%) rename lib/presentation/views/main_menu/{create_game/create_game_view.dart => match_view/create_match/create_match_view.dart} (91%) rename lib/presentation/views/main_menu/{game_result_view.dart => match_view/match_result_view.dart} (90%) rename lib/presentation/views/main_menu/{game_history_view.dart => match_view/match_view.dart} (79%) diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 68752fb..737882e 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -23,9 +23,9 @@ enum ImportResult { /// - [ExportResult.unknownException]: An exception occurred during export. enum ExportResult { success, canceled, unknownException } -/// Different rulesets available for games -/// - [Ruleset.singleWinner]: The game is won by a single player -/// - [Ruleset.singleLoser]: The game is lost by a single player +/// Different rulesets available for matches +/// - [Ruleset.singleWinner]: The match is won by a single player +/// - [Ruleset.singleLoser]: The match is lost by a single player /// - [Ruleset.mostPoints]: The player with the most points wins. /// - [Ruleset.leastPoints]: The player with the fewest points wins. enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints } diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart deleted file mode 100644 index c941499..0000000 --- a/lib/data/dao/game_dao.dart +++ /dev/null @@ -1,320 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/db/tables/game_table.dart'; -import 'package:game_tracker/data/dto/game.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/player.dart'; - -part 'game_dao.g.dart'; - -@DriftAccessor(tables: [GameTable]) -class GameDao extends DatabaseAccessor with _$GameDaoMixin { - GameDao(super.db); - - /// Retrieves all games from the database. - Future> getAllGames() async { - final query = select(gameTable); - final result = await query.get(); - - return Future.wait( - result.map((row) async { - final group = await db.groupGameDao.getGroupOfGame(gameId: row.id); - final players = await db.playerGameDao.getPlayersOfGame(gameId: row.id); - final winner = row.winnerId != null - ? await db.playerDao.getPlayerById(playerId: row.winnerId!) - : null; - return Game( - id: row.id, - name: row.name, - group: group, - players: players, - createdAt: row.createdAt, - winner: winner, - ); - }), - ); - } - - /// Retrieves a [Game] by its [gameId]. - Future getGameById({required String gameId}) async { - final query = select(gameTable)..where((g) => g.id.equals(gameId)); - final result = await query.getSingle(); - - List? players; - if (await db.playerGameDao.gameHasPlayers(gameId: gameId)) { - players = await db.playerGameDao.getPlayersOfGame(gameId: gameId); - } - Group? group; - if (await db.groupGameDao.gameHasGroup(gameId: gameId)) { - group = await db.groupGameDao.getGroupOfGame(gameId: gameId); - } - Player? winner; - if (result.winnerId != null) { - winner = await db.playerDao.getPlayerById(playerId: result.winnerId!); - } - - return Game( - id: result.id, - name: result.name, - players: players, - group: group, - winner: winner, - createdAt: result.createdAt, - ); - } - - /// Adds a new [Game] to the database. - /// Also adds associated players and group if they exist. - Future addGame({required Game game}) async { - await db.transaction(() async { - await into(gameTable).insert( - GameTableCompanion.insert( - id: game.id, - name: game.name, - winnerId: Value(game.winner?.id), - createdAt: game.createdAt, - ), - mode: InsertMode.insertOrReplace, - ); - - if (game.players != null) { - await db.playerDao.addPlayersAsList(players: game.players!); - for (final p in game.players ?? []) { - await db.playerGameDao.addPlayerToGame( - gameId: game.id, - playerId: p.id, - ); - } - } - - if (game.group != null) { - await db.groupDao.addGroup(group: game.group!); - await db.groupGameDao.addGroupToGame( - gameId: game.id, - groupId: game.group!.id, - ); - } - }); - } - - /// Adds multiple [Game]s to the database in a batch operation. - /// Also adds associated players and groups if they exist. - /// If the [games] list is empty, the method returns immediately. - Future addGamesAsList({required List games}) async { - if (games.isEmpty) return; - await db.transaction(() async { - // Add all games in batch - await db.batch( - (b) => b.insertAll( - gameTable, - games - .map( - (game) => GameTableCompanion.insert( - id: game.id, - name: game.name, - createdAt: game.createdAt, - winnerId: Value(game.winner?.id), - ), - ) - .toList(), - mode: InsertMode.insertOrReplace, - ), - ); - - // Add all groups of the games in batch - await db.batch( - (b) => b.insertAll( - db.groupTable, - games - .where((game) => game.group != null) - .map( - (game) => GroupTableCompanion.insert( - id: game.group!.id, - name: game.group!.name, - createdAt: game.group!.createdAt, - ), - ) - .toList(), - mode: InsertMode.insertOrReplace, - ), - ); - - // Add all players of the games in batch (unique) - final uniquePlayers = {}; - for (final game in games) { - if (game.players != null) { - for (final p in game.players!) { - uniquePlayers[p.id] = p; - } - } - // Also include members of groups - if (game.group != null) { - for (final m in game.group!.members) { - uniquePlayers[m.id] = m; - } - } - } - - if (uniquePlayers.isNotEmpty) { - await db.batch( - (b) => b.insertAll( - db.playerTable, - uniquePlayers.values - .map( - (p) => PlayerTableCompanion.insert( - id: p.id, - name: p.name, - createdAt: p.createdAt, - ), - ) - .toList(), - mode: InsertMode.insertOrReplace, - ), - ); - } - - // Add all player-game associations in batch - await db.batch((b) { - for (final game in games) { - if (game.players != null) { - for (final p in game.players ?? []) { - b.insert( - db.playerGameTable, - PlayerGameTableCompanion.insert( - gameId: game.id, - playerId: p.id, - ), - mode: InsertMode.insertOrReplace, - ); - } - } - } - }); - - // Add all player-group associations in batch - await db.batch((b) { - for (final game in games) { - if (game.group != null) { - for (final m in game.group!.members) { - b.insert( - db.playerGroupTable, - PlayerGroupTableCompanion.insert( - playerId: m.id, - groupId: game.group!.id, - ), - mode: InsertMode.insertOrReplace, - ); - } - } - } - }); - - // Add all group-game associations in batch - await db.batch((b) { - for (final game in games) { - if (game.group != null) { - b.insert( - db.groupGameTable, - GroupGameTableCompanion.insert( - gameId: game.id, - groupId: game.group!.id, - ), - mode: InsertMode.insertOrReplace, - ); - } - } - }); - }); - } - - /// Deletes the game with the given [gameId] from the database. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future deleteGame({required String gameId}) async { - final query = delete(gameTable)..where((g) => g.id.equals(gameId)); - final rowsAffected = await query.go(); - return rowsAffected > 0; - } - - /// Retrieves the number of games in the database. - Future getGameCount() async { - final count = - await (selectOnly(gameTable)..addColumns([gameTable.id.count()])) - .map((row) => row.read(gameTable.id.count())) - .getSingle(); - return count ?? 0; - } - - /// Checks if a game with the given [gameId] exists in the database. - /// Returns `true` if the game exists, otherwise `false`. - Future gameExists({required String gameId}) async { - final query = select(gameTable)..where((g) => g.id.equals(gameId)); - final result = await query.getSingleOrNull(); - return result != null; - } - - /// Deletes all games from the database. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future deleteAllGames() async { - final query = delete(gameTable); - final rowsAffected = await query.go(); - return rowsAffected > 0; - } - - /// Sets the winner of the game with the given [gameId] to the player with - /// the given [winnerId]. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future setWinner({ - required String gameId, - required String winnerId, - }) async { - final query = update(gameTable)..where((g) => g.id.equals(gameId)); - final rowsAffected = await query.write( - GameTableCompanion(winnerId: Value(winnerId)), - ); - return rowsAffected > 0; - } - - /// Retrieves the winner of the game with the given [gameId]. - /// Returns the [Player] who won the game, or `null` if no winner is set. - Future getWinner({required String gameId}) async { - final query = select(gameTable)..where((g) => g.id.equals(gameId)); - final result = await query.getSingleOrNull(); - if (result == null || result.winnerId == null) { - return null; - } - final winner = await db.playerDao.getPlayerById(playerId: result.winnerId!); - return winner; - } - - /// Removes the winner of the game with the given [gameId]. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future removeWinner({required String gameId}) async { - final query = update(gameTable)..where((g) => g.id.equals(gameId)); - final rowsAffected = await query.write( - const GameTableCompanion(winnerId: Value(null)), - ); - return rowsAffected > 0; - } - - /// Checks if the game with the given [gameId] has a winner set. - /// Returns `true` if a winner is set, otherwise `false`. - Future hasWinner({required String gameId}) async { - final query = select(gameTable) - ..where((g) => g.id.equals(gameId) & g.winnerId.isNotNull()); - final result = await query.getSingleOrNull(); - return result != null; - } - - /// Changes the title of the game with the given [gameId] to [newName]. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future updateGameName({ - required String gameId, - required String newName, - }) async { - final query = update(gameTable)..where((g) => g.id.equals(gameId)); - final rowsAffected = await query.write( - GameTableCompanion(name: Value(newName)), - ); - return rowsAffected > 0; - } -} diff --git a/lib/data/dao/game_dao.g.dart b/lib/data/dao/game_dao.g.dart deleted file mode 100644 index b5a29fe..0000000 --- a/lib/data/dao/game_dao.g.dart +++ /dev/null @@ -1,8 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'game_dao.dart'; - -// ignore_for_file: type=lint -mixin _$GameDaoMixin on DatabaseAccessor { - $GameTableTable get gameTable => attachedDatabase.gameTable; -} diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 643bc88..9802203 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -1,12 +1,13 @@ import 'package:drift/drift.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/tables/group_table.dart'; +import 'package:game_tracker/data/db/tables/player_group_table.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; part 'group_dao.g.dart'; -@DriftAccessor(tables: [GroupTable]) +@DriftAccessor(tables: [GroupTable, PlayerGroupTable]) class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { GroupDao(super.db); diff --git a/lib/data/dao/group_dao.g.dart b/lib/data/dao/group_dao.g.dart index 4a09208..b9534b4 100644 --- a/lib/data/dao/group_dao.g.dart +++ b/lib/data/dao/group_dao.g.dart @@ -5,4 +5,7 @@ part of 'group_dao.dart'; // ignore_for_file: type=lint mixin _$GroupDaoMixin on DatabaseAccessor { $GroupTableTable get groupTable => attachedDatabase.groupTable; + $PlayerTableTable get playerTable => attachedDatabase.playerTable; + $PlayerGroupTableTable get playerGroupTable => + attachedDatabase.playerGroupTable; } diff --git a/lib/data/dao/group_game_dao.dart b/lib/data/dao/group_game_dao.dart deleted file mode 100644 index da95607..0000000 --- a/lib/data/dao/group_game_dao.dart +++ /dev/null @@ -1,98 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/db/tables/group_game_table.dart'; -import 'package:game_tracker/data/dto/group.dart'; - -part 'group_game_dao.g.dart'; - -@DriftAccessor(tables: [GroupGameTable]) -class GroupGameDao extends DatabaseAccessor - with _$GroupGameDaoMixin { - GroupGameDao(super.db); - - /// Associates a group with a game by inserting a record into the - /// [GroupGameTable]. - Future addGroupToGame({ - required String gameId, - required String groupId, - }) async { - if (await gameHasGroup(gameId: gameId)) { - throw Exception('Game already has a group'); - } - await into(groupGameTable).insert( - GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId), - mode: InsertMode.insertOrReplace, - ); - } - - /// Retrieves the [Group] associated with the given [gameId]. - /// Returns `null` if no group is found. - Future getGroupOfGame({required String gameId}) async { - final result = await (select( - groupGameTable, - )..where((g) => g.gameId.equals(gameId))).getSingleOrNull(); - - if (result == null) { - return null; - } - - final group = await db.groupDao.getGroupById(groupId: result.groupId); - return group; - } - - /// Checks if there is a group associated with the given [gameId]. - /// Returns `true` if there is a group, otherwise `false`. - Future gameHasGroup({required String gameId}) async { - final count = - await (selectOnly(groupGameTable) - ..where(groupGameTable.gameId.equals(gameId)) - ..addColumns([groupGameTable.groupId.count()])) - .map((row) => row.read(groupGameTable.groupId.count())) - .getSingle(); - return (count ?? 0) > 0; - } - - /// Checks if a specific group is associated with a specific game. - /// Returns `true` if the group is in the game, otherwise `false`. - Future isGroupInGame({ - required String gameId, - required String groupId, - }) async { - final count = - await (selectOnly(groupGameTable) - ..where( - groupGameTable.gameId.equals(gameId) & - groupGameTable.groupId.equals(groupId), - ) - ..addColumns([groupGameTable.groupId.count()])) - .map((row) => row.read(groupGameTable.groupId.count())) - .getSingle(); - return (count ?? 0) > 0; - } - - /// Removes the association of a group from a game based on [groupId] and - /// [gameId]. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future removeGroupFromGame({ - required String gameId, - required String groupId, - }) async { - final query = delete(groupGameTable) - ..where((g) => g.gameId.equals(gameId) & g.groupId.equals(groupId)); - final rowsAffected = await query.go(); - return rowsAffected > 0; - } - - /// Updates the group associated with a game to [newGroupId] based on - /// [gameId]. - /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future updateGroupOfGame({ - required String gameId, - required String newGroupId, - }) async { - final updatedRows = - await (update(groupGameTable)..where((g) => g.gameId.equals(gameId))) - .write(GroupGameTableCompanion(groupId: Value(newGroupId))); - return updatedRows > 0; - } -} diff --git a/lib/data/dao/group_game_dao.g.dart b/lib/data/dao/group_game_dao.g.dart deleted file mode 100644 index 735a35f..0000000 --- a/lib/data/dao/group_game_dao.g.dart +++ /dev/null @@ -1,10 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'group_game_dao.dart'; - -// ignore_for_file: type=lint -mixin _$GroupGameDaoMixin on DatabaseAccessor { - $GroupTableTable get groupTable => attachedDatabase.groupTable; - $GameTableTable get gameTable => attachedDatabase.gameTable; - $GroupGameTableTable get groupGameTable => attachedDatabase.groupGameTable; -} diff --git a/lib/data/dao/group_match_dao.dart b/lib/data/dao/group_match_dao.dart new file mode 100644 index 0000000..3c16c83 --- /dev/null +++ b/lib/data/dao/group_match_dao.dart @@ -0,0 +1,98 @@ +import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/db/tables/group_match_table.dart'; +import 'package:game_tracker/data/dto/group.dart'; + +part 'group_match_dao.g.dart'; + +@DriftAccessor(tables: [GroupMatchTable]) +class GroupMatchDao extends DatabaseAccessor + with _$GroupMatchDaoMixin { + GroupMatchDao(super.db); + + /// Associates a group with a match by inserting a record into the + /// [GroupMatchTable]. + Future addGroupToMatch({ + required String matchId, + required String groupId, + }) async { + if (await matchHasGroup(matchId: matchId)) { + throw Exception('Match already has a group'); + } + await into(groupMatchTable).insert( + GroupMatchTableCompanion.insert(groupId: groupId, matchId: matchId), + mode: InsertMode.insertOrReplace, + ); + } + + /// Retrieves the [Group] associated with the given [matchId]. + /// Returns `null` if no group is found. + Future getGroupOfMatch({required String matchId}) async { + final result = await (select( + groupMatchTable, + )..where((g) => g.matchId.equals(matchId))).getSingleOrNull(); + + if (result == null) { + return null; + } + + final group = await db.groupDao.getGroupById(groupId: result.groupId); + return group; + } + + /// Checks if there is a group associated with the given [matchId]. + /// Returns `true` if there is a group, otherwise `false`. + Future matchHasGroup({required String matchId}) async { + final count = + await (selectOnly(groupMatchTable) + ..where(groupMatchTable.matchId.equals(matchId)) + ..addColumns([groupMatchTable.groupId.count()])) + .map((row) => row.read(groupMatchTable.groupId.count())) + .getSingle(); + return (count ?? 0) > 0; + } + + /// Checks if a specific group is associated with a specific match. + /// Returns `true` if the group is in the match, otherwise `false`. + Future isGroupInMatch({ + required String matchId, + required String groupId, + }) async { + final count = + await (selectOnly(groupMatchTable) + ..where( + groupMatchTable.matchId.equals(matchId) & + groupMatchTable.groupId.equals(groupId), + ) + ..addColumns([groupMatchTable.groupId.count()])) + .map((row) => row.read(groupMatchTable.groupId.count())) + .getSingle(); + return (count ?? 0) > 0; + } + + /// Removes the association of a group from a match based on [groupId] and + /// [matchId]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future removeGroupFromMatch({ + required String matchId, + required String groupId, + }) async { + final query = delete(groupMatchTable) + ..where((g) => g.matchId.equals(matchId) & g.groupId.equals(groupId)); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } + + /// Updates the group associated with a match to [newGroupId] based on + /// [matchId]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future updateGroupOfMatch({ + required String matchId, + required String newGroupId, + }) async { + final updatedRows = + await (update(groupMatchTable)..where((g) => g.matchId.equals(matchId))) + .write(GroupMatchTableCompanion(groupId: Value(newGroupId))); + return updatedRows > 0; + } +} diff --git a/lib/data/dao/group_match_dao.g.dart b/lib/data/dao/group_match_dao.g.dart new file mode 100644 index 0000000..5cc0b82 --- /dev/null +++ b/lib/data/dao/group_match_dao.g.dart @@ -0,0 +1,10 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'group_match_dao.dart'; + +// ignore_for_file: type=lint +mixin _$GroupMatchDaoMixin on DatabaseAccessor { + $GroupTableTable get groupTable => attachedDatabase.groupTable; + $MatchTableTable get matchTable => attachedDatabase.matchTable; + $GroupMatchTableTable get groupMatchTable => attachedDatabase.groupMatchTable; +} diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart new file mode 100644 index 0000000..32bd323 --- /dev/null +++ b/lib/data/dao/match_dao.dart @@ -0,0 +1,322 @@ +import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/data/db/tables/match_table.dart'; +import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/match.dart'; +import 'package:game_tracker/data/dto/player.dart'; + +part 'match_dao.g.dart'; + +@DriftAccessor(tables: [MatchTable]) +class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { + MatchDao(super.db); + + /// Retrieves all matches from the database. + Future> getAllMatches() async { + final query = select(matchTable); + final result = await query.get(); + + return Future.wait( + result.map((row) async { + final group = await db.groupMatchDao.getGroupOfMatch(matchId: row.id); + final players = await db.playerMatchDao.getPlayersOfMatch( + matchId: row.id, + ); + final winner = row.winnerId != null + ? await db.playerDao.getPlayerById(playerId: row.winnerId!) + : null; + return Match( + id: row.id, + name: row.name, + group: group, + players: players, + createdAt: row.createdAt, + winner: winner, + ); + }), + ); + } + + /// Retrieves a [Match] by its [matchId]. + Future getMatchById({required String matchId}) async { + final query = select(matchTable)..where((g) => g.id.equals(matchId)); + final result = await query.getSingle(); + + List? players; + if (await db.playerMatchDao.matchHasPlayers(matchId: matchId)) { + players = await db.playerMatchDao.getPlayersOfMatch(matchId: matchId); + } + Group? group; + if (await db.groupMatchDao.matchHasGroup(matchId: matchId)) { + group = await db.groupMatchDao.getGroupOfMatch(matchId: matchId); + } + Player? winner; + if (result.winnerId != null) { + winner = await db.playerDao.getPlayerById(playerId: result.winnerId!); + } + + return Match( + id: result.id, + name: result.name, + players: players, + group: group, + winner: winner, + createdAt: result.createdAt, + ); + } + + /// Adds a new [Match] to the database. + /// Also adds associated players and group if they exist. + Future addMatch({required Match match}) async { + await db.transaction(() async { + await into(matchTable).insert( + MatchTableCompanion.insert( + id: match.id, + name: match.name, + winnerId: Value(match.winner?.id), + createdAt: match.createdAt, + ), + mode: InsertMode.insertOrReplace, + ); + + if (match.players != null) { + await db.playerDao.addPlayersAsList(players: match.players!); + for (final p in match.players ?? []) { + await db.playerMatchDao.addPlayerToMatch( + matchId: match.id, + playerId: p.id, + ); + } + } + + if (match.group != null) { + await db.groupDao.addGroup(group: match.group!); + await db.groupMatchDao.addGroupToMatch( + matchId: match.id, + groupId: match.group!.id, + ); + } + }); + } + + /// Adds multiple [Match]s to the database in a batch operation. + /// Also adds associated players and groups if they exist. + /// If the [matches] list is empty, the method returns immediately. + Future addMatchAsList({required List matches}) async { + if (matches.isEmpty) return; + await db.transaction(() async { + // Add all matches in batch + await db.batch( + (b) => b.insertAll( + matchTable, + matches + .map( + (match) => MatchTableCompanion.insert( + id: match.id, + name: match.name, + createdAt: match.createdAt, + winnerId: Value(match.winner?.id), + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + + // Add all groups of the matches in batch + await db.batch( + (b) => b.insertAll( + db.groupTable, + matches + .where((match) => match.group != null) + .map( + (matches) => GroupTableCompanion.insert( + id: matches.group!.id, + name: matches.group!.name, + createdAt: matches.group!.createdAt, + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + + // Add all players of the matches in batch (unique) + final uniquePlayers = {}; + for (final match in matches) { + if (match.players != null) { + for (final p in match.players!) { + uniquePlayers[p.id] = p; + } + } + // Also include members of groups + if (match.group != null) { + for (final m in match.group!.members) { + uniquePlayers[m.id] = m; + } + } + } + + if (uniquePlayers.isNotEmpty) { + await db.batch( + (b) => b.insertAll( + db.playerTable, + uniquePlayers.values + .map( + (p) => PlayerTableCompanion.insert( + id: p.id, + name: p.name, + createdAt: p.createdAt, + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ), + ); + } + + // Add all player-match associations in batch + await db.batch((b) { + for (final match in matches) { + if (match.players != null) { + for (final p in match.players ?? []) { + b.insert( + db.playerMatchTable, + PlayerMatchTableCompanion.insert( + matchId: match.id, + playerId: p.id, + ), + mode: InsertMode.insertOrReplace, + ); + } + } + } + }); + + // Add all player-group associations in batch + await db.batch((b) { + for (final match in matches) { + if (match.group != null) { + for (final m in match.group!.members) { + b.insert( + db.playerGroupTable, + PlayerGroupTableCompanion.insert( + playerId: m.id, + groupId: match.group!.id, + ), + mode: InsertMode.insertOrReplace, + ); + } + } + } + }); + + // Add all group-match associations in batch + await db.batch((b) { + for (final match in matches) { + if (match.group != null) { + b.insert( + db.groupMatchTable, + GroupMatchTableCompanion.insert( + matchId: match.id, + groupId: match.group!.id, + ), + mode: InsertMode.insertOrReplace, + ); + } + } + }); + }); + } + + /// Deletes the match with the given [matchId] from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteMatch({required String matchId}) async { + final query = delete(matchTable)..where((g) => g.id.equals(matchId)); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } + + /// Retrieves the number of matches in the database. + Future getMatchCount() async { + final count = + await (selectOnly(matchTable)..addColumns([matchTable.id.count()])) + .map((row) => row.read(matchTable.id.count())) + .getSingle(); + return count ?? 0; + } + + /// Checks if a match with the given [matchId] exists in the database. + /// Returns `true` if the match exists, otherwise `false`. + Future matchExists({required String matchId}) async { + final query = select(matchTable)..where((g) => g.id.equals(matchId)); + final result = await query.getSingleOrNull(); + return result != null; + } + + /// Deletes all matches from the database. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future deleteAllMatches() async { + final query = delete(matchTable); + final rowsAffected = await query.go(); + return rowsAffected > 0; + } + + /// Sets the winner of the match with the given [matchId] to the player with + /// the given [winnerId]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future setWinner({ + required String matchId, + required String winnerId, + }) async { + final query = update(matchTable)..where((g) => g.id.equals(matchId)); + final rowsAffected = await query.write( + MatchTableCompanion(winnerId: Value(winnerId)), + ); + return rowsAffected > 0; + } + + /// Retrieves the winner of the match with the given [matchId]. + /// Returns the [Player] who won the match, or `null` if no winner is set. + Future getWinner({required String matchId}) async { + final query = select(matchTable)..where((g) => g.id.equals(matchId)); + final result = await query.getSingleOrNull(); + if (result == null || result.winnerId == null) { + return null; + } + final winner = await db.playerDao.getPlayerById(playerId: result.winnerId!); + return winner; + } + + /// Removes the winner of the match with the given [matchId]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future removeWinner({required String matchId}) async { + final query = update(matchTable)..where((g) => g.id.equals(matchId)); + final rowsAffected = await query.write( + const MatchTableCompanion(winnerId: Value(null)), + ); + return rowsAffected > 0; + } + + /// Checks if the match with the given [matchId] has a winner set. + /// Returns `true` if a winner is set, otherwise `false`. + Future hasWinner({required String matchId}) async { + final query = select(matchTable) + ..where((g) => g.id.equals(matchId) & g.winnerId.isNotNull()); + final result = await query.getSingleOrNull(); + return result != null; + } + + /// Changes the title of the match with the given [matchId] to [newName]. + /// Returns `true` if more than 0 rows were affected, otherwise `false`. + Future updateMatchName({ + required String matchId, + required String newName, + }) async { + final query = update(matchTable)..where((g) => g.id.equals(matchId)); + final rowsAffected = await query.write( + MatchTableCompanion(name: Value(newName)), + ); + return rowsAffected > 0; + } +} diff --git a/lib/data/dao/match_dao.g.dart b/lib/data/dao/match_dao.g.dart new file mode 100644 index 0000000..a9f6f4c --- /dev/null +++ b/lib/data/dao/match_dao.g.dart @@ -0,0 +1,8 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'match_dao.dart'; + +// ignore_for_file: type=lint +mixin _$MatchDaoMixin on DatabaseAccessor { + $MatchTableTable get matchTable => attachedDatabase.matchTable; +} diff --git a/lib/data/dao/player_game_dao.g.dart b/lib/data/dao/player_game_dao.g.dart deleted file mode 100644 index 4d0a192..0000000 --- a/lib/data/dao/player_game_dao.g.dart +++ /dev/null @@ -1,10 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'player_game_dao.dart'; - -// ignore_for_file: type=lint -mixin _$PlayerGameDaoMixin on DatabaseAccessor { - $PlayerTableTable get playerTable => attachedDatabase.playerTable; - $GameTableTable get gameTable => attachedDatabase.gameTable; - $PlayerGameTableTable get playerGameTable => attachedDatabase.playerGameTable; -} diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart index 8cf96c2..db45735 100644 --- a/lib/data/dao/player_group_dao.dart +++ b/lib/data/dao/player_group_dao.dart @@ -1,11 +1,12 @@ import 'package:drift/drift.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/tables/player_group_table.dart'; +import 'package:game_tracker/data/db/tables/player_table.dart'; import 'package:game_tracker/data/dto/player.dart'; part 'player_group_dao.g.dart'; -@DriftAccessor(tables: [PlayerGroupTable]) +@DriftAccessor(tables: [PlayerGroupTable, PlayerTable]) class PlayerGroupDao extends DatabaseAccessor with _$PlayerGroupDaoMixin { PlayerGroupDao(super.db); diff --git a/lib/data/dao/player_game_dao.dart b/lib/data/dao/player_match_dao.dart similarity index 53% rename from lib/data/dao/player_game_dao.dart rename to lib/data/dao/player_match_dao.dart index b7f253f..f42b8bb 100644 --- a/lib/data/dao/player_game_dao.dart +++ b/lib/data/dao/player_match_dao.dart @@ -1,33 +1,33 @@ import 'package:drift/drift.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/db/tables/player_game_table.dart'; +import 'package:game_tracker/data/db/tables/player_match_table.dart'; import 'package:game_tracker/data/dto/player.dart'; -part 'player_game_dao.g.dart'; +part 'player_match_dao.g.dart'; -@DriftAccessor(tables: [PlayerGameTable]) -class PlayerGameDao extends DatabaseAccessor - with _$PlayerGameDaoMixin { - PlayerGameDao(super.db); +@DriftAccessor(tables: [PlayerMatchTable]) +class PlayerMatchDao extends DatabaseAccessor + with _$PlayerMatchDaoMixin { + PlayerMatchDao(super.db); - /// Associates a player with a game by inserting a record into the - /// [PlayerGameTable]. - Future addPlayerToGame({ - required String gameId, + /// Associates a player with a match by inserting a record into the + /// [PlayerMatchTable]. + Future addPlayerToMatch({ + required String matchId, required String playerId, }) async { - await into(playerGameTable).insert( - PlayerGameTableCompanion.insert(playerId: playerId, gameId: gameId), + await into(playerMatchTable).insert( + PlayerMatchTableCompanion.insert(playerId: playerId, matchId: matchId), mode: InsertMode.insertOrReplace, ); } - /// Retrieves a list of [Player]s associated with the given [gameId]. + /// Retrieves a list of [Player]s associated with the given [matchId]. /// Returns null if no players are found. - Future?> getPlayersOfGame({required String gameId}) async { + Future?> getPlayersOfMatch({required String matchId}) async { final result = await (select( - playerGameTable, - )..where((p) => p.gameId.equals(gameId))).get(); + playerMatchTable, + )..where((p) => p.matchId.equals(matchId))).get(); if (result.isEmpty) return null; @@ -38,43 +38,43 @@ class PlayerGameDao extends DatabaseAccessor return players; } - /// Checks if there are any players associated with the given [gameId]. + /// Checks if there are any players associated with the given [matchId]. /// Returns `true` if there are players, otherwise `false`. - Future gameHasPlayers({required String gameId}) async { + Future matchHasPlayers({required String matchId}) async { final count = - await (selectOnly(playerGameTable) - ..where(playerGameTable.gameId.equals(gameId)) - ..addColumns([playerGameTable.playerId.count()])) - .map((row) => row.read(playerGameTable.playerId.count())) + await (selectOnly(playerMatchTable) + ..where(playerMatchTable.matchId.equals(matchId)) + ..addColumns([playerMatchTable.playerId.count()])) + .map((row) => row.read(playerMatchTable.playerId.count())) .getSingle(); return (count ?? 0) > 0; } - /// Checks if a specific player is associated with a specific game. - /// Returns `true` if the player is in the game, otherwise `false`. - Future isPlayerInGame({ - required String gameId, + /// Checks if a specific player is associated with a specific match. + /// Returns `true` if the player is in the match, otherwise `false`. + Future isPlayerInMatch({ + required String matchId, required String playerId, }) async { final count = - await (selectOnly(playerGameTable) - ..where(playerGameTable.gameId.equals(gameId)) - ..where(playerGameTable.playerId.equals(playerId)) - ..addColumns([playerGameTable.playerId.count()])) - .map((row) => row.read(playerGameTable.playerId.count())) + await (selectOnly(playerMatchTable) + ..where(playerMatchTable.matchId.equals(matchId)) + ..where(playerMatchTable.playerId.equals(playerId)) + ..addColumns([playerMatchTable.playerId.count()])) + .map((row) => row.read(playerMatchTable.playerId.count())) .getSingle(); return (count ?? 0) > 0; } /// Removes the association of a player with a game by deleting the record - /// from the [PlayerGameTable]. + /// from the [PlayerMatchTable]. /// Returns `true` if more than 0 rows were affected, otherwise `false`. - Future removePlayerFromGame({ - required String gameId, + Future removePlayerFromMatch({ + required String matchId, required String playerId, }) async { - final query = delete(playerGameTable) - ..where((pg) => pg.gameId.equals(gameId)) + final query = delete(playerMatchTable) + ..where((pg) => pg.matchId.equals(matchId)) ..where((pg) => pg.playerId.equals(playerId)); final rowsAffected = await query.go(); return rowsAffected > 0; @@ -83,11 +83,11 @@ class PlayerGameDao extends DatabaseAccessor /// Updates the players associated with a game based on the provided /// [newPlayer] list. It adds new players and removes players that are no /// longer associated with the game. - Future updatePlayersFromGame({ - required String gameId, + Future updatePlayersFromMatch({ + required String matchId, required List newPlayer, }) async { - final currentPlayers = await getPlayersOfGame(gameId: gameId); + final currentPlayers = await getPlayersOfMatch(matchId: matchId); // Create sets of player IDs for easy comparison final currentPlayerIds = currentPlayers?.map((p) => p.id).toSet() ?? {}; final newPlayerIdsSet = newPlayer.map((p) => p.id).toSet(); @@ -99,9 +99,9 @@ class PlayerGameDao extends DatabaseAccessor db.transaction(() async { // Remove old players if (playersToRemove.isNotEmpty) { - await (delete(playerGameTable)..where( + await (delete(playerMatchTable)..where( (pg) => - pg.gameId.equals(gameId) & + pg.matchId.equals(matchId) & pg.playerId.isIn(playersToRemove.toList()), )) .go(); @@ -111,14 +111,16 @@ class PlayerGameDao extends DatabaseAccessor if (playersToAdd.isNotEmpty) { final inserts = playersToAdd .map( - (id) => - PlayerGameTableCompanion.insert(playerId: id, gameId: gameId), + (id) => PlayerMatchTableCompanion.insert( + playerId: id, + matchId: matchId, + ), ) .toList(); await Future.wait( inserts.map( (c) => into( - playerGameTable, + playerMatchTable, ).insert(c, mode: InsertMode.insertOrReplace), ), ); diff --git a/lib/data/dao/player_match_dao.g.dart b/lib/data/dao/player_match_dao.g.dart new file mode 100644 index 0000000..bcc8ef7 --- /dev/null +++ b/lib/data/dao/player_match_dao.g.dart @@ -0,0 +1,11 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'player_match_dao.dart'; + +// ignore_for_file: type=lint +mixin _$PlayerMatchDaoMixin on DatabaseAccessor { + $PlayerTableTable get playerTable => attachedDatabase.playerTable; + $MatchTableTable get matchTable => attachedDatabase.matchTable; + $PlayerMatchTableTable get playerMatchTable => + attachedDatabase.playerMatchTable; +} diff --git a/lib/data/db/database.dart b/lib/data/db/database.dart index 704e1f0..e6c322f 100644 --- a/lib/data/db/database.dart +++ b/lib/data/db/database.dart @@ -1,16 +1,16 @@ import 'package:drift/drift.dart'; import 'package:drift_flutter/drift_flutter.dart'; -import 'package:game_tracker/data/dao/game_dao.dart'; import 'package:game_tracker/data/dao/group_dao.dart'; -import 'package:game_tracker/data/dao/group_game_dao.dart'; +import 'package:game_tracker/data/dao/group_match_dao.dart'; +import 'package:game_tracker/data/dao/match_dao.dart'; import 'package:game_tracker/data/dao/player_dao.dart'; -import 'package:game_tracker/data/dao/player_game_dao.dart'; import 'package:game_tracker/data/dao/player_group_dao.dart'; -import 'package:game_tracker/data/db/tables/game_table.dart'; -import 'package:game_tracker/data/db/tables/group_game_table.dart'; +import 'package:game_tracker/data/dao/player_match_dao.dart'; +import 'package:game_tracker/data/db/tables/group_match_table.dart'; import 'package:game_tracker/data/db/tables/group_table.dart'; -import 'package:game_tracker/data/db/tables/player_game_table.dart'; +import 'package:game_tracker/data/db/tables/match_table.dart'; import 'package:game_tracker/data/db/tables/player_group_table.dart'; +import 'package:game_tracker/data/db/tables/player_match_table.dart'; import 'package:game_tracker/data/db/tables/player_table.dart'; import 'package:path_provider/path_provider.dart'; @@ -20,18 +20,18 @@ part 'database.g.dart'; tables: [ PlayerTable, GroupTable, - GameTable, + MatchTable, PlayerGroupTable, - PlayerGameTable, - GroupGameTable, + PlayerMatchTable, + GroupMatchTable, ], daos: [ PlayerDao, GroupDao, - GameDao, + MatchDao, PlayerGroupDao, - PlayerGameDao, - GroupGameDao, + PlayerMatchDao, + GroupMatchDao, ], ) class AppDatabase extends _$AppDatabase { diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart index f211d0c..6bc493c 100644 --- a/lib/data/db/database.g.dart +++ b/lib/data/db/database.g.dart @@ -521,12 +521,12 @@ class GroupTableCompanion extends UpdateCompanion { } } -class $GameTableTable extends GameTable - with TableInfo<$GameTableTable, GameTableData> { +class $MatchTableTable extends MatchTable + with TableInfo<$MatchTableTable, MatchTableData> { @override final GeneratedDatabase attachedDatabase; final String? _alias; - $GameTableTable(this.attachedDatabase, [this._alias]); + $MatchTableTable(this.attachedDatabase, [this._alias]); static const VerificationMeta _idMeta = const VerificationMeta('id'); @override late final GeneratedColumn id = GeneratedColumn( @@ -573,10 +573,10 @@ class $GameTableTable extends GameTable String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; - static const String $name = 'game_table'; + static const String $name = 'match_table'; @override VerificationContext validateIntegrity( - Insertable instance, { + Insertable instance, { bool isInserting = false, }) { final context = VerificationContext(); @@ -614,9 +614,9 @@ class $GameTableTable extends GameTable @override Set get $primaryKey => {id}; @override - GameTableData map(Map data, {String? tablePrefix}) { + MatchTableData map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return GameTableData( + return MatchTableData( id: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}id'], @@ -637,17 +637,17 @@ class $GameTableTable extends GameTable } @override - $GameTableTable createAlias(String alias) { - return $GameTableTable(attachedDatabase, alias); + $MatchTableTable createAlias(String alias) { + return $MatchTableTable(attachedDatabase, alias); } } -class GameTableData extends DataClass implements Insertable { +class MatchTableData extends DataClass implements Insertable { final String id; final String name; final String? winnerId; final DateTime createdAt; - const GameTableData({ + const MatchTableData({ required this.id, required this.name, this.winnerId, @@ -665,8 +665,8 @@ class GameTableData extends DataClass implements Insertable { return map; } - GameTableCompanion toCompanion(bool nullToAbsent) { - return GameTableCompanion( + MatchTableCompanion toCompanion(bool nullToAbsent) { + return MatchTableCompanion( id: Value(id), name: Value(name), winnerId: winnerId == null && nullToAbsent @@ -676,12 +676,12 @@ class GameTableData extends DataClass implements Insertable { ); } - factory GameTableData.fromJson( + factory MatchTableData.fromJson( Map json, { ValueSerializer? serializer, }) { serializer ??= driftRuntimeOptions.defaultSerializer; - return GameTableData( + return MatchTableData( id: serializer.fromJson(json['id']), name: serializer.fromJson(json['name']), winnerId: serializer.fromJson(json['winnerId']), @@ -699,19 +699,19 @@ class GameTableData extends DataClass implements Insertable { }; } - GameTableData copyWith({ + MatchTableData copyWith({ String? id, String? name, Value winnerId = const Value.absent(), DateTime? createdAt, - }) => GameTableData( + }) => MatchTableData( id: id ?? this.id, name: name ?? this.name, winnerId: winnerId.present ? winnerId.value : this.winnerId, createdAt: createdAt ?? this.createdAt, ); - GameTableData copyWithCompanion(GameTableCompanion data) { - return GameTableData( + MatchTableData copyWithCompanion(MatchTableCompanion data) { + return MatchTableData( id: data.id.present ? data.id.value : this.id, name: data.name.present ? data.name.value : this.name, winnerId: data.winnerId.present ? data.winnerId.value : this.winnerId, @@ -721,7 +721,7 @@ class GameTableData extends DataClass implements Insertable { @override String toString() { - return (StringBuffer('GameTableData(') + return (StringBuffer('MatchTableData(') ..write('id: $id, ') ..write('name: $name, ') ..write('winnerId: $winnerId, ') @@ -735,27 +735,27 @@ class GameTableData extends DataClass implements Insertable { @override bool operator ==(Object other) => identical(this, other) || - (other is GameTableData && + (other is MatchTableData && other.id == this.id && other.name == this.name && other.winnerId == this.winnerId && other.createdAt == this.createdAt); } -class GameTableCompanion extends UpdateCompanion { +class MatchTableCompanion extends UpdateCompanion { final Value id; final Value name; final Value winnerId; final Value createdAt; final Value rowid; - const GameTableCompanion({ + const MatchTableCompanion({ this.id = const Value.absent(), this.name = const Value.absent(), this.winnerId = const Value.absent(), this.createdAt = const Value.absent(), this.rowid = const Value.absent(), }); - GameTableCompanion.insert({ + MatchTableCompanion.insert({ required String id, required String name, this.winnerId = const Value.absent(), @@ -764,7 +764,7 @@ class GameTableCompanion extends UpdateCompanion { }) : id = Value(id), name = Value(name), createdAt = Value(createdAt); - static Insertable custom({ + static Insertable custom({ Expression? id, Expression? name, Expression? winnerId, @@ -780,14 +780,14 @@ class GameTableCompanion extends UpdateCompanion { }); } - GameTableCompanion copyWith({ + MatchTableCompanion copyWith({ Value? id, Value? name, Value? winnerId, Value? createdAt, Value? rowid, }) { - return GameTableCompanion( + return MatchTableCompanion( id: id ?? this.id, name: name ?? this.name, winnerId: winnerId ?? this.winnerId, @@ -819,7 +819,7 @@ class GameTableCompanion extends UpdateCompanion { @override String toString() { - return (StringBuffer('GameTableCompanion(') + return (StringBuffer('MatchTableCompanion(') ..write('id: $id, ') ..write('name: $name, ') ..write('winnerId: $winnerId, ') @@ -1055,12 +1055,12 @@ class PlayerGroupTableCompanion extends UpdateCompanion { } } -class $PlayerGameTableTable extends PlayerGameTable - with TableInfo<$PlayerGameTableTable, PlayerGameTableData> { +class $PlayerMatchTableTable extends PlayerMatchTable + with TableInfo<$PlayerMatchTableTable, PlayerMatchTableData> { @override final GeneratedDatabase attachedDatabase; final String? _alias; - $PlayerGameTableTable(this.attachedDatabase, [this._alias]); + $PlayerMatchTableTable(this.attachedDatabase, [this._alias]); static const VerificationMeta _playerIdMeta = const VerificationMeta( 'playerId', ); @@ -1075,28 +1075,30 @@ class $PlayerGameTableTable extends PlayerGameTable 'REFERENCES player_table (id) ON DELETE CASCADE', ), ); - static const VerificationMeta _gameIdMeta = const VerificationMeta('gameId'); + static const VerificationMeta _matchIdMeta = const VerificationMeta( + 'matchId', + ); @override - late final GeneratedColumn gameId = GeneratedColumn( - 'game_id', + late final GeneratedColumn matchId = GeneratedColumn( + 'match_id', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true, defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES game_table (id) ON DELETE CASCADE', + 'REFERENCES match_table (id) ON DELETE CASCADE', ), ); @override - List get $columns => [playerId, gameId]; + List get $columns => [playerId, matchId]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; - static const String $name = 'player_game_table'; + static const String $name = 'player_match_table'; @override VerificationContext validateIntegrity( - Insertable instance, { + Insertable instance, { bool isInserting = false, }) { final context = VerificationContext(); @@ -1109,68 +1111,68 @@ class $PlayerGameTableTable extends PlayerGameTable } else if (isInserting) { context.missing(_playerIdMeta); } - if (data.containsKey('game_id')) { + if (data.containsKey('match_id')) { context.handle( - _gameIdMeta, - gameId.isAcceptableOrUnknown(data['game_id']!, _gameIdMeta), + _matchIdMeta, + matchId.isAcceptableOrUnknown(data['match_id']!, _matchIdMeta), ); } else if (isInserting) { - context.missing(_gameIdMeta); + context.missing(_matchIdMeta); } return context; } @override - Set get $primaryKey => {playerId, gameId}; + Set get $primaryKey => {playerId, matchId}; @override - PlayerGameTableData map(Map data, {String? tablePrefix}) { + PlayerMatchTableData map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return PlayerGameTableData( + return PlayerMatchTableData( playerId: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}player_id'], )!, - gameId: attachedDatabase.typeMapping.read( + matchId: attachedDatabase.typeMapping.read( DriftSqlType.string, - data['${effectivePrefix}game_id'], + data['${effectivePrefix}match_id'], )!, ); } @override - $PlayerGameTableTable createAlias(String alias) { - return $PlayerGameTableTable(attachedDatabase, alias); + $PlayerMatchTableTable createAlias(String alias) { + return $PlayerMatchTableTable(attachedDatabase, alias); } } -class PlayerGameTableData extends DataClass - implements Insertable { +class PlayerMatchTableData extends DataClass + implements Insertable { final String playerId; - final String gameId; - const PlayerGameTableData({required this.playerId, required this.gameId}); + final String matchId; + const PlayerMatchTableData({required this.playerId, required this.matchId}); @override Map toColumns(bool nullToAbsent) { final map = {}; map['player_id'] = Variable(playerId); - map['game_id'] = Variable(gameId); + map['match_id'] = Variable(matchId); return map; } - PlayerGameTableCompanion toCompanion(bool nullToAbsent) { - return PlayerGameTableCompanion( + PlayerMatchTableCompanion toCompanion(bool nullToAbsent) { + return PlayerMatchTableCompanion( playerId: Value(playerId), - gameId: Value(gameId), + matchId: Value(matchId), ); } - factory PlayerGameTableData.fromJson( + factory PlayerMatchTableData.fromJson( Map json, { ValueSerializer? serializer, }) { serializer ??= driftRuntimeOptions.defaultSerializer; - return PlayerGameTableData( + return PlayerMatchTableData( playerId: serializer.fromJson(json['playerId']), - gameId: serializer.fromJson(json['gameId']), + matchId: serializer.fromJson(json['matchId']), ); } @override @@ -1178,76 +1180,76 @@ class PlayerGameTableData extends DataClass serializer ??= driftRuntimeOptions.defaultSerializer; return { 'playerId': serializer.toJson(playerId), - 'gameId': serializer.toJson(gameId), + 'matchId': serializer.toJson(matchId), }; } - PlayerGameTableData copyWith({String? playerId, String? gameId}) => - PlayerGameTableData( + PlayerMatchTableData copyWith({String? playerId, String? matchId}) => + PlayerMatchTableData( playerId: playerId ?? this.playerId, - gameId: gameId ?? this.gameId, + matchId: matchId ?? this.matchId, ); - PlayerGameTableData copyWithCompanion(PlayerGameTableCompanion data) { - return PlayerGameTableData( + PlayerMatchTableData copyWithCompanion(PlayerMatchTableCompanion data) { + return PlayerMatchTableData( playerId: data.playerId.present ? data.playerId.value : this.playerId, - gameId: data.gameId.present ? data.gameId.value : this.gameId, + matchId: data.matchId.present ? data.matchId.value : this.matchId, ); } @override String toString() { - return (StringBuffer('PlayerGameTableData(') + return (StringBuffer('PlayerMatchTableData(') ..write('playerId: $playerId, ') - ..write('gameId: $gameId') + ..write('matchId: $matchId') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(playerId, gameId); + int get hashCode => Object.hash(playerId, matchId); @override bool operator ==(Object other) => identical(this, other) || - (other is PlayerGameTableData && + (other is PlayerMatchTableData && other.playerId == this.playerId && - other.gameId == this.gameId); + other.matchId == this.matchId); } -class PlayerGameTableCompanion extends UpdateCompanion { +class PlayerMatchTableCompanion extends UpdateCompanion { final Value playerId; - final Value gameId; + final Value matchId; final Value rowid; - const PlayerGameTableCompanion({ + const PlayerMatchTableCompanion({ this.playerId = const Value.absent(), - this.gameId = const Value.absent(), + this.matchId = const Value.absent(), this.rowid = const Value.absent(), }); - PlayerGameTableCompanion.insert({ + PlayerMatchTableCompanion.insert({ required String playerId, - required String gameId, + required String matchId, this.rowid = const Value.absent(), }) : playerId = Value(playerId), - gameId = Value(gameId); - static Insertable custom({ + matchId = Value(matchId); + static Insertable custom({ Expression? playerId, - Expression? gameId, + Expression? matchId, Expression? rowid, }) { return RawValuesInsertable({ if (playerId != null) 'player_id': playerId, - if (gameId != null) 'game_id': gameId, + if (matchId != null) 'match_id': matchId, if (rowid != null) 'rowid': rowid, }); } - PlayerGameTableCompanion copyWith({ + PlayerMatchTableCompanion copyWith({ Value? playerId, - Value? gameId, + Value? matchId, Value? rowid, }) { - return PlayerGameTableCompanion( + return PlayerMatchTableCompanion( playerId: playerId ?? this.playerId, - gameId: gameId ?? this.gameId, + matchId: matchId ?? this.matchId, rowid: rowid ?? this.rowid, ); } @@ -1258,8 +1260,8 @@ class PlayerGameTableCompanion extends UpdateCompanion { if (playerId.present) { map['player_id'] = Variable(playerId.value); } - if (gameId.present) { - map['game_id'] = Variable(gameId.value); + if (matchId.present) { + map['match_id'] = Variable(matchId.value); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -1269,21 +1271,21 @@ class PlayerGameTableCompanion extends UpdateCompanion { @override String toString() { - return (StringBuffer('PlayerGameTableCompanion(') + return (StringBuffer('PlayerMatchTableCompanion(') ..write('playerId: $playerId, ') - ..write('gameId: $gameId, ') + ..write('matchId: $matchId, ') ..write('rowid: $rowid') ..write(')')) .toString(); } } -class $GroupGameTableTable extends GroupGameTable - with TableInfo<$GroupGameTableTable, GroupGameTableData> { +class $GroupMatchTableTable extends GroupMatchTable + with TableInfo<$GroupMatchTableTable, GroupMatchTableData> { @override final GeneratedDatabase attachedDatabase; final String? _alias; - $GroupGameTableTable(this.attachedDatabase, [this._alias]); + $GroupMatchTableTable(this.attachedDatabase, [this._alias]); static const VerificationMeta _groupIdMeta = const VerificationMeta( 'groupId', ); @@ -1298,28 +1300,30 @@ class $GroupGameTableTable extends GroupGameTable 'REFERENCES group_table (id) ON DELETE CASCADE', ), ); - static const VerificationMeta _gameIdMeta = const VerificationMeta('gameId'); + static const VerificationMeta _matchIdMeta = const VerificationMeta( + 'matchId', + ); @override - late final GeneratedColumn gameId = GeneratedColumn( - 'game_id', + late final GeneratedColumn matchId = GeneratedColumn( + 'match_id', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true, defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES game_table (id) ON DELETE CASCADE', + 'REFERENCES match_table (id) ON DELETE CASCADE', ), ); @override - List get $columns => [groupId, gameId]; + List get $columns => [groupId, matchId]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; - static const String $name = 'group_game_table'; + static const String $name = 'group_match_table'; @override VerificationContext validateIntegrity( - Insertable instance, { + Insertable instance, { bool isInserting = false, }) { final context = VerificationContext(); @@ -1332,68 +1336,68 @@ class $GroupGameTableTable extends GroupGameTable } else if (isInserting) { context.missing(_groupIdMeta); } - if (data.containsKey('game_id')) { + if (data.containsKey('match_id')) { context.handle( - _gameIdMeta, - gameId.isAcceptableOrUnknown(data['game_id']!, _gameIdMeta), + _matchIdMeta, + matchId.isAcceptableOrUnknown(data['match_id']!, _matchIdMeta), ); } else if (isInserting) { - context.missing(_gameIdMeta); + context.missing(_matchIdMeta); } return context; } @override - Set get $primaryKey => {groupId, gameId}; + Set get $primaryKey => {groupId, matchId}; @override - GroupGameTableData map(Map data, {String? tablePrefix}) { + GroupMatchTableData map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return GroupGameTableData( + return GroupMatchTableData( groupId: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}group_id'], )!, - gameId: attachedDatabase.typeMapping.read( + matchId: attachedDatabase.typeMapping.read( DriftSqlType.string, - data['${effectivePrefix}game_id'], + data['${effectivePrefix}match_id'], )!, ); } @override - $GroupGameTableTable createAlias(String alias) { - return $GroupGameTableTable(attachedDatabase, alias); + $GroupMatchTableTable createAlias(String alias) { + return $GroupMatchTableTable(attachedDatabase, alias); } } -class GroupGameTableData extends DataClass - implements Insertable { +class GroupMatchTableData extends DataClass + implements Insertable { final String groupId; - final String gameId; - const GroupGameTableData({required this.groupId, required this.gameId}); + final String matchId; + const GroupMatchTableData({required this.groupId, required this.matchId}); @override Map toColumns(bool nullToAbsent) { final map = {}; map['group_id'] = Variable(groupId); - map['game_id'] = Variable(gameId); + map['match_id'] = Variable(matchId); return map; } - GroupGameTableCompanion toCompanion(bool nullToAbsent) { - return GroupGameTableCompanion( + GroupMatchTableCompanion toCompanion(bool nullToAbsent) { + return GroupMatchTableCompanion( groupId: Value(groupId), - gameId: Value(gameId), + matchId: Value(matchId), ); } - factory GroupGameTableData.fromJson( + factory GroupMatchTableData.fromJson( Map json, { ValueSerializer? serializer, }) { serializer ??= driftRuntimeOptions.defaultSerializer; - return GroupGameTableData( + return GroupMatchTableData( groupId: serializer.fromJson(json['groupId']), - gameId: serializer.fromJson(json['gameId']), + matchId: serializer.fromJson(json['matchId']), ); } @override @@ -1401,76 +1405,76 @@ class GroupGameTableData extends DataClass serializer ??= driftRuntimeOptions.defaultSerializer; return { 'groupId': serializer.toJson(groupId), - 'gameId': serializer.toJson(gameId), + 'matchId': serializer.toJson(matchId), }; } - GroupGameTableData copyWith({String? groupId, String? gameId}) => - GroupGameTableData( + GroupMatchTableData copyWith({String? groupId, String? matchId}) => + GroupMatchTableData( groupId: groupId ?? this.groupId, - gameId: gameId ?? this.gameId, + matchId: matchId ?? this.matchId, ); - GroupGameTableData copyWithCompanion(GroupGameTableCompanion data) { - return GroupGameTableData( + GroupMatchTableData copyWithCompanion(GroupMatchTableCompanion data) { + return GroupMatchTableData( groupId: data.groupId.present ? data.groupId.value : this.groupId, - gameId: data.gameId.present ? data.gameId.value : this.gameId, + matchId: data.matchId.present ? data.matchId.value : this.matchId, ); } @override String toString() { - return (StringBuffer('GroupGameTableData(') + return (StringBuffer('GroupMatchTableData(') ..write('groupId: $groupId, ') - ..write('gameId: $gameId') + ..write('matchId: $matchId') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(groupId, gameId); + int get hashCode => Object.hash(groupId, matchId); @override bool operator ==(Object other) => identical(this, other) || - (other is GroupGameTableData && + (other is GroupMatchTableData && other.groupId == this.groupId && - other.gameId == this.gameId); + other.matchId == this.matchId); } -class GroupGameTableCompanion extends UpdateCompanion { +class GroupMatchTableCompanion extends UpdateCompanion { final Value groupId; - final Value gameId; + final Value matchId; final Value rowid; - const GroupGameTableCompanion({ + const GroupMatchTableCompanion({ this.groupId = const Value.absent(), - this.gameId = const Value.absent(), + this.matchId = const Value.absent(), this.rowid = const Value.absent(), }); - GroupGameTableCompanion.insert({ + GroupMatchTableCompanion.insert({ required String groupId, - required String gameId, + required String matchId, this.rowid = const Value.absent(), }) : groupId = Value(groupId), - gameId = Value(gameId); - static Insertable custom({ + matchId = Value(matchId); + static Insertable custom({ Expression? groupId, - Expression? gameId, + Expression? matchId, Expression? rowid, }) { return RawValuesInsertable({ if (groupId != null) 'group_id': groupId, - if (gameId != null) 'game_id': gameId, + if (matchId != null) 'match_id': matchId, if (rowid != null) 'rowid': rowid, }); } - GroupGameTableCompanion copyWith({ + GroupMatchTableCompanion copyWith({ Value? groupId, - Value? gameId, + Value? matchId, Value? rowid, }) { - return GroupGameTableCompanion( + return GroupMatchTableCompanion( groupId: groupId ?? this.groupId, - gameId: gameId ?? this.gameId, + matchId: matchId ?? this.matchId, rowid: rowid ?? this.rowid, ); } @@ -1481,8 +1485,8 @@ class GroupGameTableCompanion extends UpdateCompanion { if (groupId.present) { map['group_id'] = Variable(groupId.value); } - if (gameId.present) { - map['game_id'] = Variable(gameId.value); + if (matchId.present) { + map['match_id'] = Variable(matchId.value); } if (rowid.present) { map['rowid'] = Variable(rowid.value); @@ -1492,9 +1496,9 @@ class GroupGameTableCompanion extends UpdateCompanion { @override String toString() { - return (StringBuffer('GroupGameTableCompanion(') + return (StringBuffer('GroupMatchTableCompanion(') ..write('groupId: $groupId, ') - ..write('gameId: $gameId, ') + ..write('matchId: $matchId, ') ..write('rowid: $rowid') ..write(')')) .toString(); @@ -1506,22 +1510,26 @@ abstract class _$AppDatabase extends GeneratedDatabase { $AppDatabaseManager get managers => $AppDatabaseManager(this); late final $PlayerTableTable playerTable = $PlayerTableTable(this); late final $GroupTableTable groupTable = $GroupTableTable(this); - late final $GameTableTable gameTable = $GameTableTable(this); + late final $MatchTableTable matchTable = $MatchTableTable(this); late final $PlayerGroupTableTable playerGroupTable = $PlayerGroupTableTable( this, ); - late final $PlayerGameTableTable playerGameTable = $PlayerGameTableTable( + late final $PlayerMatchTableTable playerMatchTable = $PlayerMatchTableTable( + this, + ); + late final $GroupMatchTableTable groupMatchTable = $GroupMatchTableTable( this, ); - late final $GroupGameTableTable groupGameTable = $GroupGameTableTable(this); late final PlayerDao playerDao = PlayerDao(this as AppDatabase); late final GroupDao groupDao = GroupDao(this as AppDatabase); - late final GameDao gameDao = GameDao(this as AppDatabase); + late final MatchDao matchDao = MatchDao(this as AppDatabase); late final PlayerGroupDao playerGroupDao = PlayerGroupDao( this as AppDatabase, ); - late final PlayerGameDao playerGameDao = PlayerGameDao(this as AppDatabase); - late final GroupGameDao groupGameDao = GroupGameDao(this as AppDatabase); + late final PlayerMatchDao playerMatchDao = PlayerMatchDao( + this as AppDatabase, + ); + late final GroupMatchDao groupMatchDao = GroupMatchDao(this as AppDatabase); @override Iterable> get allTables => allSchemaEntities.whereType>(); @@ -1529,10 +1537,10 @@ abstract class _$AppDatabase extends GeneratedDatabase { List get allSchemaEntities => [ playerTable, groupTable, - gameTable, + matchTable, playerGroupTable, - playerGameTable, - groupGameTable, + playerMatchTable, + groupMatchTable, ]; @override StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ @@ -1555,28 +1563,28 @@ abstract class _$AppDatabase extends GeneratedDatabase { 'player_table', limitUpdateKind: UpdateKind.delete, ), - result: [TableUpdate('player_game_table', kind: UpdateKind.delete)], + result: [TableUpdate('player_match_table', kind: UpdateKind.delete)], ), WritePropagation( on: TableUpdateQuery.onTableName( - 'game_table', + 'match_table', limitUpdateKind: UpdateKind.delete, ), - result: [TableUpdate('player_game_table', kind: UpdateKind.delete)], + result: [TableUpdate('player_match_table', kind: UpdateKind.delete)], ), WritePropagation( on: TableUpdateQuery.onTableName( 'group_table', limitUpdateKind: UpdateKind.delete, ), - result: [TableUpdate('group_game_table', kind: UpdateKind.delete)], + result: [TableUpdate('group_match_table', kind: UpdateKind.delete)], ), WritePropagation( on: TableUpdateQuery.onTableName( - 'game_table', + 'match_table', limitUpdateKind: UpdateKind.delete, ), - result: [TableUpdate('group_game_table', kind: UpdateKind.delete)], + result: [TableUpdate('group_match_table', kind: UpdateKind.delete)], ), ]); } @@ -1623,23 +1631,23 @@ final class $$PlayerTableTableReferences ); } - static MultiTypedResultKey<$PlayerGameTableTable, List> - _playerGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.playerGameTable, + static MultiTypedResultKey<$PlayerMatchTableTable, List> + _playerMatchTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.playerMatchTable, aliasName: $_aliasNameGenerator( db.playerTable.id, - db.playerGameTable.playerId, + db.playerMatchTable.playerId, ), ); - $$PlayerGameTableTableProcessedTableManager get playerGameTableRefs { - final manager = $$PlayerGameTableTableTableManager( + $$PlayerMatchTableTableProcessedTableManager get playerMatchTableRefs { + final manager = $$PlayerMatchTableTableTableManager( $_db, - $_db.playerGameTable, + $_db.playerMatchTable, ).filter((f) => f.playerId.id.sqlEquals($_itemColumn('id')!)); final cache = $_typedResult.readTableOrNull( - _playerGameTableRefsTable($_db), + _playerMatchTableRefsTable($_db), ); return ProcessedTableManager( manager.$state.copyWith(prefetchedData: cache), @@ -1696,22 +1704,22 @@ class $$PlayerTableTableFilterComposer return f(composer); } - Expression playerGameTableRefs( - Expression Function($$PlayerGameTableTableFilterComposer f) f, + Expression playerMatchTableRefs( + Expression Function($$PlayerMatchTableTableFilterComposer f) f, ) { - final $$PlayerGameTableTableFilterComposer composer = $composerBuilder( + final $$PlayerMatchTableTableFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.playerGameTable, + referencedTable: $db.playerMatchTable, getReferencedColumn: (t) => t.playerId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$PlayerGameTableTableFilterComposer( + }) => $$PlayerMatchTableTableFilterComposer( $db: $db, - $table: $db.playerGameTable, + $table: $db.playerMatchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -1790,22 +1798,22 @@ class $$PlayerTableTableAnnotationComposer return f(composer); } - Expression playerGameTableRefs( - Expression Function($$PlayerGameTableTableAnnotationComposer a) f, + Expression playerMatchTableRefs( + Expression Function($$PlayerMatchTableTableAnnotationComposer a) f, ) { - final $$PlayerGameTableTableAnnotationComposer composer = $composerBuilder( + final $$PlayerMatchTableTableAnnotationComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.playerGameTable, + referencedTable: $db.playerMatchTable, getReferencedColumn: (t) => t.playerId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$PlayerGameTableTableAnnotationComposer( + }) => $$PlayerMatchTableTableAnnotationComposer( $db: $db, - $table: $db.playerGameTable, + $table: $db.playerMatchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -1831,7 +1839,7 @@ class $$PlayerTableTableTableManager PlayerTableData, PrefetchHooks Function({ bool playerGroupTableRefs, - bool playerGameTableRefs, + bool playerMatchTableRefs, }) > { $$PlayerTableTableTableManager(_$AppDatabase db, $PlayerTableTable table) @@ -1878,12 +1886,12 @@ class $$PlayerTableTableTableManager ) .toList(), prefetchHooksCallback: - ({playerGroupTableRefs = false, playerGameTableRefs = false}) { + ({playerGroupTableRefs = false, playerMatchTableRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ if (playerGroupTableRefs) db.playerGroupTable, - if (playerGameTableRefs) db.playerGameTable, + if (playerMatchTableRefs) db.playerMatchTable, ], addJoins: null, getPrefetchedDataCallback: (items) async { @@ -1909,21 +1917,21 @@ class $$PlayerTableTableTableManager ), typedResults: items, ), - if (playerGameTableRefs) + if (playerMatchTableRefs) await $_getPrefetchedData< PlayerTableData, $PlayerTableTable, - PlayerGameTableData + PlayerMatchTableData >( currentTable: table, referencedTable: $$PlayerTableTableReferences - ._playerGameTableRefsTable(db), + ._playerMatchTableRefsTable(db), managerFromTypedResult: (p0) => $$PlayerTableTableReferences( db, table, p0, - ).playerGameTableRefs, + ).playerMatchTableRefs, referencedItemsForCurrentItem: (item, referencedItems) => referencedItems.where( (e) => e.playerId == item.id, @@ -1952,7 +1960,7 @@ typedef $$PlayerTableTableProcessedTableManager = PlayerTableData, PrefetchHooks Function({ bool playerGroupTableRefs, - bool playerGameTableRefs, + bool playerMatchTableRefs, }) >; typedef $$GroupTableTableCreateCompanionBuilder = @@ -1997,22 +2005,24 @@ final class $$GroupTableTableReferences ); } - static MultiTypedResultKey<$GroupGameTableTable, List> - _groupGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.groupGameTable, + static MultiTypedResultKey<$GroupMatchTableTable, List> + _groupMatchTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.groupMatchTable, aliasName: $_aliasNameGenerator( db.groupTable.id, - db.groupGameTable.groupId, + db.groupMatchTable.groupId, ), ); - $$GroupGameTableTableProcessedTableManager get groupGameTableRefs { - final manager = $$GroupGameTableTableTableManager( + $$GroupMatchTableTableProcessedTableManager get groupMatchTableRefs { + final manager = $$GroupMatchTableTableTableManager( $_db, - $_db.groupGameTable, + $_db.groupMatchTable, ).filter((f) => f.groupId.id.sqlEquals($_itemColumn('id')!)); - final cache = $_typedResult.readTableOrNull(_groupGameTableRefsTable($_db)); + final cache = $_typedResult.readTableOrNull( + _groupMatchTableRefsTable($_db), + ); return ProcessedTableManager( manager.$state.copyWith(prefetchedData: cache), ); @@ -2068,22 +2078,22 @@ class $$GroupTableTableFilterComposer return f(composer); } - Expression groupGameTableRefs( - Expression Function($$GroupGameTableTableFilterComposer f) f, + Expression groupMatchTableRefs( + Expression Function($$GroupMatchTableTableFilterComposer f) f, ) { - final $$GroupGameTableTableFilterComposer composer = $composerBuilder( + final $$GroupMatchTableTableFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.groupGameTable, + referencedTable: $db.groupMatchTable, getReferencedColumn: (t) => t.groupId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GroupGameTableTableFilterComposer( + }) => $$GroupMatchTableTableFilterComposer( $db: $db, - $table: $db.groupGameTable, + $table: $db.groupMatchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -2162,22 +2172,22 @@ class $$GroupTableTableAnnotationComposer return f(composer); } - Expression groupGameTableRefs( - Expression Function($$GroupGameTableTableAnnotationComposer a) f, + Expression groupMatchTableRefs( + Expression Function($$GroupMatchTableTableAnnotationComposer a) f, ) { - final $$GroupGameTableTableAnnotationComposer composer = $composerBuilder( + final $$GroupMatchTableTableAnnotationComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.groupGameTable, + referencedTable: $db.groupMatchTable, getReferencedColumn: (t) => t.groupId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GroupGameTableTableAnnotationComposer( + }) => $$GroupMatchTableTableAnnotationComposer( $db: $db, - $table: $db.groupGameTable, + $table: $db.groupMatchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -2203,7 +2213,7 @@ class $$GroupTableTableTableManager GroupTableData, PrefetchHooks Function({ bool playerGroupTableRefs, - bool groupGameTableRefs, + bool groupMatchTableRefs, }) > { $$GroupTableTableTableManager(_$AppDatabase db, $GroupTableTable table) @@ -2250,12 +2260,12 @@ class $$GroupTableTableTableManager ) .toList(), prefetchHooksCallback: - ({playerGroupTableRefs = false, groupGameTableRefs = false}) { + ({playerGroupTableRefs = false, groupMatchTableRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ if (playerGroupTableRefs) db.playerGroupTable, - if (groupGameTableRefs) db.groupGameTable, + if (groupMatchTableRefs) db.groupMatchTable, ], addJoins: null, getPrefetchedDataCallback: (items) async { @@ -2281,21 +2291,21 @@ class $$GroupTableTableTableManager ), typedResults: items, ), - if (groupGameTableRefs) + if (groupMatchTableRefs) await $_getPrefetchedData< GroupTableData, $GroupTableTable, - GroupGameTableData + GroupMatchTableData >( currentTable: table, referencedTable: $$GroupTableTableReferences - ._groupGameTableRefsTable(db), + ._groupMatchTableRefsTable(db), managerFromTypedResult: (p0) => $$GroupTableTableReferences( db, table, p0, - ).groupGameTableRefs, + ).groupMatchTableRefs, referencedItemsForCurrentItem: (item, referencedItems) => referencedItems.where( (e) => e.groupId == item.id, @@ -2324,19 +2334,19 @@ typedef $$GroupTableTableProcessedTableManager = GroupTableData, PrefetchHooks Function({ bool playerGroupTableRefs, - bool groupGameTableRefs, + bool groupMatchTableRefs, }) >; -typedef $$GameTableTableCreateCompanionBuilder = - GameTableCompanion Function({ +typedef $$MatchTableTableCreateCompanionBuilder = + MatchTableCompanion Function({ required String id, required String name, Value winnerId, required DateTime createdAt, Value rowid, }); -typedef $$GameTableTableUpdateCompanionBuilder = - GameTableCompanion Function({ +typedef $$MatchTableTableUpdateCompanionBuilder = + MatchTableCompanion Function({ Value id, Value name, Value winnerId, @@ -2344,52 +2354,60 @@ typedef $$GameTableTableUpdateCompanionBuilder = Value rowid, }); -final class $$GameTableTableReferences - extends BaseReferences<_$AppDatabase, $GameTableTable, GameTableData> { - $$GameTableTableReferences(super.$_db, super.$_table, super.$_typedResult); +final class $$MatchTableTableReferences + extends BaseReferences<_$AppDatabase, $MatchTableTable, MatchTableData> { + $$MatchTableTableReferences(super.$_db, super.$_table, super.$_typedResult); - static MultiTypedResultKey<$PlayerGameTableTable, List> - _playerGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.playerGameTable, - aliasName: $_aliasNameGenerator(db.gameTable.id, db.playerGameTable.gameId), + static MultiTypedResultKey<$PlayerMatchTableTable, List> + _playerMatchTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.playerMatchTable, + aliasName: $_aliasNameGenerator( + db.matchTable.id, + db.playerMatchTable.matchId, + ), ); - $$PlayerGameTableTableProcessedTableManager get playerGameTableRefs { - final manager = $$PlayerGameTableTableTableManager( + $$PlayerMatchTableTableProcessedTableManager get playerMatchTableRefs { + final manager = $$PlayerMatchTableTableTableManager( $_db, - $_db.playerGameTable, - ).filter((f) => f.gameId.id.sqlEquals($_itemColumn('id')!)); + $_db.playerMatchTable, + ).filter((f) => f.matchId.id.sqlEquals($_itemColumn('id')!)); final cache = $_typedResult.readTableOrNull( - _playerGameTableRefsTable($_db), + _playerMatchTableRefsTable($_db), ); return ProcessedTableManager( manager.$state.copyWith(prefetchedData: cache), ); } - static MultiTypedResultKey<$GroupGameTableTable, List> - _groupGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.groupGameTable, - aliasName: $_aliasNameGenerator(db.gameTable.id, db.groupGameTable.gameId), + static MultiTypedResultKey<$GroupMatchTableTable, List> + _groupMatchTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.groupMatchTable, + aliasName: $_aliasNameGenerator( + db.matchTable.id, + db.groupMatchTable.matchId, + ), ); - $$GroupGameTableTableProcessedTableManager get groupGameTableRefs { - final manager = $$GroupGameTableTableTableManager( + $$GroupMatchTableTableProcessedTableManager get groupMatchTableRefs { + final manager = $$GroupMatchTableTableTableManager( $_db, - $_db.groupGameTable, - ).filter((f) => f.gameId.id.sqlEquals($_itemColumn('id')!)); + $_db.groupMatchTable, + ).filter((f) => f.matchId.id.sqlEquals($_itemColumn('id')!)); - final cache = $_typedResult.readTableOrNull(_groupGameTableRefsTable($_db)); + final cache = $_typedResult.readTableOrNull( + _groupMatchTableRefsTable($_db), + ); return ProcessedTableManager( manager.$state.copyWith(prefetchedData: cache), ); } } -class $$GameTableTableFilterComposer - extends Composer<_$AppDatabase, $GameTableTable> { - $$GameTableTableFilterComposer({ +class $$MatchTableTableFilterComposer + extends Composer<_$AppDatabase, $MatchTableTable> { + $$MatchTableTableFilterComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -2416,22 +2434,22 @@ class $$GameTableTableFilterComposer builder: (column) => ColumnFilters(column), ); - Expression playerGameTableRefs( - Expression Function($$PlayerGameTableTableFilterComposer f) f, + Expression playerMatchTableRefs( + Expression Function($$PlayerMatchTableTableFilterComposer f) f, ) { - final $$PlayerGameTableTableFilterComposer composer = $composerBuilder( + final $$PlayerMatchTableTableFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.playerGameTable, - getReferencedColumn: (t) => t.gameId, + referencedTable: $db.playerMatchTable, + getReferencedColumn: (t) => t.matchId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$PlayerGameTableTableFilterComposer( + }) => $$PlayerMatchTableTableFilterComposer( $db: $db, - $table: $db.playerGameTable, + $table: $db.playerMatchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -2441,22 +2459,22 @@ class $$GameTableTableFilterComposer return f(composer); } - Expression groupGameTableRefs( - Expression Function($$GroupGameTableTableFilterComposer f) f, + Expression groupMatchTableRefs( + Expression Function($$GroupMatchTableTableFilterComposer f) f, ) { - final $$GroupGameTableTableFilterComposer composer = $composerBuilder( + final $$GroupMatchTableTableFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.groupGameTable, - getReferencedColumn: (t) => t.gameId, + referencedTable: $db.groupMatchTable, + getReferencedColumn: (t) => t.matchId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GroupGameTableTableFilterComposer( + }) => $$GroupMatchTableTableFilterComposer( $db: $db, - $table: $db.groupGameTable, + $table: $db.groupMatchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -2467,9 +2485,9 @@ class $$GameTableTableFilterComposer } } -class $$GameTableTableOrderingComposer - extends Composer<_$AppDatabase, $GameTableTable> { - $$GameTableTableOrderingComposer({ +class $$MatchTableTableOrderingComposer + extends Composer<_$AppDatabase, $MatchTableTable> { + $$MatchTableTableOrderingComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -2497,9 +2515,9 @@ class $$GameTableTableOrderingComposer ); } -class $$GameTableTableAnnotationComposer - extends Composer<_$AppDatabase, $GameTableTable> { - $$GameTableTableAnnotationComposer({ +class $$MatchTableTableAnnotationComposer + extends Composer<_$AppDatabase, $MatchTableTable> { + $$MatchTableTableAnnotationComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -2518,22 +2536,22 @@ class $$GameTableTableAnnotationComposer GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); - Expression playerGameTableRefs( - Expression Function($$PlayerGameTableTableAnnotationComposer a) f, + Expression playerMatchTableRefs( + Expression Function($$PlayerMatchTableTableAnnotationComposer a) f, ) { - final $$PlayerGameTableTableAnnotationComposer composer = $composerBuilder( + final $$PlayerMatchTableTableAnnotationComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.playerGameTable, - getReferencedColumn: (t) => t.gameId, + referencedTable: $db.playerMatchTable, + getReferencedColumn: (t) => t.matchId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$PlayerGameTableTableAnnotationComposer( + }) => $$PlayerMatchTableTableAnnotationComposer( $db: $db, - $table: $db.playerGameTable, + $table: $db.playerMatchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -2543,22 +2561,22 @@ class $$GameTableTableAnnotationComposer return f(composer); } - Expression groupGameTableRefs( - Expression Function($$GroupGameTableTableAnnotationComposer a) f, + Expression groupMatchTableRefs( + Expression Function($$GroupMatchTableTableAnnotationComposer a) f, ) { - final $$GroupGameTableTableAnnotationComposer composer = $composerBuilder( + final $$GroupMatchTableTableAnnotationComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.groupGameTable, - getReferencedColumn: (t) => t.gameId, + referencedTable: $db.groupMatchTable, + getReferencedColumn: (t) => t.matchId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GroupGameTableTableAnnotationComposer( + }) => $$GroupMatchTableTableAnnotationComposer( $db: $db, - $table: $db.groupGameTable, + $table: $db.groupMatchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -2569,35 +2587,35 @@ class $$GameTableTableAnnotationComposer } } -class $$GameTableTableTableManager +class $$MatchTableTableTableManager extends RootTableManager< _$AppDatabase, - $GameTableTable, - GameTableData, - $$GameTableTableFilterComposer, - $$GameTableTableOrderingComposer, - $$GameTableTableAnnotationComposer, - $$GameTableTableCreateCompanionBuilder, - $$GameTableTableUpdateCompanionBuilder, - (GameTableData, $$GameTableTableReferences), - GameTableData, + $MatchTableTable, + MatchTableData, + $$MatchTableTableFilterComposer, + $$MatchTableTableOrderingComposer, + $$MatchTableTableAnnotationComposer, + $$MatchTableTableCreateCompanionBuilder, + $$MatchTableTableUpdateCompanionBuilder, + (MatchTableData, $$MatchTableTableReferences), + MatchTableData, PrefetchHooks Function({ - bool playerGameTableRefs, - bool groupGameTableRefs, + bool playerMatchTableRefs, + bool groupMatchTableRefs, }) > { - $$GameTableTableTableManager(_$AppDatabase db, $GameTableTable table) + $$MatchTableTableTableManager(_$AppDatabase db, $MatchTableTable table) : super( TableManagerState( db: db, table: table, createFilteringComposer: () => - $$GameTableTableFilterComposer($db: db, $table: table), + $$MatchTableTableFilterComposer($db: db, $table: table), createOrderingComposer: () => - $$GameTableTableOrderingComposer($db: db, $table: table), + $$MatchTableTableOrderingComposer($db: db, $table: table), createComputedFieldComposer: () => - $$GameTableTableAnnotationComposer($db: db, $table: table), + $$MatchTableTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value id = const Value.absent(), @@ -2605,7 +2623,7 @@ class $$GameTableTableTableManager Value winnerId = const Value.absent(), Value createdAt = const Value.absent(), Value rowid = const Value.absent(), - }) => GameTableCompanion( + }) => MatchTableCompanion( id: id, name: name, winnerId: winnerId, @@ -2619,7 +2637,7 @@ class $$GameTableTableTableManager Value winnerId = const Value.absent(), required DateTime createdAt, Value rowid = const Value.absent(), - }) => GameTableCompanion.insert( + }) => MatchTableCompanion.insert( id: id, name: name, winnerId: winnerId, @@ -2630,60 +2648,60 @@ class $$GameTableTableTableManager .map( (e) => ( e.readTable(table), - $$GameTableTableReferences(db, table, e), + $$MatchTableTableReferences(db, table, e), ), ) .toList(), prefetchHooksCallback: - ({playerGameTableRefs = false, groupGameTableRefs = false}) { + ({playerMatchTableRefs = false, groupMatchTableRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ - if (playerGameTableRefs) db.playerGameTable, - if (groupGameTableRefs) db.groupGameTable, + if (playerMatchTableRefs) db.playerMatchTable, + if (groupMatchTableRefs) db.groupMatchTable, ], addJoins: null, getPrefetchedDataCallback: (items) async { return [ - if (playerGameTableRefs) + if (playerMatchTableRefs) await $_getPrefetchedData< - GameTableData, - $GameTableTable, - PlayerGameTableData + MatchTableData, + $MatchTableTable, + PlayerMatchTableData >( currentTable: table, - referencedTable: $$GameTableTableReferences - ._playerGameTableRefsTable(db), + referencedTable: $$MatchTableTableReferences + ._playerMatchTableRefsTable(db), managerFromTypedResult: (p0) => - $$GameTableTableReferences( + $$MatchTableTableReferences( db, table, p0, - ).playerGameTableRefs, + ).playerMatchTableRefs, referencedItemsForCurrentItem: (item, referencedItems) => referencedItems.where( - (e) => e.gameId == item.id, + (e) => e.matchId == item.id, ), typedResults: items, ), - if (groupGameTableRefs) + if (groupMatchTableRefs) await $_getPrefetchedData< - GameTableData, - $GameTableTable, - GroupGameTableData + MatchTableData, + $MatchTableTable, + GroupMatchTableData >( currentTable: table, - referencedTable: $$GameTableTableReferences - ._groupGameTableRefsTable(db), + referencedTable: $$MatchTableTableReferences + ._groupMatchTableRefsTable(db), managerFromTypedResult: (p0) => - $$GameTableTableReferences( + $$MatchTableTableReferences( db, table, p0, - ).groupGameTableRefs, + ).groupMatchTableRefs, referencedItemsForCurrentItem: (item, referencedItems) => referencedItems.where( - (e) => e.gameId == item.id, + (e) => e.matchId == item.id, ), typedResults: items, ), @@ -2695,21 +2713,21 @@ class $$GameTableTableTableManager ); } -typedef $$GameTableTableProcessedTableManager = +typedef $$MatchTableTableProcessedTableManager = ProcessedTableManager< _$AppDatabase, - $GameTableTable, - GameTableData, - $$GameTableTableFilterComposer, - $$GameTableTableOrderingComposer, - $$GameTableTableAnnotationComposer, - $$GameTableTableCreateCompanionBuilder, - $$GameTableTableUpdateCompanionBuilder, - (GameTableData, $$GameTableTableReferences), - GameTableData, + $MatchTableTable, + MatchTableData, + $$MatchTableTableFilterComposer, + $$MatchTableTableOrderingComposer, + $$MatchTableTableAnnotationComposer, + $$MatchTableTableCreateCompanionBuilder, + $$MatchTableTableUpdateCompanionBuilder, + (MatchTableData, $$MatchTableTableReferences), + MatchTableData, PrefetchHooks Function({ - bool playerGameTableRefs, - bool groupGameTableRefs, + bool playerMatchTableRefs, + bool groupMatchTableRefs, }) >; typedef $$PlayerGroupTableTableCreateCompanionBuilder = @@ -3077,27 +3095,27 @@ typedef $$PlayerGroupTableTableProcessedTableManager = PlayerGroupTableData, PrefetchHooks Function({bool playerId, bool groupId}) >; -typedef $$PlayerGameTableTableCreateCompanionBuilder = - PlayerGameTableCompanion Function({ +typedef $$PlayerMatchTableTableCreateCompanionBuilder = + PlayerMatchTableCompanion Function({ required String playerId, - required String gameId, + required String matchId, Value rowid, }); -typedef $$PlayerGameTableTableUpdateCompanionBuilder = - PlayerGameTableCompanion Function({ +typedef $$PlayerMatchTableTableUpdateCompanionBuilder = + PlayerMatchTableCompanion Function({ Value playerId, - Value gameId, + Value matchId, Value rowid, }); -final class $$PlayerGameTableTableReferences +final class $$PlayerMatchTableTableReferences extends BaseReferences< _$AppDatabase, - $PlayerGameTableTable, - PlayerGameTableData + $PlayerMatchTableTable, + PlayerMatchTableData > { - $$PlayerGameTableTableReferences( + $$PlayerMatchTableTableReferences( super.$_db, super.$_table, super.$_typedResult, @@ -3105,7 +3123,7 @@ final class $$PlayerGameTableTableReferences static $PlayerTableTable _playerIdTable(_$AppDatabase db) => db.playerTable.createAlias( - $_aliasNameGenerator(db.playerGameTable.playerId, db.playerTable.id), + $_aliasNameGenerator(db.playerMatchTable.playerId, db.playerTable.id), ); $$PlayerTableTableProcessedTableManager get playerId { @@ -3122,19 +3140,19 @@ final class $$PlayerGameTableTableReferences ); } - static $GameTableTable _gameIdTable(_$AppDatabase db) => - db.gameTable.createAlias( - $_aliasNameGenerator(db.playerGameTable.gameId, db.gameTable.id), + static $MatchTableTable _matchIdTable(_$AppDatabase db) => + db.matchTable.createAlias( + $_aliasNameGenerator(db.playerMatchTable.matchId, db.matchTable.id), ); - $$GameTableTableProcessedTableManager get gameId { - final $_column = $_itemColumn('game_id')!; + $$MatchTableTableProcessedTableManager get matchId { + final $_column = $_itemColumn('match_id')!; - final manager = $$GameTableTableTableManager( + final manager = $$MatchTableTableTableManager( $_db, - $_db.gameTable, + $_db.matchTable, ).filter((f) => f.id.sqlEquals($_column)); - final item = $_typedResult.readTableOrNull(_gameIdTable($_db)); + final item = $_typedResult.readTableOrNull(_matchIdTable($_db)); if (item == null) return manager; return ProcessedTableManager( manager.$state.copyWith(prefetchedData: [item]), @@ -3142,9 +3160,9 @@ final class $$PlayerGameTableTableReferences } } -class $$PlayerGameTableTableFilterComposer - extends Composer<_$AppDatabase, $PlayerGameTableTable> { - $$PlayerGameTableTableFilterComposer({ +class $$PlayerMatchTableTableFilterComposer + extends Composer<_$AppDatabase, $PlayerMatchTableTable> { + $$PlayerMatchTableTableFilterComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -3174,20 +3192,20 @@ class $$PlayerGameTableTableFilterComposer return composer; } - $$GameTableTableFilterComposer get gameId { - final $$GameTableTableFilterComposer composer = $composerBuilder( + $$MatchTableTableFilterComposer get matchId { + final $$MatchTableTableFilterComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.gameId, - referencedTable: $db.gameTable, + getCurrentColumn: (t) => t.matchId, + referencedTable: $db.matchTable, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableFilterComposer( + }) => $$MatchTableTableFilterComposer( $db: $db, - $table: $db.gameTable, + $table: $db.matchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -3198,9 +3216,9 @@ class $$PlayerGameTableTableFilterComposer } } -class $$PlayerGameTableTableOrderingComposer - extends Composer<_$AppDatabase, $PlayerGameTableTable> { - $$PlayerGameTableTableOrderingComposer({ +class $$PlayerMatchTableTableOrderingComposer + extends Composer<_$AppDatabase, $PlayerMatchTableTable> { + $$PlayerMatchTableTableOrderingComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -3230,20 +3248,20 @@ class $$PlayerGameTableTableOrderingComposer return composer; } - $$GameTableTableOrderingComposer get gameId { - final $$GameTableTableOrderingComposer composer = $composerBuilder( + $$MatchTableTableOrderingComposer get matchId { + final $$MatchTableTableOrderingComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.gameId, - referencedTable: $db.gameTable, + getCurrentColumn: (t) => t.matchId, + referencedTable: $db.matchTable, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableOrderingComposer( + }) => $$MatchTableTableOrderingComposer( $db: $db, - $table: $db.gameTable, + $table: $db.matchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -3254,9 +3272,9 @@ class $$PlayerGameTableTableOrderingComposer } } -class $$PlayerGameTableTableAnnotationComposer - extends Composer<_$AppDatabase, $PlayerGameTableTable> { - $$PlayerGameTableTableAnnotationComposer({ +class $$PlayerMatchTableTableAnnotationComposer + extends Composer<_$AppDatabase, $PlayerMatchTableTable> { + $$PlayerMatchTableTableAnnotationComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -3286,20 +3304,20 @@ class $$PlayerGameTableTableAnnotationComposer return composer; } - $$GameTableTableAnnotationComposer get gameId { - final $$GameTableTableAnnotationComposer composer = $composerBuilder( + $$MatchTableTableAnnotationComposer get matchId { + final $$MatchTableTableAnnotationComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.gameId, - referencedTable: $db.gameTable, + getCurrentColumn: (t) => t.matchId, + referencedTable: $db.matchTable, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableAnnotationComposer( + }) => $$MatchTableTableAnnotationComposer( $db: $db, - $table: $db.gameTable, + $table: $db.matchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -3310,63 +3328,63 @@ class $$PlayerGameTableTableAnnotationComposer } } -class $$PlayerGameTableTableTableManager +class $$PlayerMatchTableTableTableManager extends RootTableManager< _$AppDatabase, - $PlayerGameTableTable, - PlayerGameTableData, - $$PlayerGameTableTableFilterComposer, - $$PlayerGameTableTableOrderingComposer, - $$PlayerGameTableTableAnnotationComposer, - $$PlayerGameTableTableCreateCompanionBuilder, - $$PlayerGameTableTableUpdateCompanionBuilder, - (PlayerGameTableData, $$PlayerGameTableTableReferences), - PlayerGameTableData, - PrefetchHooks Function({bool playerId, bool gameId}) + $PlayerMatchTableTable, + PlayerMatchTableData, + $$PlayerMatchTableTableFilterComposer, + $$PlayerMatchTableTableOrderingComposer, + $$PlayerMatchTableTableAnnotationComposer, + $$PlayerMatchTableTableCreateCompanionBuilder, + $$PlayerMatchTableTableUpdateCompanionBuilder, + (PlayerMatchTableData, $$PlayerMatchTableTableReferences), + PlayerMatchTableData, + PrefetchHooks Function({bool playerId, bool matchId}) > { - $$PlayerGameTableTableTableManager( + $$PlayerMatchTableTableTableManager( _$AppDatabase db, - $PlayerGameTableTable table, + $PlayerMatchTableTable table, ) : super( TableManagerState( db: db, table: table, createFilteringComposer: () => - $$PlayerGameTableTableFilterComposer($db: db, $table: table), + $$PlayerMatchTableTableFilterComposer($db: db, $table: table), createOrderingComposer: () => - $$PlayerGameTableTableOrderingComposer($db: db, $table: table), + $$PlayerMatchTableTableOrderingComposer($db: db, $table: table), createComputedFieldComposer: () => - $$PlayerGameTableTableAnnotationComposer($db: db, $table: table), + $$PlayerMatchTableTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value playerId = const Value.absent(), - Value gameId = const Value.absent(), + Value matchId = const Value.absent(), Value rowid = const Value.absent(), - }) => PlayerGameTableCompanion( + }) => PlayerMatchTableCompanion( playerId: playerId, - gameId: gameId, + matchId: matchId, rowid: rowid, ), createCompanionCallback: ({ required String playerId, - required String gameId, + required String matchId, Value rowid = const Value.absent(), - }) => PlayerGameTableCompanion.insert( + }) => PlayerMatchTableCompanion.insert( playerId: playerId, - gameId: gameId, + matchId: matchId, rowid: rowid, ), withReferenceMapper: (p0) => p0 .map( (e) => ( e.readTable(table), - $$PlayerGameTableTableReferences(db, table, e), + $$PlayerMatchTableTableReferences(db, table, e), ), ) .toList(), - prefetchHooksCallback: ({playerId = false, gameId = false}) { + prefetchHooksCallback: ({playerId = false, matchId = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], @@ -3392,26 +3410,26 @@ class $$PlayerGameTableTableTableManager currentTable: table, currentColumn: table.playerId, referencedTable: - $$PlayerGameTableTableReferences + $$PlayerMatchTableTableReferences ._playerIdTable(db), referencedColumn: - $$PlayerGameTableTableReferences + $$PlayerMatchTableTableReferences ._playerIdTable(db) .id, ) as T; } - if (gameId) { + if (matchId) { state = state.withJoin( currentTable: table, - currentColumn: table.gameId, + currentColumn: table.matchId, referencedTable: - $$PlayerGameTableTableReferences - ._gameIdTable(db), + $$PlayerMatchTableTableReferences + ._matchIdTable(db), referencedColumn: - $$PlayerGameTableTableReferences - ._gameIdTable(db) + $$PlayerMatchTableTableReferences + ._matchIdTable(db) .id, ) as T; @@ -3428,41 +3446,41 @@ class $$PlayerGameTableTableTableManager ); } -typedef $$PlayerGameTableTableProcessedTableManager = +typedef $$PlayerMatchTableTableProcessedTableManager = ProcessedTableManager< _$AppDatabase, - $PlayerGameTableTable, - PlayerGameTableData, - $$PlayerGameTableTableFilterComposer, - $$PlayerGameTableTableOrderingComposer, - $$PlayerGameTableTableAnnotationComposer, - $$PlayerGameTableTableCreateCompanionBuilder, - $$PlayerGameTableTableUpdateCompanionBuilder, - (PlayerGameTableData, $$PlayerGameTableTableReferences), - PlayerGameTableData, - PrefetchHooks Function({bool playerId, bool gameId}) + $PlayerMatchTableTable, + PlayerMatchTableData, + $$PlayerMatchTableTableFilterComposer, + $$PlayerMatchTableTableOrderingComposer, + $$PlayerMatchTableTableAnnotationComposer, + $$PlayerMatchTableTableCreateCompanionBuilder, + $$PlayerMatchTableTableUpdateCompanionBuilder, + (PlayerMatchTableData, $$PlayerMatchTableTableReferences), + PlayerMatchTableData, + PrefetchHooks Function({bool playerId, bool matchId}) >; -typedef $$GroupGameTableTableCreateCompanionBuilder = - GroupGameTableCompanion Function({ +typedef $$GroupMatchTableTableCreateCompanionBuilder = + GroupMatchTableCompanion Function({ required String groupId, - required String gameId, + required String matchId, Value rowid, }); -typedef $$GroupGameTableTableUpdateCompanionBuilder = - GroupGameTableCompanion Function({ +typedef $$GroupMatchTableTableUpdateCompanionBuilder = + GroupMatchTableCompanion Function({ Value groupId, - Value gameId, + Value matchId, Value rowid, }); -final class $$GroupGameTableTableReferences +final class $$GroupMatchTableTableReferences extends BaseReferences< _$AppDatabase, - $GroupGameTableTable, - GroupGameTableData + $GroupMatchTableTable, + GroupMatchTableData > { - $$GroupGameTableTableReferences( + $$GroupMatchTableTableReferences( super.$_db, super.$_table, super.$_typedResult, @@ -3470,7 +3488,7 @@ final class $$GroupGameTableTableReferences static $GroupTableTable _groupIdTable(_$AppDatabase db) => db.groupTable.createAlias( - $_aliasNameGenerator(db.groupGameTable.groupId, db.groupTable.id), + $_aliasNameGenerator(db.groupMatchTable.groupId, db.groupTable.id), ); $$GroupTableTableProcessedTableManager get groupId { @@ -3487,19 +3505,19 @@ final class $$GroupGameTableTableReferences ); } - static $GameTableTable _gameIdTable(_$AppDatabase db) => - db.gameTable.createAlias( - $_aliasNameGenerator(db.groupGameTable.gameId, db.gameTable.id), + static $MatchTableTable _matchIdTable(_$AppDatabase db) => + db.matchTable.createAlias( + $_aliasNameGenerator(db.groupMatchTable.matchId, db.matchTable.id), ); - $$GameTableTableProcessedTableManager get gameId { - final $_column = $_itemColumn('game_id')!; + $$MatchTableTableProcessedTableManager get matchId { + final $_column = $_itemColumn('match_id')!; - final manager = $$GameTableTableTableManager( + final manager = $$MatchTableTableTableManager( $_db, - $_db.gameTable, + $_db.matchTable, ).filter((f) => f.id.sqlEquals($_column)); - final item = $_typedResult.readTableOrNull(_gameIdTable($_db)); + final item = $_typedResult.readTableOrNull(_matchIdTable($_db)); if (item == null) return manager; return ProcessedTableManager( manager.$state.copyWith(prefetchedData: [item]), @@ -3507,9 +3525,9 @@ final class $$GroupGameTableTableReferences } } -class $$GroupGameTableTableFilterComposer - extends Composer<_$AppDatabase, $GroupGameTableTable> { - $$GroupGameTableTableFilterComposer({ +class $$GroupMatchTableTableFilterComposer + extends Composer<_$AppDatabase, $GroupMatchTableTable> { + $$GroupMatchTableTableFilterComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -3539,20 +3557,20 @@ class $$GroupGameTableTableFilterComposer return composer; } - $$GameTableTableFilterComposer get gameId { - final $$GameTableTableFilterComposer composer = $composerBuilder( + $$MatchTableTableFilterComposer get matchId { + final $$MatchTableTableFilterComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.gameId, - referencedTable: $db.gameTable, + getCurrentColumn: (t) => t.matchId, + referencedTable: $db.matchTable, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableFilterComposer( + }) => $$MatchTableTableFilterComposer( $db: $db, - $table: $db.gameTable, + $table: $db.matchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -3563,9 +3581,9 @@ class $$GroupGameTableTableFilterComposer } } -class $$GroupGameTableTableOrderingComposer - extends Composer<_$AppDatabase, $GroupGameTableTable> { - $$GroupGameTableTableOrderingComposer({ +class $$GroupMatchTableTableOrderingComposer + extends Composer<_$AppDatabase, $GroupMatchTableTable> { + $$GroupMatchTableTableOrderingComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -3595,20 +3613,20 @@ class $$GroupGameTableTableOrderingComposer return composer; } - $$GameTableTableOrderingComposer get gameId { - final $$GameTableTableOrderingComposer composer = $composerBuilder( + $$MatchTableTableOrderingComposer get matchId { + final $$MatchTableTableOrderingComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.gameId, - referencedTable: $db.gameTable, + getCurrentColumn: (t) => t.matchId, + referencedTable: $db.matchTable, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableOrderingComposer( + }) => $$MatchTableTableOrderingComposer( $db: $db, - $table: $db.gameTable, + $table: $db.matchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -3619,9 +3637,9 @@ class $$GroupGameTableTableOrderingComposer } } -class $$GroupGameTableTableAnnotationComposer - extends Composer<_$AppDatabase, $GroupGameTableTable> { - $$GroupGameTableTableAnnotationComposer({ +class $$GroupMatchTableTableAnnotationComposer + extends Composer<_$AppDatabase, $GroupMatchTableTable> { + $$GroupMatchTableTableAnnotationComposer({ required super.$db, required super.$table, super.joinBuilder, @@ -3651,20 +3669,20 @@ class $$GroupGameTableTableAnnotationComposer return composer; } - $$GameTableTableAnnotationComposer get gameId { - final $$GameTableTableAnnotationComposer composer = $composerBuilder( + $$MatchTableTableAnnotationComposer get matchId { + final $$MatchTableTableAnnotationComposer composer = $composerBuilder( composer: this, - getCurrentColumn: (t) => t.gameId, - referencedTable: $db.gameTable, + getCurrentColumn: (t) => t.matchId, + referencedTable: $db.matchTable, getReferencedColumn: (t) => t.id, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GameTableTableAnnotationComposer( + }) => $$MatchTableTableAnnotationComposer( $db: $db, - $table: $db.gameTable, + $table: $db.matchTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -3675,63 +3693,63 @@ class $$GroupGameTableTableAnnotationComposer } } -class $$GroupGameTableTableTableManager +class $$GroupMatchTableTableTableManager extends RootTableManager< _$AppDatabase, - $GroupGameTableTable, - GroupGameTableData, - $$GroupGameTableTableFilterComposer, - $$GroupGameTableTableOrderingComposer, - $$GroupGameTableTableAnnotationComposer, - $$GroupGameTableTableCreateCompanionBuilder, - $$GroupGameTableTableUpdateCompanionBuilder, - (GroupGameTableData, $$GroupGameTableTableReferences), - GroupGameTableData, - PrefetchHooks Function({bool groupId, bool gameId}) + $GroupMatchTableTable, + GroupMatchTableData, + $$GroupMatchTableTableFilterComposer, + $$GroupMatchTableTableOrderingComposer, + $$GroupMatchTableTableAnnotationComposer, + $$GroupMatchTableTableCreateCompanionBuilder, + $$GroupMatchTableTableUpdateCompanionBuilder, + (GroupMatchTableData, $$GroupMatchTableTableReferences), + GroupMatchTableData, + PrefetchHooks Function({bool groupId, bool matchId}) > { - $$GroupGameTableTableTableManager( + $$GroupMatchTableTableTableManager( _$AppDatabase db, - $GroupGameTableTable table, + $GroupMatchTableTable table, ) : super( TableManagerState( db: db, table: table, createFilteringComposer: () => - $$GroupGameTableTableFilterComposer($db: db, $table: table), + $$GroupMatchTableTableFilterComposer($db: db, $table: table), createOrderingComposer: () => - $$GroupGameTableTableOrderingComposer($db: db, $table: table), + $$GroupMatchTableTableOrderingComposer($db: db, $table: table), createComputedFieldComposer: () => - $$GroupGameTableTableAnnotationComposer($db: db, $table: table), + $$GroupMatchTableTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value groupId = const Value.absent(), - Value gameId = const Value.absent(), + Value matchId = const Value.absent(), Value rowid = const Value.absent(), - }) => GroupGameTableCompanion( + }) => GroupMatchTableCompanion( groupId: groupId, - gameId: gameId, + matchId: matchId, rowid: rowid, ), createCompanionCallback: ({ required String groupId, - required String gameId, + required String matchId, Value rowid = const Value.absent(), - }) => GroupGameTableCompanion.insert( + }) => GroupMatchTableCompanion.insert( groupId: groupId, - gameId: gameId, + matchId: matchId, rowid: rowid, ), withReferenceMapper: (p0) => p0 .map( (e) => ( e.readTable(table), - $$GroupGameTableTableReferences(db, table, e), + $$GroupMatchTableTableReferences(db, table, e), ), ) .toList(), - prefetchHooksCallback: ({groupId = false, gameId = false}) { + prefetchHooksCallback: ({groupId = false, matchId = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [], @@ -3756,25 +3774,27 @@ class $$GroupGameTableTableTableManager state.withJoin( currentTable: table, currentColumn: table.groupId, - referencedTable: $$GroupGameTableTableReferences - ._groupIdTable(db), + referencedTable: + $$GroupMatchTableTableReferences + ._groupIdTable(db), referencedColumn: - $$GroupGameTableTableReferences + $$GroupMatchTableTableReferences ._groupIdTable(db) .id, ) as T; } - if (gameId) { + if (matchId) { state = state.withJoin( currentTable: table, - currentColumn: table.gameId, - referencedTable: $$GroupGameTableTableReferences - ._gameIdTable(db), + currentColumn: table.matchId, + referencedTable: + $$GroupMatchTableTableReferences + ._matchIdTable(db), referencedColumn: - $$GroupGameTableTableReferences - ._gameIdTable(db) + $$GroupMatchTableTableReferences + ._matchIdTable(db) .id, ) as T; @@ -3791,19 +3811,19 @@ class $$GroupGameTableTableTableManager ); } -typedef $$GroupGameTableTableProcessedTableManager = +typedef $$GroupMatchTableTableProcessedTableManager = ProcessedTableManager< _$AppDatabase, - $GroupGameTableTable, - GroupGameTableData, - $$GroupGameTableTableFilterComposer, - $$GroupGameTableTableOrderingComposer, - $$GroupGameTableTableAnnotationComposer, - $$GroupGameTableTableCreateCompanionBuilder, - $$GroupGameTableTableUpdateCompanionBuilder, - (GroupGameTableData, $$GroupGameTableTableReferences), - GroupGameTableData, - PrefetchHooks Function({bool groupId, bool gameId}) + $GroupMatchTableTable, + GroupMatchTableData, + $$GroupMatchTableTableFilterComposer, + $$GroupMatchTableTableOrderingComposer, + $$GroupMatchTableTableAnnotationComposer, + $$GroupMatchTableTableCreateCompanionBuilder, + $$GroupMatchTableTableUpdateCompanionBuilder, + (GroupMatchTableData, $$GroupMatchTableTableReferences), + GroupMatchTableData, + PrefetchHooks Function({bool groupId, bool matchId}) >; class $AppDatabaseManager { @@ -3813,12 +3833,12 @@ class $AppDatabaseManager { $$PlayerTableTableTableManager(_db, _db.playerTable); $$GroupTableTableTableManager get groupTable => $$GroupTableTableTableManager(_db, _db.groupTable); - $$GameTableTableTableManager get gameTable => - $$GameTableTableTableManager(_db, _db.gameTable); + $$MatchTableTableTableManager get matchTable => + $$MatchTableTableTableManager(_db, _db.matchTable); $$PlayerGroupTableTableTableManager get playerGroupTable => $$PlayerGroupTableTableTableManager(_db, _db.playerGroupTable); - $$PlayerGameTableTableTableManager get playerGameTable => - $$PlayerGameTableTableTableManager(_db, _db.playerGameTable); - $$GroupGameTableTableTableManager get groupGameTable => - $$GroupGameTableTableTableManager(_db, _db.groupGameTable); + $$PlayerMatchTableTableTableManager get playerMatchTable => + $$PlayerMatchTableTableTableManager(_db, _db.playerMatchTable); + $$GroupMatchTableTableTableManager get groupMatchTable => + $$GroupMatchTableTableTableManager(_db, _db.groupMatchTable); } diff --git a/lib/data/db/tables/group_game_table.dart b/lib/data/db/tables/group_game_table.dart deleted file mode 100644 index a16672e..0000000 --- a/lib/data/db/tables/group_game_table.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:game_tracker/data/db/tables/game_table.dart'; -import 'package:game_tracker/data/db/tables/group_table.dart'; - -class GroupGameTable extends Table { - TextColumn get groupId => - text().references(GroupTable, #id, onDelete: KeyAction.cascade)(); - TextColumn get gameId => - text().references(GameTable, #id, onDelete: KeyAction.cascade)(); - - @override - Set> get primaryKey => {groupId, gameId}; -} diff --git a/lib/data/db/tables/group_match_table.dart b/lib/data/db/tables/group_match_table.dart new file mode 100644 index 0000000..3f77dcb --- /dev/null +++ b/lib/data/db/tables/group_match_table.dart @@ -0,0 +1,13 @@ +import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/tables/group_table.dart'; +import 'package:game_tracker/data/db/tables/match_table.dart'; + +class GroupMatchTable extends Table { + TextColumn get groupId => + text().references(GroupTable, #id, onDelete: KeyAction.cascade)(); + TextColumn get matchId => + text().references(MatchTable, #id, onDelete: KeyAction.cascade)(); + + @override + Set> get primaryKey => {groupId, matchId}; +} diff --git a/lib/data/db/tables/game_table.dart b/lib/data/db/tables/match_table.dart similarity index 88% rename from lib/data/db/tables/game_table.dart rename to lib/data/db/tables/match_table.dart index 1a37a73..96aff2a 100644 --- a/lib/data/db/tables/game_table.dart +++ b/lib/data/db/tables/match_table.dart @@ -1,6 +1,6 @@ import 'package:drift/drift.dart'; -class GameTable extends Table { +class MatchTable extends Table { TextColumn get id => text()(); TextColumn get name => text()(); late final winnerId = text().nullable()(); diff --git a/lib/data/db/tables/player_game_table.dart b/lib/data/db/tables/player_game_table.dart deleted file mode 100644 index 74c36fe..0000000 --- a/lib/data/db/tables/player_game_table.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:game_tracker/data/db/tables/game_table.dart'; -import 'package:game_tracker/data/db/tables/player_table.dart'; - -class PlayerGameTable extends Table { - TextColumn get playerId => - text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); - TextColumn get gameId => - text().references(GameTable, #id, onDelete: KeyAction.cascade)(); - - @override - Set> get primaryKey => {playerId, gameId}; -} diff --git a/lib/data/db/tables/player_match_table.dart b/lib/data/db/tables/player_match_table.dart new file mode 100644 index 0000000..e155cd5 --- /dev/null +++ b/lib/data/db/tables/player_match_table.dart @@ -0,0 +1,13 @@ +import 'package:drift/drift.dart'; +import 'package:game_tracker/data/db/tables/match_table.dart'; +import 'package:game_tracker/data/db/tables/player_table.dart'; + +class PlayerMatchTable extends Table { + TextColumn get playerId => + text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); + TextColumn get matchId => + text().references(MatchTable, #id, onDelete: KeyAction.cascade)(); + + @override + Set> get primaryKey => {playerId, matchId}; +} diff --git a/lib/data/dto/game.dart b/lib/data/dto/match.dart similarity index 81% rename from lib/data/dto/game.dart rename to lib/data/dto/match.dart index 48ef902..fcb4dae 100644 --- a/lib/data/dto/game.dart +++ b/lib/data/dto/match.dart @@ -3,7 +3,7 @@ import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:uuid/uuid.dart'; -class Game { +class Match { final String id; final DateTime createdAt; final String name; @@ -11,7 +11,7 @@ class Game { final Group? group; final Player? winner; - Game({ + Match({ String? id, DateTime? createdAt, required this.name, @@ -23,11 +23,11 @@ class Game { @override String toString() { - return 'Game{\n\tid: $id,\n\tname: $name,\n\tplayers: $players,\n\tgroup: $group,\n\twinner: $winner\n}'; + return 'Match{\n\tid: $id,\n\tname: $name,\n\tplayers: $players,\n\tgroup: $group,\n\twinner: $winner\n}'; } - /// Creates a Game instance from a JSON object. - Game.fromJson(Map json) + /// Creates a Match instance from a JSON object. + Match.fromJson(Map json) : id = json['id'], name = json['name'], createdAt = DateTime.parse(json['createdAt']), @@ -39,7 +39,7 @@ class Game { group = json['group'] != null ? Group.fromJson(json['group']) : null, winner = json['winner'] != null ? Player.fromJson(json['winner']) : null; - /// Converts the Game instance to a JSON object. + /// Converts the Match instance to a JSON object. Map toJson() => { 'id': id, 'createdAt': createdAt.toIso8601String(), diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 71a072e..2ec28fa 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/views/main_menu/game_history_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/groups_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/group_view/groups_view.dart'; import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/match_view/match_view.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; import 'package:game_tracker/presentation/views/main_menu/statistics_view.dart'; import 'package:game_tracker/presentation/widgets/navbar_item.dart'; @@ -30,8 +30,8 @@ class _CustomNavigationBarState extends State final List tabs = [ KeyedSubtree(key: ValueKey('home_$tabKeyCount'), child: const HomeView()), KeyedSubtree( - key: ValueKey('games_$tabKeyCount'), - child: const GameHistoryView(), + key: ValueKey('matches_$tabKeyCount'), + child: const MatchView(), ), KeyedSubtree( key: ValueKey('groups_$tabKeyCount'), @@ -96,7 +96,7 @@ class _CustomNavigationBarState extends State index: 1, isSelected: currentIndex == 1, icon: Icons.gamepad_rounded, - label: 'Games', + label: 'Matches', onTabTapped: onTabTapped, ), NavbarItem( @@ -133,7 +133,7 @@ class _CustomNavigationBarState extends State case 0: return 'Home'; case 1: - return 'Game History'; + return 'Matches'; case 2: return 'Groups'; case 3: diff --git a/lib/presentation/views/main_menu/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart similarity index 100% rename from lib/presentation/views/main_menu/create_group_view.dart rename to lib/presentation/views/main_menu/group_view/create_group_view.dart diff --git a/lib/presentation/views/main_menu/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart similarity index 97% rename from lib/presentation/views/main_menu/groups_view.dart rename to lib/presentation/views/main_menu/group_view/groups_view.dart index ce47f90..5fd5e4b 100644 --- a/lib/presentation/views/main_menu/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -3,7 +3,7 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/presentation/views/main_menu/create_group_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/group_view/create_group_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; @@ -25,7 +25,7 @@ class _GroupsViewState extends State { late final List skeletonData = List.filled( 7, Group( - name: 'Skeleton Game', + name: 'Skeleton Match', members: [player, player, player, player, player, player], ), ); diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 1667f2b..c6322d8 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart'; @@ -18,15 +18,15 @@ class HomeView extends StatefulWidget { } class _HomeViewState extends State { - late Future _gameCountFuture; + late Future _matchCountFuture; late Future _groupCountFuture; - late Future> _recentGamesFuture; + late Future> _recentMatchesFuture; bool isLoading = true; - late final List skeletonData = List.filled( + late final List skeletonData = List.filled( 2, - Game( - name: 'Skeleton Game', + Match( + name: 'Skeleton Match', group: Group( name: 'Skeleton Group', members: [ @@ -42,20 +42,22 @@ class _HomeViewState extends State { initState() { super.initState(); final db = Provider.of(context, listen: false); - _gameCountFuture = db.gameDao.getGameCount(); + _matchCountFuture = db.matchDao.getMatchCount(); _groupCountFuture = db.groupDao.getGroupCount(); - _recentGamesFuture = db.gameDao.getAllGames(); + _recentMatchesFuture = db.matchDao.getAllMatches(); - Future.wait([_gameCountFuture, _groupCountFuture, _recentGamesFuture]).then( - (_) async { - await Future.delayed(const Duration(milliseconds: 250)); - if (mounted) { - setState(() { - isLoading = false; - }); - } - }, - ); + Future.wait([ + _matchCountFuture, + _groupCountFuture, + _recentMatchesFuture, + ]).then((_) async { + await Future.delayed(const Duration(milliseconds: 250)); + if (mounted) { + setState(() { + isLoading = false; + }); + } + }); } @override @@ -72,7 +74,7 @@ class _HomeViewState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ FutureBuilder( - future: _gameCountFuture, + future: _matchCountFuture, builder: (context, snapshot) { final int count = (snapshot.hasData) ? snapshot.data! @@ -115,11 +117,11 @@ class _HomeViewState extends State { content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), child: FutureBuilder( - future: _recentGamesFuture, + future: _recentMatchesFuture, builder: ( BuildContext context, - AsyncSnapshot> snapshot, + AsyncSnapshot> snapshot, ) { if (snapshot.hasError) { return const Center( @@ -129,7 +131,7 @@ class _HomeViewState extends State { ), ); } - final List games = + final List matches = (isLoading ? skeletonData : (snapshot.data ?? []) @@ -140,19 +142,19 @@ class _HomeViewState extends State { )) .take(2) .toList(); - if (games.isNotEmpty) { + if (matches.isNotEmpty) { return Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - GameTile( - gameTitle: games[0].name, - gameType: 'Winner', + MatchTile( + matchTitle: matches[0].name, + game: 'Winner', ruleset: 'Ruleset', - players: _getPlayerText(games[0]), - winner: games[0].winner == null + players: _getPlayerText(matches[0]), + winner: matches[0].winner == null ? 'Game in progress...' - : games[0].winner!.name, + : matches[0].winner!.name, ), const Padding( padding: EdgeInsets.symmetric( @@ -160,15 +162,15 @@ class _HomeViewState extends State { ), child: Divider(), ), - if (games.length > 1) ...[ - GameTile( - gameTitle: games[1].name, - gameType: 'Winner', + if (matches.length > 1) ...[ + MatchTile( + matchTitle: matches[1].name, + game: 'Winner', ruleset: 'Ruleset', - players: _getPlayerText(games[1]), - winner: games[1].winner == null + players: _getPlayerText(matches[1]), + winner: matches[1].winner == null ? 'Game in progress...' - : games[1].winner!.name, + : matches[1].winner!.name, ), const SizedBox(height: 8), ] else ...[ @@ -249,7 +251,7 @@ class _HomeViewState extends State { ); } - String _getPlayerText(Game game) { + String _getPlayerText(Match game) { if (game.group == null) { final playerCount = game.players?.length ?? 0; return '$playerCount Players'; diff --git a/lib/presentation/views/main_menu/create_game/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart similarity index 100% rename from lib/presentation/views/main_menu/create_game/choose_game_view.dart rename to lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart diff --git a/lib/presentation/views/main_menu/create_game/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart similarity index 100% rename from lib/presentation/views/main_menu/create_game/choose_group_view.dart rename to lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart diff --git a/lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart similarity index 100% rename from lib/presentation/views/main_menu/create_game/choose_ruleset_view.dart rename to lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart diff --git a/lib/presentation/views/main_menu/create_game/create_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart similarity index 91% rename from lib/presentation/views/main_menu/create_game/create_game_view.dart rename to lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index eef2de6..03c081c 100644 --- a/lib/presentation/views/main_menu/create_game/create_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -3,28 +3,28 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/presentation/views/main_menu/create_game/choose_game_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/create_game/choose_group_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/create_game/choose_ruleset_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/game_result_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_game_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_group_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/player_selection.dart'; import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart'; import 'package:game_tracker/presentation/widgets/tiles/choose_tile.dart'; import 'package:provider/provider.dart'; -class CreateGameView extends StatefulWidget { +class CreateMatchView extends StatefulWidget { final VoidCallback? onWinnerChanged; - const CreateGameView({super.key, this.onWinnerChanged}); + const CreateMatchView({super.key, this.onWinnerChanged}); @override - State createState() => _CreateGameViewState(); + State createState() => _CreateMatchViewState(); } -class _CreateGameViewState extends State { +class _CreateMatchViewState extends State { /// Reference to the app database late final AppDatabase db; @@ -234,20 +234,20 @@ class _CreateGameViewState extends State { buttonType: ButtonType.primary, onPressed: _enableCreateGameButton() ? () async { - Game game = Game( + Match match = Match( name: _gameNameController.text.trim(), createdAt: DateTime.now(), group: selectedGroup, players: selectedPlayers, ); - await db.gameDao.addGame(game: game); + await db.matchDao.addMatch(match: match); if (context.mounted) { Navigator.pushReplacement( context, CupertinoPageRoute( fullscreenDialog: true, builder: (context) => GameResultView( - game: game, + match: match, onWinnerChanged: widget.onWinnerChanged, ), ), diff --git a/lib/presentation/views/main_menu/game_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart similarity index 90% rename from lib/presentation/views/main_menu/game_result_view.dart rename to lib/presentation/views/main_menu/match_view/match_result_view.dart index 6e60410..58ff9ce 100644 --- a/lib/presentation/views/main_menu/game_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -1,17 +1,17 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart'; import 'package:provider/provider.dart'; class GameResultView extends StatefulWidget { - final Game game; + final Match match; final VoidCallback? onWinnerChanged; - const GameResultView({super.key, required this.game, this.onWinnerChanged}); + const GameResultView({super.key, required this.match, this.onWinnerChanged}); @override State createState() => _GameResultViewState(); } @@ -24,10 +24,10 @@ class _GameResultViewState extends State { @override void initState() { db = Provider.of(context, listen: false); - allPlayers = getAllPlayers(widget.game); - if (widget.game.winner != null) { + allPlayers = getAllPlayers(widget.match); + if (widget.match.winner != null) { _selectedPlayer = allPlayers.firstWhere( - (p) => p.id == widget.game.winner!.id, + (p) => p.id == widget.match.winner!.id, ); } super.initState(); @@ -41,7 +41,7 @@ class _GameResultViewState extends State { backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: Text( - widget.game.name, + widget.match.name, style: const TextStyle( fontSize: 20, fontWeight: FontWeight.bold, @@ -125,17 +125,17 @@ class _GameResultViewState extends State { Future _handleWinnerSaving() async { if (_selectedPlayer == null) { - await db.gameDao.removeWinner(gameId: widget.game.id); + await db.matchDao.removeWinner(matchId: widget.match.id); } else { - await db.gameDao.setWinner( - gameId: widget.game.id, + await db.matchDao.setWinner( + matchId: widget.match.id, winnerId: _selectedPlayer!.id, ); } widget.onWinnerChanged?.call(); } - List getAllPlayers(Game game) { + List getAllPlayers(Match game) { if (game.group == null && game.players != null) { return [...game.players!]; } else if (game.group != null && game.players != null) { diff --git a/lib/presentation/views/main_menu/game_history_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart similarity index 79% rename from lib/presentation/views/main_menu/game_history_view.dart rename to lib/presentation/views/main_menu/match_view/match_view.dart index 46cb1db..92fd268 100644 --- a/lib/presentation/views/main_menu/game_history_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -1,32 +1,34 @@ +import 'dart:core' hide Match; + import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/presentation/views/main_menu/create_game/create_game_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/game_result_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/create_match_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; -class GameHistoryView extends StatefulWidget { - const GameHistoryView({super.key}); +class MatchView extends StatefulWidget { + const MatchView({super.key}); @override - State createState() => _GameHistoryViewState(); + State createState() => _MatchViewState(); } -class _GameHistoryViewState extends State { - late Future> _gameListFuture; +class _MatchViewState extends State { + late Future> _gameListFuture; late final AppDatabase db; - late final List skeletonData = List.filled( + late final List skeletonData = List.filled( 4, - Game( + Match( name: 'Skeleton Gamename', group: Group( name: 'Groupname', @@ -43,7 +45,7 @@ class _GameHistoryViewState extends State { db = Provider.of(context, listen: false); _gameListFuture = Future.delayed( const Duration(milliseconds: 250), - () => db.gameDao.getAllGames(), + () => db.matchDao.getAllMatches(), ); } @@ -54,10 +56,10 @@ class _GameHistoryViewState extends State { body: Stack( alignment: Alignment.center, children: [ - FutureBuilder>( + FutureBuilder>( future: _gameListFuture, builder: - (BuildContext context, AsyncSnapshot> snapshot) { + (BuildContext context, AsyncSnapshot> snapshot) { if (snapshot.hasError) { return const Center( child: TopCenteredMessage( @@ -79,7 +81,7 @@ class _GameHistoryViewState extends State { } final bool isLoading = snapshot.connectionState == ConnectionState.waiting; - final List games = + final List matches = (isLoading ? skeletonData : (snapshot.data ?? []) ..sort( (a, b) => b.createdAt.compareTo(a.createdAt), @@ -89,9 +91,9 @@ class _GameHistoryViewState extends State { enabled: isLoading, child: ListView.builder( padding: const EdgeInsets.only(bottom: 85), - itemCount: games.length + 1, + itemCount: matches.length + 1, itemBuilder: (BuildContext context, int index) { - if (index == games.length) { + if (index == matches.length) { return SizedBox( height: MediaQuery.paddingOf(context).bottom - 80, ); @@ -103,13 +105,13 @@ class _GameHistoryViewState extends State { CupertinoPageRoute( fullscreenDialog: true, builder: (context) => GameResultView( - game: games[index], + match: matches[index], onWinnerChanged: refreshGameList, ), ), ); }, - game: games[index], + match: matches[index], ); }, ), @@ -126,7 +128,7 @@ class _GameHistoryViewState extends State { context, MaterialPageRoute( builder: (context) => - CreateGameView(onWinnerChanged: refreshGameList), + CreateMatchView(onWinnerChanged: refreshGameList), ), ); }, @@ -139,7 +141,7 @@ class _GameHistoryViewState extends State { void refreshGameList() { setState(() { - _gameListFuture = db.gameDao.getAllGames(); + _gameListFuture = db.matchDao.getAllMatches(); }); } } diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 564d0d5..17376b2 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/tiles/statistics_tile.dart'; @@ -14,10 +14,10 @@ class StatisticsView extends StatefulWidget { } class _StatisticsViewState extends State { - late Future> _gamesFuture; + late Future> _matchesFuture; late Future> _playersFuture; List<(String, int)> winCounts = List.filled(6, ('Skeleton Player', 1)); - List<(String, int)> gameCounts = List.filled(6, ('Skeleton Player', 1)); + List<(String, int)> matchCounts = List.filled(6, ('Skeleton Player', 1)); List<(String, double)> winRates = List.filled(6, ('Skeleton Player', 1)); bool isLoading = true; @@ -25,16 +25,16 @@ class _StatisticsViewState extends State { void initState() { super.initState(); final db = Provider.of(context, listen: false); - _gamesFuture = db.gameDao.getAllGames(); + _matchesFuture = db.matchDao.getAllMatches(); _playersFuture = db.playerDao.getAllPlayers(); - Future.wait([_gamesFuture, _playersFuture]).then((results) async { + Future.wait([_matchesFuture, _playersFuture]).then((results) async { await Future.delayed(const Duration(milliseconds: 250)); - final games = results[0] as List; + final matches = results[0] as List; final players = results[1] as List; - winCounts = _calculateWinsForAllPlayers(games, players); - gameCounts = _calculateGameAmountsForAllPlayers(games, players); - winRates = computeWinRatePercent(wins: winCounts, games: gameCounts); + winCounts = _calculateWinsForAllPlayers(matches, players); + matchCounts = _calculateGameAmountsForAllPlayers(matches, players); + winRates = computeWinRatePercent(wins: winCounts, matches: matchCounts); if (mounted) { setState(() { isLoading = false; @@ -78,9 +78,9 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, - title: 'Games per Player', + title: 'Match per Player', width: constraints.maxWidth * 0.95, - values: gameCounts, + values: matchCounts, itemCount: 10, barColor: Colors.green, ), @@ -97,7 +97,7 @@ class _StatisticsViewState extends State { /// Calculates the number of wins for each player /// and returns a sorted list of tuples (playerName, winCount) List<(String, int)> _calculateWinsForAllPlayers( - List games, + List games, List players, ) { List<(String, int)> winCounts = []; @@ -141,39 +141,39 @@ class _StatisticsViewState extends State { return winCounts; } - /// Calculates the number of games played for each player - /// and returns a sorted list of tuples (playerName, gameCount) + /// Calculates the number of matches played for each player + /// and returns a sorted list of tuples (playerName, matchCount) List<(String, int)> _calculateGameAmountsForAllPlayers( - List games, + List matches, List players, ) { - List<(String, int)> gameCounts = []; + List<(String, int)> matchCounts = []; - // Counting games for each player - for (var game in games) { - if (game.group != null) { - final members = game.group!.members.map((p) => p.id).toList(); + // Counting matches for each player + for (var match in matches) { + if (match.group != null) { + final members = match.group!.members.map((p) => p.id).toList(); for (var playerId in members) { - final index = gameCounts.indexWhere((entry) => entry.$1 == playerId); + final index = matchCounts.indexWhere((entry) => entry.$1 == playerId); // -1 means player not found in gameCounts if (index != -1) { - final current = gameCounts[index].$2; - gameCounts[index] = (playerId, current + 1); + final current = matchCounts[index].$2; + matchCounts[index] = (playerId, current + 1); } else { - gameCounts.add((playerId, 1)); + matchCounts.add((playerId, 1)); } } } - if (game.players != null) { - final members = game.players!.map((p) => p.id).toList(); + if (match.players != null) { + final members = match.players!.map((p) => p.id).toList(); for (var playerId in members) { - final index = gameCounts.indexWhere((entry) => entry.$1 == playerId); + final index = matchCounts.indexWhere((entry) => entry.$1 == playerId); // -1 means player not found in gameCounts if (index != -1) { - final current = gameCounts[index].$2; - gameCounts[index] = (playerId, current + 1); + final current = matchCounts[index].$2; + matchCounts[index] = (playerId, current + 1); } else { - gameCounts.add((playerId, 1)); + matchCounts.add((playerId, 1)); } } } @@ -181,35 +181,35 @@ class _StatisticsViewState extends State { // Adding all players with zero games for (var player in players) { - final index = gameCounts.indexWhere((entry) => entry.$1 == player.id); + final index = matchCounts.indexWhere((entry) => entry.$1 == player.id); // -1 means player not found in gameCounts if (index == -1) { - gameCounts.add((player.id, 0)); + matchCounts.add((player.id, 0)); } } // Replace player IDs with names - for (int i = 0; i < gameCounts.length; i++) { - final playerId = gameCounts[i].$1; + for (int i = 0; i < matchCounts.length; i++) { + final playerId = matchCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, orElse: () => Player(id: playerId, name: 'N.a.'), ); - gameCounts[i] = (player.name, gameCounts[i].$2); + matchCounts[i] = (player.name, matchCounts[i].$2); } - gameCounts.sort((a, b) => b.$2.compareTo(a.$2)); + matchCounts.sort((a, b) => b.$2.compareTo(a.$2)); - return gameCounts; + return matchCounts; } // dart List<(String, double)> computeWinRatePercent({ required List<(String, int)> wins, - required List<(String, int)> games, + required List<(String, int)> matches, }) { final Map winsMap = {for (var e in wins) e.$1: e.$2}; - final Map gamesMap = {for (var e in games) e.$1: e.$2}; + final Map gamesMap = {for (var e in matches) e.$1: e.$2}; // Get all unique player names final names = {...winsMap.keys, ...gamesMap.keys}; diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index 83da859..f79edc3 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/data/dto/game.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:intl/intl.dart'; class GameHistoryTile extends StatefulWidget { - final Game game; + final Match match; final VoidCallback onTap; - const GameHistoryTile({super.key, required this.game, required this.onTap}); + const GameHistoryTile({super.key, required this.match, required this.onTap}); @override State createState() => _GameHistoryTileState(); @@ -17,8 +17,8 @@ class GameHistoryTile extends StatefulWidget { class _GameHistoryTileState extends State { @override Widget build(BuildContext context) { - final group = widget.game.group; - final winner = widget.game.winner; + final group = widget.match.group; + final winner = widget.match.winner; final allPlayers = _getAllPlayers(); return GestureDetector( @@ -39,7 +39,7 @@ class _GameHistoryTileState extends State { children: [ Expanded( child: Text( - widget.game.name, + widget.match.name, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, @@ -48,7 +48,7 @@ class _GameHistoryTileState extends State { ), ), Text( - _formatDate(widget.game.createdAt), + _formatDate(widget.match.createdAt), style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], @@ -156,8 +156,8 @@ class _GameHistoryTileState extends State { final playerIds = {}; // Add players from game.players - if (widget.game.players != null) { - for (var player in widget.game.players!) { + if (widget.match.players != null) { + for (var player in widget.match.players!) { if (!playerIds.contains(player.id)) { allPlayers.add(player); playerIds.add(player.id); @@ -166,8 +166,8 @@ class _GameHistoryTileState extends State { } // Add players from game.group.players - if (widget.game.group?.members != null) { - for (var player in widget.game.group!.members) { + if (widget.match.group?.members != null) { + for (var player in widget.match.group!.members) { if (!playerIds.contains(player.id)) { allPlayers.add(player); playerIds.add(player.id); diff --git a/lib/presentation/widgets/tiles/game_tile.dart b/lib/presentation/widgets/tiles/game_tile.dart index fcd2f65..f98f425 100644 --- a/lib/presentation/widgets/tiles/game_tile.dart +++ b/lib/presentation/widgets/tiles/game_tile.dart @@ -2,27 +2,27 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:skeletonizer/skeletonizer.dart'; -class GameTile extends StatefulWidget { - final String gameTitle; - final String gameType; +class MatchTile extends StatefulWidget { + final String matchTitle; + final String game; final String ruleset; final String players; final String winner; - const GameTile({ + const MatchTile({ super.key, - required this.gameTitle, - required this.gameType, + required this.matchTitle, + required this.game, required this.ruleset, required this.players, required this.winner, }); @override - State createState() => _GameTileState(); + State createState() => _MatchTileState(); } -class _GameTileState extends State { +class _MatchTileState extends State { @override Widget build(BuildContext context) { return Column( @@ -31,12 +31,12 @@ class _GameTileState extends State { Row( children: [ Text( - widget.gameTitle, + widget.matchTitle, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(width: 5), Text( - widget.gameType, + widget.game, style: const TextStyle(fontSize: 14, color: Colors.grey), ), ], diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 6e3b9b2..582cf66 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -40,11 +40,11 @@ class StatisticsTile extends StatelessWidget { child: Column( children: List.generate(min(values.length, itemCount), (index) { /// The maximum wins among all players - final maxGames = values.isNotEmpty ? values[0].$2 : 0; + final maxMatches = values.isNotEmpty ? values[0].$2 : 0; /// Fraction of wins - final double fraction = (maxGames > 0) - ? (values[index].$2 / maxGames) + final double fraction = (maxMatches > 0) + ? (values[index].$2 / maxMatches) : 0.0; /// Calculated width for current the bar diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index f040b0a..957d8fd 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -6,8 +6,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:json_schema/json_schema.dart'; import 'package:provider/provider.dart'; @@ -16,7 +16,7 @@ class DataTransferService { /// Deletes all data from the database. static Future deleteAllData(BuildContext context) async { final db = Provider.of(context, listen: false); - await db.gameDao.deleteAllGames(); + await db.matchDao.deleteAllMatches(); await db.groupDao.deleteAllGroups(); await db.playerDao.deleteAllPlayers(); } @@ -25,13 +25,13 @@ class DataTransferService { /// Returns the JSON string representation of the data. static Future getAppDataAsJson(BuildContext context) async { final db = Provider.of(context, listen: false); - final games = await db.gameDao.getAllGames(); + final matches = await db.matchDao.getAllMatches(); final groups = await db.groupDao.getAllGroups(); final players = await db.playerDao.getAllPlayers(); // Construct a JSON representation of the data final Map jsonMap = { - 'games': games.map((game) => game.toJson()).toList(), + 'matches': matches.map((match) => match.toJson()).toList(), 'groups': groups.map((group) => group.toJson()).toList(), 'players': players.map((player) => player.toJson()).toList(), }; @@ -89,14 +89,15 @@ class DataTransferService { final Map jsonData = json.decode(jsonString) as Map; - final List? gamesJson = jsonData['games'] as List?; + final List? matchesJson = + jsonData['matches'] as List?; final List? groupsJson = jsonData['groups'] as List?; final List? playersJson = jsonData['players'] as List?; - final List importedGames = - gamesJson - ?.map((g) => Game.fromJson(g as Map)) + final List importedMatches = + matchesJson + ?.map((g) => Match.fromJson(g as Map)) .toList() ?? []; final List importedGroups = @@ -112,7 +113,7 @@ class DataTransferService { await db.playerDao.addPlayersAsList(players: importedPlayers); await db.groupDao.addGroupsAsList(groups: importedGroups); - await db.gameDao.addGamesAsList(games: importedGames); + await db.matchDao.addMatchAsList(matches: importedMatches); } else { return ImportResult.invalidSchema; } diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index 4a5cc32..ca86a60 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -3,8 +3,8 @@ import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; void main() { @@ -16,10 +16,10 @@ void main() { late Player testPlayer5; late Group testGroup1; late Group testGroup2; - late Game testGame1; - late Game testGame2; - late Game testGameOnlyPlayers; - late Game testGameOnlyGroup; + late Match testMatch1; + late Match testMatch2; + late Match testMatchOnlyPlayers; + late Match testMatchOnlyGroup; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -46,46 +46,51 @@ void main() { name: 'Test Group 2', members: [testPlayer4, testPlayer5], ); - testGame1 = Game( - name: 'First Test Game', + testMatch1 = Match( + name: 'First Test Match', group: testGroup1, players: [testPlayer4, testPlayer5], winner: testPlayer4, ); - testGame2 = Game( - name: 'Second Test Game', + testMatch2 = Match( + name: 'Second Test Match', group: testGroup2, players: [testPlayer1, testPlayer2, testPlayer3], winner: testPlayer2, ); - testGameOnlyPlayers = Game( - name: 'Test Game with Players', + testMatchOnlyPlayers = Match( + name: 'Test Match with Players', players: [testPlayer1, testPlayer2, testPlayer3], winner: testPlayer3, ); - testGameOnlyGroup = Game(name: 'Test Game with Group', group: testGroup2); + testMatchOnlyGroup = Match( + name: 'Test Match with Group', + group: testGroup2, + ); }); }); tearDown(() async { await database.close(); }); - group('Game Tests', () { - test('Adding and fetching single game works correctly', () async { - await database.gameDao.addGame(game: testGame1); + group('Match Tests', () { + test('Adding and fetching single match works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); - final result = await database.gameDao.getGameById(gameId: testGame1.id); + final result = await database.matchDao.getMatchById( + matchId: testMatch1.id, + ); - expect(result.id, testGame1.id); - expect(result.name, testGame1.name); - expect(result.createdAt, testGame1.createdAt); + expect(result.id, testMatch1.id); + expect(result.name, testMatch1.name); + expect(result.createdAt, testMatch1.createdAt); - if (result.winner != null && testGame1.winner != null) { - expect(result.winner!.id, testGame1.winner!.id); - expect(result.winner!.name, testGame1.winner!.name); - expect(result.winner!.createdAt, testGame1.winner!.createdAt); + if (result.winner != null && testMatch1.winner != null) { + expect(result.winner!.id, testMatch1.winner!.id); + expect(result.winner!.name, testMatch1.winner!.name); + expect(result.winner!.createdAt, testMatch1.winner!.createdAt); } else { - expect(result.winner, testGame1.winner); + expect(result.winner, testMatch1.winner); } if (result.group != null) { @@ -99,188 +104,203 @@ void main() { fail('Group is null'); } if (result.players != null) { - expect(result.players!.length, testGame1.players!.length); + expect(result.players!.length, testMatch1.players!.length); - for (int i = 0; i < testGame1.players!.length; i++) { - expect(result.players![i].id, testGame1.players![i].id); - expect(result.players![i].name, testGame1.players![i].name); - expect(result.players![i].createdAt, testGame1.players![i].createdAt); + for (int i = 0; i < testMatch1.players!.length; i++) { + expect(result.players![i].id, testMatch1.players![i].id); + expect(result.players![i].name, testMatch1.players![i].name); + expect( + result.players![i].createdAt, + testMatch1.players![i].createdAt, + ); } } else { fail('Players is null'); } }); - test('Adding and fetching multiple games works correctly', () async { - await database.gameDao.addGamesAsList( - games: [testGame1, testGame2, testGameOnlyGroup, testGameOnlyPlayers], + test('Adding and fetching multiple matches works correctly', () async { + await database.matchDao.addMatchAsList( + matches: [ + testMatch1, + testMatch2, + testMatchOnlyGroup, + testMatchOnlyPlayers, + ], ); - final allGames = await database.gameDao.getAllGames(); - expect(allGames.length, 4); + final allMatches = await database.matchDao.getAllMatches(); + expect(allMatches.length, 4); - final testGames = { - testGame1.id: testGame1, - testGame2.id: testGame2, - testGameOnlyGroup.id: testGameOnlyGroup, - testGameOnlyPlayers.id: testGameOnlyPlayers, + final testMatches = { + testMatch1.id: testMatch1, + testMatch2.id: testMatch2, + testMatchOnlyGroup.id: testMatchOnlyGroup, + testMatchOnlyPlayers.id: testMatchOnlyPlayers, }; - for (final game in allGames) { - final testGame = testGames[game.id]!; + for (final match in allMatches) { + final testMatch = testMatches[match.id]!; - // Game-Checks - expect(game.id, testGame.id); - expect(game.name, testGame.name); - expect(game.createdAt, testGame.createdAt); - if (game.winner != null && testGame.winner != null) { - expect(game.winner!.id, testGame.winner!.id); - expect(game.winner!.name, testGame.winner!.name); - expect(game.winner!.createdAt, testGame.winner!.createdAt); + // Match-Checks + expect(match.id, testMatch.id); + expect(match.name, testMatch.name); + expect(match.createdAt, testMatch.createdAt); + if (match.winner != null && testMatch.winner != null) { + expect(match.winner!.id, testMatch.winner!.id); + expect(match.winner!.name, testMatch.winner!.name); + expect(match.winner!.createdAt, testMatch.winner!.createdAt); } else { - expect(game.winner, testGame.winner); + expect(match.winner, testMatch.winner); } // Group-Checks - if (testGame.group != null) { - expect(game.group!.id, testGame.group!.id); - expect(game.group!.name, testGame.group!.name); - expect(game.group!.createdAt, testGame.group!.createdAt); + if (testMatch.group != null) { + expect(match.group!.id, testMatch.group!.id); + expect(match.group!.name, testMatch.group!.name); + expect(match.group!.createdAt, testMatch.group!.createdAt); // Group Members-Checks - expect(game.group!.members.length, testGame.group!.members.length); - for (int i = 0; i < testGame.group!.members.length; i++) { - expect(game.group!.members[i].id, testGame.group!.members[i].id); + expect(match.group!.members.length, testMatch.group!.members.length); + for (int i = 0; i < testMatch.group!.members.length; i++) { + expect(match.group!.members[i].id, testMatch.group!.members[i].id); expect( - game.group!.members[i].name, - testGame.group!.members[i].name, + match.group!.members[i].name, + testMatch.group!.members[i].name, ); expect( - game.group!.members[i].createdAt, - testGame.group!.members[i].createdAt, + match.group!.members[i].createdAt, + testMatch.group!.members[i].createdAt, ); } } else { - expect(game.group, null); + expect(match.group, null); } // Players-Checks - if (testGame.players != null) { - expect(game.players!.length, testGame.players!.length); - for (int i = 0; i < testGame.players!.length; i++) { - expect(game.players![i].id, testGame.players![i].id); - expect(game.players![i].name, testGame.players![i].name); - expect(game.players![i].createdAt, testGame.players![i].createdAt); + if (testMatch.players != null) { + expect(match.players!.length, testMatch.players!.length); + for (int i = 0; i < testMatch.players!.length; i++) { + expect(match.players![i].id, testMatch.players![i].id); + expect(match.players![i].name, testMatch.players![i].name); + expect( + match.players![i].createdAt, + testMatch.players![i].createdAt, + ); } } else { - expect(game.players, null); + expect(match.players, null); } } }); - test('Adding the same game twice does not create duplicates', () async { - await database.gameDao.addGame(game: testGame1); - await database.gameDao.addGame(game: testGame1); + test('Adding the same match twice does not create duplicates', () async { + await database.matchDao.addMatch(match: testMatch1); + await database.matchDao.addMatch(match: testMatch1); - final gameCount = await database.gameDao.getGameCount(); - expect(gameCount, 1); + final matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 1); }); - test('Game existence check works correctly', () async { - var gameExists = await database.gameDao.gameExists(gameId: testGame1.id); - expect(gameExists, false); - - await database.gameDao.addGame(game: testGame1); - - gameExists = await database.gameDao.gameExists(gameId: testGame1.id); - expect(gameExists, true); - }); - - test('Deleting a game works correctly', () async { - await database.gameDao.addGame(game: testGame1); - - final gameDeleted = await database.gameDao.deleteGame( - gameId: testGame1.id, + test('Match existence check works correctly', () async { + var matchExists = await database.matchDao.matchExists( + matchId: testMatch1.id, ); - expect(gameDeleted, true); + expect(matchExists, false); - final gameExists = await database.gameDao.gameExists( - gameId: testGame1.id, + await database.matchDao.addMatch(match: testMatch1); + + matchExists = await database.matchDao.matchExists(matchId: testMatch1.id); + expect(matchExists, true); + }); + + test('Deleting a match works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); + + final matchDeleted = await database.matchDao.deleteMatch( + matchId: testMatch1.id, ); - expect(gameExists, false); + expect(matchDeleted, true); + + final matchExists = await database.matchDao.matchExists( + matchId: testMatch1.id, + ); + expect(matchExists, false); }); - test('Getting the game count works correctly', () async { - var gameCount = await database.gameDao.getGameCount(); - expect(gameCount, 0); + test('Getting the match count works correctly', () async { + var matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 0); - await database.gameDao.addGame(game: testGame1); + await database.matchDao.addMatch(match: testMatch1); - gameCount = await database.gameDao.getGameCount(); - expect(gameCount, 1); + matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 1); - await database.gameDao.addGame(game: testGame2); + await database.matchDao.addMatch(match: testMatch2); - gameCount = await database.gameDao.getGameCount(); - expect(gameCount, 2); + matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 2); - await database.gameDao.deleteGame(gameId: testGame1.id); + await database.matchDao.deleteMatch(matchId: testMatch1.id); - gameCount = await database.gameDao.getGameCount(); - expect(gameCount, 1); + matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 1); - await database.gameDao.deleteGame(gameId: testGame2.id); + await database.matchDao.deleteMatch(matchId: testMatch2.id); - gameCount = await database.gameDao.getGameCount(); - expect(gameCount, 0); + matchCount = await database.matchDao.getMatchCount(); + expect(matchCount, 0); }); - test('Checking if game has winner works correclty', () async { - await database.gameDao.addGame(game: testGame1); - await database.gameDao.addGame(game: testGameOnlyGroup); + test('Checking if match has winner works correclty', () async { + await database.matchDao.addMatch(match: testMatch1); + await database.matchDao.addMatch(match: testMatchOnlyGroup); - var hasWinner = await database.gameDao.hasWinner(gameId: testGame1.id); + var hasWinner = await database.matchDao.hasWinner(matchId: testMatch1.id); expect(hasWinner, true); - hasWinner = await database.gameDao.hasWinner( - gameId: testGameOnlyGroup.id, + hasWinner = await database.matchDao.hasWinner( + matchId: testMatchOnlyGroup.id, ); expect(hasWinner, false); }); - test('Fetching the winner of a game works correctly', () async { - await database.gameDao.addGame(game: testGame1); + test('Fetching the winner of a match works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); - final winner = await database.gameDao.getWinner(gameId: testGame1.id); + final winner = await database.matchDao.getWinner(matchId: testMatch1.id); if (winner == null) { fail('Winner is null'); } else { - expect(winner.id, testGame1.winner!.id); - expect(winner.name, testGame1.winner!.name); - expect(winner.createdAt, testGame1.winner!.createdAt); + expect(winner.id, testMatch1.winner!.id); + expect(winner.name, testMatch1.winner!.name); + expect(winner.createdAt, testMatch1.winner!.createdAt); } }); - test('Updating the winner of a game works correctly', () async { - await database.gameDao.addGame(game: testGame1); + test('Updating the winner of a match works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); - final winner = await database.gameDao.getWinner(gameId: testGame1.id); + final winner = await database.matchDao.getWinner(matchId: testMatch1.id); if (winner == null) { fail('Winner is null'); } else { - expect(winner.id, testGame1.winner!.id); - expect(winner.name, testGame1.winner!.name); - expect(winner.createdAt, testGame1.winner!.createdAt); + expect(winner.id, testMatch1.winner!.id); + expect(winner.name, testMatch1.winner!.name); + expect(winner.createdAt, testMatch1.winner!.createdAt); expect(winner.id, testPlayer4.id); expect(winner.id != testPlayer5.id, true); } - await database.gameDao.setWinner( - gameId: testGame1.id, + await database.matchDao.setWinner( + matchId: testMatch1.id, winnerId: testPlayer5.id, ); - final newWinner = await database.gameDao.getWinner(gameId: testGame1.id); + final newWinner = await database.matchDao.getWinner( + matchId: testMatch1.id, + ); if (newWinner == null) { fail('New winner is null'); @@ -292,39 +312,41 @@ void main() { }); test('Removing a winner works correctly', () async { - await database.gameDao.addGame(game: testGame2); + await database.matchDao.addMatch(match: testMatch2); - var hasWinner = await database.gameDao.hasWinner(gameId: testGame2.id); + var hasWinner = await database.matchDao.hasWinner(matchId: testMatch2.id); expect(hasWinner, true); - await database.gameDao.removeWinner(gameId: testGame2.id); + await database.matchDao.removeWinner(matchId: testMatch2.id); - hasWinner = await database.gameDao.hasWinner(gameId: testGame2.id); + hasWinner = await database.matchDao.hasWinner(matchId: testMatch2.id); expect(hasWinner, false); - final removedWinner = await database.gameDao.getWinner( - gameId: testGame2.id, + final removedWinner = await database.matchDao.getWinner( + matchId: testMatch2.id, ); expect(removedWinner, null); }); - test('Renaming a game works correctly', () async { - await database.gameDao.addGame(game: testGame1); + test('Renaming a match works correctly', () async { + await database.matchDao.addMatch(match: testMatch1); - var fetchedGame = await database.gameDao.getGameById( - gameId: testGame1.id, + var fetchedMatch = await database.matchDao.getMatchById( + matchId: testMatch1.id, ); - expect(fetchedGame.name, testGame1.name); + expect(fetchedMatch.name, testMatch1.name); - const newName = 'Updated Game Name'; - await database.gameDao.updateGameName( - gameId: testGame1.id, + const newName = 'Updated Match Name'; + await database.matchDao.updateMatchName( + matchId: testMatch1.id, newName: newName, ); - fetchedGame = await database.gameDao.getGameById(gameId: testGame1.id); - expect(fetchedGame.name, newName); + fetchedMatch = await database.matchDao.getMatchById( + matchId: testMatch1.id, + ); + expect(fetchedMatch.name, newName); }); }); } diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart index 1e9b8fc..a4fa146 100644 --- a/test/db_tests/group_game_test.dart +++ b/test/db_tests/group_game_test.dart @@ -3,8 +3,8 @@ import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; void main() { @@ -16,8 +16,8 @@ void main() { late Player testPlayer5; late Group testGroup1; late Group testGroup2; - late Game testgameWithGroup; - late Game testgameWithPlayers; + late Match testMatchWithGroup; + late Match testMatchWithPlayers; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -44,81 +44,84 @@ void main() { name: 'Test Group', members: [testPlayer3, testPlayer2], ); - testgameWithPlayers = Game( - name: 'Test Game with Players', + testMatchWithPlayers = Match( + name: 'Test Match with Players', players: [testPlayer4, testPlayer5], ); - testgameWithGroup = Game(name: 'Test Game with Group', group: testGroup1); + testMatchWithGroup = Match( + name: 'Test Match with Group', + group: testGroup1, + ); }); }); tearDown(() async { await database.close(); }); - group('Group-Game Tests', () { - test('Game has group works correctly', () async { - await database.gameDao.addGame(game: testgameWithPlayers); + group('Group-Match Tests', () { + test('matchHasGroup() has group works correctly', () async { + await database.matchDao.addMatch(match: testMatchWithPlayers); await database.groupDao.addGroup(group: testGroup1); - var gameHasGroup = await database.groupGameDao.gameHasGroup( - gameId: testgameWithPlayers.id, + var matchHasGroup = await database.groupMatchDao.matchHasGroup( + matchId: testMatchWithPlayers.id, ); - expect(gameHasGroup, false); + expect(matchHasGroup, false); - await database.groupGameDao.addGroupToGame( - gameId: testgameWithPlayers.id, + await database.groupMatchDao.addGroupToMatch( + matchId: testMatchWithPlayers.id, groupId: testGroup1.id, ); - gameHasGroup = await database.groupGameDao.gameHasGroup( - gameId: testgameWithPlayers.id, + matchHasGroup = await database.groupMatchDao.matchHasGroup( + matchId: testMatchWithPlayers.id, ); - expect(gameHasGroup, true); + expect(matchHasGroup, true); }); - test('Adding a group to a game works correctly', () async { - await database.gameDao.addGame(game: testgameWithPlayers); + test('Adding a group to a match works correctly', () async { + await database.matchDao.addMatch(match: testMatchWithPlayers); await database.groupDao.addGroup(group: testGroup1); - await database.groupGameDao.addGroupToGame( - gameId: testgameWithPlayers.id, + await database.groupMatchDao.addGroupToMatch( + matchId: testMatchWithPlayers.id, groupId: testGroup1.id, ); - var groupAdded = await database.groupGameDao.isGroupInGame( - gameId: testgameWithPlayers.id, + var groupAdded = await database.groupMatchDao.isGroupInMatch( + matchId: testMatchWithPlayers.id, groupId: testGroup1.id, ); expect(groupAdded, true); - groupAdded = await database.groupGameDao.isGroupInGame( - gameId: testgameWithPlayers.id, + groupAdded = await database.groupMatchDao.isGroupInMatch( + matchId: testMatchWithPlayers.id, groupId: '', ); expect(groupAdded, false); }); - test('Removing group from game works correctly', () async { - await database.gameDao.addGame(game: testgameWithGroup); + test('Removing group from match works correctly', () async { + await database.matchDao.addMatch(match: testMatchWithGroup); - final groupToRemove = testgameWithGroup.group!; + final groupToRemove = testMatchWithGroup.group!; - final removed = await database.groupGameDao.removeGroupFromGame( + final removed = await database.groupMatchDao.removeGroupFromMatch( groupId: groupToRemove.id, - gameId: testgameWithGroup.id, + matchId: testMatchWithGroup.id, ); expect(removed, true); - final result = await database.gameDao.getGameById( - gameId: testgameWithGroup.id, + final result = await database.matchDao.getMatchById( + matchId: testMatchWithGroup.id, ); expect(result.group, null); }); - test('Retrieving group of a game works correctly', () async { - await database.gameDao.addGame(game: testgameWithGroup); - final group = await database.groupGameDao.getGroupOfGame( - gameId: testgameWithGroup.id, + test('Retrieving group of a match works correctly', () async { + await database.matchDao.addMatch(match: testMatchWithGroup); + final group = await database.groupMatchDao.getGroupOfMatch( + matchId: testMatchWithGroup.id, ); if (group == null) { @@ -136,11 +139,11 @@ void main() { } }); - test('Updating the group of a game works correctly', () async { - await database.gameDao.addGame(game: testgameWithGroup); + test('Updating the group of a match works correctly', () async { + await database.matchDao.addMatch(match: testMatchWithGroup); - var group = await database.groupGameDao.getGroupOfGame( - gameId: testgameWithGroup.id, + var group = await database.groupMatchDao.getGroupOfMatch( + matchId: testMatchWithGroup.id, ); if (group == null) { @@ -153,13 +156,13 @@ void main() { } await database.groupDao.addGroup(group: testGroup2); - await database.groupGameDao.updateGroupOfGame( - gameId: testgameWithGroup.id, + await database.groupMatchDao.updateGroupOfMatch( + matchId: testMatchWithGroup.id, newGroupId: testGroup2.id, ); - group = await database.groupGameDao.getGroupOfGame( - gameId: testgameWithGroup.id, + group = await database.groupMatchDao.getGroupOfMatch( + matchId: testMatchWithGroup.id, ); if (group == null) { diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index 4e2616e..e2501eb 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -3,8 +3,8 @@ import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/game.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; void main() { @@ -16,8 +16,8 @@ void main() { late Player testPlayer5; late Player testPlayer6; late Group testgroup; - late Game testGameOnlyGroup; - late Game testGameOnlyPlayers; + late Match testMatchOnlyGroup; + late Match testMatchOnlyPlayers; final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); @@ -41,9 +41,12 @@ void main() { name: 'Test Group', members: [testPlayer1, testPlayer2, testPlayer3], ); - testGameOnlyGroup = Game(name: 'Test Game with Group', group: testgroup); - testGameOnlyPlayers = Game( - name: 'Test Game with Players', + testMatchOnlyGroup = Match( + name: 'Test Match with Group', + group: testgroup, + ); + testMatchOnlyPlayers = Match( + name: 'Test Match with Players', players: [testPlayer4, testPlayer5, testPlayer6], ); }); @@ -52,67 +55,67 @@ void main() { await database.close(); }); - group('Player-Game Tests', () { - test('Game has player works correctly', () async { - await database.gameDao.addGame(game: testGameOnlyGroup); + group('Player-Match Tests', () { + test('Match has player works correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); await database.playerDao.addPlayer(player: testPlayer1); - var gameHasPlayers = await database.playerGameDao.gameHasPlayers( - gameId: testGameOnlyGroup.id, + var matchHasPlayers = await database.playerMatchDao.matchHasPlayers( + matchId: testMatchOnlyGroup.id, ); - expect(gameHasPlayers, false); + expect(matchHasPlayers, false); - await database.playerGameDao.addPlayerToGame( - gameId: testGameOnlyGroup.id, + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, playerId: testPlayer1.id, ); - gameHasPlayers = await database.playerGameDao.gameHasPlayers( - gameId: testGameOnlyGroup.id, + matchHasPlayers = await database.playerMatchDao.matchHasPlayers( + matchId: testMatchOnlyGroup.id, ); - expect(gameHasPlayers, true); + expect(matchHasPlayers, true); }); - test('Adding a player to a game works correctly', () async { - await database.gameDao.addGame(game: testGameOnlyGroup); + test('Adding a player to a match works correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); await database.playerDao.addPlayer(player: testPlayer5); - await database.playerGameDao.addPlayerToGame( - gameId: testGameOnlyGroup.id, + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, playerId: testPlayer5.id, ); - var playerAdded = await database.playerGameDao.isPlayerInGame( - gameId: testGameOnlyGroup.id, + var playerAdded = await database.playerMatchDao.isPlayerInMatch( + matchId: testMatchOnlyGroup.id, playerId: testPlayer5.id, ); expect(playerAdded, true); - playerAdded = await database.playerGameDao.isPlayerInGame( - gameId: testGameOnlyGroup.id, + playerAdded = await database.playerMatchDao.isPlayerInMatch( + matchId: testMatchOnlyGroup.id, playerId: '', ); expect(playerAdded, false); }); - test('Removing player from game works correctly', () async { - await database.gameDao.addGame(game: testGameOnlyPlayers); + test('Removing player from match works correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); - final playerToRemove = testGameOnlyPlayers.players![0]; + final playerToRemove = testMatchOnlyPlayers.players![0]; - final removed = await database.playerGameDao.removePlayerFromGame( + final removed = await database.playerMatchDao.removePlayerFromMatch( playerId: playerToRemove.id, - gameId: testGameOnlyPlayers.id, + matchId: testMatchOnlyPlayers.id, ); expect(removed, true); - final result = await database.gameDao.getGameById( - gameId: testGameOnlyPlayers.id, + final result = await database.matchDao.getMatchById( + matchId: testMatchOnlyPlayers.id, ); - expect(result.players!.length, testGameOnlyPlayers.players!.length - 1); + expect(result.players!.length, testMatchOnlyPlayers.players!.length - 1); final playerExists = result.players!.any( (p) => p.id == playerToRemove.id, @@ -120,10 +123,10 @@ void main() { expect(playerExists, false); }); - test('Retrieving players of a game works correctly', () async { - await database.gameDao.addGame(game: testGameOnlyPlayers); - final players = await database.playerGameDao.getPlayersOfGame( - gameId: testGameOnlyPlayers.id, + test('Retrieving players of a match works correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); + final players = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatchOnlyPlayers.id, ); if (players == null) { @@ -131,34 +134,37 @@ void main() { } for (int i = 0; i < players.length; i++) { - expect(players[i].id, testGameOnlyPlayers.players![i].id); - expect(players[i].name, testGameOnlyPlayers.players![i].name); - expect(players[i].createdAt, testGameOnlyPlayers.players![i].createdAt); + expect(players[i].id, testMatchOnlyPlayers.players![i].id); + expect(players[i].name, testMatchOnlyPlayers.players![i].name); + expect( + players[i].createdAt, + testMatchOnlyPlayers.players![i].createdAt, + ); } }); - test('Updating the games players works coreclty', () async { - await database.gameDao.addGame(game: testGameOnlyPlayers); + test('Updating the match players works coreclty', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); final newPlayers = [testPlayer1, testPlayer2, testPlayer4]; await database.playerDao.addPlayersAsList(players: newPlayers); // First, remove all existing players - final existingPlayers = await database.playerGameDao.getPlayersOfGame( - gameId: testGameOnlyPlayers.id, + final existingPlayers = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatchOnlyPlayers.id, ); if (existingPlayers == null || existingPlayers.isEmpty) { fail('Existing players should not be null or empty'); } - await database.playerGameDao.updatePlayersFromGame( - gameId: testGameOnlyPlayers.id, + await database.playerMatchDao.updatePlayersFromMatch( + matchId: testMatchOnlyPlayers.id, newPlayer: newPlayers, ); - final updatedPlayers = await database.playerGameDao.getPlayersOfGame( - gameId: testGameOnlyPlayers.id, + final updatedPlayers = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatchOnlyPlayers.id, ); if (updatedPlayers == null) { From c4b249b19961cdd9a3dca86676d5b4e0caf1ddb5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 11 Dec 2025 20:11:02 +0100 Subject: [PATCH 037/222] Added missing game instants --- lib/data/dao/player_match_dao.dart | 2 +- .../views/main_menu/home_view.dart | 6 ++--- .../views/main_menu/statistics_view.dart | 24 +++++++++---------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/lib/data/dao/player_match_dao.dart b/lib/data/dao/player_match_dao.dart index f42b8bb..47c7b2d 100644 --- a/lib/data/dao/player_match_dao.dart +++ b/lib/data/dao/player_match_dao.dart @@ -66,7 +66,7 @@ class PlayerMatchDao extends DatabaseAccessor return (count ?? 0) > 0; } - /// Removes the association of a player with a game by deleting the record + /// Removes the association of a player with a match by deleting the record /// from the [PlayerMatchTable]. /// Returns `true` if more than 0 rows were affected, otherwise `false`. Future removePlayerFromMatch({ diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index c6322d8..d312516 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -82,7 +82,7 @@ class _HomeViewState extends State { return QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: 'Games', + title: 'Matches', icon: Icons.groups_rounded, value: count, ); @@ -112,7 +112,7 @@ class _HomeViewState extends State { padding: const EdgeInsets.symmetric(vertical: 16.0), child: InfoTile( width: constraints.maxWidth * 0.95, - title: 'Recent Games', + title: 'Recent Matches', icon: Icons.timer, content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), @@ -127,7 +127,7 @@ class _HomeViewState extends State { return const Center( heightFactor: 4, child: Text( - 'Error while loading recent games.', + 'Error while loading recent matches.', ), ); } diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 17376b2..6104e39 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -33,7 +33,7 @@ class _StatisticsViewState extends State { final matches = results[0] as List; final players = results[1] as List; winCounts = _calculateWinsForAllPlayers(matches, players); - matchCounts = _calculateGameAmountsForAllPlayers(matches, players); + matchCounts = _calculateMatchAmountsForAllPlayers(matches, players); winRates = computeWinRatePercent(wins: winCounts, matches: matchCounts); if (mounted) { setState(() { @@ -97,14 +97,14 @@ class _StatisticsViewState extends State { /// Calculates the number of wins for each player /// and returns a sorted list of tuples (playerName, winCount) List<(String, int)> _calculateWinsForAllPlayers( - List games, + List matches, List players, ) { List<(String, int)> winCounts = []; // Getting the winners - for (var game in games) { - final winner = game.winner; + for (var match in matches) { + final winner = match.winner; if (winner != null) { final index = winCounts.indexWhere((entry) => entry.$1 == winner.id); // -1 means winner not found in winCounts @@ -143,7 +143,7 @@ class _StatisticsViewState extends State { /// Calculates the number of matches played for each player /// and returns a sorted list of tuples (playerName, matchCount) - List<(String, int)> _calculateGameAmountsForAllPlayers( + List<(String, int)> _calculateMatchAmountsForAllPlayers( List matches, List players, ) { @@ -155,7 +155,7 @@ class _StatisticsViewState extends State { final members = match.group!.members.map((p) => p.id).toList(); for (var playerId in members) { final index = matchCounts.indexWhere((entry) => entry.$1 == playerId); - // -1 means player not found in gameCounts + // -1 means player not found in matchCounts if (index != -1) { final current = matchCounts[index].$2; matchCounts[index] = (playerId, current + 1); @@ -168,7 +168,7 @@ class _StatisticsViewState extends State { final members = match.players!.map((p) => p.id).toList(); for (var playerId in members) { final index = matchCounts.indexWhere((entry) => entry.$1 == playerId); - // -1 means player not found in gameCounts + // -1 means player not found in matchCounts if (index != -1) { final current = matchCounts[index].$2; matchCounts[index] = (playerId, current + 1); @@ -179,10 +179,10 @@ class _StatisticsViewState extends State { } } - // Adding all players with zero games + // Adding all players with zero matches for (var player in players) { final index = matchCounts.indexWhere((entry) => entry.$1 == player.id); - // -1 means player not found in gameCounts + // -1 means player not found in matchCounts if (index == -1) { matchCounts.add((player.id, 0)); } @@ -209,15 +209,15 @@ class _StatisticsViewState extends State { required List<(String, int)> matches, }) { final Map winsMap = {for (var e in wins) e.$1: e.$2}; - final Map gamesMap = {for (var e in matches) e.$1: e.$2}; + final Map matchesMap = {for (var e in matches) e.$1: e.$2}; // Get all unique player names - final names = {...winsMap.keys, ...gamesMap.keys}; + final names = {...winsMap.keys, ...matchesMap.keys}; // Calculate win rates final result = names.map((name) { final int w = winsMap[name] ?? 0; - final int g = gamesMap[name] ?? 0; + final int g = matchesMap[name] ?? 0; // Calculate percentage and round to 2 decimal places // Avoid division by zero final double percent = (g > 0) From 0e5ad1c3d9953464d273080383f894aeaa8e2f1b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 11 Dec 2025 20:12:21 +0100 Subject: [PATCH 038/222] Another missing --- lib/presentation/views/main_menu/home_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index d312516..49bfa8f 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -153,7 +153,7 @@ class _HomeViewState extends State { ruleset: 'Ruleset', players: _getPlayerText(matches[0]), winner: matches[0].winner == null - ? 'Game in progress...' + ? 'Match in progress...' : matches[0].winner!.name, ), const Padding( From d578a7f9bd982091d0dc1262afa10eadd1758d76 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 11 Dec 2025 20:12:56 +0100 Subject: [PATCH 039/222] Missing comment --- lib/data/dao/player_match_dao.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/data/dao/player_match_dao.dart b/lib/data/dao/player_match_dao.dart index 47c7b2d..9e242b7 100644 --- a/lib/data/dao/player_match_dao.dart +++ b/lib/data/dao/player_match_dao.dart @@ -80,9 +80,9 @@ class PlayerMatchDao extends DatabaseAccessor return rowsAffected > 0; } - /// Updates the players associated with a game based on the provided + /// Updates the players associated with a match based on the provided /// [newPlayer] list. It adds new players and removes players that are no - /// longer associated with the game. + /// longer associated with the match. Future updatePlayersFromMatch({ required String matchId, required List newPlayer, From 0eaf3d251bb7bb256121cdf115771f1a5f1a2bee Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 15:49:58 +0100 Subject: [PATCH 040/222] added constant minimumSkeletonDuration --- lib/core/constants.dart | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 lib/core/constants.dart diff --git a/lib/core/constants.dart b/lib/core/constants.dart new file mode 100644 index 0000000..51b4c70 --- /dev/null +++ b/lib/core/constants.dart @@ -0,0 +1,2 @@ +/// Minimum duration of all app skeletons +Duration minimumSkeletonDuration = Duration(milliseconds: 250); \ No newline at end of file From d96494f608e483d61b553e239150ce1988e26cd8 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 15:50:24 +0100 Subject: [PATCH 041/222] Changed futurebuilder logic in groups_view --- .../views/main_menu/group_view/groups_view.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 5fd5e4b..67109e6 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; @@ -34,10 +35,10 @@ class _GroupsViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _allGroupsFuture = Future.delayed( - const Duration(milliseconds: 250), - () => db.groupDao.getAllGroups(), - ); + _allGroupsFuture = Future.wait([ + db.groupDao.getAllGroups(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) => results[0] as List); } @override From f05114a99e3ccf686e84b500bd5a89d3b6aef65f Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 15:50:39 +0100 Subject: [PATCH 042/222] removed futurebuilder logic in groups_view --- .../views/main_menu/home_view.dart | 191 +++++++----------- 1 file changed, 72 insertions(+), 119 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 49bfa8f..31bedf5 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/match.dart'; @@ -18,12 +19,10 @@ class HomeView extends StatefulWidget { } class _HomeViewState extends State { - late Future _matchCountFuture; - late Future _groupCountFuture; - late Future> _recentMatchesFuture; bool isLoading = true; - - late final List skeletonData = List.filled( + int matchCount = 0; + int groupCount = 0; + List recentMatches = List.filled( 2, Match( name: 'Skeleton Match', @@ -39,19 +38,24 @@ class _HomeViewState extends State { ); @override - initState() { + void initState() { super.initState(); final db = Provider.of(context, listen: false); - _matchCountFuture = db.matchDao.getMatchCount(); - _groupCountFuture = db.groupDao.getGroupCount(); - _recentMatchesFuture = db.matchDao.getAllMatches(); Future.wait([ - _matchCountFuture, - _groupCountFuture, - _recentMatchesFuture, - ]).then((_) async { - await Future.delayed(const Duration(milliseconds: 250)); + db.matchDao.getMatchCount(), + db.groupDao.getGroupCount(), + db.matchDao.getAllMatches(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) { + matchCount = results[0] as int; + groupCount = results[1] as int; + recentMatches = results[2] as List; + + recentMatches = + (recentMatches..sort((a, b) => b.createdAt.compareTo(a.createdAt))) + .take(2) + .toList(); if (mounted) { setState(() { isLoading = false; @@ -73,38 +77,20 @@ class _HomeViewState extends State { Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - FutureBuilder( - future: _matchCountFuture, - builder: (context, snapshot) { - final int count = (snapshot.hasData) - ? snapshot.data! - : 0; - return QuickInfoTile( - width: constraints.maxWidth * 0.45, - height: constraints.maxHeight * 0.15, - title: 'Matches', - icon: Icons.groups_rounded, - value: count, - ); - }, + QuickInfoTile( + width: constraints.maxWidth * 0.45, + height: constraints.maxHeight * 0.15, + title: 'Matches', + icon: Icons.groups_rounded, + value: matchCount, ), SizedBox(width: constraints.maxWidth * 0.05), - FutureBuilder( - future: _groupCountFuture, - builder: (context, snapshot) { - final int count = - (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) - ? snapshot.data! - : 0; - return QuickInfoTile( - width: constraints.maxWidth * 0.45, - height: constraints.maxHeight * 0.15, - title: 'Groups', - icon: Icons.groups_rounded, - value: count, - ); - }, + QuickInfoTile( + width: constraints.maxWidth * 0.45, + height: constraints.maxHeight * 0.15, + title: 'Groups', + icon: Icons.groups_rounded, + value: groupCount, ), ], ), @@ -116,80 +102,48 @@ class _HomeViewState extends State { icon: Icons.timer, content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), - child: FutureBuilder( - future: _recentMatchesFuture, - builder: - ( - BuildContext context, - AsyncSnapshot> snapshot, - ) { - if (snapshot.hasError) { - return const Center( - heightFactor: 4, - child: Text( - 'Error while loading recent matches.', - ), - ); - } - final List matches = - (isLoading - ? skeletonData - : (snapshot.data ?? []) - ..sort( - (a, b) => b.createdAt.compareTo( - a.createdAt, - ), - )) - .take(2) - .toList(); - if (matches.isNotEmpty) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MatchTile( - matchTitle: matches[0].name, - game: 'Winner', - ruleset: 'Ruleset', - players: _getPlayerText(matches[0]), - winner: matches[0].winner == null - ? 'Match in progress...' - : matches[0].winner!.name, - ), - const Padding( - padding: EdgeInsets.symmetric( - vertical: 8.0, - ), - child: Divider(), - ), - if (matches.length > 1) ...[ - MatchTile( - matchTitle: matches[1].name, - game: 'Winner', - ruleset: 'Ruleset', - players: _getPlayerText(matches[1]), - winner: matches[1].winner == null - ? 'Game in progress...' - : matches[1].winner!.name, - ), - const SizedBox(height: 8), - ] else ...[ - const Center( - heightFactor: 4, - child: Text( - 'No second game available.', - ), - ), - ], - ], - ); - } else { - return const Center( - heightFactor: 12, - child: Text('No recent games available.'), - ); - } - }, + child: Visibility( + visible: !isLoading, + replacement: const Center( + heightFactor: 12, + child: Text('No recent games available.'), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + MatchTile( + matchTitle: recentMatches[0].name, + game: 'Winner', + ruleset: 'Ruleset', + players: _getPlayerText(recentMatches[0]), + winner: recentMatches[0].winner == null + ? 'Match in progress...' + : recentMatches[0].winner!.name, + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Divider(), + ), + if (recentMatches.length > 1) ...[ + MatchTile( + matchTitle: recentMatches[1].name, + game: 'Winner', + ruleset: 'Ruleset', + players: _getPlayerText(recentMatches[1]), + winner: recentMatches[1].winner == null + ? 'Game in progress...' + : recentMatches[1].winner!.name, + ), + const SizedBox(height: 8), + ] else ...[ + const Center( + heightFactor: 4, + child: Text('No second game available.'), + ), + ], + ], + ), ), ), ), @@ -199,7 +153,6 @@ class _HomeViewState extends State { title: 'Quick Create', icon: Icons.add_box_rounded, content: Column( - spacing: 8, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, From 76186787e73d976ce13e7ffec44f098461cc92d7 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 15:51:24 +0100 Subject: [PATCH 043/222] changed futurebuilder logic in player selection --- lib/presentation/widgets/player_selection.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index e2114b2..96d9d9a 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/player.dart'; @@ -46,10 +47,10 @@ class _PlayerSelectionState extends State { } void loadPlayerList() { - _allPlayersFuture = Future.delayed( - const Duration(milliseconds: 250), - () => db.playerDao.getAllPlayers(), - ); + _allPlayersFuture = Future.wait([ + db.playerDao.getAllPlayers(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) => results[0] as List); suggestedPlayers = skeletonData; _allPlayersFuture.then((loadedPlayers) { setState(() { From c8532adfde02960bca203825f1678cb92b70fbdf Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 15:52:02 +0100 Subject: [PATCH 044/222] changed skeleton duration logic in statistics view and refactored --- .../views/main_menu/statistics_view.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 6104e39..7df77a7 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; @@ -14,8 +15,6 @@ class StatisticsView extends StatefulWidget { } class _StatisticsViewState extends State { - late Future> _matchesFuture; - late Future> _playersFuture; List<(String, int)> winCounts = List.filled(6, ('Skeleton Player', 1)); List<(String, int)> matchCounts = List.filled(6, ('Skeleton Player', 1)); List<(String, double)> winRates = List.filled(6, ('Skeleton Player', 1)); @@ -25,11 +24,12 @@ class _StatisticsViewState extends State { void initState() { super.initState(); final db = Provider.of(context, listen: false); - _matchesFuture = db.matchDao.getAllMatches(); - _playersFuture = db.playerDao.getAllPlayers(); - Future.wait([_matchesFuture, _playersFuture]).then((results) async { - await Future.delayed(const Duration(milliseconds: 250)); + Future.wait([ + db.matchDao.getAllMatches(), + db.playerDao.getAllPlayers(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) async { final matches = results[0] as List; final players = results[1] as List; winCounts = _calculateWinsForAllPlayers(matches, players); From 24b60bb18b937791f359b37544f30cb15887203b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 19:46:59 +0100 Subject: [PATCH 045/222] added minimumSkeletonDuration constant and changed future logic --- .../views/main_menu/match_view/match_view.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 92fd268..7622134 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -2,6 +2,7 @@ import 'dart:core' hide Match; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; @@ -43,10 +44,10 @@ class _MatchViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _gameListFuture = Future.delayed( - const Duration(milliseconds: 250), - () => db.matchDao.getAllMatches(), - ); + _gameListFuture = Future.wait([ + db.matchDao.getAllMatches(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) => results[0] as List); } @override From 4f0a1eec6d12c28a178e8eeb8bce8a9d1004e31e Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 19:50:34 +0100 Subject: [PATCH 046/222] refactor --- .../match_view/create_match/create_match_view.dart | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 03c081c..27a1ce9 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -28,12 +28,6 @@ class _CreateMatchViewState extends State { /// Reference to the app database late final AppDatabase db; - /// Futures to load all groups and players from the database - late Future> _allGroupsFuture; - - /// Future to load all players from the database - late Future> _allPlayersFuture; - /// Controller for the game name input field final TextEditingController _gameNameController = TextEditingController(); @@ -107,10 +101,10 @@ class _CreateMatchViewState extends State { db = Provider.of(context, listen: false); - _allGroupsFuture = db.groupDao.getAllGroups(); - _allPlayersFuture = db.playerDao.getAllPlayers(); - - Future.wait([_allGroupsFuture, _allPlayersFuture]).then((result) async { + Future.wait([ + db.groupDao.getAllGroups(), + db.playerDao.getAllPlayers(), + ]).then((result) async { groupsList = result[0] as List; playerList = result[1] as List; }); From df0a5207c2b9b5d8c99612d4c62e7374c1ae249b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Sun, 21 Dec 2025 20:01:48 +0100 Subject: [PATCH 047/222] add const --- lib/core/constants.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/constants.dart b/lib/core/constants.dart index 51b4c70..075b1ab 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -1,2 +1,2 @@ /// Minimum duration of all app skeletons -Duration minimumSkeletonDuration = Duration(milliseconds: 250); \ No newline at end of file +Duration minimumSkeletonDuration = const Duration(milliseconds: 250); From 9b3d61e5b0bd06497007d70102767dba6136241d Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 23 Dec 2025 22:36:11 +0100 Subject: [PATCH 048/222] remove futurebuilder from playerselection and refactor --- .../widgets/player_selection.dart | 109 +++++++----------- 1 file changed, 40 insertions(+), 69 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 96d9d9a..916ed37 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -30,6 +30,7 @@ class _PlayerSelectionState extends State { List selectedPlayers = []; List suggestedPlayers = []; List allPlayers = []; + bool isLoading = true; late final TextEditingController _searchBarController = TextEditingController(); late final AppDatabase db; @@ -43,6 +44,7 @@ class _PlayerSelectionState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); + suggestedPlayers = skeletonData; loadPlayerList(); } @@ -51,8 +53,8 @@ class _PlayerSelectionState extends State { db.playerDao.getAllPlayers(), Future.delayed(minimumSkeletonDuration), ]).then((results) => results[0] as List); - suggestedPlayers = skeletonData; _allPlayersFuture.then((loadedPlayers) { + isLoading = false; setState(() { // If a list of available players is provided, use that list. if (widget.availablePlayers.isNotEmpty) { @@ -163,75 +165,44 @@ class _PlayerSelectionState extends State { style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), - FutureBuilder( - future: _allPlayersFuture, - builder: - (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Player data couldn\'t\nbe loaded.', - ), + /* + + */ + Expanded( + child: AppSkeleton( + enabled: isLoading, + child: Visibility( + visible: suggestedPlayers.isNotEmpty, + replacement: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: allPlayers.isEmpty + ? 'No players created yet.' + : (selectedPlayers.length == allPlayers.length) + ? 'No more players to add.' + : 'No players found with that name.', + ), + child: ListView.builder( + itemCount: suggestedPlayers.length, + itemBuilder: (BuildContext context, int index) { + return TextIconListTile( + text: suggestedPlayers[index].name, + onPressed: () { + setState(() { + if (!selectedPlayers.contains( + suggestedPlayers[index], + )) { + selectedPlayers.add(suggestedPlayers[index]); + widget.onChanged([...selectedPlayers]); + suggestedPlayers.remove(suggestedPlayers[index]); + } + }); + }, ); - } - bool doneLoading = - snapshot.connectionState == ConnectionState.done; - bool snapshotDataEmpty = - !snapshot.hasData || snapshot.data!.isEmpty; - if (doneLoading && - (snapshotDataEmpty && allPlayers.isEmpty)) { - return const Center( - child: TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'No players created yet.', - ), - ); - } - final bool isLoading = - snapshot.connectionState == ConnectionState.waiting; - return Expanded( - child: AppSkeleton( - enabled: isLoading, - child: Visibility( - visible: - (suggestedPlayers.isEmpty && allPlayers.isNotEmpty), - replacement: ListView.builder( - itemCount: suggestedPlayers.length, - itemBuilder: (BuildContext context, int index) { - return TextIconListTile( - text: suggestedPlayers[index].name, - onPressed: () { - setState(() { - if (!selectedPlayers.contains( - suggestedPlayers[index], - )) { - selectedPlayers.add( - suggestedPlayers[index], - ); - widget.onChanged([...selectedPlayers]); - suggestedPlayers.remove( - suggestedPlayers[index], - ); - } - }); - }, - ); - }, - ), - child: TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: (selectedPlayers.length == allPlayers.length) - ? 'No more players to add.' - : 'No players found with that name.', - ), - ), - ), - ); - }, + }, + ), + ), + ), ), ], ), From 9ad5c4ad6fc7bd97ba91523c7a71be8ec02f4976 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 23 Dec 2025 22:58:49 +0100 Subject: [PATCH 049/222] remove futurebuilder from groups view and refactor --- .../main_menu/group_view/groups_view.dart | 85 ++++++++----------- 1 file changed, 36 insertions(+), 49 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 67109e6..c2cb6aa 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -19,15 +19,15 @@ class GroupsView extends StatefulWidget { } class _GroupsViewState extends State { - late Future> _allGroupsFuture; late final AppDatabase db; + late List loadedGroups; + bool isLoading = true; - final player = Player(name: 'Skeleton Player'); - late final List skeletonData = List.filled( + List groups = List.filled( 7, Group( - name: 'Skeleton Match', - members: [player, player, player, player, player, player], + name: 'Skeleton Group', + members: List.filled(6, Player(name: 'Skeleton Player')), ), ); @@ -35,10 +35,17 @@ class _GroupsViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _allGroupsFuture = Future.wait([ + Future.wait([ db.groupDao.getAllGroups(), Future.delayed(minimumSkeletonDuration), - ]).then((results) => results[0] as List); + ]).then((results) { + loadedGroups = results[0] as List; + setState(() { + groups = loadedGroups + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); + }); + isLoading = false; + }); } @override @@ -48,50 +55,30 @@ class _GroupsViewState extends State { body: Stack( alignment: Alignment.center, children: [ - FutureBuilder>( - future: _allGroupsFuture, - builder: - (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Group data couldn\'t\nbe loaded', - ), + AppSkeleton( + enabled: isLoading, + child: Visibility( + visible: groups.isNotEmpty, + replacement: const Center( + child: TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'No groups created yet', + ), + ), + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: groups.length + 1, + itemBuilder: (BuildContext context, int index) { + if (index == groups.length) { + return SizedBox( + height: MediaQuery.paddingOf(context).bottom - 20, ); } - if (snapshot.connectionState == ConnectionState.done && - (!snapshot.hasData || snapshot.data!.isEmpty)) { - return const Center( - child: TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'No groups created yet', - ), - ); - } - final bool isLoading = - snapshot.connectionState == ConnectionState.waiting; - final List groups = - isLoading ? skeletonData : (snapshot.data ?? []) - ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); - return AppSkeleton( - enabled: isLoading, - child: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: groups.length + 1, - itemBuilder: (BuildContext context, int index) { - if (index == groups.length) { - return SizedBox( - height: MediaQuery.paddingOf(context).bottom - 20, - ); - } - return GroupTile(group: groups[index]); - }, - ), - ); + return GroupTile(group: groups[index]); }, + ), + ), ), Positioned( bottom: MediaQuery.paddingOf(context).bottom, @@ -108,7 +95,7 @@ class _GroupsViewState extends State { ), ); setState(() { - _allGroupsFuture = db.groupDao.getAllGroups(); + //_allGroupsFuture = db.groupDao.getAllGroups(); }); }, ), From 06a9c0cd84e8089161d4ca6ed43bd0a2281d7d2c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 23 Dec 2025 22:59:01 +0100 Subject: [PATCH 050/222] remove futurebuilder from player selection and refactor --- lib/presentation/widgets/player_selection.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 916ed37..fda5ea7 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -54,7 +54,6 @@ class _PlayerSelectionState extends State { Future.delayed(minimumSkeletonDuration), ]).then((results) => results[0] as List); _allPlayersFuture.then((loadedPlayers) { - isLoading = false; setState(() { // If a list of available players is provided, use that list. if (widget.availablePlayers.isNotEmpty) { @@ -79,6 +78,7 @@ class _PlayerSelectionState extends State { suggestedPlayers = [...loadedPlayers]; } }); + isLoading = false; }); } From a747d91c5da6d696aa6524e371d36e64ee1c4574 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 23 Dec 2025 23:16:57 +0100 Subject: [PATCH 051/222] refactor group loading into reusable method `loadGroups` and call it after adding a group --- .../main_menu/group_view/groups_view.dart | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index c2cb6aa..5d303d5 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -35,17 +35,7 @@ class _GroupsViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - Future.wait([ - db.groupDao.getAllGroups(), - Future.delayed(minimumSkeletonDuration), - ]).then((results) { - loadedGroups = results[0] as List; - setState(() { - groups = loadedGroups - ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); - }); - isLoading = false; - }); + loadGroups(); } @override @@ -95,7 +85,7 @@ class _GroupsViewState extends State { ), ); setState(() { - //_allGroupsFuture = db.groupDao.getAllGroups(); + loadGroups(); }); }, ), @@ -104,4 +94,18 @@ class _GroupsViewState extends State { ), ); } + + void loadGroups() { + Future.wait([ + db.groupDao.getAllGroups(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) { + loadedGroups = results[0] as List; + setState(() { + groups = loadedGroups + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); + }); + isLoading = false; + }); + } } From 7732c6ceb9a67b011b44cc279f16f975c4c6ce6b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Tue, 23 Dec 2025 23:17:27 +0100 Subject: [PATCH 052/222] remove futurebuilder from match view and refactor --- .../main_menu/match_view/match_view.dart | 118 ++++++++---------- 1 file changed, 50 insertions(+), 68 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 7622134..eb0e83d 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -24,16 +24,16 @@ class MatchView extends StatefulWidget { } class _MatchViewState extends State { - late Future> _gameListFuture; late final AppDatabase db; + bool isLoading = true; - late final List skeletonData = List.filled( + List matches = List.filled( 4, Match( name: 'Skeleton Gamename', group: Group( name: 'Groupname', - members: List.generate(5, (index) => Player(name: 'Player')), + members: List.filled(5, Player(name: 'Player')), ), winner: Player(name: 'Player'), players: [Player(name: 'Player')], @@ -44,10 +44,7 @@ class _MatchViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - _gameListFuture = Future.wait([ - db.matchDao.getAllMatches(), - Future.delayed(minimumSkeletonDuration), - ]).then((results) => results[0] as List); + loadGames(); } @override @@ -57,67 +54,44 @@ class _MatchViewState extends State { body: Stack( alignment: Alignment.center, children: [ - FutureBuilder>( - future: _gameListFuture, - builder: - (BuildContext context, AsyncSnapshot> snapshot) { - if (snapshot.hasError) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Error', - message: 'Game data could not be loaded', - ), + AppSkeleton( + enabled: isLoading, + child: Visibility( + visible: matches.isNotEmpty, + replacement: const Center( + child: TopCenteredMessage( + icon: Icons.report, + title: 'Info', + message: 'No games created yet', + ), + ), + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: matches.length + 1, + itemBuilder: (BuildContext context, int index) { + if (index == matches.length) { + return SizedBox( + height: MediaQuery.paddingOf(context).bottom - 80, ); } - if (snapshot.connectionState == ConnectionState.done && - (!snapshot.hasData || snapshot.data!.isEmpty)) { - return const Center( - child: TopCenteredMessage( - icon: Icons.report, - title: 'Info', - message: 'No games created yet', - ), - ); - } - final bool isLoading = - snapshot.connectionState == ConnectionState.waiting; - final List matches = - (isLoading ? skeletonData : (snapshot.data ?? []) - ..sort( - (a, b) => b.createdAt.compareTo(a.createdAt), - )) - .toList(); - return AppSkeleton( - enabled: isLoading, - child: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: matches.length + 1, - itemBuilder: (BuildContext context, int index) { - if (index == matches.length) { - return SizedBox( - height: MediaQuery.paddingOf(context).bottom - 80, - ); - } - return GameHistoryTile( - onTap: () async { - Navigator.push( - context, - CupertinoPageRoute( - fullscreenDialog: true, - builder: (context) => GameResultView( - match: matches[index], - onWinnerChanged: refreshGameList, - ), - ), - ); - }, - match: matches[index], - ); - }, - ), + return GameHistoryTile( + onTap: () async { + Navigator.push( + context, + CupertinoPageRoute( + fullscreenDialog: true, + builder: (context) => GameResultView( + match: matches[index], + onWinnerChanged: loadGames, + ), + ), + ); + }, + match: matches[index], ); }, + ), + ), ), Positioned( bottom: MediaQuery.paddingOf(context).bottom, @@ -129,7 +103,7 @@ class _MatchViewState extends State { context, MaterialPageRoute( builder: (context) => - CreateMatchView(onWinnerChanged: refreshGameList), + CreateMatchView(onWinnerChanged: loadGames), ), ); }, @@ -140,9 +114,17 @@ class _MatchViewState extends State { ); } - void refreshGameList() { - setState(() { - _gameListFuture = db.matchDao.getAllMatches(); + void loadGames() { + Future.wait([ + db.matchDao.getAllMatches(), + Future.delayed(minimumSkeletonDuration), + ]).then((results) { + final loadedMatches = results[0] as List; + matches = loadedMatches + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); + setState(() { + isLoading = false; + }); }); } } From 1d92084da6a5499e8dfc55b69ff8401e8bdf53d9 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 12:53:37 +0100 Subject: [PATCH 053/222] fix rangeerror when only 1 or less matches exist --- .../views/main_menu/home_view.dart | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 31bedf5..bb2acf6 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -22,6 +22,7 @@ class _HomeViewState extends State { bool isLoading = true; int matchCount = 0; int groupCount = 0; + List loadedRecentMatches = []; List recentMatches = List.filled( 2, Match( @@ -41,7 +42,6 @@ class _HomeViewState extends State { void initState() { super.initState(); final db = Provider.of(context, listen: false); - Future.wait([ db.matchDao.getMatchCount(), db.groupDao.getGroupCount(), @@ -50,12 +50,17 @@ class _HomeViewState extends State { ]).then((results) { matchCount = results[0] as int; groupCount = results[1] as int; - recentMatches = results[2] as List; - + loadedRecentMatches = results[2] as List; recentMatches = - (recentMatches..sort((a, b) => b.createdAt.compareTo(a.createdAt))) + (loadedRecentMatches + ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) .take(2) .toList(); + if (loadedRecentMatches.length < 2) { + recentMatches.add( + Match(name: "Dummy Match", winner: null, group: null, players: null), + ); + } if (mounted) { setState(() { isLoading = false; @@ -69,6 +74,7 @@ class _HomeViewState extends State { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return AppSkeleton( + fixLayoutBuilder: true, enabled: isLoading, child: SingleChildScrollView( child: Column( @@ -103,7 +109,7 @@ class _HomeViewState extends State { content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), child: Visibility( - visible: !isLoading, + visible: !isLoading && loadedRecentMatches.isNotEmpty, replacement: const Center( heightFactor: 12, child: Text('No recent games available.'), @@ -125,7 +131,7 @@ class _HomeViewState extends State { padding: EdgeInsets.symmetric(vertical: 8.0), child: Divider(), ), - if (recentMatches.length > 1) ...[ + if (loadedRecentMatches.length > 1) ...[ MatchTile( matchTitle: recentMatches[1].name, game: 'Winner', From b29cd6dff47e90611d9bfedd410c8895ae41e222 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 12:54:05 +0100 Subject: [PATCH 054/222] remove double quotes --- lib/presentation/views/main_menu/home_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index bb2acf6..06b647c 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -58,7 +58,7 @@ class _HomeViewState extends State { .toList(); if (loadedRecentMatches.length < 2) { recentMatches.add( - Match(name: "Dummy Match", winner: null, group: null, players: null), + Match(name: 'Dummy Match', winner: null, group: null, players: null), ); } if (mounted) { From f2917a6813b80a64ee935e8e6e6bd8e81f7651cd Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:00:38 +0100 Subject: [PATCH 055/222] removed empty line --- .../main_menu/match_view/create_match/create_match_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 27a1ce9..787d200 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -108,7 +108,6 @@ class _CreateMatchViewState extends State { groupsList = result[0] as List; playerList = result[1] as List; }); - filteredPlayerList = List.from(playerList); } From f9722bc7628910c06ac5c7ded67100aa26d03541 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:01:05 +0100 Subject: [PATCH 056/222] wrap `isLoading = false` in a `mounted` check and `setState` call --- .../views/main_menu/group_view/groups_view.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 5d303d5..b2243bc 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -105,7 +105,11 @@ class _GroupsViewState extends State { groups = loadedGroups ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); }); - isLoading = false; + if (mounted) { + setState(() { + isLoading = false; + }); + } }); } } From 7eb25221d75bd989fe52b344be6dfcd63d06f359 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:01:16 +0100 Subject: [PATCH 057/222] add mounted check before calling setState in match_view --- .../views/main_menu/match_view/match_view.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index eb0e83d..97a765e 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -122,9 +122,11 @@ class _MatchViewState extends State { final loadedMatches = results[0] as List; matches = loadedMatches ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); - setState(() { - isLoading = false; - }); + if (mounted) { + setState(() { + isLoading = false; + }); + } }); } } From 1e730cebe61e0c40faa6051a09bf82b2571309b3 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:01:40 +0100 Subject: [PATCH 058/222] wrap isLoading into mounted and setState --- lib/presentation/widgets/player_selection.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index fda5ea7..1e283fb 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -78,7 +78,11 @@ class _PlayerSelectionState extends State { suggestedPlayers = [...loadedPlayers]; } }); - isLoading = false; + if (mounted) { + setState(() { + isLoading = false; + }); + } }); } From c73f37507f23b2705eab820994814e52de75d9bf Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:05:49 +0100 Subject: [PATCH 059/222] put isLoading in existing setState and move mounted check up --- .../widgets/player_selection.dart | 54 +++++++++---------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 1e283fb..fda1da1 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -53,37 +53,35 @@ class _PlayerSelectionState extends State { db.playerDao.getAllPlayers(), Future.delayed(minimumSkeletonDuration), ]).then((results) => results[0] as List); - _allPlayersFuture.then((loadedPlayers) { - setState(() { - // If a list of available players is provided, use that list. - if (widget.availablePlayers.isNotEmpty) { - widget.availablePlayers.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = [...widget.availablePlayers]; - suggestedPlayers = [...allPlayers]; - - if (widget.initialSelectedPlayers != null) { - // Ensures that only players available for selection are pre-selected. - selectedPlayers = widget.initialSelectedPlayers! - .where( - (p) => widget.availablePlayers.any( - (available) => available.id == p.id, - ), - ) - .toList(); - } - } else { - // Otherwise, use the loaded players from the database. - loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = [...loadedPlayers]; - suggestedPlayers = [...loadedPlayers]; - } - }); - if (mounted) { + if (mounted) { + _allPlayersFuture.then((loadedPlayers) { setState(() { + // If a list of available players is provided, use that list. + if (widget.availablePlayers.isNotEmpty) { + widget.availablePlayers.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...widget.availablePlayers]; + suggestedPlayers = [...allPlayers]; + + if (widget.initialSelectedPlayers != null) { + // Ensures that only players available for selection are pre-selected. + selectedPlayers = widget.initialSelectedPlayers! + .where( + (p) => widget.availablePlayers.any( + (available) => available.id == p.id, + ), + ) + .toList(); + } + } else { + // Otherwise, use the loaded players from the database. + loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...loadedPlayers]; + suggestedPlayers = [...loadedPlayers]; + } isLoading = false; }); - } - }); + }); + } } @override From 7a80c1a792e2c208f0ef773522db94027242fdb5 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:08:25 +0100 Subject: [PATCH 060/222] adjust heightFactor for empty game state in home view --- lib/presentation/views/main_menu/home_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 06b647c..ade8f6e 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -144,7 +144,7 @@ class _HomeViewState extends State { const SizedBox(height: 8), ] else ...[ const Center( - heightFactor: 4, + heightFactor: 5.35, child: Text('No second game available.'), ), ], From b0a5145490ef353b134b3aa2d2e16824c70db044 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 13:14:31 +0100 Subject: [PATCH 061/222] move setState to include match loading and sorting in MatchView --- lib/presentation/views/main_menu/match_view/match_view.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 97a765e..462e1b5 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -119,11 +119,11 @@ class _MatchViewState extends State { db.matchDao.getAllMatches(), Future.delayed(minimumSkeletonDuration), ]).then((results) { - final loadedMatches = results[0] as List; - matches = loadedMatches - ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); if (mounted) { setState(() { + final loadedMatches = results[0] as List; + matches = loadedMatches + ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); isLoading = false; }); } From 12856342a812f11f1d3b68efd950b5000082001c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 22:53:17 +0100 Subject: [PATCH 062/222] remove dots after sentences --- lib/presentation/views/main_menu/home_view.dart | 4 ++-- lib/presentation/widgets/player_selection.dart | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index ade8f6e..3e221e7 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -112,7 +112,7 @@ class _HomeViewState extends State { visible: !isLoading && loadedRecentMatches.isNotEmpty, replacement: const Center( heightFactor: 12, - child: Text('No recent games available.'), + child: Text('No recent games available'), ), child: Column( mainAxisAlignment: MainAxisAlignment.start, @@ -145,7 +145,7 @@ class _HomeViewState extends State { ] else ...[ const Center( heightFactor: 5.35, - child: Text('No second game available.'), + child: Text('No second game available'), ), ], ], diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index fda1da1..01c0338 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -179,10 +179,10 @@ class _PlayerSelectionState extends State { icon: Icons.info, title: 'Info', message: allPlayers.isEmpty - ? 'No players created yet.' + ? 'No players created yet' : (selectedPlayers.length == allPlayers.length) - ? 'No more players to add.' - : 'No players found with that name.', + ? 'No more players to add' + : 'No players found with that name', ), child: ListView.builder( itemCount: suggestedPlayers.length, From d7a006dd87f2449f1ee74dc94e758b840470aa4c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 24 Dec 2025 23:51:34 +0100 Subject: [PATCH 063/222] fixed last gametile not visible --- lib/presentation/views/main_menu/match_view/match_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 92fd268..7a7cb01 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -95,7 +95,7 @@ class _MatchViewState extends State { itemBuilder: (BuildContext context, int index) { if (index == matches.length) { return SizedBox( - height: MediaQuery.paddingOf(context).bottom - 80, + height: MediaQuery.paddingOf(context).bottom - 20, ); } return GameHistoryTile( From 966f7caa54ad02b0665858338108428ec6899a90 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 29 Dec 2025 18:19:29 +0100 Subject: [PATCH 064/222] Fixed info message --- .../create_match/choose_group_view.dart | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index c30d6ef..93686bd 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -74,10 +74,18 @@ class _ChooseGroupViewState extends State { Expanded( child: Visibility( visible: filteredGroups.isNotEmpty, - replacement: const TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'There is no group matching your search', + replacement: Visibility( + visible: controller.text.isNotEmpty, + replacement: const TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'You have no groups created yet', + ), + child: const TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'There is no group matching your search', + ), ), child: ListView.builder( padding: const EdgeInsets.only(bottom: 85), From 856ce43c49fac4627871ff5327b0c1d9096fbbfd Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 29 Dec 2025 18:28:06 +0100 Subject: [PATCH 065/222] Updated statistics title --- lib/presentation/views/main_menu/statistics_view.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 7df77a7..e94f2b6 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -60,7 +60,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.01), StatisticsTile( icon: Icons.sports_score, - title: 'Wins per Player', + title: 'Wins', width: constraints.maxWidth * 0.95, values: winCounts, itemCount: 3, @@ -69,7 +69,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.percent, - title: 'Winrate per Player', + title: 'Winrate', width: constraints.maxWidth * 0.95, values: winRates, itemCount: 5, @@ -78,7 +78,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, - title: 'Match per Player', + title: 'Amount of Matches', width: constraints.maxWidth * 0.95, values: matchCounts, itemCount: 10, From dac5cc8a892e7d2d1c4ff4b175e935ca1c15027e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 29 Dec 2025 18:37:44 +0100 Subject: [PATCH 066/222] Hab leider nur von Wand bis Tapete gedacht --- .../main_menu/match_view/create_match/choose_group_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 93686bd..37096b9 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -75,7 +75,7 @@ class _ChooseGroupViewState extends State { child: Visibility( visible: filteredGroups.isNotEmpty, replacement: Visibility( - visible: controller.text.isNotEmpty, + visible: widget.groups.isNotEmpty, replacement: const TopCenteredMessage( icon: Icons.info, title: 'Info', From 5ae569f2e7ce3117d0ff68abcb81dec1743f2d40 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 29 Dec 2025 19:16:25 +0100 Subject: [PATCH 067/222] Fixed updating error --- .../main_menu/match_view/create_match/create_match_view.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 787d200..0eea2b1 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -234,6 +234,8 @@ class _CreateMatchViewState extends State { players: selectedPlayers, ); await db.matchDao.addMatch(match: match); + widget.onWinnerChanged?.call(); + if (context.mounted) { Navigator.pushReplacement( context, From 31c85982226df68cdc24132e9c0b42b2e6877e91 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 29 Dec 2025 19:28:47 +0100 Subject: [PATCH 068/222] Made cleaner version --- .../match_view/create_match/create_match_view.dart | 2 -- .../views/main_menu/match_view/match_result_view.dart | 7 +++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 0eea2b1..787d200 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -234,8 +234,6 @@ class _CreateMatchViewState extends State { players: selectedPlayers, ); await db.matchDao.addMatch(match: match); - widget.onWinnerChanged?.call(); - if (context.mounted) { Navigator.pushReplacement( context, diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index 58ff9ce..c6c3dae 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -38,6 +38,13 @@ class _GameResultViewState extends State { return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( + leading: IconButton( + icon: const Icon(Icons.close), + onPressed: () { + widget.onWinnerChanged?.call(); + Navigator.of(context).pop(); + }, + ), backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: Text( From 356cb1fb4316c2c6e64ea696c48c19482a89dd71 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 18:21:39 +0100 Subject: [PATCH 069/222] Updated insert mode to fix problem with replacing existing player match connections --- lib/data/dao/group_match_dao.dart | 2 +- lib/data/dao/player_match_dao.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/data/dao/group_match_dao.dart b/lib/data/dao/group_match_dao.dart index 3c16c83..d428fb5 100644 --- a/lib/data/dao/group_match_dao.dart +++ b/lib/data/dao/group_match_dao.dart @@ -21,7 +21,7 @@ class GroupMatchDao extends DatabaseAccessor } await into(groupMatchTable).insert( GroupMatchTableCompanion.insert(groupId: groupId, matchId: matchId), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ); } diff --git a/lib/data/dao/player_match_dao.dart b/lib/data/dao/player_match_dao.dart index 9e242b7..7ebaee6 100644 --- a/lib/data/dao/player_match_dao.dart +++ b/lib/data/dao/player_match_dao.dart @@ -18,7 +18,7 @@ class PlayerMatchDao extends DatabaseAccessor }) async { await into(playerMatchTable).insert( PlayerMatchTableCompanion.insert(playerId: playerId, matchId: matchId), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ); } @@ -121,7 +121,7 @@ class PlayerMatchDao extends DatabaseAccessor inserts.map( (c) => into( playerMatchTable, - ).insert(c, mode: InsertMode.insertOrReplace), + ).insert(c, mode: InsertMode.insertOrIgnore), ), ); } From b8b65c4ca18cd4bcfccc3b040c51a06910287909 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 18:23:15 +0100 Subject: [PATCH 070/222] Removed lines that added the group and players to the db again in addMatch() --- lib/data/dao/match_dao.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart index 32bd323..76c3709 100644 --- a/lib/data/dao/match_dao.dart +++ b/lib/data/dao/match_dao.dart @@ -80,7 +80,6 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ); if (match.players != null) { - await db.playerDao.addPlayersAsList(players: match.players!); for (final p in match.players ?? []) { await db.playerMatchDao.addPlayerToMatch( matchId: match.id, @@ -90,7 +89,6 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { } if (match.group != null) { - await db.groupDao.addGroup(group: match.group!); await db.groupMatchDao.addGroupToMatch( matchId: match.id, groupId: match.group!.id, @@ -186,7 +184,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { matchId: match.id, playerId: p.id, ), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ); } } @@ -204,7 +202,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { playerId: m.id, groupId: match.group!.id, ), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ); } } @@ -221,7 +219,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { matchId: match.id, groupId: match.group!.id, ), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ); } } From 9cb35dc4a1fd5676073641f80364d5728f896238 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 18:45:28 +0100 Subject: [PATCH 071/222] Updated comments --- lib/data/dao/match_dao.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart index 76c3709..b56a38b 100644 --- a/lib/data/dao/match_dao.dart +++ b/lib/data/dao/match_dao.dart @@ -65,8 +65,9 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ); } - /// Adds a new [Match] to the database. - /// Also adds associated players and group if they exist. + /// Adds a new [Match] to the database. Also adds players and group + /// associations. This method assumes that the players and groups added to + /// this match are already present in the database. Future addMatch({required Match match}) async { await db.transaction(() async { await into(matchTable).insert( @@ -100,6 +101,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { /// Adds multiple [Match]s to the database in a batch operation. /// Also adds associated players and groups if they exist. /// If the [matches] list is empty, the method returns immediately. + /// This Method should only be used to import matches from a different device. Future addMatchAsList({required List matches}) async { if (matches.isEmpty) return; await db.transaction(() async { From e19f6967141a3ee586a696f681f6c7b7271a686d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 18:51:57 +0100 Subject: [PATCH 072/222] Fixed tests with adding players and groups to database on setup --- test/db_tests/game_test.dart | 12 +++++++++++- test/db_tests/group_game_test.dart | 12 +++++++++++- test/db_tests/player_game_test.dart | 13 ++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index ca86a60..d750156 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -23,7 +23,7 @@ void main() { final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); - setUp(() { + setUp(() async { database = AppDatabase( DatabaseConnection( NativeDatabase.memory(), @@ -68,6 +68,16 @@ void main() { group: testGroup2, ); }); + await database.playerDao.addPlayersAsList( + players: [ + testPlayer1, + testPlayer2, + testPlayer3, + testPlayer4, + testPlayer5, + ], + ); + await database.groupDao.addGroupsAsList(groups: [testGroup1, testGroup2]); }); tearDown(() async { await database.close(); diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart index a4fa146..06d201b 100644 --- a/test/db_tests/group_game_test.dart +++ b/test/db_tests/group_game_test.dart @@ -21,7 +21,7 @@ void main() { final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); - setUp(() { + setUp(() async { database = AppDatabase( DatabaseConnection( NativeDatabase.memory(), @@ -53,6 +53,16 @@ void main() { group: testGroup1, ); }); + await database.playerDao.addPlayersAsList( + players: [ + testPlayer1, + testPlayer2, + testPlayer3, + testPlayer4, + testPlayer5, + ], + ); + await database.groupDao.addGroupsAsList(groups: [testGroup1, testGroup2]); }); tearDown(() async { await database.close(); diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index e2501eb..71e4088 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -21,7 +21,7 @@ void main() { final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); final fakeClock = Clock(() => fixedDate); - setUp(() { + setUp(() async { database = AppDatabase( DatabaseConnection( NativeDatabase.memory(), @@ -50,6 +50,17 @@ void main() { players: [testPlayer4, testPlayer5, testPlayer6], ); }); + await database.playerDao.addPlayersAsList( + players: [ + testPlayer1, + testPlayer2, + testPlayer3, + testPlayer4, + testPlayer5, + testPlayer6, + ], + ); + await database.groupDao.addGroup(group: testgroup); }); tearDown(() async { await database.close(); From c1789458e0a7193d093380b058375ddd7e62d5de Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 20:56:39 +0100 Subject: [PATCH 073/222] Added tests for bug causing behaviour --- test/db_tests/game_test.dart | 2 +- test/db_tests/group_game_test.dart | 30 +++++++++++++++++++++- test/db_tests/player_game_test.dart | 39 ++++++++++++++++++++++++++++- 3 files changed, 68 insertions(+), 3 deletions(-) diff --git a/test/db_tests/game_test.dart b/test/db_tests/game_test.dart index d750156..0ec2cfc 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/game_test.dart @@ -263,7 +263,7 @@ void main() { expect(matchCount, 0); }); - test('Checking if match has winner works correclty', () async { + test('Checking if match has winner works correctly', () async { await database.matchDao.addMatch(match: testMatch1); await database.matchDao.addMatch(match: testMatchOnlyGroup); diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_game_test.dart index 06d201b..7d812bd 100644 --- a/test/db_tests/group_game_test.dart +++ b/test/db_tests/group_game_test.dart @@ -1,5 +1,5 @@ import 'package:clock/clock.dart'; -import 'package:drift/drift.dart'; +import 'package:drift/drift.dart' hide isNotNull; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:game_tracker/data/db/database.dart'; @@ -189,5 +189,33 @@ void main() { } } }); + + test('Adding the same group to seperate matches works correctly', () async { + final match1 = Match(name: 'Match 1', group: testGroup1); + final match2 = Match(name: 'Match 2', group: testGroup1); + + await Future.wait([ + database.matchDao.addMatch(match: match1), + database.matchDao.addMatch(match: match2), + ]); + + final group1 = await database.groupMatchDao.getGroupOfMatch( + matchId: match1.id, + ); + final group2 = await database.groupMatchDao.getGroupOfMatch( + matchId: match2.id, + ); + + expect(group1, isNotNull); + expect(group2, isNotNull); + + final groups = [group1!, group2!]; + for (final group in groups) { + expect(group.members.length, testGroup1.members.length); + expect(group.id, testGroup1.id); + expect(group.name, testGroup1.name); + expect(group.createdAt, testGroup1.createdAt); + } + }); }); } diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_game_test.dart index 71e4088..8a4f569 100644 --- a/test/db_tests/player_game_test.dart +++ b/test/db_tests/player_game_test.dart @@ -1,5 +1,5 @@ import 'package:clock/clock.dart'; -import 'package:drift/drift.dart'; +import 'package:drift/drift.dart' hide isNotNull; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:game_tracker/data/db/database.dart'; @@ -196,5 +196,42 @@ void main() { expect(player.createdAt, testPlayer.createdAt); } }); + + test( + 'Adding the same player to seperate matches works correctly', + () async { + final playersList = [testPlayer1, testPlayer2, testPlayer3]; + final match1 = Match(name: 'Match 1', players: playersList); + final match2 = Match(name: 'Match 2', players: playersList); + + await Future.wait([ + database.matchDao.addMatch(match: match1), + database.matchDao.addMatch(match: match2), + ]); + + final players1 = await database.playerMatchDao.getPlayersOfMatch( + matchId: match1.id, + ); + final players2 = await database.playerMatchDao.getPlayersOfMatch( + matchId: match2.id, + ); + + expect(players1, isNotNull); + expect(players2, isNotNull); + + expect( + players1!.map((p) => p.id).toList(), + equals(players2!.map((p) => p.id).toList()), + ); + expect( + players1.map((p) => p.name).toList(), + equals(players2.map((p) => p.name).toList()), + ); + expect( + players1.map((p) => p.createdAt).toList(), + equals(players2.map((p) => p.createdAt).toList()), + ); + }, + ); }); } From 9821af39fed15f8b7fc6c0fe9995f7709e19290c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 21:00:56 +0100 Subject: [PATCH 074/222] Renamed test files --- test/db_tests/{group_game_test.dart => group_match_test.dart} | 0 test/db_tests/{player_game_test.dart => player_match_test.dart} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename test/db_tests/{group_game_test.dart => group_match_test.dart} (100%) rename test/db_tests/{player_game_test.dart => player_match_test.dart} (100%) diff --git a/test/db_tests/group_game_test.dart b/test/db_tests/group_match_test.dart similarity index 100% rename from test/db_tests/group_game_test.dart rename to test/db_tests/group_match_test.dart diff --git a/test/db_tests/player_game_test.dart b/test/db_tests/player_match_test.dart similarity index 100% rename from test/db_tests/player_game_test.dart rename to test/db_tests/player_match_test.dart From 05c6625bf32b4318f112f7bb614f522edff68b04 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 21:01:07 +0100 Subject: [PATCH 075/222] Renamed MatchResultView and match variable --- .../create_match/create_match_view.dart | 2 +- .../match_view/match_result_view.dart | 20 +++++++++---------- .../main_menu/match_view/match_view.dart | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 787d200..ea48392 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -239,7 +239,7 @@ class _CreateMatchViewState extends State { context, CupertinoPageRoute( fullscreenDialog: true, - builder: (context) => GameResultView( + builder: (context) => MatchResultView( match: match, onWinnerChanged: widget.onWinnerChanged, ), diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index c6c3dae..e8075f6 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -6,17 +6,17 @@ import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart'; import 'package:provider/provider.dart'; -class GameResultView extends StatefulWidget { +class MatchResultView extends StatefulWidget { final Match match; final VoidCallback? onWinnerChanged; - const GameResultView({super.key, required this.match, this.onWinnerChanged}); + const MatchResultView({super.key, required this.match, this.onWinnerChanged}); @override - State createState() => _GameResultViewState(); + State createState() => _MatchResultViewState(); } -class _GameResultViewState extends State { +class _MatchResultViewState extends State { late final List allPlayers; late final AppDatabase db; Player? _selectedPlayer; @@ -142,12 +142,12 @@ class _GameResultViewState extends State { widget.onWinnerChanged?.call(); } - List getAllPlayers(Match game) { - if (game.group == null && game.players != null) { - return [...game.players!]; - } else if (game.group != null && game.players != null) { - return [...game.players!, ...game.group!.members]; + List getAllPlayers(Match match) { + if (match.group == null && match.players != null) { + return [...match.players!]; + } else if (match.group != null && match.players != null) { + return [...match.players!, ...match.group!.members]; } - return [...game.group!.members]; + return [...match.group!.members]; } } diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 462e1b5..e7d29c0 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -80,7 +80,7 @@ class _MatchViewState extends State { context, CupertinoPageRoute( fullscreenDialog: true, - builder: (context) => GameResultView( + builder: (context) => MatchResultView( match: matches[index], onWinnerChanged: loadGames, ), From ecd03dfd9d5efea2c0ac099b9e7372ccbc2e0c3d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 21:01:40 +0100 Subject: [PATCH 076/222] Updated hint text --- .../main_menu/match_view/create_match/create_match_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index ea48392..6f966af 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -132,7 +132,7 @@ class _CreateMatchViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), child: TextInputField( controller: _gameNameController, - hintText: 'Game name', + hintText: 'Match name', ), ), ChooseTile( From 58fe38fd72b6a8efd24063e822e7681507e4b35e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 21:02:03 +0100 Subject: [PATCH 077/222] Updated comment --- .../main_menu/match_view/create_match/create_match_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 6f966af..2d67aa4 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -28,7 +28,7 @@ class _CreateMatchViewState extends State { /// Reference to the app database late final AppDatabase db; - /// Controller for the game name input field + /// Controller for the match name input field final TextEditingController _gameNameController = TextEditingController(); /// List of all groups from the database From 5428064c53aa37e8b795788b2b10960d6b60efb7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Tue, 30 Dec 2025 21:22:07 +0100 Subject: [PATCH 078/222] Some last changes --- .../match_view/create_match/create_match_view.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 2d67aa4..f3b4d79 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -29,7 +29,7 @@ class _CreateMatchViewState extends State { late final AppDatabase db; /// Controller for the match name input field - final TextEditingController _gameNameController = TextEditingController(); + final TextEditingController _matchNameController = TextEditingController(); /// List of all groups from the database List groupsList = []; @@ -95,7 +95,7 @@ class _CreateMatchViewState extends State { @override void initState() { super.initState(); - _gameNameController.addListener(() { + _matchNameController.addListener(() { setState(() {}); }); @@ -119,7 +119,7 @@ class _CreateMatchViewState extends State { backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: const Text( - 'Create new game', + 'Create new match', style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -131,7 +131,7 @@ class _CreateMatchViewState extends State { Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), child: TextInputField( - controller: _gameNameController, + controller: _matchNameController, hintText: 'Match name', ), ), @@ -222,13 +222,13 @@ class _CreateMatchViewState extends State { ), ), CustomWidthButton( - text: 'Create game', + text: 'Create match', sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: _enableCreateGameButton() ? () async { Match match = Match( - name: _gameNameController.text.trim(), + name: _matchNameController.text.trim(), createdAt: DateTime.now(), group: selectedGroup, players: selectedPlayers, @@ -258,7 +258,7 @@ class _CreateMatchViewState extends State { /// Determines whether the "Create Game" button should be enabled based on /// the current state of the input fields. bool _enableCreateGameButton() { - return _gameNameController.text.isNotEmpty && + return _matchNameController.text.isNotEmpty && (selectedGroup != null || (selectedPlayers != null && selectedPlayers!.length > 1)) && selectedRuleset != null; From bfca41bd36af578a80e34f9b84c3cc7856972c63 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 31 Dec 2025 16:38:09 +0100 Subject: [PATCH 079/222] Fixed info message placement --- .../widgets/player_selection.dart | 68 +++++++++++-------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index e2114b2..88841e6 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -128,33 +128,47 @@ class _PlayerSelectionState extends State { style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), - Wrap( - alignment: WrapAlignment.start, - crossAxisAlignment: WrapCrossAlignment.start, - spacing: 8.0, - runSpacing: 8.0, - children: [ - // Generates a TextIconTile for each selected player. - for (var player in selectedPlayers) - TextIconTile( - text: player.name, - onIconTap: () { - setState(() { - // Removes the player from the selection and notifies the parent. - final currentSearch = _searchBarController.text - .toLowerCase(); - selectedPlayers.remove(player); - widget.onChanged([...selectedPlayers]); - // If the player matches the current search query (or search is empty), - // they are added back to the suggestions and the list is re-sorted. - if (currentSearch.isEmpty || - player.name.toLowerCase().contains(currentSearch)) { - suggestedPlayers.add(player); - } - }); - }, - ), - ], + SizedBox( + height: 50, + child: selectedPlayers.isEmpty + ? const Center( + child: Text( + 'No players selected', + style: TextStyle(color: Colors.grey), + ), + ) + : SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: [ + for (var player in selectedPlayers) + Padding( + padding: const EdgeInsets.only(right: 8.0), + child: TextIconTile( + text: player.name, + onIconTap: () { + setState(() { + // Removes the player from the selection and notifies the parent. + final currentSearch = _searchBarController + .text + .toLowerCase(); + selectedPlayers.remove(player); + widget.onChanged([...selectedPlayers]); + // If the player matches the current search query (or search is empty), + // they are added back to the suggestions and the list is re-sorted. + if (currentSearch.isEmpty || + player.name.toLowerCase().contains( + currentSearch, + )) { + suggestedPlayers.add(player); + } + }); + }, + ), + ), + ], + ), + ), ), const SizedBox(height: 10), const Text( From 8c3282b954642f823a8e7f063d5a7ef9b5ceb977 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 31 Dec 2025 17:10:47 +0100 Subject: [PATCH 080/222] Changed color to white --- lib/presentation/widgets/player_selection.dart | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 007a95f..eb70ae0 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -136,12 +136,7 @@ class _PlayerSelectionState extends State { SizedBox( height: 50, child: selectedPlayers.isEmpty - ? const Center( - child: Text( - 'No players selected', - style: TextStyle(color: Colors.grey), - ), - ) + ? const Center(child: Text('No players selected')) : SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( From 16cdf9db3ea5bd3277086e6dec4871c050095b79 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Wed, 31 Dec 2025 17:39:20 +0100 Subject: [PATCH 081/222] Restricted app orientation to portrait mode on iOS and Android --- android/app/src/main/AndroidManifest.xml | 1 + ios/Runner/Info.plist | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4cded1b..7e89f4b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ UISupportedInterfaceOrientations UIInterfaceOrientationPortrait - UIInterfaceOrientationLandscapeLeft - UIInterfaceOrientationLandscapeRight UISupportedInterfaceOrientations~ipad From 18f0626e95befce350f434b53566b5615712c1ba Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 31 Dec 2025 18:33:09 +0100 Subject: [PATCH 082/222] Added empty statistics message --- .../views/main_menu/statistics_view.dart | 66 ++++++++++++------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index e94f2b6..0e95721 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -5,6 +5,7 @@ import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/tiles/statistics_tile.dart'; +import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; class StatisticsView extends StatefulWidget { @@ -58,31 +59,46 @@ class _StatisticsViewState extends State { crossAxisAlignment: CrossAxisAlignment.center, children: [ SizedBox(height: constraints.maxHeight * 0.01), - StatisticsTile( - icon: Icons.sports_score, - title: 'Wins', - width: constraints.maxWidth * 0.95, - values: winCounts, - itemCount: 3, - barColor: Colors.blue, - ), - SizedBox(height: constraints.maxHeight * 0.02), - StatisticsTile( - icon: Icons.percent, - title: 'Winrate', - width: constraints.maxWidth * 0.95, - values: winRates, - itemCount: 5, - barColor: Colors.orange[700]!, - ), - SizedBox(height: constraints.maxHeight * 0.02), - StatisticsTile( - icon: Icons.casino, - title: 'Amount of Matches', - width: constraints.maxWidth * 0.95, - values: matchCounts, - itemCount: 10, - barColor: Colors.green, + Visibility( + visible: + winCounts.isEmpty && + matchCounts.isEmpty && + winRates.isEmpty, + replacement: Column( + children: [ + StatisticsTile( + icon: Icons.sports_score, + title: 'Wins', + width: constraints.maxWidth * 0.95, + values: winCounts, + itemCount: 3, + barColor: Colors.blue, + ), + SizedBox(height: constraints.maxHeight * 0.02), + StatisticsTile( + icon: Icons.percent, + title: 'Winrate', + width: constraints.maxWidth * 0.95, + values: winRates, + itemCount: 5, + barColor: Colors.orange[700]!, + ), + SizedBox(height: constraints.maxHeight * 0.02), + StatisticsTile( + icon: Icons.casino, + title: 'Amount of Matches', + width: constraints.maxWidth * 0.95, + values: matchCounts, + itemCount: 10, + barColor: Colors.green, + ), + ], + ), + child: const TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'No statistics available', + ), ), SizedBox(height: MediaQuery.paddingOf(context).bottom), ], From 175a9cb3490a3554fd10d24b9962a3a5426287c2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 31 Dec 2025 18:36:33 +0100 Subject: [PATCH 083/222] Added hiding statistics tiles if their corresponding data is not available --- .../views/main_menu/statistics_view.dart | 58 ++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 0e95721..46b01b7 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -66,32 +66,38 @@ class _StatisticsViewState extends State { winRates.isEmpty, replacement: Column( children: [ - StatisticsTile( - icon: Icons.sports_score, - title: 'Wins', - width: constraints.maxWidth * 0.95, - values: winCounts, - itemCount: 3, - barColor: Colors.blue, - ), - SizedBox(height: constraints.maxHeight * 0.02), - StatisticsTile( - icon: Icons.percent, - title: 'Winrate', - width: constraints.maxWidth * 0.95, - values: winRates, - itemCount: 5, - barColor: Colors.orange[700]!, - ), - SizedBox(height: constraints.maxHeight * 0.02), - StatisticsTile( - icon: Icons.casino, - title: 'Amount of Matches', - width: constraints.maxWidth * 0.95, - values: matchCounts, - itemCount: 10, - barColor: Colors.green, - ), + if (winCounts.isNotEmpty) ...[ + StatisticsTile( + icon: Icons.sports_score, + title: 'Wins', + width: constraints.maxWidth * 0.95, + values: winCounts, + itemCount: 3, + barColor: Colors.blue, + ), + SizedBox(height: constraints.maxHeight * 0.02), + ], + if (winRates.isNotEmpty) ...[ + StatisticsTile( + icon: Icons.percent, + title: 'Winrate', + width: constraints.maxWidth * 0.95, + values: winRates, + itemCount: 5, + barColor: Colors.orange[700]!, + ), + SizedBox(height: constraints.maxHeight * 0.02), + ], + if (matchCounts.isNotEmpty) ...[ + StatisticsTile( + icon: Icons.casino, + title: 'Amount of Matches', + width: constraints.maxWidth * 0.95, + values: matchCounts, + itemCount: 10, + barColor: Colors.green, + ), + ], ], ), child: const TopCenteredMessage( From 2b78617924fc29e30d138071b38cdd16ae4e3f64 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 31 Dec 2025 19:11:34 +0100 Subject: [PATCH 084/222] Redo seperate visibility --- .../views/main_menu/statistics_view.dart | 58 +++++++++---------- 1 file changed, 26 insertions(+), 32 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 46b01b7..0e95721 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -66,38 +66,32 @@ class _StatisticsViewState extends State { winRates.isEmpty, replacement: Column( children: [ - if (winCounts.isNotEmpty) ...[ - StatisticsTile( - icon: Icons.sports_score, - title: 'Wins', - width: constraints.maxWidth * 0.95, - values: winCounts, - itemCount: 3, - barColor: Colors.blue, - ), - SizedBox(height: constraints.maxHeight * 0.02), - ], - if (winRates.isNotEmpty) ...[ - StatisticsTile( - icon: Icons.percent, - title: 'Winrate', - width: constraints.maxWidth * 0.95, - values: winRates, - itemCount: 5, - barColor: Colors.orange[700]!, - ), - SizedBox(height: constraints.maxHeight * 0.02), - ], - if (matchCounts.isNotEmpty) ...[ - StatisticsTile( - icon: Icons.casino, - title: 'Amount of Matches', - width: constraints.maxWidth * 0.95, - values: matchCounts, - itemCount: 10, - barColor: Colors.green, - ), - ], + StatisticsTile( + icon: Icons.sports_score, + title: 'Wins', + width: constraints.maxWidth * 0.95, + values: winCounts, + itemCount: 3, + barColor: Colors.blue, + ), + SizedBox(height: constraints.maxHeight * 0.02), + StatisticsTile( + icon: Icons.percent, + title: 'Winrate', + width: constraints.maxWidth * 0.95, + values: winRates, + itemCount: 5, + barColor: Colors.orange[700]!, + ), + SizedBox(height: constraints.maxHeight * 0.02), + StatisticsTile( + icon: Icons.casino, + title: 'Amount of Matches', + width: constraints.maxWidth * 0.95, + values: matchCounts, + itemCount: 10, + barColor: Colors.green, + ), ], ), child: const TopCenteredMessage( From 179ac2fe213ae22c2129275a75a4d7d2caa81f2f Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:20:16 +0100 Subject: [PATCH 085/222] Update localization dependencies and enable code generation --- pubspec.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 07e4df2..866f662 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,7 +24,10 @@ dependencies: json_schema: ^5.2.2 file_saver: ^0.3.1 clock: ^1.1.2 - intl: ^0.18.0 + intl: any + flutter_localizations: + sdk: flutter + auto_localize: ^0.0.5 dev_dependencies: flutter_test: @@ -35,5 +38,6 @@ dev_dependencies: flutter: uses-material-design: true + generate: true assets: - assets/schema.json From 8e05e9d61b71e1591b7ab7a7821ce408e821fafe Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:20:22 +0100 Subject: [PATCH 086/222] Added l10n.yaml for localization configuration --- l10n.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 l10n.yaml diff --git a/l10n.yaml b/l10n.yaml new file mode 100644 index 0000000..f5730dc --- /dev/null +++ b/l10n.yaml @@ -0,0 +1,4 @@ +arb-dir: lib/l10n/arb +template-arb-file: app_en.arb +output-localization-file: app_localizations.dart +output-dir: lib/l10n/generated \ No newline at end of file From d9a26a8cf7df889298cc3e80a3e2435be4edd313 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:20:44 +0100 Subject: [PATCH 087/222] Added English and German localization files containing all strings used in app --- lib/l10n/arb/app_de.arb | 84 ++++++++++ lib/l10n/arb/app_en.arb | 357 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 441 insertions(+) create mode 100644 lib/l10n/arb/app_de.arb create mode 100644 lib/l10n/arb/app_en.arb diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb new file mode 100644 index 0000000..3e090e3 --- /dev/null +++ b/lib/l10n/arb/app_de.arb @@ -0,0 +1,84 @@ +{ + "@@locale": "de", + "choose_group": "Gruppe wählen", + "create_new_match": "Neues Match erstellen", + "choose_ruleset": "Regelwerk wählen", + "choose_game": "Spiel wählen", + "select_winner": "Gewinner wählen:", + "no_recent_matches_available": "Keine letzten Matches verfügbar", + "no_second_match_available": "Kein zweites Match verfügbar", + "delete_all_data": "Alle Daten löschen?", + "cancel": "Abbrechen", + "delete": "Löschen", + "create_new_group": "Neue Gruppe erstellen", + "error_while_creating_group_please_try_again": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", + "selected_players": "Ausgewählte Spieler: {count}", + "no_players_selected": "Keine Spieler ausgewählt", + "all_players": "Alle Spieler:", + "successfully_added_player": "Spieler {playerName} erfolgreich hinzugefügt.", + "could_not_add_player": "Spieler {playerName} konnte nicht hinzugefügt werden.", + "winner": "Gewinner: {winnerName}", + "players": "Spieler", + "player_name": "Spielername", + "no_data_available": "Keine Daten verfügbar.", + "matches": "Matches", + "groups": "Gruppen", + "recent_matches": "Letzte Matches", + "quick_create": "Schnellzugriff", + "winner_label": "Gewinner", + "ruleset_label": "Regelwerk", + "match_in_progress": "Match läuft...", + "menu": "Menü", + "settings": "Einstellungen", + "export_data": "Daten exportieren", + "import_data": "Daten importieren", + "this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden", + "data_successfully_deleted": "Daten erfolgreich gelöscht", + "data_successfully_imported": "Daten erfolgreich importiert", + "invalid_schema": "Ungültiges Schema", + "error_reading_file": "Fehler beim Lesen der Datei", + "import_canceled": "Import abgebrochen", + "format_exception": "Formatfehler (siehe Konsole)", + "unknown_exception": "Unbekannter Fehler (siehe Konsole)", + "data_successfully_exported": "Daten erfolgreich exportiert", + "export_canceled": "Export abgebrochen", + "undo": "Rückgängig", + "wins": "Siege", + "winrate": "Siegquote", + "amount_of_matches": "Anzahl der Matches", + "info": "Info", + "no_groups_created_yet": "Noch keine Gruppen erstellt", + "create_group": "Gruppe erstellen", + "group_name": "Gruppenname", + "no_matches_created_yet": "Noch keine Matches erstellt", + "create_game": "Match erstellen", + "match_name": "Matchname", + "game": "Spiel", + "ruleset": "Regelwerk", + "group": "Gruppe", + "none": "Keine", + "create_match": "Match erstellen", + "search_for_players": "Nach Spielern suchen", + "search_for_groups": "Nach Gruppen suchen", + "no_players_created_yet": "Noch keine Spieler erstellt", + "all_players_selected": "Alle Spieler ausgewählt", + "no_players_found_with_that_name": "Keine Spieler mit diesem Namen gefunden", + "today_at": "Heute um {time}", + "yesterday_at": "Gestern um {time}", + "days_ago": "vor {count} Tagen", + "home": "Startseite", + "statistics": "Statistiken", + "stats": "Statistiken", + "players_count": "{count} Spieler", + "you_have_no_groups_created_yet": "Du hast noch keine Gruppen erstellt", + "there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht", + "game_name": "Spielname", + "ruleset_single_winner_desc": "Genau ein Gewinner wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", + "ruleset_single_loser_desc": "Genau ein Verlierer wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.", + "ruleset_most_points_desc": "Traditionelles Regelwerk: Der Spieler mit den meisten Punkten gewinnt.", + "ruleset_least_points_desc": "Umgekehrte Wertung: Der Spieler mit den wenigsten Punkten gewinnt.", + "single_winner": "Ein Gewinner", + "single_loser": "Ein Verlierer", + "most_points": "Höchste Punkte", + "least_points": "Niedrigste Punkte" +} diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb new file mode 100644 index 0000000..44a8f67 --- /dev/null +++ b/lib/l10n/arb/app_en.arb @@ -0,0 +1,357 @@ +{ + "@@locale": "en", + "choose_group": "Choose Group", + "@choose_group": { + "description": "Label for choosing a group" + }, + "create_new_match": "Create new match", + "@create_new_match": { + "description": "Button text to create a new match" + }, + "choose_ruleset": "Choose Ruleset", + "@choose_ruleset": { + "description": "Label for choosing a ruleset" + }, + "choose_game": "Choose Game", + "@choose_game": { + "description": "Label for choosing a game" + }, + "select_winner": "Select Winner:", + "@select_winner": { + "description": "Label to select the winner" + }, + "no_recent_matches_available": "No recent matches available", + "@no_recent_matches_available": { + "description": "Message when no recent matches exist" + }, + "no_second_match_available": "No second match available", + "@no_second_matcb_available": { + "description": "Message when no second match exists" + }, + "delete_all_data": "Delete all data?", + "@delete_all_data": { + "description": "Confirmation dialog for deleting all data" + }, + "cancel": "Cancel", + "@cancel": { + "description": "Cancel button text" + }, + "delete": "Delete", + "@delete": { + "description": "Delete button text" + }, + "create_new_group": "Create new group", + "@create_new_group": { + "description": "Button text to create a new group" + }, + "error_while_creating_group_please_try_again": "Error while creating group, please try again", + "@error_while_creating_group_please_try_again": { + "description": "Error message when group creation fails" + }, + "selected_players": "Selected players: {count}", + "@selected_players": { + "description": "Shows the number of selected players", + "placeholders": { + "count": { + "type": "int", + "format": "compact" + } + } + }, + "no_players_selected": "No players selected", + "@no_players_selected": { + "description": "Message when no players are selected" + }, + "all_players": "All players:", + "@all_players": { + "description": "Label for all players list" + }, + "successfully_added_player": "Successfully added player {playerName}.", + "@successfully_added_player": { + "description": "Success message when adding a player", + "placeholders": { + "playerName": { + "type": "String", + "example": "John" + } + } + }, + "could_not_add_player": "Could not add player {playerName}.", + "@could_not_add_player": { + "description": "Error message when adding a player fails", + "placeholders": { + "playerName": { + "type": "String", + "example": "John" + } + } + }, + "winner": "Winner: {winnerName}", + "@winner": { + "description": "Shows the winner's name", + "placeholders": { + "winnerName": { + "type": "String", + "example": "John" + } + } + }, + "players": "Players", + "@players": { + "description": "Players label" + }, + "no_data_available": "No data available.", + "@no_data_available": { + "description": "Message when no data is available" + }, + "matches": "Matches", + "@matches": { + "description": "Label for matches" + }, + "groups": "Groups", + "@groups": { + "description": "Label for groups" + }, + "recent_matches": "Recent Matches", + "@recent_matches": { + "description": "Title for recent matches section" + }, + "quick_create": "Quick Create", + "@quick_create": { + "description": "Title for quick create section" + }, + "winner_label": "Winner", + "@winner_label": { + "description": "Label for winner field" + }, + "ruleset_label": "Ruleset", + "@ruleset_label": { + "description": "Label for ruleset field" + }, + "match_in_progress": "Match in progress...", + "@match_in_progress": { + "description": "Message when match is in progress" + }, + "menu": "Menu", + "@menu": { + "description": "Menu label" + }, + "settings": "Settings", + "@settings": { + "description": "Settings label" + }, + "export_data": "Export data", + "@export_data": { + "description": "Export data menu item" + }, + "import_data": "Import data", + "@import_data": { + "description": "Import data menu item" + }, + "this_cannot_be_undone": "This can't be undone", + "@this_cannot_be_undone": { + "description": "Warning message for irreversible actions" + }, + "data_successfully_deleted": "Data successfully deleted", + "@data_successfully_deleted": { + "description": "Success message after deleting data" + }, + "data_successfully_imported": "Data successfully imported", + "@data_successfully_imported": { + "description": "Success message after importing data" + }, + "invalid_schema": "Invalid Schema", + "@invalid_schema": { + "description": "Error message for invalid schema" + }, + "error_reading_file": "Error reading file", + "@error_reading_file": { + "description": "Error message when file cannot be read" + }, + "import_canceled": "Import canceled", + "@import_canceled": { + "description": "Message when import is canceled" + }, + "format_exception": "Format Exception (see console)", + "@format_exception": { + "description": "Error message for format exceptions" + }, + "unknown_exception": "Unknown Exception (see console)", + "@unknown_exception": { + "description": "Error message for unknown exceptions" + }, + "data_successfully_exported": "Data successfully exported", + "@data_successfully_exported": { + "description": "Success message after exporting data" + }, + "export_canceled": "Export canceled", + "@export_canceled": { + "description": "Message when export is canceled" + }, + "undo": "Undo", + "@undo": { + "description": "Undo button text" + }, + "wins": "Wins", + "@wins": { + "description": "Label for wins statistic" + }, + "winrate": "Winrate", + "@winrate": { + "description": "Label for winrate statistic" + }, + "amount_of_matches": "Amount of Matches", + "@amount_of_matches": { + "description": "Label for amount of matches statistic" + }, + "info": "Info", + "@info": { + "description": "Info label" + }, + "no_groups_created_yet": "No groups created yet", + "@no_groups_created_yet": { + "description": "Message when no groups exist" + }, + "no_players_created_yet": "No players created yet", + "@no_players_created_yet": { + "description": "Message when no players exist" + }, + "create_group": "Create Group", + "@create_group": { + "description": "Button text to create a group" + }, + "group_name": "Group name", + "@group_name": { + "description": "Placeholder for group name input" + }, + "player_name": "Player name", + "@player_name": { + "description": "Placeholder for player name input" + }, + "no_matches_created_yet": "No matches created yet", + "@no_matches_created_yet": { + "description": "Message when no matches exist" + }, + "match_name": "Match name", + "@match_name": { + "description": "Placeholder for match name input" + }, + "game": "Game", + "@game": { + "description": "Game label" + }, + "ruleset": "Ruleset", + "@ruleset": { + "description": "Ruleset label" + }, + "group": "Group", + "@group": { + "description": "Group label" + }, + "none": "None", + "@none": { + "description": "None option label" + }, + "create_match": "Create match", + "@create_match": { + "description": "Button text to create a match" + }, + "no_players_found_with_that_name": "No players found with that name", + "@no_players_found_with_that_name": { + "description": "Message when search returns no results" + }, + "all_players_selected": "All players selected", + "@all_players_selected": { + "description": "Message when all players are added to selection" + }, + "today_at": "Today at {time}", + "@today_at": { + "description": "Date format for today", + "placeholders": { + "time": { + "type": "String", + "example": "14:30" + } + } + }, + "yesterday_at": "Yesterday at {time}", + "@yesterday_at": { + "description": "Date format for yesterday", + "placeholders": { + "time": { + "type": "String", + "example": "14:30" + } + } + }, + "days_ago": "{count} days ago", + "@days_ago": { + "description": "Date format for days ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "home": "Home", + "@home": { + "description": "Home tab label" + }, + "statistics": "Statistics", + "@statistics": { + "description": "Statistics tab label" + }, + "stats": "Stats", + "@stats": { + "description": "Stats tab label (short)" + }, + "players_count": "{count} Players", + "@players_count": { + "description": "Shows the number of players", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "there_is_no_group_matching_your_search": "There is no group matching your search", + "@there_is_no_group_matching_your_search": { + "description": "Message when search returns no groups" + }, + "game_name": "Game Name", + "@game_name": { + "description": "Placeholder for game name search" + }, + "ruleset_single_winner_desc": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", + "@ruleset_single_winner_desc": { + "description": "Description for single winner ruleset" + }, + "ruleset_single_loser_desc": "Exactly one loser is determined; last place receives the penalty or consequence.", + "@ruleset_single_loser_desc": { + "description": "Description for single loser ruleset" + }, + "ruleset_most_points_desc": "Traditional ruleset: the player with the most points wins.", + "@ruleset_most_points_desc": { + "description": "Description for most points ruleset" + }, + "ruleset_least_points_desc": "Inverse scoring: the player with the fewest points wins.", + "@ruleset_least_points_desc": { + "description": "Description for least points ruleset" + }, + "single_winner": "Single Winner", + "@single_winner": { + "description": "Title for single winner ruleset" + }, + "single_loser": "Single Loser", + "@single_loser": { + "description": "Title for single loser ruleset" + }, + "most_points": "Most Points", + "@most_points": { + "description": "Title for most points ruleset" + }, + "least_points": "Least Points", + "@least_points": { + "description": "Title for least points ruleset" + } +} From 534d19efc3dfd6fd961ddb9e68466ae0a8cd1042 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:21:09 +0100 Subject: [PATCH 088/222] Implemented localization across the application. --- lib/core/enums.dart | 15 +-- lib/main.dart | 3 + .../main_menu/custom_navigation_bar.dart | 17 ++-- .../group_view/create_group_view.dart | 17 ++-- .../main_menu/group_view/groups_view.dart | 9 +- .../views/main_menu/home_view.dart | 49 +++++++--- .../create_match/choose_game_view.dart | 13 ++- .../create_match/choose_group_view.dart | 22 +++-- .../create_match/choose_ruleset_view.dart | 10 +- .../create_match/create_match_view.dart | 92 +++++++++---------- .../match_view/match_result_view.dart | 5 +- .../main_menu/match_view/match_view.dart | 9 +- .../views/main_menu/settings_view.dart | 83 +++++++++++------ .../views/main_menu/statistics_view.dart | 7 +- .../widgets/player_selection.dart | 31 +++++-- .../widgets/tiles/game_history_tile.dart | 17 ++-- .../widgets/tiles/statistics_tile.dart | 5 +- 17 files changed, 247 insertions(+), 157 deletions(-) diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 737882e..fc1ac91 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -1,3 +1,6 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; + /// Button types used for styling the [CustomWidthButton] enum ButtonType { primary, secondary, tertiary } @@ -30,16 +33,16 @@ enum ExportResult { success, canceled, unknownException } /// - [Ruleset.leastPoints]: The player with the fewest points wins. enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints } -/// Translates a [Ruleset] enum value to its corresponding string representation. -String translateRulesetToString(Ruleset ruleset) { +/// Translates a [Ruleset] enum value to its corresponding localized string. +String translateRulesetToString(Ruleset ruleset, BuildContext context) { switch (ruleset) { case Ruleset.singleWinner: - return 'Single Winner'; + return AppLocalizations.of(context)!.single_winner; case Ruleset.singleLoser: - return 'Single Loser'; + return AppLocalizations.of(context)!.single_loser; case Ruleset.mostPoints: - return 'Most Points'; + return AppLocalizations.of(context)!.most_points; case Ruleset.leastPoints: - return 'Least Points'; + return AppLocalizations.of(context)!.least_points; } } diff --git a/lib/main.dart b/lib/main.dart index 98c40f8..0f3b6b0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart'; import 'package:provider/provider.dart'; @@ -20,6 +21,8 @@ class GameTracker extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, debugShowCheckedModeBanner: false, title: 'Game Tracker', darkTheme: ThemeData.dark(), diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 2ec28fa..980bf1a 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/group_view/groups_view.dart'; import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/match_view.dart'; @@ -89,28 +90,28 @@ class _CustomNavigationBarState extends State index: 0, isSelected: currentIndex == 0, icon: Icons.home_rounded, - label: 'Home', + label: AppLocalizations.of(context)!.home, onTabTapped: onTabTapped, ), NavbarItem( index: 1, isSelected: currentIndex == 1, icon: Icons.gamepad_rounded, - label: 'Matches', + label: AppLocalizations.of(context)!.matches, onTabTapped: onTabTapped, ), NavbarItem( index: 2, isSelected: currentIndex == 2, icon: Icons.group_rounded, - label: 'Groups', + label: AppLocalizations.of(context)!.groups, onTabTapped: onTabTapped, ), NavbarItem( index: 3, isSelected: currentIndex == 3, icon: Icons.bar_chart_rounded, - label: 'Stats', + label: AppLocalizations.of(context)!.statistics, onTabTapped: onTabTapped, ), ], @@ -131,13 +132,13 @@ class _CustomNavigationBarState extends State String _currentTabTitle() { switch (currentIndex) { case 0: - return 'Home'; + return AppLocalizations.of(context)!.home; case 1: - return 'Matches'; + return AppLocalizations.of(context)!.matches; case 2: - return 'Groups'; + return AppLocalizations.of(context)!.groups; case 3: - return 'Statistics'; + return AppLocalizations.of(context)!.statistics; default: return ''; } diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index f20fb4e..bceb3bd 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -4,6 +4,7 @@ import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/player_selection.dart'; import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart'; @@ -43,8 +44,8 @@ class _CreateGroupViewState extends State { appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, - title: const Text( - 'Create new group', + title: Text( + AppLocalizations.of(context)!.create_new_group, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -57,7 +58,7 @@ class _CreateGroupViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: TextInputField( controller: _groupNameController, - hintText: 'Group name', + hintText: AppLocalizations.of(context)!.group_name, onChanged: (value) { setState(() {}); }, @@ -73,7 +74,7 @@ class _CreateGroupViewState extends State { ), ), CustomWidthButton( - text: 'Create group', + text: AppLocalizations.of(context)!.create_group, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: @@ -94,10 +95,12 @@ class _CreateGroupViewState extends State { ScaffoldMessenger.of(context).showSnackBar( SnackBar( backgroundColor: CustomTheme.boxColor, - content: const Center( + content: Center( child: Text( - 'Error while creating group, please try again', - style: TextStyle(color: Colors.white), + AppLocalizations.of( + context, + )!.error_while_creating_group_please_try_again, + style: const TextStyle(color: Colors.white), ), ), ), diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index b2243bc..0e9ddbb 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -4,6 +4,7 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/group_view/create_group_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; @@ -49,11 +50,11 @@ class _GroupsViewState extends State { enabled: isLoading, child: Visibility( visible: groups.isNotEmpty, - replacement: const Center( + replacement: Center( child: TopCenteredMessage( icon: Icons.info, - title: 'Info', - message: 'No groups created yet', + title: AppLocalizations.of(context)!.info, + message: AppLocalizations.of(context)!.no_groups_created_yet, ), ), child: ListView.builder( @@ -73,7 +74,7 @@ class _GroupsViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: 'Create Group', + text: AppLocalizations.of(context)!.create_group, sizeRelativeToWidth: 0.90, onPressed: () async { await Navigator.push( diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 3e221e7..1f0f233 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -4,6 +4,7 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/game_tile.dart'; @@ -86,7 +87,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: 'Matches', + title: AppLocalizations.of(context)!.matches, icon: Icons.groups_rounded, value: matchCount, ), @@ -94,7 +95,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: 'Groups', + title: AppLocalizations.of(context)!.groups, icon: Icons.groups_rounded, value: groupCount, ), @@ -104,15 +105,19 @@ class _HomeViewState extends State { padding: const EdgeInsets.symmetric(vertical: 16.0), child: InfoTile( width: constraints.maxWidth * 0.95, - title: 'Recent Matches', + title: AppLocalizations.of(context)!.recent_matches, icon: Icons.timer, content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), child: Visibility( visible: !isLoading && loadedRecentMatches.isNotEmpty, - replacement: const Center( + replacement: Center( heightFactor: 12, - child: Text('No recent games available'), + child: Text( + AppLocalizations.of( + context, + )!.no_recent_matches_available, + ), ), child: Column( mainAxisAlignment: MainAxisAlignment.start, @@ -120,11 +125,15 @@ class _HomeViewState extends State { children: [ MatchTile( matchTitle: recentMatches[0].name, - game: 'Winner', - ruleset: 'Ruleset', + game: AppLocalizations.of(context)!.winner_label, + ruleset: AppLocalizations.of( + context, + )!.ruleset_label, players: _getPlayerText(recentMatches[0]), winner: recentMatches[0].winner == null - ? 'Match in progress...' + ? AppLocalizations.of( + context, + )!.match_in_progress : recentMatches[0].winner!.name, ), const Padding( @@ -134,18 +143,28 @@ class _HomeViewState extends State { if (loadedRecentMatches.length > 1) ...[ MatchTile( matchTitle: recentMatches[1].name, - game: 'Winner', - ruleset: 'Ruleset', + game: AppLocalizations.of( + context, + )!.winner_label, + ruleset: AppLocalizations.of( + context, + )!.ruleset_label, players: _getPlayerText(recentMatches[1]), winner: recentMatches[1].winner == null - ? 'Game in progress...' + ? AppLocalizations.of( + context, + )!.match_in_progress : recentMatches[1].winner!.name, ), const SizedBox(height: 8), ] else ...[ - const Center( + Center( heightFactor: 5.35, - child: Text('No second game available'), + child: Text( + AppLocalizations.of( + context, + )!.no_second_match_available, + ), ), ], ], @@ -156,7 +175,7 @@ class _HomeViewState extends State { ), InfoTile( width: constraints.maxWidth * 0.95, - title: 'Quick Create', + title: AppLocalizations.of(context)!.quick_create, icon: Icons.add_box_rounded, content: Column( children: [ @@ -213,7 +232,7 @@ class _HomeViewState extends State { String _getPlayerText(Match game) { if (game.group == null) { final playerCount = game.players?.length ?? 0; - return '$playerCount Players'; + return AppLocalizations.of(context)!.players_count(playerCount); } if (game.players == null || game.players!.isEmpty) { return game.group!.name; diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 53a4fcb..9ffe5b2 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; @@ -41,8 +42,8 @@ class _ChooseGameViewState extends State { Navigator.of(context).pop(selectedGameIndex); }, ), - title: const Text( - 'Choose Game', + title: Text( + AppLocalizations.of(context)!.choose_game, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -53,7 +54,7 @@ class _ChooseGameViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: searchBarController, - hintText: 'Game Name', + hintText: AppLocalizations.of(context)!.game_name, ), ), const SizedBox(height: 5), @@ -64,8 +65,10 @@ class _ChooseGameViewState extends State { return TitleDescriptionListTile( title: widget.games[index].$1, description: widget.games[index].$2, - badgeText: translateRulesetToString(widget.games[index].$3), - isHighlighted: selectedGameIndex == index, + badgeText: translateRulesetToString( + widget.games[index].$3, + context, + ), onPressed: () async { setState(() { if (selectedGameIndex == index) { diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 37096b9..2f5dc75 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/group.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; @@ -22,7 +23,6 @@ class ChooseGroupView extends StatefulWidget { class _ChooseGroupViewState extends State { late String selectedGroupId; final TextEditingController controller = TextEditingController(); - final String hintText = 'Group Name'; late final List filteredGroups; @override @@ -51,8 +51,8 @@ class _ChooseGroupViewState extends State { ); }, ), - title: const Text( - 'Choose Group', + title: Text( + AppLocalizations.of(context)!.choose_group, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -63,7 +63,7 @@ class _ChooseGroupViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: controller, - hintText: hintText, + hintText: AppLocalizations.of(context)!.group_name, onChanged: (value) { setState(() { filterGroups(value); @@ -76,15 +76,17 @@ class _ChooseGroupViewState extends State { visible: filteredGroups.isNotEmpty, replacement: Visibility( visible: widget.groups.isNotEmpty, - replacement: const TopCenteredMessage( + replacement: TopCenteredMessage( icon: Icons.info, - title: 'Info', - message: 'You have no groups created yet', + title: AppLocalizations.of(context)!.info, + message: AppLocalizations.of(context)!.no_groups_created_yet, ), - child: const TopCenteredMessage( + child: TopCenteredMessage( icon: Icons.info, - title: 'Info', - message: 'There is no group matching your search', + title: AppLocalizations.of(context)!.info, + message: AppLocalizations.of( + context, + )!.there_is_no_group_matching_your_search, ), ), child: ListView.builder( diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index 537f749..ff6ab83 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; class ChooseRulesetView extends StatefulWidget { @@ -46,8 +47,8 @@ class _ChooseRulesetViewState extends State { ); }, ), - title: const Text( - 'Choose Ruleset', + title: Text( + AppLocalizations.of(context)!.choose_ruleset, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -66,7 +67,10 @@ class _ChooseRulesetViewState extends State { } }); }, - title: translateRulesetToString(widget.rulesets[index].$1), + title: translateRulesetToString( + widget.rulesets[index].$1, + context, + ), description: widget.rulesets[index].$2, isHighlighted: selectedRulesetIndex == index, ); diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index f3b4d79..62da943 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -6,6 +6,7 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_game_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart'; @@ -64,34 +65,6 @@ class _CreateMatchViewState extends State { /// The currently selected players List? selectedPlayers; - /// List of available rulesets with their descriptions - /// as tuples of (Ruleset, String) - /// TODO: Replace when rulesets are implemented - List<(Ruleset, String)> rulesets = [ - ( - Ruleset.singleWinner, - 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.', - ), - ( - Ruleset.singleLoser, - 'Exactly one loser is determined; last place receives the penalty or consequence.', - ), - ( - Ruleset.mostPoints, - 'Traditional ruleset: the player with the most points wins.', - ), - ( - Ruleset.leastPoints, - 'Inverse scoring: the player with the fewest points wins.', - ), - ]; - - // TODO: Replace when games are implemented - List<(String, String, Ruleset)> games = [ - ('Example Game 1', 'This is a discription', Ruleset.leastPoints), - ('Example Game 2', '', Ruleset.singleWinner), - ]; - @override void initState() { super.initState(); @@ -111,6 +84,33 @@ class _CreateMatchViewState extends State { filteredPlayerList = List.from(playerList); } + List<(Ruleset, String)> _getRulesets(BuildContext context) { + return [ + ( + Ruleset.singleWinner, + AppLocalizations.of(context)!.ruleset_single_winner_desc, + ), + ( + Ruleset.singleLoser, + AppLocalizations.of(context)!.ruleset_single_loser_desc, + ), + ( + Ruleset.mostPoints, + AppLocalizations.of(context)!.ruleset_most_points_desc, + ), + ( + Ruleset.leastPoints, + AppLocalizations.of(context)!.ruleset_least_points_desc, + ), + ]; + } + + // TODO: Replace when games are implemented + List<(String, String, Ruleset)> games = [ + ('Example Game 1', 'This is a description', Ruleset.leastPoints), + ('Example Game 2', '', Ruleset.singleWinner), + ]; + @override Widget build(BuildContext context) { return Scaffold( @@ -118,8 +118,8 @@ class _CreateMatchViewState extends State { appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, - title: const Text( - 'Create new match', + title: Text( + AppLocalizations.of(context)!.create_new_match, style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -132,13 +132,13 @@ class _CreateMatchViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), child: TextInputField( controller: _matchNameController, - hintText: 'Match name', + hintText: AppLocalizations.of(context)!.match_name, ), ), ChooseTile( - title: 'Game', + title: AppLocalizations.of(context)!.game, trailingText: selectedGameIndex == -1 - ? 'None' + ? AppLocalizations.of(context)!.none : games[selectedGameIndex].$1, onPressed: () async { selectedGameIndex = await Navigator.of(context).push( @@ -152,9 +152,9 @@ class _CreateMatchViewState extends State { setState(() { if (selectedGameIndex != -1) { selectedRuleset = games[selectedGameIndex].$3; - selectedRulesetIndex = rulesets.indexWhere( - (r) => r.$1 == selectedRuleset, - ); + selectedRulesetIndex = _getRulesets( + context, + ).indexWhere((r) => r.$1 == selectedRuleset); } else { selectedRuleset = null; } @@ -162,30 +162,30 @@ class _CreateMatchViewState extends State { }, ), ChooseTile( - title: 'Ruleset', + title: AppLocalizations.of(context)!.ruleset, trailingText: selectedRuleset == null - ? 'None' - : translateRulesetToString(selectedRuleset!), + ? AppLocalizations.of(context)!.none + : translateRulesetToString(selectedRuleset!, context), onPressed: () async { selectedRuleset = await Navigator.of(context).push( MaterialPageRoute( builder: (context) => ChooseRulesetView( - rulesets: rulesets, + rulesets: _getRulesets(context), initialRulesetIndex: selectedRulesetIndex, ), ), ); - selectedRulesetIndex = rulesets.indexWhere( - (r) => r.$1 == selectedRuleset, - ); + selectedRulesetIndex = _getRulesets( + context, + ).indexWhere((r) => r.$1 == selectedRuleset); selectedGameIndex = -1; setState(() {}); }, ), ChooseTile( - title: 'Group', + title: AppLocalizations.of(context)!.group, trailingText: selectedGroup == null - ? 'None' + ? AppLocalizations.of(context)!.none : selectedGroup!.name, onPressed: () async { selectedGroup = await Navigator.of(context).push( @@ -222,7 +222,7 @@ class _CreateMatchViewState extends State { ), ), CustomWidthButton( - text: 'Create match', + text: AppLocalizations.of(context)!.create_match, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: _enableCreateGameButton() diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index e8075f6..1639dce 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -3,6 +3,7 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart'; import 'package:provider/provider.dart'; @@ -79,8 +80,8 @@ class _MatchResultViewState extends State { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Text( - 'Select Winner:', + Text( + AppLocalizations.of(context)!.select_winner, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index e7d29c0..af7f7ed 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -8,6 +8,7 @@ import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/create_match_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; @@ -58,11 +59,11 @@ class _MatchViewState extends State { enabled: isLoading, child: Visibility( visible: matches.isNotEmpty, - replacement: const Center( + replacement: Center( child: TopCenteredMessage( icon: Icons.report, - title: 'Info', - message: 'No games created yet', + title: AppLocalizations.of(context)!.info, + message: AppLocalizations.of(context)!.no_matches_created_yet, ), ), child: ListView.builder( @@ -96,7 +97,7 @@ class _MatchViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: 'Create Game', + text: AppLocalizations.of(context)!.create_match, sizeRelativeToWidth: 0.90, onPressed: () async { Navigator.push( diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 6ebb7fb..8899e40 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; import 'package:game_tracker/services/data_transfer_service.dart'; @@ -24,30 +25,33 @@ class _SettingsViewState extends State { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Padding( - padding: EdgeInsets.fromLTRB(24, 0, 24, 10), + Padding( + padding: const EdgeInsets.fromLTRB(24, 0, 24, 10), child: Text( textAlign: TextAlign.start, - 'Menu', - style: TextStyle( + AppLocalizations.of(context)!.menu, + style: const TextStyle( fontSize: 28, fontWeight: FontWeight.bold, ), ), ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 10, + ), child: Text( textAlign: TextAlign.start, - 'Settings', - style: TextStyle( + AppLocalizations.of(context)!.settings, + style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, ), ), ), SettingsListTile( - title: 'Export data', + title: AppLocalizations.of(context)!.export_data, icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -62,7 +66,7 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: 'Import data', + title: AppLocalizations.of(context)!.import_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -74,23 +78,27 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: 'Delete all data', + title: AppLocalizations.of(context)!.delete_all_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () { showDialog( context: context, builder: (context) => AlertDialog( - title: const Text('Delete all data?'), - content: const Text('This can\'t be undone'), + title: Text( + AppLocalizations.of(context)!.delete_all_data, + ), + content: Text( + AppLocalizations.of(context)!.this_cannot_be_undone, + ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), - child: const Text('Abbrechen'), + child: Text(AppLocalizations.of(context)!.cancel), ), TextButton( onPressed: () => Navigator.of(context).pop(true), - child: const Text('Löschen'), + child: Text(AppLocalizations.of(context)!.delete), ), ], ), @@ -99,7 +107,9 @@ class _SettingsViewState extends State { DataTransferService.deleteAllData(context); showSnackbar( context: context, - message: 'Daten erfolgreich gelöscht', + message: AppLocalizations.of( + context, + )!.data_successfully_deleted, ); } }); @@ -122,22 +132,34 @@ class _SettingsViewState extends State { }) { switch (result) { case ImportResult.success: - showSnackbar(context: context, message: 'Data successfully imported'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.data_successfully_imported, + ); case ImportResult.invalidSchema: - showSnackbar(context: context, message: 'Invalid Schema'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.invalid_schema, + ); case ImportResult.fileReadError: - showSnackbar(context: context, message: 'Error reading file'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.error_reading_file, + ); case ImportResult.canceled: - showSnackbar(context: context, message: 'Import canceled'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.import_canceled, + ); case ImportResult.formatException: showSnackbar( context: context, - message: 'Format Exception (see console)', + message: AppLocalizations.of(context)!.format_exception, ); case ImportResult.unknownException: showSnackbar( context: context, - message: 'Unknown Exception (see console)', + message: AppLocalizations.of(context)!.unknown_exception, ); } } @@ -152,13 +174,19 @@ class _SettingsViewState extends State { }) { switch (result) { case ExportResult.success: - showSnackbar(context: context, message: 'Data successfully exported'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.data_successfully_exported, + ); case ExportResult.canceled: - showSnackbar(context: context, message: 'Export canceled'); + showSnackbar( + context: context, + message: AppLocalizations.of(context)!.export_canceled, + ); case ExportResult.unknownException: showSnackbar( context: context, - message: 'Unknown Exception (see console)', + message: AppLocalizations.of(context)!.unknown_exception, ); } } @@ -183,7 +211,10 @@ class _SettingsViewState extends State { backgroundColor: CustomTheme.onBoxColor, duration: duration, action: action != null - ? SnackBarAction(label: 'Rückgängig', onPressed: action) + ? SnackBarAction( + label: AppLocalizations.of(context)!.undo, + onPressed: action, + ) : null, ), ); diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index e94f2b6..07c27aa 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -3,6 +3,7 @@ import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/tiles/statistics_tile.dart'; import 'package:provider/provider.dart'; @@ -60,7 +61,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.01), StatisticsTile( icon: Icons.sports_score, - title: 'Wins', + title: AppLocalizations.of(context)!.wins, width: constraints.maxWidth * 0.95, values: winCounts, itemCount: 3, @@ -69,7 +70,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.percent, - title: 'Winrate', + title: AppLocalizations.of(context)!.winrate, width: constraints.maxWidth * 0.95, values: winRates, itemCount: 5, @@ -78,7 +79,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, - title: 'Amount of Matches', + title: AppLocalizations.of(context)!.amount_of_matches, width: constraints.maxWidth * 0.95, values: matchCounts, itemCount: 10, diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index eb70ae0..a827cc0 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -3,6 +3,7 @@ import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/player.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; @@ -129,14 +130,20 @@ class _PlayerSelectionState extends State { ), const SizedBox(height: 10), Text( - 'Selected players: (${selectedPlayers.length})', + AppLocalizations.of( + context, + )!.selected_players(selectedPlayers.length), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), SizedBox( height: 50, child: selectedPlayers.isEmpty - ? const Center(child: Text('No players selected')) + ? Center( + child: Text( + AppLocalizations.of(context)!.no_players_selected, + ), + ) : SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( @@ -171,8 +178,8 @@ class _PlayerSelectionState extends State { ), ), const SizedBox(height: 10), - const Text( - 'All players:', + Text( + AppLocalizations.of(context)!.all_players, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), @@ -186,12 +193,14 @@ class _PlayerSelectionState extends State { visible: suggestedPlayers.isNotEmpty, replacement: TopCenteredMessage( icon: Icons.info, - title: 'Info', + title: AppLocalizations.of(context)!.info, message: allPlayers.isEmpty - ? 'No players created yet' + ? AppLocalizations.of(context)!.no_players_created_yet : (selectedPlayers.length == allPlayers.length) - ? 'No more players to add' - : 'No players found with that name', + ? AppLocalizations.of(context)!.all_players_selected + : AppLocalizations.of( + context, + )!.no_players_found_with_that_name, ), child: ListView.builder( itemCount: suggestedPlayers.length, @@ -243,7 +252,9 @@ class _PlayerSelectionState extends State { backgroundColor: CustomTheme.boxColor, content: Center( child: Text( - 'Successfully added player $playerName.', + AppLocalizations.of( + context, + )!.successfully_added_player(playerName), style: const TextStyle(color: Colors.white), ), ), @@ -255,7 +266,7 @@ class _PlayerSelectionState extends State { backgroundColor: CustomTheme.boxColor, content: Center( child: Text( - 'Could not add player $playerName.', + AppLocalizations.of(context)!.could_not_add_player(playerName), style: const TextStyle(color: Colors.white), ), ), diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index f79edc3..4f40fc6 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/match.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:intl/intl.dart'; @@ -97,7 +98,7 @@ class _GameHistoryTileState extends State { const SizedBox(width: 8), Expanded( child: Text( - 'Winner: ${winner.name}', + AppLocalizations.of(context)!.winner(winner.name), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, @@ -113,8 +114,8 @@ class _GameHistoryTileState extends State { ], if (allPlayers.isNotEmpty) ...[ - const Text( - 'Players', + Text( + AppLocalizations.of(context)!.players, style: TextStyle( fontSize: 13, color: Colors.grey, @@ -141,11 +142,15 @@ class _GameHistoryTileState extends State { final difference = now.difference(dateTime); if (difference.inDays == 0) { - return 'Today at ${DateFormat('HH:mm').format(dateTime)}'; + return AppLocalizations.of( + context, + )!.today_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays == 1) { - return 'Yesterday at ${DateFormat('HH:mm').format(dateTime)}'; + return AppLocalizations.of( + context, + )!.yesterday_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays < 7) { - return '${difference.inDays} days ago'; + return AppLocalizations.of(context)!.days_ago(difference.inDays); } else { return DateFormat('MMM d, yyyy').format(dateTime); } diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 582cf66..e783ab4 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; class StatisticsTile extends StatelessWidget { @@ -33,9 +34,9 @@ class StatisticsTile extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 20.0), child: Visibility( visible: values.isNotEmpty, - replacement: const Center( + replacement: Center( heightFactor: 4, - child: Text('No data available.'), + child: Text(AppLocalizations.of(context)!.no_data_available), ), child: Column( children: List.generate(min(values.length, itemCount), (index) { From ed9e3af76869efa9027d75be1bc874dbcea9d3c2 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:34:55 +0100 Subject: [PATCH 089/222] merged dev into branch and fixed missing comma --- lib/presentation/views/main_menu/statistics_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index e66a2ce..dabfff5 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -100,6 +100,7 @@ class _StatisticsViewState extends State { title: 'Info', message: 'No statistics available', ), + ), StatisticsTile( icon: Icons.sports_score, title: AppLocalizations.of(context)!.wins, From 73aea0d0f56853f1da87522191b1e6c5cb63c5cc Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:39:15 +0100 Subject: [PATCH 090/222] added missings consts --- .../views/main_menu/group_view/create_group_view.dart | 2 +- .../main_menu/match_view/create_match/choose_game_view.dart | 2 +- .../main_menu/match_view/create_match/choose_group_view.dart | 2 +- .../main_menu/match_view/create_match/choose_ruleset_view.dart | 2 +- .../main_menu/match_view/create_match/create_match_view.dart | 2 +- .../views/main_menu/match_view/match_result_view.dart | 2 +- lib/presentation/widgets/player_selection.dart | 2 +- lib/presentation/widgets/tiles/game_history_tile.dart | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index bceb3bd..e9e5fe1 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -46,7 +46,7 @@ class _CreateGroupViewState extends State { scrolledUnderElevation: 0, title: Text( AppLocalizations.of(context)!.create_new_group, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 9ffe5b2..6a75ae1 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -44,7 +44,7 @@ class _ChooseGameViewState extends State { ), title: Text( AppLocalizations.of(context)!.choose_game, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 2f5dc75..7308dce 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -53,7 +53,7 @@ class _ChooseGroupViewState extends State { ), title: Text( AppLocalizations.of(context)!.choose_group, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index ff6ab83..a6129cc 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -49,7 +49,7 @@ class _ChooseRulesetViewState extends State { ), title: Text( AppLocalizations.of(context)!.choose_ruleset, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 62da943..8eeaca6 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -120,7 +120,7 @@ class _CreateMatchViewState extends State { scrolledUnderElevation: 0, title: Text( AppLocalizations.of(context)!.create_new_match, - style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, ), diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index 1639dce..f13ef87 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -82,7 +82,7 @@ class _MatchResultViewState extends State { children: [ Text( AppLocalizations.of(context)!.select_winner, - style: TextStyle( + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index a827cc0..9a1ffd1 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -180,7 +180,7 @@ class _PlayerSelectionState extends State { const SizedBox(height: 10), Text( AppLocalizations.of(context)!.all_players, - style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), /* diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/game_history_tile.dart index 4f40fc6..091935e 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/game_history_tile.dart @@ -116,7 +116,7 @@ class _GameHistoryTileState extends State { if (allPlayers.isNotEmpty) ...[ Text( AppLocalizations.of(context)!.players, - style: TextStyle( + style: const TextStyle( fontSize: 13, color: Colors.grey, fontWeight: FontWeight.w500, From 86dbc9afb0cc04584448c83948e092f16025428c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 00:42:21 +0100 Subject: [PATCH 091/222] Optimized ruleset selection in CreateMatchView --- .../match_view/create_match/create_match_view.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 8eeaca6..5e35f41 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -167,17 +167,19 @@ class _CreateMatchViewState extends State { ? AppLocalizations.of(context)!.none : translateRulesetToString(selectedRuleset!, context), onPressed: () async { + final rulesets = _getRulesets(context); selectedRuleset = await Navigator.of(context).push( MaterialPageRoute( builder: (context) => ChooseRulesetView( - rulesets: _getRulesets(context), + rulesets: rulesets, initialRulesetIndex: selectedRulesetIndex, ), ), ); - selectedRulesetIndex = _getRulesets( - context, - ).indexWhere((r) => r.$1 == selectedRuleset); + if (!mounted) return; + selectedRulesetIndex = rulesets.indexWhere( + (r) => r.$1 == selectedRuleset, + ); selectedGameIndex = -1; setState(() {}); }, From bbb7914fc5360bd46a6fd0df4710bc53456e9af6 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 1 Jan 2026 16:39:57 +0100 Subject: [PATCH 092/222] Updated sorting logic & added comments --- .../widgets/player_selection.dart | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index eb70ae0..5f77e1f 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -149,18 +149,24 @@ class _PlayerSelectionState extends State { onIconTap: () { setState(() { // Removes the player from the selection and notifies the parent. + selectedPlayers.remove(player); + widget.onChanged([...selectedPlayers]); + + // Get the current search query final currentSearch = _searchBarController .text .toLowerCase(); - selectedPlayers.remove(player); - widget.onChanged([...selectedPlayers]); + // If the player matches the current search query (or search is empty), - // they are added back to the suggestions and the list is re-sorted. + // they are added back to the `suggestedPlayers` and the list is re-sorted. if (currentSearch.isEmpty || player.name.toLowerCase().contains( currentSearch, )) { suggestedPlayers.add(player); + suggestedPlayers.sort( + (a, b) => a.name.compareTo(b.name), + ); } }); }, @@ -200,11 +206,15 @@ class _PlayerSelectionState extends State { text: suggestedPlayers[index].name, onPressed: () { setState(() { + // If the player is not already selected if (!selectedPlayers.contains( suggestedPlayers[index], )) { - selectedPlayers.add(suggestedPlayers[index]); + // Add to player to the front of the selectedPlayers + selectedPlayers.insert(0, suggestedPlayers[index]); + // Notify the parent widget of the change widget.onChanged([...selectedPlayers]); + // Remove the player from the suggestedPlayers suggestedPlayers.remove(suggestedPlayers[index]); } }); @@ -221,7 +231,7 @@ class _PlayerSelectionState extends State { } /// Adds a new player to the database from the search bar input. - /// Shows a snackbar indicating success or failure. + /// Shows a snackbar indicating success xfor failure. /// [context] - BuildContext to show the snackbar. void addNewPlayerFromSearch({required BuildContext context}) async { String playerName = _searchBarController.text.trim(); From 0bfaba42259470d758fb7e81f7932f6fe8c84c8b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 17:40:36 +0100 Subject: [PATCH 093/222] Refactored German localization strings by removing redundant entries --- lib/l10n/arb/app_de.arb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 3e090e3..a922996 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -51,15 +51,12 @@ "create_group": "Gruppe erstellen", "group_name": "Gruppenname", "no_matches_created_yet": "Noch keine Matches erstellt", - "create_game": "Match erstellen", "match_name": "Matchname", "game": "Spiel", "ruleset": "Regelwerk", "group": "Gruppe", "none": "Keine", "create_match": "Match erstellen", - "search_for_players": "Nach Spielern suchen", - "search_for_groups": "Nach Gruppen suchen", "no_players_created_yet": "Noch keine Spieler erstellt", "all_players_selected": "Alle Spieler ausgewählt", "no_players_found_with_that_name": "Keine Spieler mit diesem Namen gefunden", @@ -70,7 +67,6 @@ "statistics": "Statistiken", "stats": "Statistiken", "players_count": "{count} Spieler", - "you_have_no_groups_created_yet": "Du hast noch keine Gruppen erstellt", "there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht", "game_name": "Spielname", "ruleset_single_winner_desc": "Genau ein Gewinner wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", From 7bf03ec3889c372bba27225e8948766c124159f0 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 17:42:38 +0100 Subject: [PATCH 094/222] added auto-generated files to git --- lib/l10n/generated/app_localizations.dart | 596 +++++++++++++++++++ lib/l10n/generated/app_localizations_de.dart | 270 +++++++++ lib/l10n/generated/app_localizations_en.dart | 269 +++++++++ 3 files changed, 1135 insertions(+) create mode 100644 lib/l10n/generated/app_localizations.dart create mode 100644 lib/l10n/generated/app_localizations_de.dart create mode 100644 lib/l10n/generated/app_localizations_en.dart diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart new file mode 100644 index 0000000..71091ab --- /dev/null +++ b/lib/l10n/generated/app_localizations.dart @@ -0,0 +1,596 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'app_localizations_de.dart'; +import 'app_localizations_en.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of AppLocalizations +/// returned by `AppLocalizations.of(context)`. +/// +/// Applications need to include `AppLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'generated/app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: AppLocalizations.localizationsDelegates, +/// supportedLocales: AppLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the AppLocalizations.supportedLocales +/// property. +abstract class AppLocalizations { + AppLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static AppLocalizations? of(BuildContext context) { + return Localizations.of(context, AppLocalizations); + } + + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = + >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('de'), + Locale('en'), + ]; + + /// Label for choosing a group + /// + /// In en, this message translates to: + /// **'Choose Group'** + String get choose_group; + + /// Button text to create a new match + /// + /// In en, this message translates to: + /// **'Create new match'** + String get create_new_match; + + /// Label for choosing a ruleset + /// + /// In en, this message translates to: + /// **'Choose Ruleset'** + String get choose_ruleset; + + /// Label for choosing a game + /// + /// In en, this message translates to: + /// **'Choose Game'** + String get choose_game; + + /// Label to select the winner + /// + /// In en, this message translates to: + /// **'Select Winner:'** + String get select_winner; + + /// Message when no recent matches exist + /// + /// In en, this message translates to: + /// **'No recent matches available'** + String get no_recent_matches_available; + + /// No description provided for @no_second_match_available. + /// + /// In en, this message translates to: + /// **'No second match available'** + String get no_second_match_available; + + /// Confirmation dialog for deleting all data + /// + /// In en, this message translates to: + /// **'Delete all data?'** + String get delete_all_data; + + /// Cancel button text + /// + /// In en, this message translates to: + /// **'Cancel'** + String get cancel; + + /// Delete button text + /// + /// In en, this message translates to: + /// **'Delete'** + String get delete; + + /// Button text to create a new group + /// + /// In en, this message translates to: + /// **'Create new group'** + String get create_new_group; + + /// Error message when group creation fails + /// + /// In en, this message translates to: + /// **'Error while creating group, please try again'** + String get error_while_creating_group_please_try_again; + + /// Shows the number of selected players + /// + /// In en, this message translates to: + /// **'Selected players: {count}'** + String selected_players(int count); + + /// Message when no players are selected + /// + /// In en, this message translates to: + /// **'No players selected'** + String get no_players_selected; + + /// Label for all players list + /// + /// In en, this message translates to: + /// **'All players:'** + String get all_players; + + /// Success message when adding a player + /// + /// In en, this message translates to: + /// **'Successfully added player {playerName}.'** + String successfully_added_player(String playerName); + + /// Error message when adding a player fails + /// + /// In en, this message translates to: + /// **'Could not add player {playerName}.'** + String could_not_add_player(String playerName); + + /// Shows the winner's name + /// + /// In en, this message translates to: + /// **'Winner: {winnerName}'** + String winner(String winnerName); + + /// Players label + /// + /// In en, this message translates to: + /// **'Players'** + String get players; + + /// Message when no data is available + /// + /// In en, this message translates to: + /// **'No data available.'** + String get no_data_available; + + /// Label for matches + /// + /// In en, this message translates to: + /// **'Matches'** + String get matches; + + /// Label for groups + /// + /// In en, this message translates to: + /// **'Groups'** + String get groups; + + /// Title for recent matches section + /// + /// In en, this message translates to: + /// **'Recent Matches'** + String get recent_matches; + + /// Title for quick create section + /// + /// In en, this message translates to: + /// **'Quick Create'** + String get quick_create; + + /// Label for winner field + /// + /// In en, this message translates to: + /// **'Winner'** + String get winner_label; + + /// Label for ruleset field + /// + /// In en, this message translates to: + /// **'Ruleset'** + String get ruleset_label; + + /// Message when match is in progress + /// + /// In en, this message translates to: + /// **'Match in progress...'** + String get match_in_progress; + + /// Menu label + /// + /// In en, this message translates to: + /// **'Menu'** + String get menu; + + /// Settings label + /// + /// In en, this message translates to: + /// **'Settings'** + String get settings; + + /// Export data menu item + /// + /// In en, this message translates to: + /// **'Export data'** + String get export_data; + + /// Import data menu item + /// + /// In en, this message translates to: + /// **'Import data'** + String get import_data; + + /// Warning message for irreversible actions + /// + /// In en, this message translates to: + /// **'This can\'t be undone'** + String get this_cannot_be_undone; + + /// Success message after deleting data + /// + /// In en, this message translates to: + /// **'Data successfully deleted'** + String get data_successfully_deleted; + + /// Success message after importing data + /// + /// In en, this message translates to: + /// **'Data successfully imported'** + String get data_successfully_imported; + + /// Error message for invalid schema + /// + /// In en, this message translates to: + /// **'Invalid Schema'** + String get invalid_schema; + + /// Error message when file cannot be read + /// + /// In en, this message translates to: + /// **'Error reading file'** + String get error_reading_file; + + /// Message when import is canceled + /// + /// In en, this message translates to: + /// **'Import canceled'** + String get import_canceled; + + /// Error message for format exceptions + /// + /// In en, this message translates to: + /// **'Format Exception (see console)'** + String get format_exception; + + /// Error message for unknown exceptions + /// + /// In en, this message translates to: + /// **'Unknown Exception (see console)'** + String get unknown_exception; + + /// Success message after exporting data + /// + /// In en, this message translates to: + /// **'Data successfully exported'** + String get data_successfully_exported; + + /// Message when export is canceled + /// + /// In en, this message translates to: + /// **'Export canceled'** + String get export_canceled; + + /// Undo button text + /// + /// In en, this message translates to: + /// **'Undo'** + String get undo; + + /// Label for wins statistic + /// + /// In en, this message translates to: + /// **'Wins'** + String get wins; + + /// Label for winrate statistic + /// + /// In en, this message translates to: + /// **'Winrate'** + String get winrate; + + /// Label for amount of matches statistic + /// + /// In en, this message translates to: + /// **'Amount of Matches'** + String get amount_of_matches; + + /// Info label + /// + /// In en, this message translates to: + /// **'Info'** + String get info; + + /// Message when no groups exist + /// + /// In en, this message translates to: + /// **'No groups created yet'** + String get no_groups_created_yet; + + /// Message when no players exist + /// + /// In en, this message translates to: + /// **'No players created yet'** + String get no_players_created_yet; + + /// Button text to create a group + /// + /// In en, this message translates to: + /// **'Create Group'** + String get create_group; + + /// Placeholder for group name input + /// + /// In en, this message translates to: + /// **'Group name'** + String get group_name; + + /// Placeholder for player name input + /// + /// In en, this message translates to: + /// **'Player name'** + String get player_name; + + /// Message when no matches exist + /// + /// In en, this message translates to: + /// **'No matches created yet'** + String get no_matches_created_yet; + + /// Placeholder for match name input + /// + /// In en, this message translates to: + /// **'Match name'** + String get match_name; + + /// Game label + /// + /// In en, this message translates to: + /// **'Game'** + String get game; + + /// Ruleset label + /// + /// In en, this message translates to: + /// **'Ruleset'** + String get ruleset; + + /// Group label + /// + /// In en, this message translates to: + /// **'Group'** + String get group; + + /// None option label + /// + /// In en, this message translates to: + /// **'None'** + String get none; + + /// Button text to create a match + /// + /// In en, this message translates to: + /// **'Create match'** + String get create_match; + + /// Message when search returns no results + /// + /// In en, this message translates to: + /// **'No players found with that name'** + String get no_players_found_with_that_name; + + /// Message when all players are added to selection + /// + /// In en, this message translates to: + /// **'All players selected'** + String get all_players_selected; + + /// Date format for today + /// + /// In en, this message translates to: + /// **'Today at {time}'** + String today_at(String time); + + /// Date format for yesterday + /// + /// In en, this message translates to: + /// **'Yesterday at {time}'** + String yesterday_at(String time); + + /// Date format for days ago + /// + /// In en, this message translates to: + /// **'{count} days ago'** + String days_ago(int count); + + /// Home tab label + /// + /// In en, this message translates to: + /// **'Home'** + String get home; + + /// Statistics tab label + /// + /// In en, this message translates to: + /// **'Statistics'** + String get statistics; + + /// Stats tab label (short) + /// + /// In en, this message translates to: + /// **'Stats'** + String get stats; + + /// Shows the number of players + /// + /// In en, this message translates to: + /// **'{count} Players'** + String players_count(int count); + + /// Message when search returns no groups + /// + /// In en, this message translates to: + /// **'There is no group matching your search'** + String get there_is_no_group_matching_your_search; + + /// Placeholder for game name search + /// + /// In en, this message translates to: + /// **'Game Name'** + String get game_name; + + /// Description for single winner ruleset + /// + /// In en, this message translates to: + /// **'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'** + String get ruleset_single_winner_desc; + + /// Description for single loser ruleset + /// + /// In en, this message translates to: + /// **'Exactly one loser is determined; last place receives the penalty or consequence.'** + String get ruleset_single_loser_desc; + + /// Description for most points ruleset + /// + /// In en, this message translates to: + /// **'Traditional ruleset: the player with the most points wins.'** + String get ruleset_most_points_desc; + + /// Description for least points ruleset + /// + /// In en, this message translates to: + /// **'Inverse scoring: the player with the fewest points wins.'** + String get ruleset_least_points_desc; + + /// Title for single winner ruleset + /// + /// In en, this message translates to: + /// **'Single Winner'** + String get single_winner; + + /// Title for single loser ruleset + /// + /// In en, this message translates to: + /// **'Single Loser'** + String get single_loser; + + /// Title for most points ruleset + /// + /// In en, this message translates to: + /// **'Most Points'** + String get most_points; + + /// Title for least points ruleset + /// + /// In en, this message translates to: + /// **'Least Points'** + String get least_points; +} + +class _AppLocalizationsDelegate + extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => + ['de', 'en'].contains(locale.languageCode); + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} + +AppLocalizations lookupAppLocalizations(Locale locale) { + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'de': + return AppLocalizationsDe(); + case 'en': + return AppLocalizationsEn(); + } + + throw FlutterError( + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.', + ); +} diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart new file mode 100644 index 0000000..77476f2 --- /dev/null +++ b/lib/l10n/generated/app_localizations_de.dart @@ -0,0 +1,270 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for German (`de`). +class AppLocalizationsDe extends AppLocalizations { + AppLocalizationsDe([String locale = 'de']) : super(locale); + + @override + String get choose_group => 'Gruppe wählen'; + + @override + String get create_new_match => 'Neues Match erstellen'; + + @override + String get choose_ruleset => 'Regelwerk wählen'; + + @override + String get choose_game => 'Spiel wählen'; + + @override + String get select_winner => 'Gewinner wählen:'; + + @override + String get no_recent_matches_available => 'Keine letzten Matches verfügbar'; + + @override + String get no_second_match_available => 'Kein zweites Match verfügbar'; + + @override + String get delete_all_data => 'Alle Daten löschen?'; + + @override + String get cancel => 'Abbrechen'; + + @override + String get delete => 'Löschen'; + + @override + String get create_new_group => 'Neue Gruppe erstellen'; + + @override + String get error_while_creating_group_please_try_again => + 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; + + @override + String selected_players(int count) { + final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( + locale: localeName, + ); + final String countString = countNumberFormat.format(count); + + return 'Ausgewählte Spieler: $countString'; + } + + @override + String get no_players_selected => 'Keine Spieler ausgewählt'; + + @override + String get all_players => 'Alle Spieler:'; + + @override + String successfully_added_player(String playerName) { + return 'Spieler $playerName erfolgreich hinzugefügt.'; + } + + @override + String could_not_add_player(String playerName) { + return 'Spieler $playerName konnte nicht hinzugefügt werden.'; + } + + @override + String winner(String winnerName) { + return 'Gewinner: $winnerName'; + } + + @override + String get players => 'Spieler'; + + @override + String get no_data_available => 'Keine Daten verfügbar.'; + + @override + String get matches => 'Matches'; + + @override + String get groups => 'Gruppen'; + + @override + String get recent_matches => 'Letzte Matches'; + + @override + String get quick_create => 'Schnellzugriff'; + + @override + String get winner_label => 'Gewinner'; + + @override + String get ruleset_label => 'Regelwerk'; + + @override + String get match_in_progress => 'Match läuft...'; + + @override + String get menu => 'Menü'; + + @override + String get settings => 'Einstellungen'; + + @override + String get export_data => 'Daten exportieren'; + + @override + String get import_data => 'Daten importieren'; + + @override + String get this_cannot_be_undone => + 'Dies kann nicht rückgängig gemacht werden'; + + @override + String get data_successfully_deleted => 'Daten erfolgreich gelöscht'; + + @override + String get data_successfully_imported => 'Daten erfolgreich importiert'; + + @override + String get invalid_schema => 'Ungültiges Schema'; + + @override + String get error_reading_file => 'Fehler beim Lesen der Datei'; + + @override + String get import_canceled => 'Import abgebrochen'; + + @override + String get format_exception => 'Formatfehler (siehe Konsole)'; + + @override + String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; + + @override + String get data_successfully_exported => 'Daten erfolgreich exportiert'; + + @override + String get export_canceled => 'Export abgebrochen'; + + @override + String get undo => 'Rückgängig'; + + @override + String get wins => 'Siege'; + + @override + String get winrate => 'Siegquote'; + + @override + String get amount_of_matches => 'Anzahl der Matches'; + + @override + String get info => 'Info'; + + @override + String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; + + @override + String get no_players_created_yet => 'Noch keine Spieler erstellt'; + + @override + String get create_group => 'Gruppe erstellen'; + + @override + String get group_name => 'Gruppenname'; + + @override + String get player_name => 'Spielername'; + + @override + String get no_matches_created_yet => 'Noch keine Matches erstellt'; + + @override + String get match_name => 'Matchname'; + + @override + String get game => 'Spiel'; + + @override + String get ruleset => 'Regelwerk'; + + @override + String get group => 'Gruppe'; + + @override + String get none => 'Keine'; + + @override + String get create_match => 'Match erstellen'; + + @override + String get no_players_found_with_that_name => + 'Keine Spieler mit diesem Namen gefunden'; + + @override + String get all_players_selected => 'Alle Spieler ausgewählt'; + + @override + String today_at(String time) { + return 'Heute um $time'; + } + + @override + String yesterday_at(String time) { + return 'Gestern um $time'; + } + + @override + String days_ago(int count) { + return 'vor $count Tagen'; + } + + @override + String get home => 'Startseite'; + + @override + String get statistics => 'Statistiken'; + + @override + String get stats => 'Statistiken'; + + @override + String players_count(int count) { + return '$count Spieler'; + } + + @override + String get there_is_no_group_matching_your_search => + 'Es gibt keine Gruppe, die deiner Suche entspricht'; + + @override + String get game_name => 'Spielname'; + + @override + String get ruleset_single_winner_desc => + 'Genau ein Gewinner wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; + + @override + String get ruleset_single_loser_desc => + 'Genau ein Verlierer wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; + + @override + String get ruleset_most_points_desc => + 'Traditionelles Regelwerk: Der Spieler mit den meisten Punkten gewinnt.'; + + @override + String get ruleset_least_points_desc => + 'Umgekehrte Wertung: Der Spieler mit den wenigsten Punkten gewinnt.'; + + @override + String get single_winner => 'Ein Gewinner'; + + @override + String get single_loser => 'Ein Verlierer'; + + @override + String get most_points => 'Höchste Punkte'; + + @override + String get least_points => 'Niedrigste Punkte'; +} diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart new file mode 100644 index 0000000..f6a5b5f --- /dev/null +++ b/lib/l10n/generated/app_localizations_en.dart @@ -0,0 +1,269 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class AppLocalizationsEn extends AppLocalizations { + AppLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get choose_group => 'Choose Group'; + + @override + String get create_new_match => 'Create new match'; + + @override + String get choose_ruleset => 'Choose Ruleset'; + + @override + String get choose_game => 'Choose Game'; + + @override + String get select_winner => 'Select Winner:'; + + @override + String get no_recent_matches_available => 'No recent matches available'; + + @override + String get no_second_match_available => 'No second match available'; + + @override + String get delete_all_data => 'Delete all data?'; + + @override + String get cancel => 'Cancel'; + + @override + String get delete => 'Delete'; + + @override + String get create_new_group => 'Create new group'; + + @override + String get error_while_creating_group_please_try_again => + 'Error while creating group, please try again'; + + @override + String selected_players(int count) { + final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( + locale: localeName, + ); + final String countString = countNumberFormat.format(count); + + return 'Selected players: $countString'; + } + + @override + String get no_players_selected => 'No players selected'; + + @override + String get all_players => 'All players:'; + + @override + String successfully_added_player(String playerName) { + return 'Successfully added player $playerName.'; + } + + @override + String could_not_add_player(String playerName) { + return 'Could not add player $playerName.'; + } + + @override + String winner(String winnerName) { + return 'Winner: $winnerName'; + } + + @override + String get players => 'Players'; + + @override + String get no_data_available => 'No data available.'; + + @override + String get matches => 'Matches'; + + @override + String get groups => 'Groups'; + + @override + String get recent_matches => 'Recent Matches'; + + @override + String get quick_create => 'Quick Create'; + + @override + String get winner_label => 'Winner'; + + @override + String get ruleset_label => 'Ruleset'; + + @override + String get match_in_progress => 'Match in progress...'; + + @override + String get menu => 'Menu'; + + @override + String get settings => 'Settings'; + + @override + String get export_data => 'Export data'; + + @override + String get import_data => 'Import data'; + + @override + String get this_cannot_be_undone => 'This can\'t be undone'; + + @override + String get data_successfully_deleted => 'Data successfully deleted'; + + @override + String get data_successfully_imported => 'Data successfully imported'; + + @override + String get invalid_schema => 'Invalid Schema'; + + @override + String get error_reading_file => 'Error reading file'; + + @override + String get import_canceled => 'Import canceled'; + + @override + String get format_exception => 'Format Exception (see console)'; + + @override + String get unknown_exception => 'Unknown Exception (see console)'; + + @override + String get data_successfully_exported => 'Data successfully exported'; + + @override + String get export_canceled => 'Export canceled'; + + @override + String get undo => 'Undo'; + + @override + String get wins => 'Wins'; + + @override + String get winrate => 'Winrate'; + + @override + String get amount_of_matches => 'Amount of Matches'; + + @override + String get info => 'Info'; + + @override + String get no_groups_created_yet => 'No groups created yet'; + + @override + String get no_players_created_yet => 'No players created yet'; + + @override + String get create_group => 'Create Group'; + + @override + String get group_name => 'Group name'; + + @override + String get player_name => 'Player name'; + + @override + String get no_matches_created_yet => 'No matches created yet'; + + @override + String get match_name => 'Match name'; + + @override + String get game => 'Game'; + + @override + String get ruleset => 'Ruleset'; + + @override + String get group => 'Group'; + + @override + String get none => 'None'; + + @override + String get create_match => 'Create match'; + + @override + String get no_players_found_with_that_name => + 'No players found with that name'; + + @override + String get all_players_selected => 'All players selected'; + + @override + String today_at(String time) { + return 'Today at $time'; + } + + @override + String yesterday_at(String time) { + return 'Yesterday at $time'; + } + + @override + String days_ago(int count) { + return '$count days ago'; + } + + @override + String get home => 'Home'; + + @override + String get statistics => 'Statistics'; + + @override + String get stats => 'Stats'; + + @override + String players_count(int count) { + return '$count Players'; + } + + @override + String get there_is_no_group_matching_your_search => + 'There is no group matching your search'; + + @override + String get game_name => 'Game Name'; + + @override + String get ruleset_single_winner_desc => + 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'; + + @override + String get ruleset_single_loser_desc => + 'Exactly one loser is determined; last place receives the penalty or consequence.'; + + @override + String get ruleset_most_points_desc => + 'Traditional ruleset: the player with the most points wins.'; + + @override + String get ruleset_least_points_desc => + 'Inverse scoring: the player with the fewest points wins.'; + + @override + String get single_winner => 'Single Winner'; + + @override + String get single_loser => 'Single Loser'; + + @override + String get most_points => 'Most Points'; + + @override + String get least_points => 'Least Points'; +} From f97c341b81c050574c739216c09759de70f7bc26 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 17:43:24 +0100 Subject: [PATCH 095/222] added devtools_options.yaml to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index e000548..72eb56e 100644 --- a/.gitignore +++ b/.gitignore @@ -195,3 +195,4 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release +/devtools_options.yaml From 15d09f381aaa60798626871fbccb66925e14ff05 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 1 Jan 2026 17:47:15 +0100 Subject: [PATCH 096/222] New created players will be added now at the front too --- lib/presentation/widgets/player_selection.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 5f77e1f..d497fcd 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -239,7 +239,7 @@ class _PlayerSelectionState extends State { bool success = await db.playerDao.addPlayer(player: createdPlayer); if (!context.mounted) return; if (success) { - selectedPlayers.add(createdPlayer); + selectedPlayers.insert(0, createdPlayer); widget.onChanged([...selectedPlayers]); allPlayers.add(createdPlayer); setState(() { From 81f63c1c070145cfa5b3252518a2bdc152ef0a44 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:09:50 +0100 Subject: [PATCH 097/222] made statistics_view.dart use localization and fix merge error --- .../views/main_menu/statistics_view.dart | 40 ++++--------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index dabfff5..8fad901 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -69,7 +69,7 @@ class _StatisticsViewState extends State { children: [ StatisticsTile( icon: Icons.sports_score, - title: 'Wins', + title: AppLocalizations.of(context)!.wins, width: constraints.maxWidth * 0.95, values: winCounts, itemCount: 3, @@ -78,7 +78,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.percent, - title: 'Winrate', + title: AppLocalizations.of(context)!.winrate, width: constraints.maxWidth * 0.95, values: winRates, itemCount: 5, @@ -87,7 +87,9 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, - title: 'Amount of Matches', + title: AppLocalizations.of( + context, + )!.amount_of_matches, width: constraints.maxWidth * 0.95, values: matchCounts, itemCount: 10, @@ -95,38 +97,12 @@ class _StatisticsViewState extends State { ), ], ), - child: const TopCenteredMessage( + child: TopCenteredMessage( icon: Icons.info, - title: 'Info', - message: 'No statistics available', + title: AppLocalizations.of(context)!.info, + message: AppLocalizations.of(context)!.no_data_available, ), ), - StatisticsTile( - icon: Icons.sports_score, - title: AppLocalizations.of(context)!.wins, - width: constraints.maxWidth * 0.95, - values: winCounts, - itemCount: 3, - barColor: Colors.blue, - ), - SizedBox(height: constraints.maxHeight * 0.02), - StatisticsTile( - icon: Icons.percent, - title: AppLocalizations.of(context)!.winrate, - width: constraints.maxWidth * 0.95, - values: winRates, - itemCount: 5, - barColor: Colors.orange[700]!, - ), - SizedBox(height: constraints.maxHeight * 0.02), - StatisticsTile( - icon: Icons.casino, - title: AppLocalizations.of(context)!.amount_of_matches, - width: constraints.maxWidth * 0.95, - values: matchCounts, - itemCount: 10, - barColor: Colors.green, - ), SizedBox(height: MediaQuery.paddingOf(context).bottom), ], ), From 633a21d829d2556ef3a07befc31c12735c6ae2f5 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:10:08 +0100 Subject: [PATCH 098/222] Updated search bar hint texts to use localization for group and player searches --- .../main_menu/match_view/create_match/choose_group_view.dart | 2 +- lib/presentation/widgets/player_selection.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 7308dce..bfc0fed 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -63,7 +63,7 @@ class _ChooseGroupViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: controller, - hintText: AppLocalizations.of(context)!.group_name, + hintText: AppLocalizations.of(context)!.search_for_groups, onChanged: (value) { setState(() { filterGroups(value); diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 9a1ffd1..939b211 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -97,7 +97,7 @@ class _PlayerSelectionState extends State { CustomSearchBar( controller: _searchBarController, constraints: const BoxConstraints(maxHeight: 45, minHeight: 45), - hintText: 'Search for players', + hintText: AppLocalizations.of(context)!.search_for_players, trailingButtonShown: true, trailingButtonicon: Icons.add_circle, trailingButtonEnabled: _searchBarController.text.trim().isNotEmpty, From ad87dca67429366c9da1d2c2950aeb7d458ebd86 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:10:24 +0100 Subject: [PATCH 099/222] add generated files --- lib/l10n/generated/app_localizations.dart | 14 +++++++++++++- lib/l10n/generated/app_localizations_de.dart | 6 ++++++ lib/l10n/generated/app_localizations_en.dart | 6 ++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 71091ab..05ce4b6 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -134,7 +134,7 @@ abstract class AppLocalizations { /// **'No recent matches available'** String get no_recent_matches_available; - /// No description provided for @no_second_match_available. + /// Message when no second match exists /// /// In en, this message translates to: /// **'No second match available'** @@ -559,6 +559,18 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Least Points'** String get least_points; + + /// Hint text for player search input field + /// + /// In en, this message translates to: + /// **'Search for players'** + String get search_for_players; + + /// Hint text for group search input field + /// + /// In en, this message translates to: + /// **'Search for groups'** + String get search_for_groups; } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 77476f2..070cb9a 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -267,4 +267,10 @@ class AppLocalizationsDe extends AppLocalizations { @override String get least_points => 'Niedrigste Punkte'; + + @override + String get search_for_players => 'Nach Spielern suchen'; + + @override + String get search_for_groups => 'Nach Gruppen suchen'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index f6a5b5f..8c78e86 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -266,4 +266,10 @@ class AppLocalizationsEn extends AppLocalizations { @override String get least_points => 'Least Points'; + + @override + String get search_for_players => 'Search for players'; + + @override + String get search_for_groups => 'Search for groups'; } From bc01a6de9a2ab9a3dd5c757de7b7aa423a0c4145 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:10:44 +0100 Subject: [PATCH 100/222] Add localization for search input fields in English and German --- lib/l10n/arb/app_de.arb | 4 +++- lib/l10n/arb/app_en.arb | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index a922996..f5e4855 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -76,5 +76,7 @@ "single_winner": "Ein Gewinner", "single_loser": "Ein Verlierer", "most_points": "Höchste Punkte", - "least_points": "Niedrigste Punkte" + "least_points": "Niedrigste Punkte", + "search_for_players": "Nach Spielern suchen", + "search_for_groups": "Nach Gruppen suchen" } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 44a8f67..ae3fa16 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -25,7 +25,7 @@ "description": "Message when no recent matches exist" }, "no_second_match_available": "No second match available", - "@no_second_matcb_available": { + "@no_second_match_available": { "description": "Message when no second match exists" }, "delete_all_data": "Delete all data?", @@ -353,5 +353,13 @@ "least_points": "Least Points", "@least_points": { "description": "Title for least points ruleset" + }, + "search_for_players": "Search for players", + "@search_for_players": { + "description": "Hint text for player search input field" + }, + "search_for_groups": "Search for groups", + "@search_for_groups": { + "description": "Hint text for group search input field" } } From 7103765054aa2f60605d02efdaef48c505e46632 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:17:53 +0100 Subject: [PATCH 101/222] Update statistics_view.dart to use localized message no statistics available instead of no data available --- lib/presentation/views/main_menu/statistics_view.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 8fad901..fc7825a 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -100,7 +100,9 @@ class _StatisticsViewState extends State { child: TopCenteredMessage( icon: Icons.info, title: AppLocalizations.of(context)!.info, - message: AppLocalizations.of(context)!.no_data_available, + message: AppLocalizations.of( + context, + )!.no_statistics_available, ), ), SizedBox(height: MediaQuery.paddingOf(context).bottom), From 8afba5680b830dfc762b4b8730fbf7f9ffad0a6d Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:18:14 +0100 Subject: [PATCH 102/222] Add localization for no statistics available message and removed dots from all messages --- lib/l10n/arb/app_de.arb | 9 +++++---- lib/l10n/arb/app_en.arb | 12 ++++++++---- lib/l10n/generated/app_localizations.dart | 14 ++++++++++---- lib/l10n/generated/app_localizations_de.dart | 9 ++++++--- lib/l10n/generated/app_localizations_en.dart | 9 ++++++--- 5 files changed, 35 insertions(+), 18 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index f5e4855..4228671 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -15,12 +15,12 @@ "selected_players": "Ausgewählte Spieler: {count}", "no_players_selected": "Keine Spieler ausgewählt", "all_players": "Alle Spieler:", - "successfully_added_player": "Spieler {playerName} erfolgreich hinzugefügt.", - "could_not_add_player": "Spieler {playerName} konnte nicht hinzugefügt werden.", + "successfully_added_player": "Spieler {playerName} erfolgreich hinzugefügt", + "could_not_add_player": "Spieler {playerName} konnte nicht hinzugefügt werden", "winner": "Gewinner: {winnerName}", "players": "Spieler", "player_name": "Spielername", - "no_data_available": "Keine Daten verfügbar.", + "no_statistics_available": "Keine Statistiken verfügbar", "matches": "Matches", "groups": "Gruppen", "recent_matches": "Letzte Matches", @@ -78,5 +78,6 @@ "most_points": "Höchste Punkte", "least_points": "Niedrigste Punkte", "search_for_players": "Nach Spielern suchen", - "search_for_groups": "Nach Gruppen suchen" + "search_for_groups": "Nach Gruppen suchen", + "no_data_available": "Keine Daten verfügbar" } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index ae3fa16..72e307c 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -66,7 +66,7 @@ "@all_players": { "description": "Label for all players list" }, - "successfully_added_player": "Successfully added player {playerName}.", + "successfully_added_player": "Successfully added player {playerName}", "@successfully_added_player": { "description": "Success message when adding a player", "placeholders": { @@ -76,7 +76,7 @@ } } }, - "could_not_add_player": "Could not add player {playerName}.", + "could_not_add_player": "Could not add player {playerName}", "@could_not_add_player": { "description": "Error message when adding a player fails", "placeholders": { @@ -100,9 +100,13 @@ "@players": { "description": "Players label" }, - "no_data_available": "No data available.", + "no_statistics_available": "No statistics available", + "@no_statistics_available": { + "description": "Message when no statistics are available, because no matches were played yet" + }, + "no_data_available": "No data available", "@no_data_available": { - "description": "Message when no data is available" + "description": "Message when no data in the statistic tiles is given" }, "matches": "Matches", "@matches": { diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 05ce4b6..edd14df 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -191,13 +191,13 @@ abstract class AppLocalizations { /// Success message when adding a player /// /// In en, this message translates to: - /// **'Successfully added player {playerName}.'** + /// **'Successfully added player {playerName}'** String successfully_added_player(String playerName); /// Error message when adding a player fails /// /// In en, this message translates to: - /// **'Could not add player {playerName}.'** + /// **'Could not add player {playerName}'** String could_not_add_player(String playerName); /// Shows the winner's name @@ -212,10 +212,16 @@ abstract class AppLocalizations { /// **'Players'** String get players; - /// Message when no data is available + /// Message when no statistics are available, because no matches were played yet /// /// In en, this message translates to: - /// **'No data available.'** + /// **'No statistics available'** + String get no_statistics_available; + + /// Message when no data in the statistic tiles is given + /// + /// In en, this message translates to: + /// **'No data available'** String get no_data_available; /// Label for matches diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 070cb9a..df46454 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -63,12 +63,12 @@ class AppLocalizationsDe extends AppLocalizations { @override String successfully_added_player(String playerName) { - return 'Spieler $playerName erfolgreich hinzugefügt.'; + return 'Spieler $playerName erfolgreich hinzugefügt'; } @override String could_not_add_player(String playerName) { - return 'Spieler $playerName konnte nicht hinzugefügt werden.'; + return 'Spieler $playerName konnte nicht hinzugefügt werden'; } @override @@ -80,7 +80,10 @@ class AppLocalizationsDe extends AppLocalizations { String get players => 'Spieler'; @override - String get no_data_available => 'Keine Daten verfügbar.'; + String get no_statistics_available => 'Keine Statistiken verfügbar'; + + @override + String get no_data_available => 'Keine Daten verfügbar'; @override String get matches => 'Matches'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 8c78e86..7f000e5 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -63,12 +63,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String successfully_added_player(String playerName) { - return 'Successfully added player $playerName.'; + return 'Successfully added player $playerName'; } @override String could_not_add_player(String playerName) { - return 'Could not add player $playerName.'; + return 'Could not add player $playerName'; } @override @@ -80,7 +80,10 @@ class AppLocalizationsEn extends AppLocalizations { String get players => 'Players'; @override - String get no_data_available => 'No data available.'; + String get no_statistics_available => 'No statistics available'; + + @override + String get no_data_available => 'No data available'; @override String get matches => 'Matches'; From 3c3bf506cbe58c9d74c29969490c53b0da0d248c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:31:37 +0100 Subject: [PATCH 103/222] Add TODO for implementing quick create functionality in home_view.dart --- lib/presentation/views/main_menu/home_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 1f0f233..1f8240d 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -173,6 +173,7 @@ class _HomeViewState extends State { ), ), ), + // TODO: Implement quick create functionality InfoTile( width: constraints.maxWidth * 0.95, title: AppLocalizations.of(context)!.quick_create, @@ -221,7 +222,6 @@ class _HomeViewState extends State { ], ), ), - ], ), ), ); From d77b5f20f99aa2e077bbed9b073d3ae8777d1687 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:31:53 +0100 Subject: [PATCH 104/222] Update statistics_view.dart to use localized 'not available' message for players --- lib/presentation/views/main_menu/statistics_view.dart | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index fc7825a..ed90a84 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -152,7 +152,10 @@ class _StatisticsViewState extends State { final playerId = winCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, - orElse: () => Player(id: playerId, name: 'N.a.'), + orElse: () => Player( + id: playerId, + name: AppLocalizations.of(context)!.not_available, + ), ); winCounts[i] = (player.name, winCounts[i].$2); } @@ -214,7 +217,10 @@ class _StatisticsViewState extends State { final playerId = matchCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, - orElse: () => Player(id: playerId, name: 'N.a.'), + orElse: () => Player( + id: playerId, + name: AppLocalizations.of(context)!.not_available, + ), ); matchCounts[i] = (player.name, matchCounts[i].$2); } From cc23c03f6b44d896e53967bd20224f77ee242a2d Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:32:01 +0100 Subject: [PATCH 105/222] Add localization for 'not available' message in English and German --- lib/l10n/arb/app_de.arb | 3 ++- lib/l10n/arb/app_en.arb | 4 ++++ lib/l10n/generated/app_localizations.dart | 6 ++++++ lib/l10n/generated/app_localizations_de.dart | 3 +++ lib/l10n/generated/app_localizations_en.dart | 3 +++ 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 4228671..c3605da 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -79,5 +79,6 @@ "least_points": "Niedrigste Punkte", "search_for_players": "Nach Spielern suchen", "search_for_groups": "Nach Gruppen suchen", - "no_data_available": "Keine Daten verfügbar" + "no_data_available": "Keine Daten verfügbar", + "not_available": "Nicht verfügbar" } diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 72e307c..a16d327 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -365,5 +365,9 @@ "search_for_groups": "Search for groups", "@search_for_groups": { "description": "Hint text for group search input field" + }, + "not_available": "Not available", + "@not_available": { + "description": "Abbreviation for not available" } } diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index edd14df..c7036d8 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -577,6 +577,12 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Search for groups'** String get search_for_groups; + + /// Abbreviation for not available + /// + /// In en, this message translates to: + /// **'Not available'** + String get not_available; } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index df46454..247e3e3 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -276,4 +276,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String get search_for_groups => 'Nach Gruppen suchen'; + + @override + String get not_available => 'Nicht verfügbar'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 7f000e5..a4f62e0 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -275,4 +275,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String get search_for_groups => 'Search for groups'; + + @override + String get not_available => 'Not available'; } From 00519901685e782f58def671444b3d966b66d6b4 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:43:06 +0100 Subject: [PATCH 106/222] Remove auto_localize dependency from pubspec.yaml --- pubspec.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 866f662..e79ca17 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,7 +27,6 @@ dependencies: intl: any flutter_localizations: sdk: flutter - auto_localize: ^0.0.5 dev_dependencies: flutter_test: From 25fe10eb9a3896434d6051a6b389d4baf858724c Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:44:28 +0100 Subject: [PATCH 107/222] added missing square bracket --- lib/presentation/views/main_menu/home_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 1f8240d..680afde 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -222,6 +222,7 @@ class _HomeViewState extends State { ], ), ), + ], ), ), ); From f22595e6782e3ebff54e121befe5073bb1e92860 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 18:44:43 +0100 Subject: [PATCH 108/222] Add localization for 'none_group' option in English and German --- lib/l10n/arb/app_de.arb | 3 ++- lib/l10n/arb/app_en.arb | 4 ++++ lib/l10n/generated/app_localizations.dart | 6 ++++++ lib/l10n/generated/app_localizations_de.dart | 5 ++++- lib/l10n/generated/app_localizations_en.dart | 3 +++ .../match_view/create_match/create_match_view.dart | 2 +- 6 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index c3605da..26ed145 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -55,7 +55,8 @@ "game": "Spiel", "ruleset": "Regelwerk", "group": "Gruppe", - "none": "Keine", + "none": "Kein", + "none_group": "Keine", "create_match": "Match erstellen", "no_players_created_yet": "Noch keine Spieler erstellt", "all_players_selected": "Alle Spieler ausgewählt", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index a16d327..0a68994 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -256,6 +256,10 @@ "@none": { "description": "None option label" }, + "none_group": "None", + "@none_group": { + "description": "None group option label" + }, "create_match": "Create match", "@create_match": { "description": "Button text to create a match" diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index c7036d8..5152962 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -446,6 +446,12 @@ abstract class AppLocalizations { /// **'None'** String get none; + /// None group option label + /// + /// In en, this message translates to: + /// **'None'** + String get none_group; + /// Button text to create a match /// /// In en, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 247e3e3..a270c82 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -195,7 +195,10 @@ class AppLocalizationsDe extends AppLocalizations { String get group => 'Gruppe'; @override - String get none => 'Keine'; + String get none => 'Kein'; + + @override + String get none_group => 'Keine'; @override String get create_match => 'Match erstellen'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index a4f62e0..63a7d0e 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -196,6 +196,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get none => 'None'; + @override + String get none_group => 'None'; + @override String get create_match => 'Create match'; diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 5e35f41..b72a4fa 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -187,7 +187,7 @@ class _CreateMatchViewState extends State { ChooseTile( title: AppLocalizations.of(context)!.group, trailingText: selectedGroup == null - ? AppLocalizations.of(context)!.none + ? AppLocalizations.of(context)!.none_group : selectedGroup!.name, onPressed: () async { selectedGroup = await Navigator.of(context).push( From 5ee0d59377a70b31bfe69eddd25277fed3f9678b Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 19:13:50 +0100 Subject: [PATCH 109/222] implement optional match name with game name as default --- .../create_match/create_match_view.dart | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index f3b4d79..f2f86be 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -31,6 +31,8 @@ class _CreateMatchViewState extends State { /// Controller for the match name input field final TextEditingController _matchNameController = TextEditingController(); + String hintText = "Match Name"; + /// List of all groups from the database List groupsList = []; @@ -132,7 +134,7 @@ class _CreateMatchViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), child: TextInputField( controller: _matchNameController, - hintText: 'Match name', + hintText: hintText, ), ), ChooseTile( @@ -151,11 +153,13 @@ class _CreateMatchViewState extends State { ); setState(() { if (selectedGameIndex != -1) { + hintText = games[selectedGameIndex].$1; selectedRuleset = games[selectedGameIndex].$3; selectedRulesetIndex = rulesets.indexWhere( (r) => r.$1 == selectedRuleset, ); } else { + hintText = "Match Name"; selectedRuleset = null; } }); @@ -228,7 +232,9 @@ class _CreateMatchViewState extends State { onPressed: _enableCreateGameButton() ? () async { Match match = Match( - name: _matchNameController.text.trim(), + name: _matchNameController.text.isEmpty + ? hintText.trim() + : _matchNameController.text.trim(), createdAt: DateTime.now(), group: selectedGroup, players: selectedPlayers, @@ -258,9 +264,8 @@ class _CreateMatchViewState extends State { /// Determines whether the "Create Game" button should be enabled based on /// the current state of the input fields. bool _enableCreateGameButton() { - return _matchNameController.text.isNotEmpty && - (selectedGroup != null || - (selectedPlayers != null && selectedPlayers!.length > 1)) && - selectedRuleset != null; + return selectedGroup != null || + (selectedPlayers != null && selectedPlayers!.length > 1) && + selectedRuleset != null; } } From db30b0fd5ab004fbacb1a7e72dfecdbb6eda66eb Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 19:15:57 +0100 Subject: [PATCH 110/222] removed double quotes --- .../main_menu/match_view/create_match/create_match_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index f2f86be..05da60d 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -31,7 +31,7 @@ class _CreateMatchViewState extends State { /// Controller for the match name input field final TextEditingController _matchNameController = TextEditingController(); - String hintText = "Match Name"; + String hintText = 'Match Name'; /// List of all groups from the database List groupsList = []; @@ -159,7 +159,7 @@ class _CreateMatchViewState extends State { (r) => r.$1 == selectedRuleset, ); } else { - hintText = "Match Name"; + hintText = 'Match Name'; selectedRuleset = null; } }); From 55a22b292f3cda7ddb1183dff4c5a758835e3513 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 21:40:18 +0100 Subject: [PATCH 111/222] changed bottom padding --- lib/presentation/views/main_menu/match_view/match_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index e7d29c0..96d40fb 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -71,7 +71,7 @@ class _MatchViewState extends State { itemBuilder: (BuildContext context, int index) { if (index == matches.length) { return SizedBox( - height: MediaQuery.paddingOf(context).bottom - 80, + height: MediaQuery.paddingOf(context).bottom - 20, ); } return GameHistoryTile( From 6899bb0c3c48f61d8f4f78d541674e8744de8d95 Mon Sep 17 00:00:00 2001 From: mathiskirchner Date: Thu, 1 Jan 2026 21:47:13 +0100 Subject: [PATCH 112/222] add comment --- .../main_menu/match_view/create_match/create_match_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 05da60d..6ef53ba 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -31,6 +31,7 @@ class _CreateMatchViewState extends State { /// Controller for the match name input field final TextEditingController _matchNameController = TextEditingController(); + /// Hint text for the match name input field String hintText = 'Match Name'; /// List of all groups from the database From 1d4fdae84b0a3410ee5df6470727878835a34337 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 1 Jan 2026 22:40:26 +0100 Subject: [PATCH 113/222] Hotfix: Missing game renames --- lib/presentation/views/main_menu/home_view.dart | 12 ++++++------ .../views/main_menu/match_view/match_view.dart | 10 +++++----- .../{game_tile.dart => match_summary_tile.dart} | 8 ++++---- .../{game_history_tile.dart => match_tile.dart} | 8 ++++---- 4 files changed, 19 insertions(+), 19 deletions(-) rename lib/presentation/widgets/tiles/{game_tile.dart => match_summary_tile.dart} (92%) rename lib/presentation/widgets/tiles/{game_history_tile.dart => match_tile.dart} (95%) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 3e221e7..8f911cd 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -6,8 +6,8 @@ import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart'; -import 'package:game_tracker/presentation/widgets/tiles/game_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/match_summary_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; import 'package:provider/provider.dart'; @@ -112,13 +112,13 @@ class _HomeViewState extends State { visible: !isLoading && loadedRecentMatches.isNotEmpty, replacement: const Center( heightFactor: 12, - child: Text('No recent games available'), + child: Text('No recent matches available'), ), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - MatchTile( + MatchSummaryTile( matchTitle: recentMatches[0].name, game: 'Winner', ruleset: 'Ruleset', @@ -132,20 +132,20 @@ class _HomeViewState extends State { child: Divider(), ), if (loadedRecentMatches.length > 1) ...[ - MatchTile( + MatchSummaryTile( matchTitle: recentMatches[1].name, game: 'Winner', ruleset: 'Ruleset', players: _getPlayerText(recentMatches[1]), winner: recentMatches[1].winner == null - ? 'Game in progress...' + ? 'Match in progress...' : recentMatches[1].winner!.name, ), const SizedBox(height: 8), ] else ...[ const Center( heightFactor: 5.35, - child: Text('No second game available'), + child: Text('No second match available'), ), ], ], diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index e7d29c0..a5f6200 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -12,7 +12,7 @@ import 'package:game_tracker/presentation/views/main_menu/match_view/create_matc import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; -import 'package:game_tracker/presentation/widgets/tiles/game_history_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/match_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; @@ -30,9 +30,9 @@ class _MatchViewState extends State { List matches = List.filled( 4, Match( - name: 'Skeleton Gamename', + name: 'Skeleton match name', group: Group( - name: 'Groupname', + name: 'Group name', members: List.filled(5, Player(name: 'Player')), ), winner: Player(name: 'Player'), @@ -74,7 +74,7 @@ class _MatchViewState extends State { height: MediaQuery.paddingOf(context).bottom - 80, ); } - return GameHistoryTile( + return MatchTile( onTap: () async { Navigator.push( context, @@ -96,7 +96,7 @@ class _MatchViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: 'Create Game', + text: 'Create Match', sizeRelativeToWidth: 0.90, onPressed: () async { Navigator.push( diff --git a/lib/presentation/widgets/tiles/game_tile.dart b/lib/presentation/widgets/tiles/match_summary_tile.dart similarity index 92% rename from lib/presentation/widgets/tiles/game_tile.dart rename to lib/presentation/widgets/tiles/match_summary_tile.dart index f98f425..719037b 100644 --- a/lib/presentation/widgets/tiles/game_tile.dart +++ b/lib/presentation/widgets/tiles/match_summary_tile.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:skeletonizer/skeletonizer.dart'; -class MatchTile extends StatefulWidget { +class MatchSummaryTile extends StatefulWidget { final String matchTitle; final String game; final String ruleset; final String players; final String winner; - const MatchTile({ + const MatchSummaryTile({ super.key, required this.matchTitle, required this.game, @@ -19,10 +19,10 @@ class MatchTile extends StatefulWidget { }); @override - State createState() => _MatchTileState(); + State createState() => _MatchSummaryTileState(); } -class _MatchTileState extends State { +class _MatchSummaryTileState extends State { @override Widget build(BuildContext context) { return Column( diff --git a/lib/presentation/widgets/tiles/game_history_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart similarity index 95% rename from lib/presentation/widgets/tiles/game_history_tile.dart rename to lib/presentation/widgets/tiles/match_tile.dart index f79edc3..543a542 100644 --- a/lib/presentation/widgets/tiles/game_history_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -4,17 +4,17 @@ import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:intl/intl.dart'; -class GameHistoryTile extends StatefulWidget { +class MatchTile extends StatefulWidget { final Match match; final VoidCallback onTap; - const GameHistoryTile({super.key, required this.match, required this.onTap}); + const MatchTile({super.key, required this.match, required this.onTap}); @override - State createState() => _GameHistoryTileState(); + State createState() => _MatchTileState(); } -class _GameHistoryTileState extends State { +class _MatchTileState extends State { @override Widget build(BuildContext context) { final group = widget.match.group; From b0073addf84062cf7c3fc99d4bfbc003aa95fce8 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Thu, 1 Jan 2026 22:45:55 +0100 Subject: [PATCH 114/222] remove unnecessary trim --- .../main_menu/match_view/create_match/create_match_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 6ef53ba..a91a45b 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -234,7 +234,7 @@ class _CreateMatchViewState extends State { ? () async { Match match = Match( name: _matchNameController.text.isEmpty - ? hintText.trim() + ? hintText : _matchNameController.text.trim(), createdAt: DateTime.now(), group: selectedGroup, From 304fa82a93612401260c8159e3f117f114e3ab9c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 1 Jan 2026 23:14:52 +0100 Subject: [PATCH 115/222] Fixed bug in player selection --- .../widgets/player_selection.dart | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index d497fcd..c7191a4 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -12,13 +12,13 @@ import 'package:provider/provider.dart'; class PlayerSelection extends StatefulWidget { final Function(List value) onChanged; - final List availablePlayers; + final List? availablePlayers; final List? initialSelectedPlayers; const PlayerSelection({ super.key, required this.onChanged, - this.availablePlayers = const [], + this.availablePlayers, this.initialSelectedPlayers, }); @@ -56,17 +56,17 @@ class _PlayerSelectionState extends State { if (mounted) { _allPlayersFuture.then((loadedPlayers) { setState(() { - // If a list of available players is provided, use that list. - if (widget.availablePlayers.isNotEmpty) { - widget.availablePlayers.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = [...widget.availablePlayers]; + // If a list of available players is provided (even if empty), use that list. + if (widget.availablePlayers != null) { + widget.availablePlayers!.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...widget.availablePlayers!]; suggestedPlayers = [...allPlayers]; if (widget.initialSelectedPlayers != null) { // Ensures that only players available for selection are pre-selected. selectedPlayers = widget.initialSelectedPlayers! .where( - (p) => widget.availablePlayers.any( + (p) => widget.availablePlayers!.any( (available) => available.id == p.id, ), ) @@ -193,11 +193,7 @@ class _PlayerSelectionState extends State { replacement: TopCenteredMessage( icon: Icons.info, title: 'Info', - message: allPlayers.isEmpty - ? 'No players created yet' - : (selectedPlayers.length == allPlayers.length) - ? 'No more players to add' - : 'No players found with that name', + message: _getInfoText(), ), child: ListView.builder( itemCount: suggestedPlayers.length, @@ -273,4 +269,22 @@ class _PlayerSelectionState extends State { ); } } + + /// Determines the appropriate info text to display when no players + /// are available in the suggested players list. + String _getInfoText() { + if (widget.availablePlayers != null && widget.availablePlayers!.isEmpty) { + // Available players list is provided but empty + return 'All players added to the match'; + } else if (allPlayers.isEmpty) { + // No players exist in the database + return 'No players created yet'; + } else if (selectedPlayers.length == allPlayers.length) { + // All players have been selected + return 'No more players to add'; + } else { + // No players match the search query + return 'No players found with that name'; + } + } } From cb7804cf55a66b2e5bc6a9fd367d1256e9f9de29 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 2 Jan 2026 18:38:13 +0100 Subject: [PATCH 116/222] Fixed Problem with widget state --- .../main_menu/match_view/create_match/create_match_view.dart | 4 +++- lib/presentation/widgets/player_selection.dart | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index a91a45b..9923d7a 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -110,8 +110,10 @@ class _CreateMatchViewState extends State { ]).then((result) async { groupsList = result[0] as List; playerList = result[1] as List; + setState(() { + filteredPlayerList = List.from(playerList); + }); }); - filteredPlayerList = List.from(playerList); } @override diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index c7191a4..112df65 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -182,9 +182,6 @@ class _PlayerSelectionState extends State { style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), - /* - - */ Expanded( child: AppSkeleton( enabled: isLoading, From 87856e6548d4a8ed9595d3f752b709ecf081f690 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 2 Jan 2026 18:41:39 +0100 Subject: [PATCH 117/222] Simplified info texts --- lib/presentation/widgets/player_selection.dart | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 112df65..e9aacd3 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -270,14 +270,13 @@ class _PlayerSelectionState extends State { /// Determines the appropriate info text to display when no players /// are available in the suggested players list. String _getInfoText() { - if (widget.availablePlayers != null && widget.availablePlayers!.isEmpty) { - // Available players list is provided but empty - return 'All players added to the match'; - } else if (allPlayers.isEmpty) { + if (allPlayers.isEmpty) { // No players exist in the database return 'No players created yet'; - } else if (selectedPlayers.length == allPlayers.length) { - // All players have been selected + } else if (selectedPlayers.length == allPlayers.length || + widget.availablePlayers?.isEmpty == true) { + // All players have been selected or + // available players list is provided but empty return 'No more players to add'; } else { // No players match the search query From 3c22b084d63ece1fce8e512a1c72dc3882f65a45 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 19:01:44 +0100 Subject: [PATCH 118/222] made getters non nullable and removed all null assertion operators --- l10n.yaml | 3 +- lib/core/enums.dart | 8 ++-- lib/l10n/generated/app_localizations.dart | 4 +- .../main_menu/custom_navigation_bar.dart | 16 ++++---- .../group_view/create_group_view.dart | 6 +-- .../main_menu/group_view/groups_view.dart | 6 +-- .../views/main_menu/home_view.dart | 12 +++--- .../create_match/choose_game_view.dart | 4 +- .../create_match/choose_group_view.dart | 10 ++--- .../create_match/choose_ruleset_view.dart | 2 +- .../create_match/create_match_view.dart | 24 ++++++------ .../match_view/match_result_view.dart | 2 +- .../main_menu/match_view/match_view.dart | 6 +-- .../views/main_menu/settings_view.dart | 38 +++++++++---------- .../views/main_menu/statistics_view.dart | 10 ++--- .../widgets/player_selection.dart | 14 +++---- .../widgets/tiles/match_tile.dart | 6 +-- .../widgets/tiles/statistics_tile.dart | 2 +- 18 files changed, 87 insertions(+), 86 deletions(-) diff --git a/l10n.yaml b/l10n.yaml index f5730dc..f7805c8 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -1,4 +1,5 @@ arb-dir: lib/l10n/arb template-arb-file: app_en.arb output-localization-file: app_localizations.dart -output-dir: lib/l10n/generated \ No newline at end of file +output-dir: lib/l10n/generated +nullable-getter: false \ No newline at end of file diff --git a/lib/core/enums.dart b/lib/core/enums.dart index fc1ac91..74ae023 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -37,12 +37,12 @@ enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints } String translateRulesetToString(Ruleset ruleset, BuildContext context) { switch (ruleset) { case Ruleset.singleWinner: - return AppLocalizations.of(context)!.single_winner; + return AppLocalizations.of(context).single_winner; case Ruleset.singleLoser: - return AppLocalizations.of(context)!.single_loser; + return AppLocalizations.of(context).single_loser; case Ruleset.mostPoints: - return AppLocalizations.of(context)!.most_points; + return AppLocalizations.of(context).most_points; case Ruleset.leastPoints: - return AppLocalizations.of(context)!.least_points; + return AppLocalizations.of(context).least_points; } } diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 5152962..e3acecb 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -67,8 +67,8 @@ abstract class AppLocalizations { final String localeName; - static AppLocalizations? of(BuildContext context) { - return Localizations.of(context, AppLocalizations); + static AppLocalizations of(BuildContext context) { + return Localizations.of(context, AppLocalizations)!; } static const LocalizationsDelegate delegate = diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 980bf1a..2faef8b 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -90,28 +90,28 @@ class _CustomNavigationBarState extends State index: 0, isSelected: currentIndex == 0, icon: Icons.home_rounded, - label: AppLocalizations.of(context)!.home, + label: AppLocalizations.of(context).home, onTabTapped: onTabTapped, ), NavbarItem( index: 1, isSelected: currentIndex == 1, icon: Icons.gamepad_rounded, - label: AppLocalizations.of(context)!.matches, + label: AppLocalizations.of(context).matches, onTabTapped: onTabTapped, ), NavbarItem( index: 2, isSelected: currentIndex == 2, icon: Icons.group_rounded, - label: AppLocalizations.of(context)!.groups, + label: AppLocalizations.of(context).groups, onTabTapped: onTabTapped, ), NavbarItem( index: 3, isSelected: currentIndex == 3, icon: Icons.bar_chart_rounded, - label: AppLocalizations.of(context)!.statistics, + label: AppLocalizations.of(context).statistics, onTabTapped: onTabTapped, ), ], @@ -132,13 +132,13 @@ class _CustomNavigationBarState extends State String _currentTabTitle() { switch (currentIndex) { case 0: - return AppLocalizations.of(context)!.home; + return AppLocalizations.of(context).home; case 1: - return AppLocalizations.of(context)!.matches; + return AppLocalizations.of(context).matches; case 2: - return AppLocalizations.of(context)!.groups; + return AppLocalizations.of(context).groups; case 3: - return AppLocalizations.of(context)!.statistics; + return AppLocalizations.of(context).statistics; default: return ''; } diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index e9e5fe1..eddcf6d 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -45,7 +45,7 @@ class _CreateGroupViewState extends State { backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: Text( - AppLocalizations.of(context)!.create_new_group, + AppLocalizations.of(context).create_new_group, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -58,7 +58,7 @@ class _CreateGroupViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: TextInputField( controller: _groupNameController, - hintText: AppLocalizations.of(context)!.group_name, + hintText: AppLocalizations.of(context).group_name, onChanged: (value) { setState(() {}); }, @@ -74,7 +74,7 @@ class _CreateGroupViewState extends State { ), ), CustomWidthButton( - text: AppLocalizations.of(context)!.create_group, + text: AppLocalizations.of(context).create_group, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 0e9ddbb..4d42417 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -53,8 +53,8 @@ class _GroupsViewState extends State { replacement: Center( child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context)!.info, - message: AppLocalizations.of(context)!.no_groups_created_yet, + title: AppLocalizations.of(context).info, + message: AppLocalizations.of(context).no_groups_created_yet, ), ), child: ListView.builder( @@ -74,7 +74,7 @@ class _GroupsViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: AppLocalizations.of(context)!.create_group, + text: AppLocalizations.of(context).create_group, sizeRelativeToWidth: 0.90, onPressed: () async { await Navigator.push( diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 3fe85fd..e1dd9f8 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -87,7 +87,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: AppLocalizations.of(context)!.matches, + title: AppLocalizations.of(context).matches, icon: Icons.groups_rounded, value: matchCount, ), @@ -95,7 +95,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: AppLocalizations.of(context)!.groups, + title: AppLocalizations.of(context).groups, icon: Icons.groups_rounded, value: groupCount, ), @@ -105,7 +105,7 @@ class _HomeViewState extends State { padding: const EdgeInsets.symmetric(vertical: 16.0), child: InfoTile( width: constraints.maxWidth * 0.95, - title: AppLocalizations.of(context)!.recent_matches, + title: AppLocalizations.of(context).recent_matches, icon: Icons.timer, content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), @@ -125,7 +125,7 @@ class _HomeViewState extends State { children: [ MatchSummaryTile( matchTitle: recentMatches[0].name, - game: AppLocalizations.of(context)!.winner_label, + game: AppLocalizations.of(context).winner_label, ruleset: AppLocalizations.of( context, )!.ruleset_label, @@ -175,7 +175,7 @@ class _HomeViewState extends State { ), InfoTile( width: constraints.maxWidth * 0.95, - title: AppLocalizations.of(context)!.quick_create, + title: AppLocalizations.of(context).quick_create, icon: Icons.add_box_rounded, content: Column( children: [ @@ -232,7 +232,7 @@ class _HomeViewState extends State { String _getPlayerText(Match game) { if (game.group == null) { final playerCount = game.players?.length ?? 0; - return AppLocalizations.of(context)!.players_count(playerCount); + return AppLocalizations.of(context).players_count(playerCount); } if (game.players == null || game.players!.isEmpty) { return game.group!.name; diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 6a75ae1..8e40ab8 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -43,7 +43,7 @@ class _ChooseGameViewState extends State { }, ), title: Text( - AppLocalizations.of(context)!.choose_game, + AppLocalizations.of(context).choose_game, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -54,7 +54,7 @@ class _ChooseGameViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: searchBarController, - hintText: AppLocalizations.of(context)!.game_name, + hintText: AppLocalizations.of(context).game_name, ), ), const SizedBox(height: 5), diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index bfc0fed..83997e7 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -52,7 +52,7 @@ class _ChooseGroupViewState extends State { }, ), title: Text( - AppLocalizations.of(context)!.choose_group, + AppLocalizations.of(context).choose_group, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -63,7 +63,7 @@ class _ChooseGroupViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: controller, - hintText: AppLocalizations.of(context)!.search_for_groups, + hintText: AppLocalizations.of(context).search_for_groups, onChanged: (value) { setState(() { filterGroups(value); @@ -78,12 +78,12 @@ class _ChooseGroupViewState extends State { visible: widget.groups.isNotEmpty, replacement: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context)!.info, - message: AppLocalizations.of(context)!.no_groups_created_yet, + title: AppLocalizations.of(context).info, + message: AppLocalizations.of(context).no_groups_created_yet, ), child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context)!.info, + title: AppLocalizations.of(context).info, message: AppLocalizations.of( context, )!.there_is_no_group_matching_your_search, diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index a6129cc..9383be2 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -48,7 +48,7 @@ class _ChooseRulesetViewState extends State { }, ), title: Text( - AppLocalizations.of(context)!.choose_ruleset, + AppLocalizations.of(context).choose_ruleset, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 63da16d..03aa6c0 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -91,19 +91,19 @@ class _CreateMatchViewState extends State { return [ ( Ruleset.singleWinner, - AppLocalizations.of(context)!.ruleset_single_winner_desc, + AppLocalizations.of(context).ruleset_single_winner_desc, ), ( Ruleset.singleLoser, - AppLocalizations.of(context)!.ruleset_single_loser_desc, + AppLocalizations.of(context).ruleset_single_loser_desc, ), ( Ruleset.mostPoints, - AppLocalizations.of(context)!.ruleset_most_points_desc, + AppLocalizations.of(context).ruleset_most_points_desc, ), ( Ruleset.leastPoints, - AppLocalizations.of(context)!.ruleset_least_points_desc, + AppLocalizations.of(context).ruleset_least_points_desc, ), ]; } @@ -122,7 +122,7 @@ class _CreateMatchViewState extends State { backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: Text( - AppLocalizations.of(context)!.create_new_match, + AppLocalizations.of(context).create_new_match, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -139,9 +139,9 @@ class _CreateMatchViewState extends State { ), ), ChooseTile( - title: AppLocalizations.of(context)!.game, + title: AppLocalizations.of(context).game, trailingText: selectedGameIndex == -1 - ? AppLocalizations.of(context)!.none + ? AppLocalizations.of(context).none : games[selectedGameIndex].$1, onPressed: () async { selectedGameIndex = await Navigator.of(context).push( @@ -167,9 +167,9 @@ class _CreateMatchViewState extends State { }, ), ChooseTile( - title: AppLocalizations.of(context)!.ruleset, + title: AppLocalizations.of(context).ruleset, trailingText: selectedRuleset == null - ? AppLocalizations.of(context)!.none + ? AppLocalizations.of(context).none : translateRulesetToString(selectedRuleset!, context), onPressed: () async { final rulesets = _getRulesets(context); @@ -190,9 +190,9 @@ class _CreateMatchViewState extends State { }, ), ChooseTile( - title: AppLocalizations.of(context)!.group, + title: AppLocalizations.of(context).group, trailingText: selectedGroup == null - ? AppLocalizations.of(context)!.none_group + ? AppLocalizations.of(context).none_group : selectedGroup!.name, onPressed: () async { selectedGroup = await Navigator.of(context).push( @@ -229,7 +229,7 @@ class _CreateMatchViewState extends State { ), ), CustomWidthButton( - text: AppLocalizations.of(context)!.create_match, + text: AppLocalizations.of(context).create_match, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: _enableCreateGameButton() diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index f13ef87..e99ce85 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -81,7 +81,7 @@ class _MatchResultViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - AppLocalizations.of(context)!.select_winner, + AppLocalizations.of(context).select_winner, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 30804de..2b1b110 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -62,8 +62,8 @@ class _MatchViewState extends State { replacement: Center( child: TopCenteredMessage( icon: Icons.report, - title: AppLocalizations.of(context)!.info, - message: AppLocalizations.of(context)!.no_matches_created_yet, + title: AppLocalizations.of(context).info, + message: AppLocalizations.of(context).no_matches_created_yet, ), ), child: ListView.builder( @@ -97,7 +97,7 @@ class _MatchViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: AppLocalizations.of(context)!.create_match, + text: AppLocalizations.of(context).create_match, sizeRelativeToWidth: 0.90, onPressed: () async { Navigator.push( diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 8899e40..3ab401f 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -29,7 +29,7 @@ class _SettingsViewState extends State { padding: const EdgeInsets.fromLTRB(24, 0, 24, 10), child: Text( textAlign: TextAlign.start, - AppLocalizations.of(context)!.menu, + AppLocalizations.of(context).menu, style: const TextStyle( fontSize: 28, fontWeight: FontWeight.bold, @@ -43,7 +43,7 @@ class _SettingsViewState extends State { ), child: Text( textAlign: TextAlign.start, - AppLocalizations.of(context)!.settings, + AppLocalizations.of(context).settings, style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, @@ -51,7 +51,7 @@ class _SettingsViewState extends State { ), ), SettingsListTile( - title: AppLocalizations.of(context)!.export_data, + title: AppLocalizations.of(context).export_data, icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -66,7 +66,7 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: AppLocalizations.of(context)!.import_data, + title: AppLocalizations.of(context).import_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -78,7 +78,7 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: AppLocalizations.of(context)!.delete_all_data, + title: AppLocalizations.of(context).delete_all_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () { @@ -86,19 +86,19 @@ class _SettingsViewState extends State { context: context, builder: (context) => AlertDialog( title: Text( - AppLocalizations.of(context)!.delete_all_data, + AppLocalizations.of(context).delete_all_data, ), content: Text( - AppLocalizations.of(context)!.this_cannot_be_undone, + AppLocalizations.of(context).this_cannot_be_undone, ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), - child: Text(AppLocalizations.of(context)!.cancel), + child: Text(AppLocalizations.of(context).cancel), ), TextButton( onPressed: () => Navigator.of(context).pop(true), - child: Text(AppLocalizations.of(context)!.delete), + child: Text(AppLocalizations.of(context).delete), ), ], ), @@ -134,32 +134,32 @@ class _SettingsViewState extends State { case ImportResult.success: showSnackbar( context: context, - message: AppLocalizations.of(context)!.data_successfully_imported, + message: AppLocalizations.of(context).data_successfully_imported, ); case ImportResult.invalidSchema: showSnackbar( context: context, - message: AppLocalizations.of(context)!.invalid_schema, + message: AppLocalizations.of(context).invalid_schema, ); case ImportResult.fileReadError: showSnackbar( context: context, - message: AppLocalizations.of(context)!.error_reading_file, + message: AppLocalizations.of(context).error_reading_file, ); case ImportResult.canceled: showSnackbar( context: context, - message: AppLocalizations.of(context)!.import_canceled, + message: AppLocalizations.of(context).import_canceled, ); case ImportResult.formatException: showSnackbar( context: context, - message: AppLocalizations.of(context)!.format_exception, + message: AppLocalizations.of(context).format_exception, ); case ImportResult.unknownException: showSnackbar( context: context, - message: AppLocalizations.of(context)!.unknown_exception, + message: AppLocalizations.of(context).unknown_exception, ); } } @@ -176,17 +176,17 @@ class _SettingsViewState extends State { case ExportResult.success: showSnackbar( context: context, - message: AppLocalizations.of(context)!.data_successfully_exported, + message: AppLocalizations.of(context).data_successfully_exported, ); case ExportResult.canceled: showSnackbar( context: context, - message: AppLocalizations.of(context)!.export_canceled, + message: AppLocalizations.of(context).export_canceled, ); case ExportResult.unknownException: showSnackbar( context: context, - message: AppLocalizations.of(context)!.unknown_exception, + message: AppLocalizations.of(context).unknown_exception, ); } } @@ -212,7 +212,7 @@ class _SettingsViewState extends State { duration: duration, action: action != null ? SnackBarAction( - label: AppLocalizations.of(context)!.undo, + label: AppLocalizations.of(context).undo, onPressed: action, ) : null, diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index ed90a84..d752119 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -69,7 +69,7 @@ class _StatisticsViewState extends State { children: [ StatisticsTile( icon: Icons.sports_score, - title: AppLocalizations.of(context)!.wins, + title: AppLocalizations.of(context).wins, width: constraints.maxWidth * 0.95, values: winCounts, itemCount: 3, @@ -78,7 +78,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.percent, - title: AppLocalizations.of(context)!.winrate, + title: AppLocalizations.of(context).winrate, width: constraints.maxWidth * 0.95, values: winRates, itemCount: 5, @@ -99,7 +99,7 @@ class _StatisticsViewState extends State { ), child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context)!.info, + title: AppLocalizations.of(context).info, message: AppLocalizations.of( context, )!.no_statistics_available, @@ -154,7 +154,7 @@ class _StatisticsViewState extends State { (p) => p.id == playerId, orElse: () => Player( id: playerId, - name: AppLocalizations.of(context)!.not_available, + name: AppLocalizations.of(context).not_available, ), ); winCounts[i] = (player.name, winCounts[i].$2); @@ -219,7 +219,7 @@ class _StatisticsViewState extends State { (p) => p.id == playerId, orElse: () => Player( id: playerId, - name: AppLocalizations.of(context)!.not_available, + name: AppLocalizations.of(context).not_available, ), ); matchCounts[i] = (player.name, matchCounts[i].$2); diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index dc8731c..d33d83e 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -97,7 +97,7 @@ class _PlayerSelectionState extends State { CustomSearchBar( controller: _searchBarController, constraints: const BoxConstraints(maxHeight: 45, minHeight: 45), - hintText: AppLocalizations.of(context)!.search_for_players, + hintText: AppLocalizations.of(context).search_for_players, trailingButtonShown: true, trailingButtonicon: Icons.add_circle, trailingButtonEnabled: _searchBarController.text.trim().isNotEmpty, @@ -141,7 +141,7 @@ class _PlayerSelectionState extends State { child: selectedPlayers.isEmpty ? Center( child: Text( - AppLocalizations.of(context)!.no_players_selected, + AppLocalizations.of(context).no_players_selected, ), ) : SingleChildScrollView( @@ -185,7 +185,7 @@ class _PlayerSelectionState extends State { ), const SizedBox(height: 10), Text( - AppLocalizations.of(context)!.all_players, + AppLocalizations.of(context).all_players, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), @@ -199,11 +199,11 @@ class _PlayerSelectionState extends State { visible: suggestedPlayers.isNotEmpty, replacement: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context)!.info, + title: AppLocalizations.of(context).info, message: allPlayers.isEmpty - ? AppLocalizations.of(context)!.no_players_created_yet + ? AppLocalizations.of(context).no_players_created_yet : (selectedPlayers.length == allPlayers.length) - ? AppLocalizations.of(context)!.all_players_selected + ? AppLocalizations.of(context).all_players_selected : AppLocalizations.of( context, )!.no_players_found_with_that_name, @@ -276,7 +276,7 @@ class _PlayerSelectionState extends State { backgroundColor: CustomTheme.boxColor, content: Center( child: Text( - AppLocalizations.of(context)!.could_not_add_player(playerName), + AppLocalizations.of(context).could_not_add_player(playerName), style: const TextStyle(color: Colors.white), ), ), diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 9eedec6..93e6f32 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -98,7 +98,7 @@ class _MatchTileState extends State { const SizedBox(width: 8), Expanded( child: Text( - AppLocalizations.of(context)!.winner(winner.name), + AppLocalizations.of(context).winner(winner.name), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, @@ -115,7 +115,7 @@ class _MatchTileState extends State { if (allPlayers.isNotEmpty) ...[ Text( - AppLocalizations.of(context)!.players, + AppLocalizations.of(context).players, style: const TextStyle( fontSize: 13, color: Colors.grey, @@ -150,7 +150,7 @@ class _MatchTileState extends State { context, )!.yesterday_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays < 7) { - return AppLocalizations.of(context)!.days_ago(difference.inDays); + return AppLocalizations.of(context).days_ago(difference.inDays); } else { return DateFormat('MMM d, yyyy').format(dateTime); } diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index e783ab4..8d81270 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -36,7 +36,7 @@ class StatisticsTile extends StatelessWidget { visible: values.isNotEmpty, replacement: Center( heightFactor: 4, - child: Text(AppLocalizations.of(context)!.no_data_available), + child: Text(AppLocalizations.of(context).no_data_available), ), child: Column( children: List.generate(min(values.length, itemCount), (index) { From 4a67dae45617178f7103394434b7bc83d9415dee Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Fri, 2 Jan 2026 20:37:10 +0100 Subject: [PATCH 119/222] change import/export json logic to remove redundant data --- lib/services/data_transfer_service.dart | 128 +++++++++++++++++------- 1 file changed, 91 insertions(+), 37 deletions(-) diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 957d8fd..2fc5c42 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -31,9 +31,25 @@ class DataTransferService { // Construct a JSON representation of the data final Map jsonMap = { - 'matches': matches.map((match) => match.toJson()).toList(), - 'groups': groups.map((group) => group.toJson()).toList(), - 'players': players.map((player) => player.toJson()).toList(), + 'players': players.map((p) => p.toJson()).toList(), + + 'groups': groups + .map((g) => { + 'id': g.id, + 'name': g.name, + 'createdAt': g.createdAt.toIso8601String(), + 'memberIds': (g.members ?? []).map((m) => m.id).toList(), + }).toList(), + + 'matches': matches + .map((m) => { + 'id': m.id, + 'name': m.name, + 'createdAt': m.createdAt.toIso8601String(), + 'groupId': m.group?.id, + 'playerIds': (m.players ?? []).map((p) => p.id).toList(), + 'winnerId': m.winner?.id, + }).toList(), }; return json.encode(jsonMap); @@ -46,7 +62,7 @@ class DataTransferService { /// [fileName] The desired name for the exported file (without extension). static Future exportData( String jsonString, - String fileName, + String fileName ) async { try { final bytes = Uint8List.fromList(utf8.encode(jsonString)); @@ -54,11 +70,13 @@ class DataTransferService { fileName: '$fileName.json', bytes: bytes, ); + if (path == null) { return ExportResult.canceled; } else { return ExportResult.success; } + } catch (e, stack) { print('[exportData] $e'); print(stack); @@ -81,42 +99,78 @@ class DataTransferService { try { final jsonString = await _readFileContent(path.files.single); - if (jsonString == null) { - return ImportResult.fileReadError; - } + if (jsonString == null) return ImportResult.fileReadError; - if (await _validateJsonSchema(jsonString)) { - final Map jsonData = - json.decode(jsonString) as Map; + final isValid = await _validateJsonSchema(jsonString); + if (!isValid) return ImportResult.invalidSchema; - final List? matchesJson = - jsonData['matches'] as List?; - final List? groupsJson = jsonData['groups'] as List?; - final List? playersJson = - jsonData['players'] as List?; + final dynamic decoded = json.decode(jsonString); + if (decoded is! Map) return ImportResult.invalidSchema; - final List importedMatches = - matchesJson - ?.map((g) => Match.fromJson(g as Map)) - .toList() ?? - []; - final List importedGroups = - groupsJson - ?.map((g) => Group.fromJson(g as Map)) - .toList() ?? - []; - final List importedPlayers = - playersJson - ?.map((p) => Player.fromJson(p as Map)) - .toList() ?? - []; + final List playersJson = (decoded['players'] as List?) ?? []; + final List groupsJson = (decoded['groups'] as List?) ?? []; + final List matchesJson = (decoded['matches'] as List?) ?? []; + + // Players + final List importedPlayers = playersJson + .map((p) => Player.fromJson(p as Map)) + .toList(); + + final Map playerById = { + for (final p in importedPlayers) p.id: p, + }; + + // Groups + final List importedGroups = groupsJson.map((g) { + final map = g as Map; + final memberIds = (map['memberIds'] as List? ?? []).cast(); + + final members = memberIds + .map((id) => playerById[id]) + .whereType() + .toList(); + + return Group( + id: map['id'] as String, + name: map['name'] as String, + members: members, + createdAt: DateTime.parse(map['createdAt'] as String), + ); + }).toList(); + + final Map groupById = { + for (final g in importedGroups) g.id: g, + }; + + // Matches + final List importedMatches = matchesJson.map((m) { + final map = m as Map; + + final String? groupId = map['groupId'] as String?; + final List playerIds = (map['playerIds'] as List? ?? []).cast(); + final String? winnerId = map['winnerId'] as String?; + + final group = (groupId == null) ? null : groupById[groupId]; + final players = playerIds + .map((id) => playerById[id]) + .whereType() + .toList(); + final winner = (winnerId == null) ? null : playerById[winnerId]; + + return Match( + id: map['id'] as String, + name: map['name'] as String, + group: group, + players: players, + createdAt: DateTime.parse(map['createdAt'] as String), + winner: winner, + ); + }).toList(); + + await db.playerDao.addPlayersAsList(players: importedPlayers); + await db.groupDao.addGroupsAsList(groups: importedGroups); + await db.matchDao.addMatchAsList(matches: importedMatches); - await db.playerDao.addPlayersAsList(players: importedPlayers); - await db.groupDao.addGroupsAsList(groups: importedGroups); - await db.matchDao.addMatchAsList(matches: importedMatches); - } else { - return ImportResult.invalidSchema; - } return ImportResult.success; } on FormatException catch (e, stack) { print('[importData] FormatException'); @@ -159,4 +213,4 @@ class DataTransferService { return false; } } -} +} \ No newline at end of file From 22fcff73bd3aa0ec2b3e4c1239c1fe6716860a8e Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 21:11:37 +0100 Subject: [PATCH 120/222] configure en locale as fallback --- lib/main.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index 0f3b6b0..a232256 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,9 +20,21 @@ class GameTracker extends StatelessWidget { @override Widget build(BuildContext context) { + print(AppLocalizations.supportedLocales.first); return MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, + localeResolutionCallback: (locale, supportedLocales) { + for (final supportedLocale in supportedLocales) { + if (supportedLocale.languageCode == locale?.languageCode) { + return supportedLocale; + } + } + return supportedLocales.firstWhere( + (locale) => locale.languageCode == 'en', + orElse: () => supportedLocales.first, + ); + }, debugShowCheckedModeBanner: false, title: 'Game Tracker', darkTheme: ThemeData.dark(), From a038c22ba69d78ad50d4bd6460e55730c9c838ff Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 21:13:00 +0100 Subject: [PATCH 121/222] removed else in fallback --- lib/main.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index a232256..8ef243a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -32,7 +32,6 @@ class GameTracker extends StatelessWidget { } return supportedLocales.firstWhere( (locale) => locale.languageCode == 'en', - orElse: () => supportedLocales.first, ); }, debugShowCheckedModeBanner: false, From 678ab90af37e73f7c901684f2a7b427aa49eba57 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 21:16:13 +0100 Subject: [PATCH 122/222] removed unneccessary null assertion operators --- .../main_menu/group_view/create_group_view.dart | 2 +- lib/presentation/views/main_menu/home_view.dart | 16 +++++++--------- .../create_match/choose_group_view.dart | 2 +- .../views/main_menu/settings_view.dart | 2 +- .../views/main_menu/statistics_view.dart | 6 ++---- lib/presentation/widgets/player_selection.dart | 6 +++--- lib/presentation/widgets/tiles/match_tile.dart | 4 ++-- 7 files changed, 17 insertions(+), 21 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index eddcf6d..0d8c28f 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -99,7 +99,7 @@ class _CreateGroupViewState extends State { child: Text( AppLocalizations.of( context, - )!.error_while_creating_group_please_try_again, + ).error_while_creating_group_please_try_again, style: const TextStyle(color: Colors.white), ), ), diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index e1dd9f8..6446ea8 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -116,7 +116,7 @@ class _HomeViewState extends State { child: Text( AppLocalizations.of( context, - )!.no_recent_matches_available, + ).no_recent_matches_available, ), ), child: Column( @@ -128,12 +128,12 @@ class _HomeViewState extends State { game: AppLocalizations.of(context).winner_label, ruleset: AppLocalizations.of( context, - )!.ruleset_label, + ).ruleset_label, players: _getPlayerText(recentMatches[0]), winner: recentMatches[0].winner == null ? AppLocalizations.of( context, - )!.match_in_progress + ).match_in_progress : recentMatches[0].winner!.name, ), const Padding( @@ -143,17 +143,15 @@ class _HomeViewState extends State { if (loadedRecentMatches.length > 1) ...[ MatchSummaryTile( matchTitle: recentMatches[1].name, - game: AppLocalizations.of( - context, - )!.winner_label, + game: AppLocalizations.of(context).winner_label, ruleset: AppLocalizations.of( context, - )!.ruleset_label, + ).ruleset_label, players: _getPlayerText(recentMatches[1]), winner: recentMatches[1].winner == null ? AppLocalizations.of( context, - )!.match_in_progress + ).match_in_progress : recentMatches[1].winner!.name, ), const SizedBox(height: 8), @@ -163,7 +161,7 @@ class _HomeViewState extends State { child: Text( AppLocalizations.of( context, - )!.no_second_match_available, + ).no_second_match_available, ), ), ], diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 83997e7..d05fff9 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -86,7 +86,7 @@ class _ChooseGroupViewState extends State { title: AppLocalizations.of(context).info, message: AppLocalizations.of( context, - )!.there_is_no_group_matching_your_search, + ).there_is_no_group_matching_your_search, ), ), child: ListView.builder( diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 3ab401f..c554950 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -109,7 +109,7 @@ class _SettingsViewState extends State { context: context, message: AppLocalizations.of( context, - )!.data_successfully_deleted, + ).data_successfully_deleted, ); } }); diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index d752119..43de037 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -87,9 +87,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, - title: AppLocalizations.of( - context, - )!.amount_of_matches, + title: AppLocalizations.of(context).amount_of_matches, width: constraints.maxWidth * 0.95, values: matchCounts, itemCount: 10, @@ -102,7 +100,7 @@ class _StatisticsViewState extends State { title: AppLocalizations.of(context).info, message: AppLocalizations.of( context, - )!.no_statistics_available, + ).no_statistics_available, ), ), SizedBox(height: MediaQuery.paddingOf(context).bottom), diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index d33d83e..af95081 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -132,7 +132,7 @@ class _PlayerSelectionState extends State { Text( AppLocalizations.of( context, - )!.selected_players(selectedPlayers.length), + ).selected_players(selectedPlayers.length), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), @@ -206,7 +206,7 @@ class _PlayerSelectionState extends State { ? AppLocalizations.of(context).all_players_selected : AppLocalizations.of( context, - )!.no_players_found_with_that_name, + ).no_players_found_with_that_name, ), child: ListView.builder( itemCount: suggestedPlayers.length, @@ -264,7 +264,7 @@ class _PlayerSelectionState extends State { child: Text( AppLocalizations.of( context, - )!.successfully_added_player(playerName), + ).successfully_added_player(playerName), style: const TextStyle(color: Colors.white), ), ), diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 93e6f32..7727827 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -144,11 +144,11 @@ class _MatchTileState extends State { if (difference.inDays == 0) { return AppLocalizations.of( context, - )!.today_at(DateFormat('HH:mm').format(dateTime)); + ).today_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays == 1) { return AppLocalizations.of( context, - )!.yesterday_at(DateFormat('HH:mm').format(dateTime)); + ).yesterday_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays < 7) { return AppLocalizations.of(context).days_ago(difference.inDays); } else { From bdc3453d7fc9072bf8951314e2b8c204d3e8f357 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 21:20:30 +0100 Subject: [PATCH 123/222] correct spelling mistake --- lib/presentation/widgets/player_selection.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index e9aacd3..6ca6373 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -224,7 +224,7 @@ class _PlayerSelectionState extends State { } /// Adds a new player to the database from the search bar input. - /// Shows a snackbar indicating success xfor failure. + /// Shows a snackbar indicating success or failure. /// [context] - BuildContext to show the snackbar. void addNewPlayerFromSearch({required BuildContext context}) async { String playerName = _searchBarController.text.trim(); From 77095725de452c62f1cf994d8c133c491c18b569 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 21:26:54 +0100 Subject: [PATCH 124/222] fix game choose view highlighting not working --- .../main_menu/match_view/create_match/choose_game_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 8e40ab8..8e6eceb 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -69,6 +69,7 @@ class _ChooseGameViewState extends State { widget.games[index].$3, context, ), + isHighlighted: selectedGameIndex == index, onPressed: () async { setState(() { if (selectedGameIndex == index) { From 132966f3d26145cbf4337818087b6bd13db4f96a Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 2 Jan 2026 21:28:46 +0100 Subject: [PATCH 125/222] increase title_description_list_tile width to make german translation fully visible --- lib/presentation/widgets/tiles/title_description_list_tile.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/title_description_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart index 7a138a0..465c94d 100644 --- a/lib/presentation/widgets/tiles/title_description_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -54,7 +54,7 @@ class TitleDescriptionListTile extends StatelessWidget { if (badgeText != null) ...[ const Spacer(), Container( - constraints: const BoxConstraints(maxWidth: 100), + constraints: const BoxConstraints(maxWidth: 115), margin: const EdgeInsets.only(top: 4), padding: const EdgeInsets.symmetric( vertical: 2, From e4ae526d93e78c0b7bdbb887462f5bf025f978ff Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 3 Jan 2026 10:16:28 +0100 Subject: [PATCH 126/222] fix choose_game_view not saving --- .../create_match/choose_game_view.dart | 73 +++++++++++-------- 1 file changed, 42 insertions(+), 31 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 53a4fcb..20e1d14 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -47,39 +47,50 @@ class _ChooseGameViewState extends State { ), centerTitle: true, ), - body: Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: CustomSearchBar( - controller: searchBarController, - hintText: 'Game Name', + body: PopScope( + canPop: false, + onPopInvokedWithResult: (bool didPop, Object? result) { + print(result); + print(didPop); + if (didPop) { + return; + } + Navigator.of(context).pop(selectedGameIndex); + }, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: CustomSearchBar( + controller: searchBarController, + hintText: 'Game Name', + ), ), - ), - const SizedBox(height: 5), - Expanded( - child: ListView.builder( - itemCount: widget.games.length, - itemBuilder: (BuildContext context, int index) { - return TitleDescriptionListTile( - title: widget.games[index].$1, - description: widget.games[index].$2, - badgeText: translateRulesetToString(widget.games[index].$3), - isHighlighted: selectedGameIndex == index, - onPressed: () async { - setState(() { - if (selectedGameIndex == index) { - selectedGameIndex = -1; - } else { - selectedGameIndex = index; - } - }); - }, - ); - }, + const SizedBox(height: 5), + Expanded( + child: ListView.builder( + itemCount: widget.games.length, + itemBuilder: (BuildContext context, int index) { + return TitleDescriptionListTile( + title: widget.games[index].$1, + description: widget.games[index].$2, + badgeText: translateRulesetToString(widget.games[index].$3), + isHighlighted: selectedGameIndex == index, + onPressed: () async { + setState(() { + if (selectedGameIndex == index) { + selectedGameIndex = -1; + } else { + selectedGameIndex = index; + } + }); + }, + ); + }, + ), ), - ), - ], + ], + ), ), ); } From 16e1f542f57774055be6b7fc7b05de250ecd0983 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 3 Jan 2026 11:02:38 +0100 Subject: [PATCH 127/222] fix choose rulset & choose group view not saving --- .../create_match/choose_group_view.dart | 121 ++++++++++-------- .../create_match/choose_ruleset_view.dart | 49 ++++--- 2 files changed, 101 insertions(+), 69 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 37096b9..92b66ad 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -57,61 +57,78 @@ class _ChooseGroupViewState extends State { ), centerTitle: true, ), - body: Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 10), - child: CustomSearchBar( - controller: controller, - hintText: hintText, - onChanged: (value) { - setState(() { - filterGroups(value); - }); - }, - ), - ), - Expanded( - child: Visibility( - visible: filteredGroups.isNotEmpty, - replacement: Visibility( - visible: widget.groups.isNotEmpty, - replacement: const TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'You have no groups created yet', - ), - child: const TopCenteredMessage( - icon: Icons.info, - title: 'Info', - message: 'There is no group matching your search', - ), - ), - child: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: filteredGroups.length, - itemBuilder: (BuildContext context, int index) { - return GestureDetector( - onTap: () { - setState(() { - if (selectedGroupId != filteredGroups[index].id) { - selectedGroupId = filteredGroups[index].id; - } else { - selectedGroupId = ''; - } - }); - }, - child: GroupTile( - group: filteredGroups[index], - isHighlighted: - selectedGroupId == filteredGroups[index].id, - ), - ); + body: PopScope( + // This fixes that the Android Back Gesture didn't return the + // selectedGroupId and therefore the selected Group wasn't saved + canPop: false, + onPopInvokedWithResult: (bool didPop, Object? result) { + if (didPop) { + return; + } + Navigator.of(context).pop( + selectedGroupId == '' + ? null + : widget.groups.firstWhere( + (group) => group.id == selectedGroupId, + ), + ); + }, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: CustomSearchBar( + controller: controller, + hintText: hintText, + onChanged: (value) { + setState(() { + filterGroups(value); + }); }, ), ), - ), - ], + Expanded( + child: Visibility( + visible: filteredGroups.isNotEmpty, + replacement: Visibility( + visible: widget.groups.isNotEmpty, + replacement: const TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'You have no groups created yet', + ), + child: const TopCenteredMessage( + icon: Icons.info, + title: 'Info', + message: 'There is no group matching your search', + ), + ), + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: filteredGroups.length, + itemBuilder: (BuildContext context, int index) { + return GestureDetector( + onTap: () { + setState(() { + if (selectedGroupId != filteredGroups[index].id) { + selectedGroupId = filteredGroups[index].id; + } else { + selectedGroupId = ''; + } + }); + }, + child: GroupTile( + group: filteredGroups[index], + isHighlighted: + selectedGroupId == filteredGroups[index].id, + ), + ); + }, + ), + ), + ), + ], + ), ), ); } diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index 537f749..479106c 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -52,25 +52,40 @@ class _ChooseRulesetViewState extends State { ), centerTitle: true, ), - body: ListView.builder( - padding: const EdgeInsets.only(bottom: 85), - itemCount: widget.rulesets.length, - itemBuilder: (BuildContext context, int index) { - return TitleDescriptionListTile( - onPressed: () async { - setState(() { - if (selectedRulesetIndex == index) { - selectedRulesetIndex = -1; - } else { - selectedRulesetIndex = index; - } - }); - }, - title: translateRulesetToString(widget.rulesets[index].$1), - description: widget.rulesets[index].$2, - isHighlighted: selectedRulesetIndex == index, + body: PopScope( + // This fixes that the Android Back Gesture didn't return the + // selectedRulesetIndex and therefore the selected Ruleset wasn't saved + canPop: false, + onPopInvokedWithResult: (bool didPop, Object? result) { + if (didPop) { + return; + } + Navigator.of(context).pop( + selectedRulesetIndex == -1 + ? null + : widget.rulesets[selectedRulesetIndex].$1, ); }, + child: ListView.builder( + padding: const EdgeInsets.only(bottom: 85), + itemCount: widget.rulesets.length, + itemBuilder: (BuildContext context, int index) { + return TitleDescriptionListTile( + onPressed: () async { + setState(() { + if (selectedRulesetIndex == index) { + selectedRulesetIndex = -1; + } else { + selectedRulesetIndex = index; + } + }); + }, + title: translateRulesetToString(widget.rulesets[index].$1), + description: widget.rulesets[index].$2, + isHighlighted: selectedRulesetIndex == index, + ); + }, + ), ), ), ); From 528966032706008dc4cfc26a73d1b3a456c172bc Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 3 Jan 2026 11:02:43 +0100 Subject: [PATCH 128/222] add comment --- .../main_menu/match_view/create_match/choose_game_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 20e1d14..e21e868 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -48,10 +48,10 @@ class _ChooseGameViewState extends State { centerTitle: true, ), body: PopScope( + // This fixes that the Android Back Gesture didn't return the + // selectedGameIndex and therefore the selected Game wasn't saved canPop: false, onPopInvokedWithResult: (bool didPop, Object? result) { - print(result); - print(didPop); if (didPop) { return; } From cecfd5da4e817d78f9c1ff7282fb97832af786d5 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Sat, 3 Jan 2026 13:19:58 +0100 Subject: [PATCH 129/222] update schema.json to current data import version --- assets/schema.json | 244 +++++++++++++++------------------------------ 1 file changed, 82 insertions(+), 162 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index c80915c..fa748d9 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -2,178 +2,98 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { - "games": { + "players": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - }, - "players": { - "type": [ - "array", - "null" - ], - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": [ - "id", - "createdAt", - "name" - ] - } + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "group": { - "type": [ - "object", - "null" - ], - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - }, - "members": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": [ - "id", - "createdAt", - "name" - ] - } - ] - } - }, - "required": [ - "id", - "createdAt", - "name", - "members" - ] + "createdAt": { + "type": "string" }, - "winner": { - "type": ["object","null"] - }, - "required": [ - "id", - "createdAt", - "name" - ] - } - ] + "name": { + "type": "string" + } + }, + "required": [ + "id", + "createdAt", + "name" + ] + } }, "groups": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - }, - "members": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": [ - "id", - "createdAt", - "name" - ] - } - ] - } + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "required": [ - "id", - "createdAt", - "name", - "members" - ] - } - ] + "name": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "memberIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "name", + "createdAt", + "memberIds" + ] + } }, - "players": { + "matches": { "type": "array", - "items": [ - { - "type": [ - "object", - "null" - ], - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "playerIds": { + "type": "array", + "items": { "type": "string" } }, - "required": [ - "id", - "createdAt", - "name" - ] - } - ] + "winnerId": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "createdAt", + "groupId", + "playerIds", + "winnerId" + ] + } } - } -} - + }, + "required": [ + "players", + "groups", + "matches" + ] +} \ No newline at end of file From 4c084cae4aa12f795cf4cd7e787e7df7585f2232 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Sat, 3 Jan 2026 13:20:36 +0100 Subject: [PATCH 130/222] remove ?? as members cant be null --- lib/services/data_transfer_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 2fc5c42..e60240c 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -38,7 +38,7 @@ class DataTransferService { 'id': g.id, 'name': g.name, 'createdAt': g.createdAt.toIso8601String(), - 'memberIds': (g.members ?? []).map((m) => m.id).toList(), + 'memberIds': (g.members).map((m) => m.id).toList(), }).toList(), 'matches': matches From ec94e12ed7793e230b543c3e2f38ee099a0fe68e Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 3 Jan 2026 15:38:25 +0100 Subject: [PATCH 131/222] implement changes --- lib/core/enums.dart | 9 ++- lib/l10n/arb/app_de.arb | 48 ++++++----- lib/l10n/arb/app_en.arb | 32 ++++---- lib/l10n/generated/app_localizations.dart | 28 +++---- lib/l10n/generated/app_localizations_de.dart | 63 +++++++-------- lib/l10n/generated/app_localizations_en.dart | 19 ++--- lib/main.dart | 3 +- .../main_menu/custom_navigation_bar.dart | 27 +++---- .../group_view/create_group_view.dart | 11 ++- .../main_menu/group_view/groups_view.dart | 8 +- .../views/main_menu/home_view.dart | 32 ++++---- .../create_match/choose_game_view.dart | 12 +-- .../create_match/choose_group_view.dart | 15 ++-- .../create_match/choose_ruleset_view.dart | 9 ++- .../create_match/create_match_view.dart | 38 ++++----- .../match_view/match_result_view.dart | 3 +- .../main_menu/match_view/match_view.dart | 8 +- .../views/main_menu/settings_view.dart | 81 +++++++------------ .../views/main_menu/statistics_view.dart | 41 +++++----- .../widgets/player_selection.dart | 27 +++---- .../widgets/tiles/match_tile.dart | 12 +-- .../widgets/tiles/statistics_tile.dart | 3 +- 22 files changed, 247 insertions(+), 282 deletions(-) diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 74ae023..ce06f85 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -35,14 +35,15 @@ enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints } /// Translates a [Ruleset] enum value to its corresponding localized string. String translateRulesetToString(Ruleset ruleset, BuildContext context) { + final loc = AppLocalizations.of(context); switch (ruleset) { case Ruleset.singleWinner: - return AppLocalizations.of(context).single_winner; + return loc.single_winner; case Ruleset.singleLoser: - return AppLocalizations.of(context).single_loser; + return loc.single_loser; case Ruleset.mostPoints: - return AppLocalizations.of(context).most_points; + return loc.most_points; case Ruleset.leastPoints: - return AppLocalizations.of(context).least_points; + return loc.least_points; } } diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 26ed145..d8a9e7e 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -3,30 +3,28 @@ "choose_group": "Gruppe wählen", "create_new_match": "Neues Match erstellen", "choose_ruleset": "Regelwerk wählen", - "choose_game": "Spiel wählen", - "select_winner": "Gewinner wählen:", + "choose_game": "Spielvorlage wählen", + "select_winner": "Gewinner:in wählen:", "no_recent_matches_available": "Keine letzten Matches verfügbar", "no_second_match_available": "Kein zweites Match verfügbar", "delete_all_data": "Alle Daten löschen?", "cancel": "Abbrechen", "delete": "Löschen", "create_new_group": "Neue Gruppe erstellen", - "error_while_creating_group_please_try_again": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", - "selected_players": "Ausgewählte Spieler: {count}", - "no_players_selected": "Keine Spieler ausgewählt", - "all_players": "Alle Spieler:", - "successfully_added_player": "Spieler {playerName} erfolgreich hinzugefügt", - "could_not_add_player": "Spieler {playerName} konnte nicht hinzugefügt werden", - "winner": "Gewinner: {winnerName}", - "players": "Spieler", - "player_name": "Spielername", + "error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", + "selected_players": "Ausgewählte Spieler:in: {count}", + "no_players_selected": "Keine Spieler:in ausgewählt", + "all_players": "Alle Spieler:innen:", + "successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt", + "could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden", + "winner": "Gewinner:in: {winnerName}", + "players": "Spieler:in", + "player_name": "Spieler:innenname", "no_statistics_available": "Keine Statistiken verfügbar", "matches": "Matches", "groups": "Gruppen", "recent_matches": "Letzte Matches", "quick_create": "Schnellzugriff", - "winner_label": "Gewinner", - "ruleset_label": "Regelwerk", "match_in_progress": "Match läuft...", "menu": "Menü", "settings": "Einstellungen", @@ -52,15 +50,15 @@ "group_name": "Gruppenname", "no_matches_created_yet": "Noch keine Matches erstellt", "match_name": "Matchname", - "game": "Spiel", + "game": "Spielvorlage", "ruleset": "Regelwerk", "group": "Gruppe", "none": "Kein", "none_group": "Keine", "create_match": "Match erstellen", - "no_players_created_yet": "Noch keine Spieler erstellt", - "all_players_selected": "Alle Spieler ausgewählt", - "no_players_found_with_that_name": "Keine Spieler mit diesem Namen gefunden", + "no_players_created_yet": "Noch keine Spieler:in erstellt", + "all_players_selected": "Alle Spieler:innen ausgewählt", + "no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden", "today_at": "Heute um {time}", "yesterday_at": "Gestern um {time}", "days_ago": "vor {count} Tagen", @@ -69,16 +67,16 @@ "stats": "Statistiken", "players_count": "{count} Spieler", "there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht", - "game_name": "Spielname", - "ruleset_single_winner_desc": "Genau ein Gewinner wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", - "ruleset_single_loser_desc": "Genau ein Verlierer wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.", - "ruleset_most_points_desc": "Traditionelles Regelwerk: Der Spieler mit den meisten Punkten gewinnt.", - "ruleset_least_points_desc": "Umgekehrte Wertung: Der Spieler mit den wenigsten Punkten gewinnt.", - "single_winner": "Ein Gewinner", - "single_loser": "Ein Verlierer", + "game_name": "Spielvorlagenname", + "ruleset_single_winner": "Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", + "ruleset_single_loser": "Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.", + "ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.", + "ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.", + "single_winner": "Ein:e Gewinner:in", + "single_loser": "Ein:e Verlierer:in", "most_points": "Höchste Punkte", "least_points": "Niedrigste Punkte", - "search_for_players": "Nach Spielern suchen", + "search_for_players": "Nach Spieler:innen suchen", "search_for_groups": "Nach Gruppen suchen", "no_data_available": "Keine Daten verfügbar", "not_available": "Nicht verfügbar" diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 0a68994..8e6d63a 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -20,6 +20,10 @@ "@select_winner": { "description": "Label to select the winner" }, + "game_tracker": "Game Tracker", + "@game_tracker": { + "description": "App Name" + }, "no_recent_matches_available": "No recent matches available", "@no_recent_matches_available": { "description": "Message when no recent matches exist" @@ -44,8 +48,8 @@ "@create_new_group": { "description": "Button text to create a new group" }, - "error_while_creating_group_please_try_again": "Error while creating group, please try again", - "@error_while_creating_group_please_try_again": { + "error_creating_group": "Error while creating group, please try again", + "@error_creating_group": { "description": "Error message when group creation fails" }, "selected_players": "Selected players: {count}", @@ -124,14 +128,6 @@ "@quick_create": { "description": "Title for quick create section" }, - "winner_label": "Winner", - "@winner_label": { - "description": "Label for winner field" - }, - "ruleset_label": "Ruleset", - "@ruleset_label": { - "description": "Label for ruleset field" - }, "match_in_progress": "Match in progress...", "@match_in_progress": { "description": "Message when match is in progress" @@ -330,20 +326,20 @@ "@game_name": { "description": "Placeholder for game name search" }, - "ruleset_single_winner_desc": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", - "@ruleset_single_winner_desc": { + "ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", + "@ruleset_single_winner": { "description": "Description for single winner ruleset" }, - "ruleset_single_loser_desc": "Exactly one loser is determined; last place receives the penalty or consequence.", - "@ruleset_single_loser_desc": { + "ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.", + "@ruleset_single_loser": { "description": "Description for single loser ruleset" }, - "ruleset_most_points_desc": "Traditional ruleset: the player with the most points wins.", - "@ruleset_most_points_desc": { + "ruleset_most_points": "Traditional ruleset: the player with the most points wins.", + "@ruleset_most_points": { "description": "Description for most points ruleset" }, - "ruleset_least_points_desc": "Inverse scoring: the player with the fewest points wins.", - "@ruleset_least_points_desc": { + "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", + "@ruleset_least_points": { "description": "Description for least points ruleset" }, "single_winner": "Single Winner", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index e3acecb..951ff22 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -128,6 +128,12 @@ abstract class AppLocalizations { /// **'Select Winner:'** String get select_winner; + /// App Name + /// + /// In en, this message translates to: + /// **'Game Tracker'** + String get game_tracker; + /// Message when no recent matches exist /// /// In en, this message translates to: @@ -168,7 +174,7 @@ abstract class AppLocalizations { /// /// In en, this message translates to: /// **'Error while creating group, please try again'** - String get error_while_creating_group_please_try_again; + String get error_creating_group; /// Shows the number of selected players /// @@ -248,18 +254,6 @@ abstract class AppLocalizations { /// **'Quick Create'** String get quick_create; - /// Label for winner field - /// - /// In en, this message translates to: - /// **'Winner'** - String get winner_label; - - /// Label for ruleset field - /// - /// In en, this message translates to: - /// **'Ruleset'** - String get ruleset_label; - /// Message when match is in progress /// /// In en, this message translates to: @@ -528,25 +522,25 @@ abstract class AppLocalizations { /// /// In en, this message translates to: /// **'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'** - String get ruleset_single_winner_desc; + String get ruleset_single_winner; /// Description for single loser ruleset /// /// In en, this message translates to: /// **'Exactly one loser is determined; last place receives the penalty or consequence.'** - String get ruleset_single_loser_desc; + String get ruleset_single_loser; /// Description for most points ruleset /// /// In en, this message translates to: /// **'Traditional ruleset: the player with the most points wins.'** - String get ruleset_most_points_desc; + String get ruleset_most_points; /// Description for least points ruleset /// /// In en, this message translates to: /// **'Inverse scoring: the player with the fewest points wins.'** - String get ruleset_least_points_desc; + String get ruleset_least_points; /// Title for single winner ruleset /// diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index a270c82..3f3e36e 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -18,10 +18,13 @@ class AppLocalizationsDe extends AppLocalizations { String get choose_ruleset => 'Regelwerk wählen'; @override - String get choose_game => 'Spiel wählen'; + String get choose_game => 'Spielvorlage wählen'; @override - String get select_winner => 'Gewinner wählen:'; + String get select_winner => 'Gewinner:in wählen:'; + + @override + String get game_tracker => 'Game Tracker'; @override String get no_recent_matches_available => 'Keine letzten Matches verfügbar'; @@ -42,7 +45,7 @@ class AppLocalizationsDe extends AppLocalizations { String get create_new_group => 'Neue Gruppe erstellen'; @override - String get error_while_creating_group_please_try_again => + String get error_creating_group => 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; @override @@ -52,32 +55,32 @@ class AppLocalizationsDe extends AppLocalizations { ); final String countString = countNumberFormat.format(count); - return 'Ausgewählte Spieler: $countString'; + return 'Ausgewählte Spieler:in: $countString'; } @override - String get no_players_selected => 'Keine Spieler ausgewählt'; + String get no_players_selected => 'Keine Spieler:in ausgewählt'; @override - String get all_players => 'Alle Spieler:'; + String get all_players => 'Alle Spieler:innen:'; @override String successfully_added_player(String playerName) { - return 'Spieler $playerName erfolgreich hinzugefügt'; + return 'Spieler:in $playerName erfolgreich hinzugefügt'; } @override String could_not_add_player(String playerName) { - return 'Spieler $playerName konnte nicht hinzugefügt werden'; + return 'Spieler:in $playerName konnte nicht hinzugefügt werden'; } @override String winner(String winnerName) { - return 'Gewinner: $winnerName'; + return 'Gewinner:in: $winnerName'; } @override - String get players => 'Spieler'; + String get players => 'Spieler:in'; @override String get no_statistics_available => 'Keine Statistiken verfügbar'; @@ -97,12 +100,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get quick_create => 'Schnellzugriff'; - @override - String get winner_label => 'Gewinner'; - - @override - String get ruleset_label => 'Regelwerk'; - @override String get match_in_progress => 'Match läuft...'; @@ -168,7 +165,7 @@ class AppLocalizationsDe extends AppLocalizations { String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; @override - String get no_players_created_yet => 'Noch keine Spieler erstellt'; + String get no_players_created_yet => 'Noch keine Spieler:in erstellt'; @override String get create_group => 'Gruppe erstellen'; @@ -177,7 +174,7 @@ class AppLocalizationsDe extends AppLocalizations { String get group_name => 'Gruppenname'; @override - String get player_name => 'Spielername'; + String get player_name => 'Spieler:innenname'; @override String get no_matches_created_yet => 'Noch keine Matches erstellt'; @@ -186,7 +183,7 @@ class AppLocalizationsDe extends AppLocalizations { String get match_name => 'Matchname'; @override - String get game => 'Spiel'; + String get game => 'Spielvorlage'; @override String get ruleset => 'Regelwerk'; @@ -205,10 +202,10 @@ class AppLocalizationsDe extends AppLocalizations { @override String get no_players_found_with_that_name => - 'Keine Spieler mit diesem Namen gefunden'; + 'Keine Spieler:in mit diesem Namen gefunden'; @override - String get all_players_selected => 'Alle Spieler ausgewählt'; + String get all_players_selected => 'Alle Spieler:innen ausgewählt'; @override String today_at(String time) { @@ -244,29 +241,29 @@ class AppLocalizationsDe extends AppLocalizations { 'Es gibt keine Gruppe, die deiner Suche entspricht'; @override - String get game_name => 'Spielname'; + String get game_name => 'Spielvorlagenname'; @override - String get ruleset_single_winner_desc => - 'Genau ein Gewinner wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; + String get ruleset_single_winner => + 'Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; @override - String get ruleset_single_loser_desc => - 'Genau ein Verlierer wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; + String get ruleset_single_loser => + 'Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; @override - String get ruleset_most_points_desc => - 'Traditionelles Regelwerk: Der Spieler mit den meisten Punkten gewinnt.'; + String get ruleset_most_points => + 'Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.'; @override - String get ruleset_least_points_desc => - 'Umgekehrte Wertung: Der Spieler mit den wenigsten Punkten gewinnt.'; + String get ruleset_least_points => + 'Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.'; @override - String get single_winner => 'Ein Gewinner'; + String get single_winner => 'Ein:e Gewinner:in'; @override - String get single_loser => 'Ein Verlierer'; + String get single_loser => 'Ein:e Verlierer:in'; @override String get most_points => 'Höchste Punkte'; @@ -275,7 +272,7 @@ class AppLocalizationsDe extends AppLocalizations { String get least_points => 'Niedrigste Punkte'; @override - String get search_for_players => 'Nach Spielern suchen'; + String get search_for_players => 'Nach Spieler:innen suchen'; @override String get search_for_groups => 'Nach Gruppen suchen'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 63a7d0e..263714c 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -23,6 +23,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get select_winner => 'Select Winner:'; + @override + String get game_tracker => 'Game Tracker'; + @override String get no_recent_matches_available => 'No recent matches available'; @@ -42,7 +45,7 @@ class AppLocalizationsEn extends AppLocalizations { String get create_new_group => 'Create new group'; @override - String get error_while_creating_group_please_try_again => + String get error_creating_group => 'Error while creating group, please try again'; @override @@ -97,12 +100,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get quick_create => 'Quick Create'; - @override - String get winner_label => 'Winner'; - - @override - String get ruleset_label => 'Ruleset'; - @override String get match_in_progress => 'Match in progress...'; @@ -246,19 +243,19 @@ class AppLocalizationsEn extends AppLocalizations { String get game_name => 'Game Name'; @override - String get ruleset_single_winner_desc => + String get ruleset_single_winner => 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'; @override - String get ruleset_single_loser_desc => + String get ruleset_single_loser => 'Exactly one loser is determined; last place receives the penalty or consequence.'; @override - String get ruleset_most_points_desc => + String get ruleset_most_points => 'Traditional ruleset: the player with the most points wins.'; @override - String get ruleset_least_points_desc => + String get ruleset_least_points => 'Inverse scoring: the player with the fewest points wins.'; @override diff --git a/lib/main.dart b/lib/main.dart index 8ef243a..c1ed977 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,7 +20,6 @@ class GameTracker extends StatelessWidget { @override Widget build(BuildContext context) { - print(AppLocalizations.supportedLocales.first); return MaterialApp( localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, @@ -35,7 +34,7 @@ class GameTracker extends StatelessWidget { ); }, debugShowCheckedModeBanner: false, - title: 'Game Tracker', + onGenerateTitle: (context) => AppLocalizations.of(context).game_tracker, darkTheme: ThemeData.dark(), themeMode: ThemeMode.dark, // forces dark mode diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 2faef8b..1e38808 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -20,13 +20,9 @@ class _CustomNavigationBarState extends State int currentIndex = 0; int tabKeyCount = 0; - @override - void initState() { - super.initState(); - } - @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); // Pretty ugly but works final List tabs = [ KeyedSubtree(key: ValueKey('home_$tabKeyCount'), child: const HomeView()), @@ -47,7 +43,7 @@ class _CustomNavigationBarState extends State appBar: AppBar( centerTitle: true, title: Text( - _currentTabTitle(), + _currentTabTitle(context), style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), backgroundColor: CustomTheme.backgroundColor, @@ -90,28 +86,28 @@ class _CustomNavigationBarState extends State index: 0, isSelected: currentIndex == 0, icon: Icons.home_rounded, - label: AppLocalizations.of(context).home, + label: loc.home, onTabTapped: onTabTapped, ), NavbarItem( index: 1, isSelected: currentIndex == 1, icon: Icons.gamepad_rounded, - label: AppLocalizations.of(context).matches, + label: loc.matches, onTabTapped: onTabTapped, ), NavbarItem( index: 2, isSelected: currentIndex == 2, icon: Icons.group_rounded, - label: AppLocalizations.of(context).groups, + label: loc.groups, onTabTapped: onTabTapped, ), NavbarItem( index: 3, isSelected: currentIndex == 3, icon: Icons.bar_chart_rounded, - label: AppLocalizations.of(context).statistics, + label: loc.statistics, onTabTapped: onTabTapped, ), ], @@ -129,16 +125,17 @@ class _CustomNavigationBarState extends State }); } - String _currentTabTitle() { + String _currentTabTitle(context) { + final loc = AppLocalizations.of(context); switch (currentIndex) { case 0: - return AppLocalizations.of(context).home; + return loc.home; case 1: - return AppLocalizations.of(context).matches; + return loc.matches; case 2: - return AppLocalizations.of(context).groups; + return loc.groups; case 3: - return AppLocalizations.of(context).statistics; + return loc.statistics; default: return ''; } diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index 0d8c28f..cba22ef 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -20,11 +20,13 @@ class CreateGroupView extends StatefulWidget { class _CreateGroupViewState extends State { final _groupNameController = TextEditingController(); late final AppDatabase db; + List selectedPlayers = []; @override void initState() { super.initState(); + db = Provider.of(context, listen: false); _groupNameController.addListener(() { setState(() {}); @@ -39,13 +41,14 @@ class _CreateGroupViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: Text( - AppLocalizations.of(context).create_new_group, + loc.create_new_group, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -58,7 +61,7 @@ class _CreateGroupViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: TextInputField( controller: _groupNameController, - hintText: AppLocalizations.of(context).group_name, + hintText: loc.group_name, onChanged: (value) { setState(() {}); }, @@ -74,7 +77,7 @@ class _CreateGroupViewState extends State { ), ), CustomWidthButton( - text: AppLocalizations.of(context).create_group, + text: loc.create_group, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: @@ -99,7 +102,7 @@ class _CreateGroupViewState extends State { child: Text( AppLocalizations.of( context, - ).error_while_creating_group_please_try_again, + ).error_creating_group, style: const TextStyle(color: Colors.white), ), ), diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 4d42417..3505a3c 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -35,12 +35,14 @@ class _GroupsViewState extends State { @override void initState() { super.initState(); + db = Provider.of(context, listen: false); loadGroups(); } @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, body: Stack( @@ -53,8 +55,8 @@ class _GroupsViewState extends State { replacement: Center( child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context).info, - message: AppLocalizations.of(context).no_groups_created_yet, + title: loc.info, + message: loc.no_groups_created_yet, ), ), child: ListView.builder( @@ -74,7 +76,7 @@ class _GroupsViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: AppLocalizations.of(context).create_group, + text: loc.create_group, sizeRelativeToWidth: 0.90, onPressed: () async { await Navigator.push( diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 6446ea8..96280ce 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -72,6 +72,7 @@ class _HomeViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return AppSkeleton( @@ -87,7 +88,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: AppLocalizations.of(context).matches, + title: loc.matches, icon: Icons.groups_rounded, value: matchCount, ), @@ -95,7 +96,7 @@ class _HomeViewState extends State { QuickInfoTile( width: constraints.maxWidth * 0.45, height: constraints.maxHeight * 0.15, - title: AppLocalizations.of(context).groups, + title: loc.groups, icon: Icons.groups_rounded, value: groupCount, ), @@ -105,7 +106,7 @@ class _HomeViewState extends State { padding: const EdgeInsets.symmetric(vertical: 16.0), child: InfoTile( width: constraints.maxWidth * 0.95, - title: AppLocalizations.of(context).recent_matches, + title: loc.recent_matches, icon: Icons.timer, content: Padding( padding: const EdgeInsets.symmetric(horizontal: 40.0), @@ -125,11 +126,12 @@ class _HomeViewState extends State { children: [ MatchSummaryTile( matchTitle: recentMatches[0].name, - game: AppLocalizations.of(context).winner_label, - ruleset: AppLocalizations.of( + game: 'Winner', + ruleset: 'Ruleset', + players: _getPlayerText( + recentMatches[0], context, - ).ruleset_label, - players: _getPlayerText(recentMatches[0]), + ), winner: recentMatches[0].winner == null ? AppLocalizations.of( context, @@ -143,11 +145,12 @@ class _HomeViewState extends State { if (loadedRecentMatches.length > 1) ...[ MatchSummaryTile( matchTitle: recentMatches[1].name, - game: AppLocalizations.of(context).winner_label, - ruleset: AppLocalizations.of( + game: 'Winner', + ruleset: 'Ruleset', + players: _getPlayerText( + recentMatches[1], context, - ).ruleset_label, - players: _getPlayerText(recentMatches[1]), + ), winner: recentMatches[1].winner == null ? AppLocalizations.of( context, @@ -173,7 +176,7 @@ class _HomeViewState extends State { ), InfoTile( width: constraints.maxWidth * 0.95, - title: AppLocalizations.of(context).quick_create, + title: loc.quick_create, icon: Icons.add_box_rounded, content: Column( children: [ @@ -227,10 +230,11 @@ class _HomeViewState extends State { ); } - String _getPlayerText(Match game) { + String _getPlayerText(Match game, context) { + final loc = AppLocalizations.of(context); if (game.group == null) { final playerCount = game.players?.length ?? 0; - return AppLocalizations.of(context).players_count(playerCount); + return loc.players_count(playerCount); } if (game.players == null || game.players!.isEmpty) { return game.group!.name; diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 5ed9d95..18e1e9d 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -21,6 +21,7 @@ class ChooseGameView extends StatefulWidget { class _ChooseGameViewState extends State { late int selectedGameIndex; + final TextEditingController searchBarController = TextEditingController(); @override @@ -31,6 +32,7 @@ class _ChooseGameViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( @@ -43,7 +45,7 @@ class _ChooseGameViewState extends State { }, ), title: Text( - AppLocalizations.of(context).choose_game, + loc.choose_game, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -64,7 +66,7 @@ class _ChooseGameViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: searchBarController, - hintText: AppLocalizations.of(context).game_name, + hintText: loc.game_name, ), ), const SizedBox(height: 5), @@ -76,9 +78,9 @@ class _ChooseGameViewState extends State { title: widget.games[index].$1, description: widget.games[index].$2, badgeText: translateRulesetToString( - widget.games[index].$3, - context, - ), + widget.games[index].$3, + context, + ), isHighlighted: selectedGameIndex == index, onPressed: () async { setState(() { diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 8a3f0f2..5101db6 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -34,6 +34,7 @@ class _ChooseGroupViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( @@ -52,7 +53,7 @@ class _ChooseGroupViewState extends State { }, ), title: Text( - AppLocalizations.of(context).choose_group, + loc.choose_group, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -79,7 +80,7 @@ class _ChooseGroupViewState extends State { padding: const EdgeInsets.symmetric(horizontal: 10), child: CustomSearchBar( controller: controller, - hintText: AppLocalizations.of(context).search_for_groups, + hintText: loc.search_for_groups, onChanged: (value) { setState(() { filterGroups(value); @@ -94,15 +95,15 @@ class _ChooseGroupViewState extends State { visible: widget.groups.isNotEmpty, replacement: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context).info, - message: AppLocalizations.of(context).no_groups_created_yet, + title: loc.info, + message: loc.no_groups_created_yet, ), child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context).info, + title: loc.info, message: AppLocalizations.of( - context, - ).there_is_no_group_matching_your_search, + context, + ).there_is_no_group_matching_your_search, ), ), child: ListView.builder( diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index f47b535..7a41417 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -29,6 +29,7 @@ class _ChooseRulesetViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return DefaultTabController( length: 2, initialIndex: 0, @@ -48,7 +49,7 @@ class _ChooseRulesetViewState extends State { }, ), title: Text( - AppLocalizations.of(context).choose_ruleset, + loc.choose_ruleset, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -82,9 +83,9 @@ class _ChooseRulesetViewState extends State { }); }, title: translateRulesetToString( - widget.rulesets[index].$1, - context, - ), + widget.rulesets[index].$1, + context, + ), description: widget.rulesets[index].$2, isHighlighted: selectedRulesetIndex == index, ); diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 77fa4e4..d3a23ae 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -90,23 +90,12 @@ class _CreateMatchViewState extends State { } List<(Ruleset, String)> _getRulesets(BuildContext context) { + final loc = AppLocalizations.of(context); return [ - ( - Ruleset.singleWinner, - AppLocalizations.of(context).ruleset_single_winner_desc, - ), - ( - Ruleset.singleLoser, - AppLocalizations.of(context).ruleset_single_loser_desc, - ), - ( - Ruleset.mostPoints, - AppLocalizations.of(context).ruleset_most_points_desc, - ), - ( - Ruleset.leastPoints, - AppLocalizations.of(context).ruleset_least_points_desc, - ), + (Ruleset.singleWinner, loc.ruleset_single_winner), + (Ruleset.singleLoser, loc.ruleset_single_loser), + (Ruleset.mostPoints, loc.ruleset_most_points), + (Ruleset.leastPoints, loc.ruleset_least_points), ]; } @@ -118,13 +107,14 @@ class _CreateMatchViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, title: Text( - AppLocalizations.of(context).create_new_match, + loc.create_new_match, style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), ), centerTitle: true, @@ -141,9 +131,9 @@ class _CreateMatchViewState extends State { ), ), ChooseTile( - title: AppLocalizations.of(context).game, + title: loc.game, trailingText: selectedGameIndex == -1 - ? AppLocalizations.of(context).none + ? loc.none : games[selectedGameIndex].$1, onPressed: () async { selectedGameIndex = await Navigator.of(context).push( @@ -169,9 +159,9 @@ class _CreateMatchViewState extends State { }, ), ChooseTile( - title: AppLocalizations.of(context).ruleset, + title: loc.ruleset, trailingText: selectedRuleset == null - ? AppLocalizations.of(context).none + ? loc.none : translateRulesetToString(selectedRuleset!, context), onPressed: () async { final rulesets = _getRulesets(context); @@ -192,9 +182,9 @@ class _CreateMatchViewState extends State { }, ), ChooseTile( - title: AppLocalizations.of(context).group, + title: loc.group, trailingText: selectedGroup == null - ? AppLocalizations.of(context).none_group + ? loc.none_group : selectedGroup!.name, onPressed: () async { selectedGroup = await Navigator.of(context).push( @@ -231,7 +221,7 @@ class _CreateMatchViewState extends State { ), ), CustomWidthButton( - text: AppLocalizations.of(context).create_match, + text: loc.create_match, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: _enableCreateGameButton() diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index e99ce85..8e178bf 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -36,6 +36,7 @@ class _MatchResultViewState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( @@ -81,7 +82,7 @@ class _MatchResultViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - AppLocalizations.of(context).select_winner, + loc.select_winner, style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 2b1b110..73f596f 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -44,12 +44,14 @@ class _MatchViewState extends State { @override void initState() { super.initState(); + db = Provider.of(context, listen: false); loadGames(); } @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, body: Stack( @@ -62,8 +64,8 @@ class _MatchViewState extends State { replacement: Center( child: TopCenteredMessage( icon: Icons.report, - title: AppLocalizations.of(context).info, - message: AppLocalizations.of(context).no_matches_created_yet, + title: loc.info, + message: loc.no_matches_created_yet, ), ), child: ListView.builder( @@ -97,7 +99,7 @@ class _MatchViewState extends State { Positioned( bottom: MediaQuery.paddingOf(context).bottom, child: CustomWidthButton( - text: AppLocalizations.of(context).create_match, + text: loc.create_match, sizeRelativeToWidth: 0.90, onPressed: () async { Navigator.push( diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index c554950..8f1e68a 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -13,8 +13,14 @@ class SettingsView extends StatefulWidget { } class _SettingsViewState extends State { + @override + void initState() { + super.initState(); + } + @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Scaffold( appBar: AppBar(backgroundColor: CustomTheme.backgroundColor), backgroundColor: CustomTheme.backgroundColor, @@ -29,7 +35,7 @@ class _SettingsViewState extends State { padding: const EdgeInsets.fromLTRB(24, 0, 24, 10), child: Text( textAlign: TextAlign.start, - AppLocalizations.of(context).menu, + loc.menu, style: const TextStyle( fontSize: 28, fontWeight: FontWeight.bold, @@ -43,7 +49,7 @@ class _SettingsViewState extends State { ), child: Text( textAlign: TextAlign.start, - AppLocalizations.of(context).settings, + loc.settings, style: const TextStyle( fontSize: 22, fontWeight: FontWeight.bold, @@ -51,7 +57,7 @@ class _SettingsViewState extends State { ), ), SettingsListTile( - title: AppLocalizations.of(context).export_data, + title: loc.export_data, icon: Icons.upload_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -66,7 +72,7 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: AppLocalizations.of(context).import_data, + title: loc.import_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { @@ -78,27 +84,23 @@ class _SettingsViewState extends State { }, ), SettingsListTile( - title: AppLocalizations.of(context).delete_all_data, + title: loc.delete_all_data, icon: Icons.download_outlined, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () { showDialog( context: context, builder: (context) => AlertDialog( - title: Text( - AppLocalizations.of(context).delete_all_data, - ), - content: Text( - AppLocalizations.of(context).this_cannot_be_undone, - ), + title: Text(loc.delete_all_data), + content: Text(loc.this_cannot_be_undone), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(false), - child: Text(AppLocalizations.of(context).cancel), + child: Text(loc.cancel), ), TextButton( onPressed: () => Navigator.of(context).pop(true), - child: Text(AppLocalizations.of(context).delete), + child: Text(loc.delete), ), ], ), @@ -130,37 +132,20 @@ class _SettingsViewState extends State { required BuildContext context, required ImportResult result, }) { + final loc = AppLocalizations.of(context); switch (result) { case ImportResult.success: - showSnackbar( - context: context, - message: AppLocalizations.of(context).data_successfully_imported, - ); + showSnackbar(context: context, message: loc.data_successfully_imported); case ImportResult.invalidSchema: - showSnackbar( - context: context, - message: AppLocalizations.of(context).invalid_schema, - ); + showSnackbar(context: context, message: loc.invalid_schema); case ImportResult.fileReadError: - showSnackbar( - context: context, - message: AppLocalizations.of(context).error_reading_file, - ); + showSnackbar(context: context, message: loc.error_reading_file); case ImportResult.canceled: - showSnackbar( - context: context, - message: AppLocalizations.of(context).import_canceled, - ); + showSnackbar(context: context, message: loc.import_canceled); case ImportResult.formatException: - showSnackbar( - context: context, - message: AppLocalizations.of(context).format_exception, - ); + showSnackbar(context: context, message: loc.format_exception); case ImportResult.unknownException: - showSnackbar( - context: context, - message: AppLocalizations.of(context).unknown_exception, - ); + showSnackbar(context: context, message: loc.unknown_exception); } } @@ -172,22 +157,14 @@ class _SettingsViewState extends State { required BuildContext context, required ExportResult result, }) { + final loc = AppLocalizations.of(context); switch (result) { case ExportResult.success: - showSnackbar( - context: context, - message: AppLocalizations.of(context).data_successfully_exported, - ); + showSnackbar(context: context, message: loc.data_successfully_exported); case ExportResult.canceled: - showSnackbar( - context: context, - message: AppLocalizations.of(context).export_canceled, - ); + showSnackbar(context: context, message: loc.export_canceled); case ExportResult.unknownException: - showSnackbar( - context: context, - message: AppLocalizations.of(context).unknown_exception, - ); + showSnackbar(context: context, message: loc.unknown_exception); } } @@ -203,6 +180,7 @@ class _SettingsViewState extends State { Duration duration = const Duration(seconds: 3), VoidCallback? action, }) { + final loc = AppLocalizations.of(context); final messenger = ScaffoldMessenger.of(context); messenger.hideCurrentSnackBar(); messenger.showSnackBar( @@ -211,10 +189,7 @@ class _SettingsViewState extends State { backgroundColor: CustomTheme.onBoxColor, duration: duration, action: action != null - ? SnackBarAction( - label: AppLocalizations.of(context).undo, - onPressed: action, - ) + ? SnackBarAction(label: loc.undo, onPressed: action) : null, ), ); diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 43de037..6c30483 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -25,6 +25,7 @@ class _StatisticsViewState extends State { @override void initState() { super.initState(); + final db = Provider.of(context, listen: false); Future.wait([ @@ -32,21 +33,25 @@ class _StatisticsViewState extends State { db.playerDao.getAllPlayers(), Future.delayed(minimumSkeletonDuration), ]).then((results) async { + if (!mounted) return; final matches = results[0] as List; final players = results[1] as List; - winCounts = _calculateWinsForAllPlayers(matches, players); - matchCounts = _calculateMatchAmountsForAllPlayers(matches, players); + winCounts = _calculateWinsForAllPlayers(matches, players, context); + matchCounts = _calculateMatchAmountsForAllPlayers( + matches, + players, + context, + ); winRates = computeWinRatePercent(wins: winCounts, matches: matchCounts); - if (mounted) { - setState(() { - isLoading = false; - }); - } + setState(() { + isLoading = false; + }); }); } @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { return SingleChildScrollView( @@ -69,7 +74,7 @@ class _StatisticsViewState extends State { children: [ StatisticsTile( icon: Icons.sports_score, - title: AppLocalizations.of(context).wins, + title: loc.wins, width: constraints.maxWidth * 0.95, values: winCounts, itemCount: 3, @@ -78,7 +83,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.percent, - title: AppLocalizations.of(context).winrate, + title: loc.winrate, width: constraints.maxWidth * 0.95, values: winRates, itemCount: 5, @@ -87,7 +92,7 @@ class _StatisticsViewState extends State { SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( icon: Icons.casino, - title: AppLocalizations.of(context).amount_of_matches, + title: loc.amount_of_matches, width: constraints.maxWidth * 0.95, values: matchCounts, itemCount: 10, @@ -97,7 +102,7 @@ class _StatisticsViewState extends State { ), child: TopCenteredMessage( icon: Icons.info, - title: AppLocalizations.of(context).info, + title: loc.info, message: AppLocalizations.of( context, ).no_statistics_available, @@ -118,8 +123,10 @@ class _StatisticsViewState extends State { List<(String, int)> _calculateWinsForAllPlayers( List matches, List players, + BuildContext context, ) { List<(String, int)> winCounts = []; + final loc = AppLocalizations.of(context); // Getting the winners for (var match in matches) { @@ -150,10 +157,7 @@ class _StatisticsViewState extends State { final playerId = winCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, - orElse: () => Player( - id: playerId, - name: AppLocalizations.of(context).not_available, - ), + orElse: () => Player(id: playerId, name: loc.not_available), ); winCounts[i] = (player.name, winCounts[i].$2); } @@ -168,8 +172,10 @@ class _StatisticsViewState extends State { List<(String, int)> _calculateMatchAmountsForAllPlayers( List matches, List players, + BuildContext context, ) { List<(String, int)> matchCounts = []; + final loc = AppLocalizations.of(context); // Counting matches for each player for (var match in matches) { @@ -215,10 +221,7 @@ class _StatisticsViewState extends State { final playerId = matchCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, - orElse: () => Player( - id: playerId, - name: AppLocalizations.of(context).not_available, - ), + orElse: () => Player(id: playerId, name: loc.not_available), ); matchCounts[i] = (player.name, matchCounts[i].$2); } diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index e1761b1..eac4480 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -87,6 +87,7 @@ class _PlayerSelectionState extends State { @override Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); return Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), @@ -97,7 +98,7 @@ class _PlayerSelectionState extends State { CustomSearchBar( controller: _searchBarController, constraints: const BoxConstraints(maxHeight: 45, minHeight: 45), - hintText: AppLocalizations.of(context).search_for_players, + hintText: loc.search_for_players, trailingButtonShown: true, trailingButtonicon: Icons.add_circle, trailingButtonEnabled: _searchBarController.text.trim().isNotEmpty, @@ -139,11 +140,7 @@ class _PlayerSelectionState extends State { SizedBox( height: 50, child: selectedPlayers.isEmpty - ? Center( - child: Text( - AppLocalizations.of(context).no_players_selected, - ), - ) + ? Center(child: Text(loc.no_players_selected)) : SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( @@ -185,7 +182,7 @@ class _PlayerSelectionState extends State { ), const SizedBox(height: 10), Text( - AppLocalizations.of(context).all_players, + loc.all_players, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), @@ -196,8 +193,8 @@ class _PlayerSelectionState extends State { visible: suggestedPlayers.isNotEmpty, replacement: TopCenteredMessage( icon: Icons.info, - title: 'Info', - message: _getInfoText(), + title: loc.info, + message: _getInfoText(context), ), child: ListView.builder( itemCount: suggestedPlayers.length, @@ -234,6 +231,7 @@ class _PlayerSelectionState extends State { /// Shows a snackbar indicating success or failure. /// [context] - BuildContext to show the snackbar. void addNewPlayerFromSearch({required BuildContext context}) async { + final loc = AppLocalizations.of(context); String playerName = _searchBarController.text.trim(); Player createdPlayer = Player(name: playerName); bool success = await db.playerDao.addPlayer(player: createdPlayer); @@ -267,7 +265,7 @@ class _PlayerSelectionState extends State { backgroundColor: CustomTheme.boxColor, content: Center( child: Text( - AppLocalizations.of(context).could_not_add_player(playerName), + loc.could_not_add_player(playerName), style: const TextStyle(color: Colors.white), ), ), @@ -278,18 +276,19 @@ class _PlayerSelectionState extends State { /// Determines the appropriate info text to display when no players /// are available in the suggested players list. - String _getInfoText() { + String _getInfoText(BuildContext context) { + final loc = AppLocalizations.of(context); if (allPlayers.isEmpty) { // No players exist in the database - return AppLocalizations.of(context).no_players_created_yet; + return loc.no_players_created_yet; } else if (selectedPlayers.length == allPlayers.length || widget.availablePlayers?.isEmpty == true) { // All players have been selected or // available players list is provided but empty - return AppLocalizations.of(context).all_players_selected; + return loc.all_players_selected; } else { // No players match the search query - return AppLocalizations.of(context).no_players_found_with_that_name; + return loc.no_players_found_with_that_name; } } } diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 7727827..291a256 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -21,6 +21,7 @@ class _MatchTileState extends State { final group = widget.match.group; final winner = widget.match.winner; final allPlayers = _getAllPlayers(); + final loc = AppLocalizations.of(context); return GestureDetector( onTap: widget.onTap, @@ -49,7 +50,7 @@ class _MatchTileState extends State { ), ), Text( - _formatDate(widget.match.createdAt), + _formatDate(widget.match.createdAt, context), style: const TextStyle(fontSize: 12, color: Colors.grey), ), ], @@ -98,7 +99,7 @@ class _MatchTileState extends State { const SizedBox(width: 8), Expanded( child: Text( - AppLocalizations.of(context).winner(winner.name), + loc.winner(winner.name), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, @@ -115,7 +116,7 @@ class _MatchTileState extends State { if (allPlayers.isNotEmpty) ...[ Text( - AppLocalizations.of(context).players, + loc.players, style: const TextStyle( fontSize: 13, color: Colors.grey, @@ -137,9 +138,10 @@ class _MatchTileState extends State { ); } - String _formatDate(DateTime dateTime) { + String _formatDate(DateTime dateTime, BuildContext context) { final now = DateTime.now(); final difference = now.difference(dateTime); + final loc = AppLocalizations.of(context); if (difference.inDays == 0) { return AppLocalizations.of( @@ -150,7 +152,7 @@ class _MatchTileState extends State { context, ).yesterday_at(DateFormat('HH:mm').format(dateTime)); } else if (difference.inDays < 7) { - return AppLocalizations.of(context).days_ago(difference.inDays); + return loc.days_ago(difference.inDays); } else { return DateFormat('MMM d, yyyy').format(dateTime); } diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 8d81270..598fad0 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -25,6 +25,7 @@ class StatisticsTile extends StatelessWidget { @override Widget build(BuildContext context) { final maxBarWidth = MediaQuery.of(context).size.width * 0.65; + final loc = AppLocalizations.of(context); return InfoTile( width: width, @@ -36,7 +37,7 @@ class StatisticsTile extends StatelessWidget { visible: values.isNotEmpty, replacement: Center( heightFactor: 4, - child: Text(AppLocalizations.of(context).no_data_available), + child: Text(loc.no_data_available), ), child: Column( children: List.generate(min(values.length, itemCount), (index) { From 3c7c4598ff5a3eaff4c634c5c469d29c7e60d4e1 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 3 Jan 2026 17:42:25 +0100 Subject: [PATCH 132/222] sort players in winner selection --- .../main_menu/match_view/match_result_view.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index e8075f6..c2076cc 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -143,11 +143,17 @@ class _MatchResultViewState extends State { } List getAllPlayers(Match match) { + List players = []; + if (match.group == null && match.players != null) { - return [...match.players!]; + players = [...match.players!]; } else if (match.group != null && match.players != null) { - return [...match.players!, ...match.group!.members]; + players = [...match.players!, ...match.group!.members]; + } else { + players = [...match.group!.members]; } - return [...match.group!.members]; + + players.sort((a, b) => a.name.compareTo(b.name)); + return players; } } From 072dba1cde3f11c4a3854925cbcc59091be0aae6 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 3 Jan 2026 17:45:35 +0100 Subject: [PATCH 133/222] sort players in match tile --- lib/presentation/widgets/tiles/match_tile.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 543a542..e89be2f 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -175,6 +175,8 @@ class _MatchTileState extends State { } } + allPlayers.sort((a, b) => a.name.compareTo(b.name)); return allPlayers; + ; } } From bdcee85eb9e283698fbb1f751616b13c5d0d183b Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 3 Jan 2026 17:49:15 +0100 Subject: [PATCH 134/222] sort players in group tile --- lib/presentation/widgets/tiles/group_tile.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index 248c1c6..5f870de 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -56,7 +56,9 @@ class GroupTile extends StatelessWidget { spacing: 12.0, runSpacing: 8.0, children: [ - for (var member in group.members) + for (var member in [ + ...group.members, + ]..sort((a, b) => a.name.compareTo(b.name))) TextIconTile(text: member.name, iconEnabled: false), ], ), From 7fa434782c04d8d03010436adf4fa31b2530f9da Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 3 Jan 2026 17:49:37 +0100 Subject: [PATCH 135/222] remove semicolon and empty line --- lib/presentation/widgets/tiles/match_tile.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index e89be2f..bfe1b9f 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -177,6 +177,5 @@ class _MatchTileState extends State { allPlayers.sort((a, b) => a.name.compareTo(b.name)); return allPlayers; - ; } } From 7f6c1cb9a65c85d7bd087ef2df3fff9eb0152476 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Mon, 5 Jan 2026 11:40:57 +0100 Subject: [PATCH 136/222] change InsertMode to insertOrIgnore not insertOrReplace --- lib/data/dao/group_dao.dart | 8 ++++++-- lib/data/dao/match_dao.dart | 8 ++++++-- lib/data/dao/player_dao.dart | 4 +++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 9802203..98c602a 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -95,6 +95,8 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { } // Insert unique groups in batch + // Using insertOrIgnore to avoid triggering cascade deletes on + // player_group associations when groups already exist await db.batch( (b) => b.insertAll( groupTable, @@ -107,7 +109,7 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { ), ) .toList(), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ), ); @@ -120,6 +122,8 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { } if (uniquePlayers.isNotEmpty) { + // Using insertOrIgnore to avoid triggering cascade deletes on + // player_group associations when players already exist await db.batch( (b) => b.insertAll( db.playerTable, @@ -132,7 +136,7 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { ), ) .toList(), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ), ); } diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart index b56a38b..160686a 100644 --- a/lib/data/dao/match_dao.dart +++ b/lib/data/dao/match_dao.dart @@ -124,6 +124,8 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ); // Add all groups of the matches in batch + // Using insertOrIgnore to avoid overwriting existing groups (which would + // trigger cascade deletes on player_group associations) await db.batch( (b) => b.insertAll( db.groupTable, @@ -137,7 +139,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ), ) .toList(), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ), ); @@ -158,6 +160,8 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { } if (uniquePlayers.isNotEmpty) { + // Using insertOrIgnore to avoid triggering cascade deletes on + // player_group/player_match associations when players already exist await db.batch( (b) => b.insertAll( db.playerTable, @@ -170,7 +174,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ), ) .toList(), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ), ); } diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart index 8a58504..c8db800 100644 --- a/lib/data/dao/player_dao.dart +++ b/lib/data/dao/player_dao.dart @@ -50,6 +50,8 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { } /// Adds multiple [players] to the database in a batch operation. + /// Uses insertOrIgnore to avoid triggering cascade deletes on + /// player_group associations when players already exist. Future addPlayersAsList({required List players}) async { if (players.isEmpty) return false; @@ -65,7 +67,7 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { ), ) .toList(), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ), ); From 7b2314a25e9543fe202e918ae6181d0d4509aecb Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Mon, 5 Jan 2026 11:46:26 +0100 Subject: [PATCH 137/222] fix null error for winnerId and groupId in match import --- assets/schema.json | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index fa748d9..b3a8a2c 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -68,7 +68,10 @@ "type": "string" }, "groupId": { - "type": "string" + "anyOf": [ + {"type": "string"}, + {"type": "null"} + ] }, "playerIds": { "type": "array", @@ -77,7 +80,10 @@ } }, "winnerId": { - "type": "string" + "anyOf": [ + {"type": "string"}, + {"type": "null"} + ] } }, "required": [ @@ -85,8 +91,7 @@ "name", "createdAt", "groupId", - "playerIds", - "winnerId" + "playerIds" ] } } From 69effa2b7d287f47945dfc6f43b04cd9b23c0aa8 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Wed, 7 Jan 2026 11:49:10 +0100 Subject: [PATCH 138/222] change winner localization to not include placeholder --- lib/l10n/arb/app_en.arb | 10 ++-------- lib/presentation/widgets/tiles/match_tile.dart | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 8e6d63a..e3666b3 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -90,15 +90,9 @@ } } }, - "winner": "Winner: {winnerName}", + "winner": "Winner", "@winner": { - "description": "Shows the winner's name", - "placeholders": { - "winnerName": { - "type": "String", - "example": "John" - } - } + "description": "Winner label", }, "players": "Players", "@players": { diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 2b94cf2..b037af2 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -99,7 +99,7 @@ class _MatchTileState extends State { const SizedBox(width: 8), Expanded( child: Text( - loc.winner(winner.name), + "${loc.winner}: ${winner.name}", style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, From 99044c0ea0219deefe0f703d95f86ebf9a785ed5 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Wed, 7 Jan 2026 12:00:25 +0100 Subject: [PATCH 139/222] remove unnecessary check --- lib/services/data_transfer_service.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index e60240c..8767c59 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -104,8 +104,7 @@ class DataTransferService { final isValid = await _validateJsonSchema(jsonString); if (!isValid) return ImportResult.invalidSchema; - final dynamic decoded = json.decode(jsonString); - if (decoded is! Map) return ImportResult.invalidSchema; + final Map decoded = json.decode(jsonString) as Map; final List playersJson = (decoded['players'] as List?) ?? []; final List groupsJson = (decoded['groups'] as List?) ?? []; From a487e4071f9500d439501a80e70a7193c03546b0 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Wed, 7 Jan 2026 12:00:25 +0100 Subject: [PATCH 140/222] Sort files --- lib/l10n/arb/app_de.arb | 144 ++++---- lib/l10n/arb/app_en.arb | 732 ++++++++++++++++++++-------------------- 2 files changed, 438 insertions(+), 438 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index d8a9e7e..89354bd 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -1,83 +1,83 @@ { "@@locale": "de", - "choose_group": "Gruppe wählen", - "create_new_match": "Neues Match erstellen", - "choose_ruleset": "Regelwerk wählen", + "all_players": "Alle Spieler:innen:", + "all_players_selected": "Alle Spieler:innen ausgewählt", + "amount_of_matches": "Anzahl der Matches", + "cancel": "Abbrechen", "choose_game": "Spielvorlage wählen", - "select_winner": "Gewinner:in wählen:", + "choose_group": "Gruppe wählen", + "choose_ruleset": "Regelwerk wählen", + "could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden", + "create_group": "Gruppe erstellen", + "create_match": "Match erstellen", + "create_new_group": "Neue Gruppe erstellen", + "create_new_match": "Neues Match erstellen", + "data_successfully_deleted": "Daten erfolgreich gelöscht", + "data_successfully_exported": "Daten erfolgreich exportiert", + "data_successfully_imported": "Daten erfolgreich importiert", + "days_ago": "vor {count} Tagen", + "delete": "Löschen", + "delete_all_data": "Alle Daten löschen?", + "error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", + "error_reading_file": "Fehler beim Lesen der Datei", + "export_canceled": "Export abgebrochen", + "export_data": "Daten exportieren", + "format_exception": "Formatfehler (siehe Konsole)", + "game": "Spielvorlage", + "game_name": "Spielvorlagenname", + "group": "Gruppe", + "group_name": "Gruppenname", + "groups": "Gruppen", + "home": "Startseite", + "import_canceled": "Import abgebrochen", + "import_data": "Daten importieren", + "info": "Info", + "invalid_schema": "Ungültiges Schema", + "least_points": "Niedrigste Punkte", + "match_in_progress": "Match läuft...", + "match_name": "Matchname", + "matches": "Matches", + "menu": "Menü", + "most_points": "Höchste Punkte", + "no_data_available": "Keine Daten verfügbar", + "no_groups_created_yet": "Noch keine Gruppen erstellt", + "no_matches_created_yet": "Noch keine Matches erstellt", + "no_players_created_yet": "Noch keine Spieler:in erstellt", + "no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden", + "no_players_selected": "Keine Spieler:in ausgewählt", "no_recent_matches_available": "Keine letzten Matches verfügbar", "no_second_match_available": "Kein zweites Match verfügbar", - "delete_all_data": "Alle Daten löschen?", - "cancel": "Abbrechen", - "delete": "Löschen", - "create_new_group": "Neue Gruppe erstellen", - "error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", - "selected_players": "Ausgewählte Spieler:in: {count}", - "no_players_selected": "Keine Spieler:in ausgewählt", - "all_players": "Alle Spieler:innen:", - "successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt", - "could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden", - "winner": "Gewinner:in: {winnerName}", - "players": "Spieler:in", - "player_name": "Spieler:innenname", "no_statistics_available": "Keine Statistiken verfügbar", - "matches": "Matches", - "groups": "Gruppen", - "recent_matches": "Letzte Matches", - "quick_create": "Schnellzugriff", - "match_in_progress": "Match läuft...", - "menu": "Menü", - "settings": "Einstellungen", - "export_data": "Daten exportieren", - "import_data": "Daten importieren", - "this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden", - "data_successfully_deleted": "Daten erfolgreich gelöscht", - "data_successfully_imported": "Daten erfolgreich importiert", - "invalid_schema": "Ungültiges Schema", - "error_reading_file": "Fehler beim Lesen der Datei", - "import_canceled": "Import abgebrochen", - "format_exception": "Formatfehler (siehe Konsole)", - "unknown_exception": "Unbekannter Fehler (siehe Konsole)", - "data_successfully_exported": "Daten erfolgreich exportiert", - "export_canceled": "Export abgebrochen", - "undo": "Rückgängig", - "wins": "Siege", - "winrate": "Siegquote", - "amount_of_matches": "Anzahl der Matches", - "info": "Info", - "no_groups_created_yet": "Noch keine Gruppen erstellt", - "create_group": "Gruppe erstellen", - "group_name": "Gruppenname", - "no_matches_created_yet": "Noch keine Matches erstellt", - "match_name": "Matchname", - "game": "Spielvorlage", - "ruleset": "Regelwerk", - "group": "Gruppe", "none": "Kein", "none_group": "Keine", - "create_match": "Match erstellen", - "no_players_created_yet": "Noch keine Spieler:in erstellt", - "all_players_selected": "Alle Spieler:innen ausgewählt", - "no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden", - "today_at": "Heute um {time}", - "yesterday_at": "Gestern um {time}", - "days_ago": "vor {count} Tagen", - "home": "Startseite", + "not_available": "Nicht verfügbar", + "player_name": "Spieler:innenname", + "players": "Spieler:in", + "players_count": "{count} Spieler", + "quick_create": "Schnellzugriff", + "recent_matches": "Letzte Matches", + "ruleset": "Regelwerk", + "ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.", + "ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.", + "ruleset_single_loser": "Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.", + "ruleset_single_winner": "Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", + "search_for_groups": "Nach Gruppen suchen", + "search_for_players": "Nach Spieler:innen suchen", + "select_winner": "Gewinner:in wählen:", + "selected_players": "Ausgewählte Spieler:in: {count}", + "settings": "Einstellungen", + "single_loser": "Ein:e Verlierer:in", + "single_winner": "Ein:e Gewinner:in", "statistics": "Statistiken", "stats": "Statistiken", - "players_count": "{count} Spieler", + "successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt", "there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht", - "game_name": "Spielvorlagenname", - "ruleset_single_winner": "Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.", - "ruleset_single_loser": "Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.", - "ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.", - "ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.", - "single_winner": "Ein:e Gewinner:in", - "single_loser": "Ein:e Verlierer:in", - "most_points": "Höchste Punkte", - "least_points": "Niedrigste Punkte", - "search_for_players": "Nach Spieler:innen suchen", - "search_for_groups": "Nach Gruppen suchen", - "no_data_available": "Keine Daten verfügbar", - "not_available": "Nicht verfügbar" -} + "this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden", + "today_at": "Heute um {time}", + "undo": "Rückgängig", + "unknown_exception": "Unbekannter Fehler (siehe Konsole)", + "winner": "Gewinner:in: {winnerName}", + "winrate": "Siegquote", + "wins": "Siege", + "yesterday_at": "Gestern um {time}" +} \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index e3666b3..d567f50 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1,367 +1,367 @@ { - "@@locale": "en", - "choose_group": "Choose Group", - "@choose_group": { - "description": "Label for choosing a group" - }, - "create_new_match": "Create new match", - "@create_new_match": { - "description": "Button text to create a new match" - }, - "choose_ruleset": "Choose Ruleset", - "@choose_ruleset": { - "description": "Label for choosing a ruleset" - }, - "choose_game": "Choose Game", - "@choose_game": { - "description": "Label for choosing a game" - }, - "select_winner": "Select Winner:", - "@select_winner": { - "description": "Label to select the winner" - }, - "game_tracker": "Game Tracker", - "@game_tracker": { - "description": "App Name" - }, - "no_recent_matches_available": "No recent matches available", - "@no_recent_matches_available": { - "description": "Message when no recent matches exist" - }, - "no_second_match_available": "No second match available", - "@no_second_match_available": { - "description": "Message when no second match exists" - }, - "delete_all_data": "Delete all data?", - "@delete_all_data": { - "description": "Confirmation dialog for deleting all data" - }, - "cancel": "Cancel", - "@cancel": { - "description": "Cancel button text" - }, - "delete": "Delete", - "@delete": { - "description": "Delete button text" - }, - "create_new_group": "Create new group", - "@create_new_group": { - "description": "Button text to create a new group" - }, - "error_creating_group": "Error while creating group, please try again", - "@error_creating_group": { - "description": "Error message when group creation fails" - }, - "selected_players": "Selected players: {count}", - "@selected_players": { - "description": "Shows the number of selected players", - "placeholders": { - "count": { - "type": "int", - "format": "compact" - } - } - }, - "no_players_selected": "No players selected", - "@no_players_selected": { - "description": "Message when no players are selected" - }, - "all_players": "All players:", - "@all_players": { - "description": "Label for all players list" - }, - "successfully_added_player": "Successfully added player {playerName}", - "@successfully_added_player": { - "description": "Success message when adding a player", - "placeholders": { - "playerName": { - "type": "String", - "example": "John" - } - } - }, - "could_not_add_player": "Could not add player {playerName}", - "@could_not_add_player": { - "description": "Error message when adding a player fails", - "placeholders": { - "playerName": { - "type": "String", - "example": "John" - } - } - }, - "winner": "Winner", - "@winner": { - "description": "Winner label", - }, - "players": "Players", - "@players": { - "description": "Players label" - }, - "no_statistics_available": "No statistics available", - "@no_statistics_available": { - "description": "Message when no statistics are available, because no matches were played yet" - }, - "no_data_available": "No data available", - "@no_data_available": { - "description": "Message when no data in the statistic tiles is given" - }, - "matches": "Matches", - "@matches": { - "description": "Label for matches" - }, - "groups": "Groups", - "@groups": { - "description": "Label for groups" - }, - "recent_matches": "Recent Matches", - "@recent_matches": { - "description": "Title for recent matches section" - }, - "quick_create": "Quick Create", - "@quick_create": { - "description": "Title for quick create section" - }, - "match_in_progress": "Match in progress...", - "@match_in_progress": { - "description": "Message when match is in progress" - }, - "menu": "Menu", - "@menu": { - "description": "Menu label" - }, - "settings": "Settings", - "@settings": { - "description": "Settings label" - }, - "export_data": "Export data", - "@export_data": { - "description": "Export data menu item" - }, - "import_data": "Import data", - "@import_data": { - "description": "Import data menu item" - }, - "this_cannot_be_undone": "This can't be undone", - "@this_cannot_be_undone": { - "description": "Warning message for irreversible actions" - }, - "data_successfully_deleted": "Data successfully deleted", - "@data_successfully_deleted": { - "description": "Success message after deleting data" - }, - "data_successfully_imported": "Data successfully imported", - "@data_successfully_imported": { - "description": "Success message after importing data" - }, - "invalid_schema": "Invalid Schema", - "@invalid_schema": { - "description": "Error message for invalid schema" - }, - "error_reading_file": "Error reading file", - "@error_reading_file": { - "description": "Error message when file cannot be read" - }, - "import_canceled": "Import canceled", - "@import_canceled": { - "description": "Message when import is canceled" - }, - "format_exception": "Format Exception (see console)", - "@format_exception": { - "description": "Error message for format exceptions" - }, - "unknown_exception": "Unknown Exception (see console)", - "@unknown_exception": { - "description": "Error message for unknown exceptions" - }, - "data_successfully_exported": "Data successfully exported", - "@data_successfully_exported": { - "description": "Success message after exporting data" - }, - "export_canceled": "Export canceled", - "@export_canceled": { - "description": "Message when export is canceled" - }, - "undo": "Undo", - "@undo": { - "description": "Undo button text" - }, - "wins": "Wins", - "@wins": { - "description": "Label for wins statistic" - }, - "winrate": "Winrate", - "@winrate": { - "description": "Label for winrate statistic" - }, - "amount_of_matches": "Amount of Matches", - "@amount_of_matches": { - "description": "Label for amount of matches statistic" - }, - "info": "Info", - "@info": { - "description": "Info label" - }, - "no_groups_created_yet": "No groups created yet", - "@no_groups_created_yet": { - "description": "Message when no groups exist" - }, - "no_players_created_yet": "No players created yet", - "@no_players_created_yet": { - "description": "Message when no players exist" - }, - "create_group": "Create Group", - "@create_group": { - "description": "Button text to create a group" - }, - "group_name": "Group name", - "@group_name": { - "description": "Placeholder for group name input" - }, - "player_name": "Player name", - "@player_name": { - "description": "Placeholder for player name input" - }, - "no_matches_created_yet": "No matches created yet", - "@no_matches_created_yet": { - "description": "Message when no matches exist" - }, - "match_name": "Match name", - "@match_name": { - "description": "Placeholder for match name input" - }, - "game": "Game", - "@game": { - "description": "Game label" - }, - "ruleset": "Ruleset", - "@ruleset": { - "description": "Ruleset label" - }, - "group": "Group", - "@group": { - "description": "Group label" - }, - "none": "None", - "@none": { - "description": "None option label" - }, - "none_group": "None", - "@none_group": { - "description": "None group option label" - }, - "create_match": "Create match", - "@create_match": { - "description": "Button text to create a match" - }, - "no_players_found_with_that_name": "No players found with that name", - "@no_players_found_with_that_name": { - "description": "Message when search returns no results" - }, - "all_players_selected": "All players selected", - "@all_players_selected": { - "description": "Message when all players are added to selection" - }, - "today_at": "Today at {time}", - "@today_at": { - "description": "Date format for today", - "placeholders": { - "time": { - "type": "String", - "example": "14:30" - } - } - }, - "yesterday_at": "Yesterday at {time}", - "@yesterday_at": { - "description": "Date format for yesterday", - "placeholders": { - "time": { - "type": "String", - "example": "14:30" - } - } - }, - "days_ago": "{count} days ago", - "@days_ago": { - "description": "Date format for days ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "home": "Home", - "@home": { - "description": "Home tab label" - }, - "statistics": "Statistics", - "@statistics": { - "description": "Statistics tab label" - }, - "stats": "Stats", - "@stats": { - "description": "Stats tab label (short)" - }, - "players_count": "{count} Players", - "@players_count": { - "description": "Shows the number of players", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "there_is_no_group_matching_your_search": "There is no group matching your search", - "@there_is_no_group_matching_your_search": { - "description": "Message when search returns no groups" - }, - "game_name": "Game Name", - "@game_name": { - "description": "Placeholder for game name search" - }, - "ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", - "@ruleset_single_winner": { - "description": "Description for single winner ruleset" - }, - "ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.", - "@ruleset_single_loser": { - "description": "Description for single loser ruleset" - }, - "ruleset_most_points": "Traditional ruleset: the player with the most points wins.", - "@ruleset_most_points": { - "description": "Description for most points ruleset" - }, - "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", - "@ruleset_least_points": { - "description": "Description for least points ruleset" - }, - "single_winner": "Single Winner", - "@single_winner": { - "description": "Title for single winner ruleset" - }, - "single_loser": "Single Loser", - "@single_loser": { - "description": "Title for single loser ruleset" - }, - "most_points": "Most Points", - "@most_points": { - "description": "Title for most points ruleset" - }, - "least_points": "Least Points", - "@least_points": { - "description": "Title for least points ruleset" - }, - "search_for_players": "Search for players", - "@search_for_players": { - "description": "Hint text for player search input field" - }, - "search_for_groups": "Search for groups", - "@search_for_groups": { - "description": "Hint text for group search input field" - }, - "not_available": "Not available", - "@not_available": { - "description": "Abbreviation for not available" - } -} + "@@locale": "en", + "@all_players": { + "description": "Label for all players list" + }, + "@all_players_selected": { + "description": "Message when all players are added to selection" + }, + "@amount_of_matches": { + "description": "Label for amount of matches statistic" + }, + "@cancel": { + "description": "Cancel button text" + }, + "@choose_game": { + "description": "Label for choosing a game" + }, + "@choose_group": { + "description": "Label for choosing a group" + }, + "@choose_ruleset": { + "description": "Label for choosing a ruleset" + }, + "@could_not_add_player": { + "description": "Error message when adding a player fails", + "placeholders": { + "playerName": { + "type": "String", + "example": "John" + } + } + }, + "@create_group": { + "description": "Button text to create a group" + }, + "@create_match": { + "description": "Button text to create a match" + }, + "@create_new_group": { + "description": "Button text to create a new group" + }, + "@create_new_match": { + "description": "Button text to create a new match" + }, + "@data_successfully_deleted": { + "description": "Success message after deleting data" + }, + "@data_successfully_exported": { + "description": "Success message after exporting data" + }, + "@data_successfully_imported": { + "description": "Success message after importing data" + }, + "@days_ago": { + "description": "Date format for days ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "@delete": { + "description": "Delete button text" + }, + "@delete_all_data": { + "description": "Confirmation dialog for deleting all data" + }, + "@error_creating_group": { + "description": "Error message when group creation fails" + }, + "@error_reading_file": { + "description": "Error message when file cannot be read" + }, + "@export_canceled": { + "description": "Message when export is canceled" + }, + "@export_data": { + "description": "Export data menu item" + }, + "@format_exception": { + "description": "Error message for format exceptions" + }, + "@game": { + "description": "Game label" + }, + "@game_name": { + "description": "Placeholder for game name search" + }, + "@game_tracker": { + "description": "App Name" + }, + "@group": { + "description": "Group label" + }, + "@group_name": { + "description": "Placeholder for group name input" + }, + "@groups": { + "description": "Label for groups" + }, + "@home": { + "description": "Home tab label" + }, + "@import_canceled": { + "description": "Message when import is canceled" + }, + "@import_data": { + "description": "Import data menu item" + }, + "@info": { + "description": "Info label" + }, + "@invalid_schema": { + "description": "Error message for invalid schema" + }, + "@least_points": { + "description": "Title for least points ruleset" + }, + "@match_in_progress": { + "description": "Message when match is in progress" + }, + "@match_name": { + "description": "Placeholder for match name input" + }, + "@matches": { + "description": "Label for matches" + }, + "@menu": { + "description": "Menu label" + }, + "@most_points": { + "description": "Title for most points ruleset" + }, + "@no_data_available": { + "description": "Message when no data in the statistic tiles is given" + }, + "@no_groups_created_yet": { + "description": "Message when no groups exist" + }, + "@no_matches_created_yet": { + "description": "Message when no matches exist" + }, + "@no_players_created_yet": { + "description": "Message when no players exist" + }, + "@no_players_found_with_that_name": { + "description": "Message when search returns no results" + }, + "@no_players_selected": { + "description": "Message when no players are selected" + }, + "@no_recent_matches_available": { + "description": "Message when no recent matches exist" + }, + "@no_second_match_available": { + "description": "Message when no second match exists" + }, + "@no_statistics_available": { + "description": "Message when no statistics are available, because no matches were played yet" + }, + "@none": { + "description": "None option label" + }, + "@none_group": { + "description": "None group option label" + }, + "@not_available": { + "description": "Abbreviation for not available" + }, + "@player_name": { + "description": "Placeholder for player name input" + }, + "@players": { + "description": "Players label" + }, + "@players_count": { + "description": "Shows the number of players", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "@quick_create": { + "description": "Title for quick create section" + }, + "@recent_matches": { + "description": "Title for recent matches section" + }, + "@ruleset": { + "description": "Ruleset label" + }, + "@ruleset_least_points": { + "description": "Description for least points ruleset" + }, + "@ruleset_most_points": { + "description": "Description for most points ruleset" + }, + "@ruleset_single_loser": { + "description": "Description for single loser ruleset" + }, + "@ruleset_single_winner": { + "description": "Description for single winner ruleset" + }, + "@search_for_groups": { + "description": "Hint text for group search input field" + }, + "@search_for_players": { + "description": "Hint text for player search input field" + }, + "@select_winner": { + "description": "Label to select the winner" + }, + "@selected_players": { + "description": "Shows the number of selected players", + "placeholders": { + "count": { + "type": "int", + "format": "compact" + } + } + }, + "@settings": { + "description": "Settings label" + }, + "@single_loser": { + "description": "Title for single loser ruleset" + }, + "@single_winner": { + "description": "Title for single winner ruleset" + }, + "@statistics": { + "description": "Statistics tab label" + }, + "@stats": { + "description": "Stats tab label (short)" + }, + "@successfully_added_player": { + "description": "Success message when adding a player", + "placeholders": { + "playerName": { + "type": "String", + "example": "John" + } + } + }, + "@there_is_no_group_matching_your_search": { + "description": "Message when search returns no groups" + }, + "@this_cannot_be_undone": { + "description": "Warning message for irreversible actions" + }, + "@today_at": { + "description": "Date format for today", + "placeholders": { + "time": { + "type": "String", + "example": "14:30" + } + } + }, + "@undo": { + "description": "Undo button text" + }, + "@unknown_exception": { + "description": "Error message for unknown exceptions" + }, + "@winner": { + "description": "Winner label" + }, + "@winrate": { + "description": "Label for winrate statistic" + }, + "@wins": { + "description": "Label for wins statistic" + }, + "@yesterday_at": { + "description": "Date format for yesterday", + "placeholders": { + "time": { + "type": "String", + "example": "14:30" + } + } + }, + "all_players": "All players:", + "all_players_selected": "All players selected", + "amount_of_matches": "Amount of Matches", + "cancel": "Cancel", + "choose_game": "Choose Game", + "choose_group": "Choose Group", + "choose_ruleset": "Choose Ruleset", + "could_not_add_player": "Could not add player {playerName}", + "create_group": "Create Group", + "create_match": "Create match", + "create_new_group": "Create new group", + "create_new_match": "Create new match", + "data_successfully_deleted": "Data successfully deleted", + "data_successfully_exported": "Data successfully exported", + "data_successfully_imported": "Data successfully imported", + "days_ago": "{count} days ago", + "delete": "Delete", + "delete_all_data": "Delete all data?", + "error_creating_group": "Error while creating group, please try again", + "error_reading_file": "Error reading file", + "export_canceled": "Export canceled", + "export_data": "Export data", + "format_exception": "Format Exception (see console)", + "game": "Game", + "game_name": "Game Name", + "game_tracker": "Game Tracker", + "group": "Group", + "group_name": "Group name", + "groups": "Groups", + "home": "Home", + "import_canceled": "Import canceled", + "import_data": "Import data", + "info": "Info", + "invalid_schema": "Invalid Schema", + "least_points": "Least Points", + "match_in_progress": "Match in progress...", + "match_name": "Match name", + "matches": "Matches", + "menu": "Menu", + "most_points": "Most Points", + "no_data_available": "No data available", + "no_groups_created_yet": "No groups created yet", + "no_matches_created_yet": "No matches created yet", + "no_players_created_yet": "No players created yet", + "no_players_found_with_that_name": "No players found with that name", + "no_players_selected": "No players selected", + "no_recent_matches_available": "No recent matches available", + "no_second_match_available": "No second match available", + "no_statistics_available": "No statistics available", + "none": "None", + "none_group": "None", + "not_available": "Not available", + "player_name": "Player name", + "players": "Players", + "players_count": "{count} Players", + "quick_create": "Quick Create", + "recent_matches": "Recent Matches", + "ruleset": "Ruleset", + "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", + "ruleset_most_points": "Traditional ruleset: the player with the most points wins.", + "ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.", + "ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", + "search_for_groups": "Search for groups", + "search_for_players": "Search for players", + "select_winner": "Select Winner:", + "selected_players": "Selected players: {count}", + "settings": "Settings", + "single_loser": "Single Loser", + "single_winner": "Single Winner", + "statistics": "Statistics", + "stats": "Stats", + "successfully_added_player": "Successfully added player {playerName}", + "there_is_no_group_matching_your_search": "There is no group matching your search", + "this_cannot_be_undone": "This can't be undone", + "today_at": "Today at {time}", + "undo": "Undo", + "unknown_exception": "Unknown Exception (see console)", + "winner": "Winner", + "winrate": "Winrate", + "wins": "Wins", + "yesterday_at": "Yesterday at {time}" +} \ No newline at end of file From 1dc5286c6b4c21a7552a592a070413caa67d39bd Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Wed, 7 Jan 2026 12:08:21 +0100 Subject: [PATCH 141/222] change double to single quotes --- lib/presentation/widgets/tiles/match_tile.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index b037af2..c455949 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -99,7 +99,7 @@ class _MatchTileState extends State { const SizedBox(width: 8), Expanded( child: Text( - "${loc.winner}: ${winner.name}", + '${loc.winner}: ${winner.name}', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, From a78614851bdeac36e363957ffd80bf939953a09d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 13:02:09 +0100 Subject: [PATCH 142/222] Added constants class --- lib/core/constants.dart | 8 ++++++-- .../views/main_menu/group_view/groups_view.dart | 2 +- lib/presentation/views/main_menu/home_view.dart | 2 +- .../views/main_menu/match_view/match_view.dart | 2 +- lib/presentation/views/main_menu/statistics_view.dart | 2 +- lib/presentation/widgets/player_selection.dart | 2 +- 6 files changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/core/constants.dart b/lib/core/constants.dart index 075b1ab..8d3c8cc 100644 --- a/lib/core/constants.dart +++ b/lib/core/constants.dart @@ -1,2 +1,6 @@ -/// Minimum duration of all app skeletons -Duration minimumSkeletonDuration = const Duration(milliseconds: 250); +class Constants { + Constants._(); // Private constructor to prevent instantiation + + /// Minimum duration of all app skeletons + static Duration minimumSkeletonDuration = const Duration(milliseconds: 250); +} diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 3505a3c..c641dde 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -101,7 +101,7 @@ class _GroupsViewState extends State { void loadGroups() { Future.wait([ db.groupDao.getAllGroups(), - Future.delayed(minimumSkeletonDuration), + Future.delayed(Constants.minimumSkeletonDuration), ]).then((results) { loadedGroups = results[0] as List; setState(() { diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 96280ce..072699e 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -47,7 +47,7 @@ class _HomeViewState extends State { db.matchDao.getMatchCount(), db.groupDao.getGroupCount(), db.matchDao.getAllMatches(), - Future.delayed(minimumSkeletonDuration), + Future.delayed(Constants.minimumSkeletonDuration), ]).then((results) { matchCount = results[0] as int; groupCount = results[1] as int; diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 73f596f..bea2f14 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -120,7 +120,7 @@ class _MatchViewState extends State { void loadGames() { Future.wait([ db.matchDao.getAllMatches(), - Future.delayed(minimumSkeletonDuration), + Future.delayed(Constants.minimumSkeletonDuration), ]).then((results) { if (mounted) { setState(() { diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 6c30483..1125118 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -31,7 +31,7 @@ class _StatisticsViewState extends State { Future.wait([ db.matchDao.getAllMatches(), db.playerDao.getAllPlayers(), - Future.delayed(minimumSkeletonDuration), + Future.delayed(Constants.minimumSkeletonDuration), ]).then((results) async { if (!mounted) return; final matches = results[0] as List; diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index eac4480..9eb005a 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -52,7 +52,7 @@ class _PlayerSelectionState extends State { void loadPlayerList() { _allPlayersFuture = Future.wait([ db.playerDao.getAllPlayers(), - Future.delayed(minimumSkeletonDuration), + Future.delayed(Constants.minimumSkeletonDuration), ]).then((results) => results[0] as List); if (mounted) { _allPlayersFuture.then((loadedPlayers) { From 6e45e9435b5ef9c7bc1606922eae2bd68fbf3f70 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 13:27:39 +0100 Subject: [PATCH 143/222] Refactored views --- .../main_menu/custom_navigation_bar.dart | 5 ++ .../group_view/create_group_view.dart | 6 +- .../main_menu/group_view/groups_view.dart | 4 + .../views/main_menu/home_view.dart | 69 +++++++++++------- .../create_match/choose_game_view.dart | 6 +- .../create_match/choose_group_view.dart | 3 +- .../create_match/choose_ruleset_view.dart | 1 + .../create_match/create_match_view.dart | 1 - .../match_view/match_result_view.dart | 12 ++- .../main_menu/match_view/match_view.dart | 4 +- .../views/main_menu/statistics_view.dart | 73 +++++++++++-------- 11 files changed, 117 insertions(+), 67 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 1e38808..a8b18c8 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -17,7 +17,10 @@ class CustomNavigationBar extends StatefulWidget { class _CustomNavigationBarState extends State with SingleTickerProviderStateMixin { + /// Currently selected tab index int currentIndex = 0; + + /// Key count to force rebuild of tab views int tabKeyCount = 0; @override @@ -119,12 +122,14 @@ class _CustomNavigationBarState extends State ); } + /// Handles tab tap events. Updates the current [index] state. void onTabTapped(int index) { setState(() { currentIndex = index; }); } + /// Returns the title of the current tab based on [currentIndex]. String _currentTabTitle(context) { final loc = AppLocalizations.of(context); switch (currentIndex) { diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index cba22ef..022a4b5 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -18,15 +18,17 @@ class CreateGroupView extends StatefulWidget { } class _CreateGroupViewState extends State { - final _groupNameController = TextEditingController(); late final AppDatabase db; + /// Controller for the group name input field + final _groupNameController = TextEditingController(); + + /// List of currently selected players List selectedPlayers = []; @override void initState() { super.initState(); - db = Provider.of(context, listen: false); _groupNameController.addListener(() { setState(() {}); diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index c641dde..57d05a4 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -21,7 +21,11 @@ class GroupsView extends StatefulWidget { class _GroupsViewState extends State { late final AppDatabase db; + + /// Loaded groups from the database late List loadedGroups; + + /// Loading state bool isLoading = true; List groups = List.filled( diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 072699e..170adb4 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -21,9 +21,17 @@ class HomeView extends StatefulWidget { class _HomeViewState extends State { bool isLoading = true; + + /// Amount of matches in the database int matchCount = 0; + + /// Amount of groups in the database int groupCount = 0; + + /// Loaded recent matches from the database List loadedRecentMatches = []; + + /// Recent matches to display, initially filled with skeleton matches List recentMatches = List.filled( 2, Match( @@ -42,32 +50,7 @@ class _HomeViewState extends State { @override void initState() { super.initState(); - final db = Provider.of(context, listen: false); - Future.wait([ - db.matchDao.getMatchCount(), - db.groupDao.getGroupCount(), - db.matchDao.getAllMatches(), - Future.delayed(Constants.minimumSkeletonDuration), - ]).then((results) { - matchCount = results[0] as int; - groupCount = results[1] as int; - loadedRecentMatches = results[2] as List; - recentMatches = - (loadedRecentMatches - ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) - .take(2) - .toList(); - if (loadedRecentMatches.length < 2) { - recentMatches.add( - Match(name: 'Dummy Match', winner: null, group: null, players: null), - ); - } - if (mounted) { - setState(() { - isLoading = false; - }); - } - }); + loadHomeViewData(); } @override @@ -230,6 +213,40 @@ class _HomeViewState extends State { ); } + /// Loads the data for the HomeView from the database. + /// This includes the match count, group count, and recent matches. + void loadHomeViewData() { + final db = Provider.of(context, listen: false); + Future.wait([ + db.matchDao.getMatchCount(), + db.groupDao.getGroupCount(), + db.matchDao.getAllMatches(), + Future.delayed(Constants.minimumSkeletonDuration), + ]).then((results) { + matchCount = results[0] as int; + groupCount = results[1] as int; + loadedRecentMatches = results[2] as List; + recentMatches = + (loadedRecentMatches + ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) + .take(2) + .toList(); + if (loadedRecentMatches.length < 2) { + recentMatches.add( + Match(name: 'Dummy Match', winner: null, group: null, players: null), + ); + } + if (mounted) { + setState(() { + isLoading = false; + }); + } + }); + } + + /// Generates a text representation of the players in the match. + /// If the match has a group, it returns the group name and the number of additional players. + /// If there is no group, it returns the count of players. String _getPlayerText(Match game, context) { final loc = AppLocalizations.of(context); if (game.group == null) { diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 18e1e9d..01981e2 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -20,10 +20,12 @@ class ChooseGameView extends StatefulWidget { } class _ChooseGameViewState extends State { - late int selectedGameIndex; - + /// Controller for the search bar final TextEditingController searchBarController = TextEditingController(); + /// Currently selected game index + late int selectedGameIndex; + @override void initState() { selectedGameIndex = widget.initialGameIndex; diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 5101db6..011b819 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -136,8 +136,7 @@ class _ChooseGroupViewState extends State { ); } - /// Filters the groups based on the search query. - /// TODO: Maybe implement also targetting player names? + /// Filters the groups based on the search [query]. void filterGroups(String query) { setState(() { if (query.isEmpty) { diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index 7a41417..73be471 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -19,6 +19,7 @@ class ChooseRulesetView extends StatefulWidget { } class _ChooseRulesetViewState extends State { + /// Currently selected ruleset index late int selectedRulesetIndex; @override diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index d3a23ae..775f29d 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -26,7 +26,6 @@ class CreateMatchView extends StatefulWidget { } class _CreateMatchViewState extends State { - /// Reference to the app database late final AppDatabase db; /// Controller for the match name input field diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index 5c455f6..93ebbc6 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -18,8 +18,12 @@ class MatchResultView extends StatefulWidget { } class _MatchResultViewState extends State { - late final List allPlayers; late final AppDatabase db; + + /// List of all players who participated in the match + late final List allPlayers; + + /// Currently selected winner player Player? _selectedPlayer; @override @@ -132,6 +136,8 @@ class _MatchResultViewState extends State { ); } + /// Handles saving or removing the winner in the database + /// based on the current selection. Future _handleWinnerSaving() async { if (_selectedPlayer == null) { await db.matchDao.removeWinner(matchId: widget.match.id); @@ -144,6 +150,10 @@ class _MatchResultViewState extends State { widget.onWinnerChanged?.call(); } + /// Retrieves all players associated with the given [match]. + /// This includes players directly assigned to the match + /// as well as members of the group (if any). + /// The returned list is sorted alphabetically by player name. List getAllPlayers(Match match) { List players = []; diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index bea2f14..45b957f 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -28,6 +28,8 @@ class _MatchViewState extends State { late final AppDatabase db; bool isLoading = true; + /// Loaded matches from the database, + /// initially filled with skeleton matches List matches = List.filled( 4, Match( @@ -44,7 +46,6 @@ class _MatchViewState extends State { @override void initState() { super.initState(); - db = Provider.of(context, listen: false); loadGames(); } @@ -117,6 +118,7 @@ class _MatchViewState extends State { ); } + /// Loads the games from the database and sorts them by creation date. void loadGames() { Future.wait([ db.matchDao.getAllMatches(), diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index 1125118..a60b854 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -25,28 +25,7 @@ class _StatisticsViewState extends State { @override void initState() { super.initState(); - - final db = Provider.of(context, listen: false); - - Future.wait([ - db.matchDao.getAllMatches(), - db.playerDao.getAllPlayers(), - Future.delayed(Constants.minimumSkeletonDuration), - ]).then((results) async { - if (!mounted) return; - final matches = results[0] as List; - final players = results[1] as List; - winCounts = _calculateWinsForAllPlayers(matches, players, context); - matchCounts = _calculateMatchAmountsForAllPlayers( - matches, - players, - context, - ); - winRates = computeWinRatePercent(wins: winCounts, matches: matchCounts); - setState(() { - isLoading = false; - }); - }); + loadStatisticData(); } @override @@ -118,13 +97,43 @@ class _StatisticsViewState extends State { ); } + /// Loads matches and players from the database + /// and calculates statistics for each player + void loadStatisticData() { + final db = Provider.of(context, listen: false); + + Future.wait([ + db.matchDao.getAllMatches(), + db.playerDao.getAllPlayers(), + Future.delayed(Constants.minimumSkeletonDuration), + ]).then((results) async { + if (!mounted) return; + final matches = results[0] as List; + final players = results[1] as List; + winCounts = _calculateWinsForAllPlayers( + matches: matches, + players: players, + context: context, + ); + matchCounts = _calculateMatchAmountsForAllPlayers( + matches: matches, + players: players, + context: context, + ); + winRates = computeWinRatePercent(wins: winCounts, matches: matchCounts); + setState(() { + isLoading = false; + }); + }); + } + /// Calculates the number of wins for each player /// and returns a sorted list of tuples (playerName, winCount) - List<(String, int)> _calculateWinsForAllPlayers( - List matches, - List players, - BuildContext context, - ) { + List<(String, int)> _calculateWinsForAllPlayers({ + required List matches, + required List players, + required BuildContext context, + }) { List<(String, int)> winCounts = []; final loc = AppLocalizations.of(context); @@ -169,11 +178,11 @@ class _StatisticsViewState extends State { /// Calculates the number of matches played for each player /// and returns a sorted list of tuples (playerName, matchCount) - List<(String, int)> _calculateMatchAmountsForAllPlayers( - List matches, - List players, - BuildContext context, - ) { + List<(String, int)> _calculateMatchAmountsForAllPlayers({ + required List matches, + required List players, + required BuildContext context, + }) { List<(String, int)> matchCounts = []; final loc = AppLocalizations.of(context); From 21c74b74bc16b8ea54e297ffdaa41a00ca35580f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:05:19 +0100 Subject: [PATCH 144/222] Refactored components --- lib/core/enums.dart | 3 + lib/presentation/widgets/app_skeleton.dart | 17 +- .../widgets/buttons/custom_width_button.dart | 13 ++ .../widgets/buttons/quick_create_button.dart | 11 +- lib/presentation/widgets/navbar_item.dart | 27 ++- .../widgets/player_selection.dart | 198 ++++++++++-------- .../widgets/text_input/custom_search_bar.dart | 42 +++- .../widgets/text_input/text_input_field.dart | 17 +- .../widgets/tiles/choose_tile.dart | 16 +- .../widgets/tiles/custom_radio_list_tile.dart | 17 +- .../widgets/tiles/group_tile.dart | 6 + lib/presentation/widgets/tiles/info_tile.dart | 31 ++- .../widgets/tiles/match_tile.dart | 35 +++- .../widgets/tiles/quick_info_tile.dart | 31 ++- .../widgets/tiles/settings_list_tile.dart | 23 +- .../widgets/tiles/statistics_tile.dart | 18 ++ .../widgets/tiles/text_icon_list_tile.dart | 19 +- .../widgets/tiles/text_icon_tile.dart | 19 +- .../tiles/title_description_list_tile.dart | 32 ++- .../widgets/top_centered_message.dart | 13 +- 20 files changed, 429 insertions(+), 159 deletions(-) diff --git a/lib/core/enums.dart b/lib/core/enums.dart index ce06f85..17a01f6 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -2,6 +2,9 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; /// Button types used for styling the [CustomWidthButton] +/// - [ButtonType.primary]: Primary button style. +/// - [ButtonType.secondary]: Secondary button style. +/// - [ButtonType.tertiary]: Tertiary button style. enum ButtonType { primary, secondary, tertiary } /// Result types for import operations in the [SettingsView] diff --git a/lib/presentation/widgets/app_skeleton.dart b/lib/presentation/widgets/app_skeleton.dart index 209f1d8..1d74456 100644 --- a/lib/presentation/widgets/app_skeleton.dart +++ b/lib/presentation/widgets/app_skeleton.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:skeletonizer/skeletonizer.dart'; +/// A widget that provides a skeleton loading effect to its child widget tree. +/// - [child]: The widget tree to apply the skeleton effect to. +/// - [enabled]: A boolean to enable or disable the skeleton effect. +/// - [fixLayoutBuilder]: A boolean to fix the layout builder for AnimatedSwitcher. class AppSkeleton extends StatefulWidget { - final Widget child; - final bool enabled; - final bool fixLayoutBuilder; - const AppSkeleton({ super.key, required this.child, @@ -13,6 +13,15 @@ class AppSkeleton extends StatefulWidget { this.fixLayoutBuilder = false, }); + /// The widget tree to apply the skeleton effect to. + final Widget child; + + /// A boolean to enable or disable the skeleton effect. + final bool enabled; + + /// A boolean to fix the layout builder for AnimatedSwitcher. + final bool fixLayoutBuilder; + @override State createState() => _AppSkeletonState(); } diff --git a/lib/presentation/widgets/buttons/custom_width_button.dart b/lib/presentation/widgets/buttons/custom_width_button.dart index 17c9dc5..e27f009 100644 --- a/lib/presentation/widgets/buttons/custom_width_button.dart +++ b/lib/presentation/widgets/buttons/custom_width_button.dart @@ -2,6 +2,12 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; +/// A custom button widget that is designed to have a width relative to the screen size. +/// It supports three types of buttons: primary, secondary, and text buttons. +/// - [text]: The text to display on the button. +/// - [buttonType]: The type of button to display. Defaults to [ButtonType.primary]. +/// - [sizeRelativeToWidth]: The size of the button relative to the width of the screen. +/// - [onPressed]: The callback to be invoked when the button is pressed. class CustomWidthButton extends StatelessWidget { const CustomWidthButton({ super.key, @@ -11,9 +17,16 @@ class CustomWidthButton extends StatelessWidget { this.onPressed, }); + /// The text to display on the button. final String text; + + /// The size of the button relative to the width of the screen. final double sizeRelativeToWidth; + + /// The callback to be invoked when the button is pressed. final VoidCallback? onPressed; + + /// The type of button to display. Depends on the enum [ButtonType]. final ButtonType buttonType; @override diff --git a/lib/presentation/widgets/buttons/quick_create_button.dart b/lib/presentation/widgets/buttons/quick_create_button.dart index 3860f1c..2f61805 100644 --- a/lib/presentation/widgets/buttons/quick_create_button.dart +++ b/lib/presentation/widgets/buttons/quick_create_button.dart @@ -1,15 +1,22 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A button widget designed for quick creating matches in the [HomeView] +/// - [text]: The text to display on the button. +/// - [onPressed]: The callback to be invoked when the button is pressed. class QuickCreateButton extends StatefulWidget { - final String text; - final VoidCallback? onPressed; const QuickCreateButton({ super.key, required this.text, required this.onPressed, }); + /// The text to display on the button. + final String text; + + /// The callback to be invoked when the button is pressed. + final VoidCallback? onPressed; + @override State createState() => _QuickCreateButtonState(); } diff --git a/lib/presentation/widgets/navbar_item.dart b/lib/presentation/widgets/navbar_item.dart index b249571..13a8d4d 100644 --- a/lib/presentation/widgets/navbar_item.dart +++ b/lib/presentation/widgets/navbar_item.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; +/// A navigation bar item widget that represents a single tab in a navigation bar. +/// - [index]: The index of the tab. +/// - [isSelected]: A boolean indicating whether the tab is currently selected. +/// - [icon]: The icon to display for the tab. +/// - [label]: The label to display for the tab. +/// - [onTabTapped]: The callback to be invoked when the tab is tapped. class NavbarItem extends StatefulWidget { - final int index; - final bool isSelected; - final IconData icon; - final String label; - final Function(int) onTabTapped; - const NavbarItem({ super.key, required this.index, @@ -16,6 +16,21 @@ class NavbarItem extends StatefulWidget { required this.onTabTapped, }); + /// The index of the tab. + final int index; + + /// A boolean indicating whether the tab is currently selected. + final bool isSelected; + + /// The icon to display for the tab. + final IconData icon; + + /// The label to display for the tab. + final String label; + + /// The callback to be invoked when the tab is tapped. + final Function(int) onTabTapped; + @override State createState() => _NavbarItemState(); } diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 9eb005a..99b1e1c 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -11,31 +11,55 @@ import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; +/// A widget that allows users to select players from a list, +/// with search functionality and the ability to add new players. +/// - [availablePlayers]: An optional list of players to choose from. If null, all +/// players from the database are used. +/// - [initialSelectedPlayers]: An optional list of players that should be pre-selected. +/// - [onChanged]: A callback function that is invoked whenever the selection changes, +/// providing the updated list of selected players. class PlayerSelection extends StatefulWidget { - final Function(List value) onChanged; - final List? availablePlayers; - final List? initialSelectedPlayers; - const PlayerSelection({ super.key, - required this.onChanged, this.availablePlayers, this.initialSelectedPlayers, + required this.onChanged, }); + /// An optional list of players to choose from. If null, all players from the database are used. + final List? availablePlayers; + + /// An optional list of players that should be pre-selected. + final List? initialSelectedPlayers; + + /// A callback function that is invoked whenever the selection changes, + final Function(List value) onChanged; + @override State createState() => _PlayerSelectionState(); } class _PlayerSelectionState extends State { - List selectedPlayers = []; - List suggestedPlayers = []; - List allPlayers = []; + late final AppDatabase db; bool isLoading = true; + + /// Future that loads all players from the database. + late Future> _allPlayersFuture; + + /// The complete list of all available players. + List allPlayers = []; + + /// The list of players suggested based on the search input. + List suggestedPlayers = []; + + /// The list of currently selected players. + List selectedPlayers = []; + + /// Controller for the search bar input. late final TextEditingController _searchBarController = TextEditingController(); - late final AppDatabase db; - late Future> _allPlayersFuture; + + /// Skeleton data used while loading players. late final List skeletonData = List.filled( 7, Player(name: 'Player 0'), @@ -49,42 +73,6 @@ class _PlayerSelectionState extends State { loadPlayerList(); } - void loadPlayerList() { - _allPlayersFuture = Future.wait([ - db.playerDao.getAllPlayers(), - Future.delayed(Constants.minimumSkeletonDuration), - ]).then((results) => results[0] as List); - if (mounted) { - _allPlayersFuture.then((loadedPlayers) { - setState(() { - // If a list of available players is provided (even if empty), use that list. - if (widget.availablePlayers != null) { - widget.availablePlayers!.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = [...widget.availablePlayers!]; - suggestedPlayers = [...allPlayers]; - - if (widget.initialSelectedPlayers != null) { - // Ensures that only players available for selection are pre-selected. - selectedPlayers = widget.initialSelectedPlayers! - .where( - (p) => widget.availablePlayers!.any( - (available) => available.id == p.id, - ), - ) - .toList(); - } - } else { - // Otherwise, use the loaded players from the database. - loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); - allPlayers = [...loadedPlayers]; - suggestedPlayers = [...loadedPlayers]; - } - isLoading = false; - }); - }); - } - } - @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context); @@ -227,53 +215,97 @@ class _PlayerSelectionState extends State { ); } + /// Loads the list of players from the database or uses the provided available players. + /// Sets the loading state and updates the player lists accordingly. + void loadPlayerList() { + _allPlayersFuture = Future.wait([ + db.playerDao.getAllPlayers(), + Future.delayed(Constants.minimumSkeletonDuration), + ]).then((results) => results[0] as List); + if (mounted) { + _allPlayersFuture.then((loadedPlayers) { + setState(() { + // If a list of available players is provided (even if empty), use that list. + if (widget.availablePlayers != null) { + widget.availablePlayers!.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...widget.availablePlayers!]; + suggestedPlayers = [...allPlayers]; + + if (widget.initialSelectedPlayers != null) { + // Ensures that only players available for selection are pre-selected. + selectedPlayers = widget.initialSelectedPlayers! + .where( + (p) => widget.availablePlayers!.any( + (available) => available.id == p.id, + ), + ) + .toList(); + } + } else { + // Otherwise, use the loaded players from the database. + loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); + allPlayers = [...loadedPlayers]; + suggestedPlayers = [...loadedPlayers]; + } + isLoading = false; + }); + }); + } + } + /// Adds a new player to the database from the search bar input. /// Shows a snackbar indicating success or failure. /// [context] - BuildContext to show the snackbar. - void addNewPlayerFromSearch({required BuildContext context}) async { + Future addNewPlayerFromSearch({required BuildContext context}) async { final loc = AppLocalizations.of(context); - String playerName = _searchBarController.text.trim(); - Player createdPlayer = Player(name: playerName); - bool success = await db.playerDao.addPlayer(player: createdPlayer); + final playerName = _searchBarController.text.trim(); + + final createdPlayer = Player(name: playerName); + final success = await db.playerDao.addPlayer(player: createdPlayer); + if (!context.mounted) return; + if (success) { - selectedPlayers.insert(0, createdPlayer); - widget.onChanged([...selectedPlayers]); - allPlayers.add(createdPlayer); - setState(() { - _searchBarController.clear(); - suggestedPlayers = allPlayers.where((player) { - return !selectedPlayers.contains(player); - }).toList(); - }); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: CustomTheme.boxColor, - content: Center( - child: Text( - AppLocalizations.of( - context, - ).successfully_added_player(playerName), - style: const TextStyle(color: Colors.white), - ), - ), - ), - ); + _handleSuccessfulPlayerCreation(createdPlayer); + showSnackBarMessage(loc.successfully_added_player(playerName)); } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: CustomTheme.boxColor, - content: Center( - child: Text( - loc.could_not_add_player(playerName), - style: const TextStyle(color: Colors.white), - ), - ), - ), - ); + showSnackBarMessage(loc.could_not_add_player(playerName)); } } + /// Updates the state after successfully adding a new player. + void _handleSuccessfulPlayerCreation(Player player) { + selectedPlayers.insert(0, player); + widget.onChanged([...selectedPlayers]); + allPlayers.add(player); + + setState(() { + _searchBarController.clear(); + _updateSuggestedPlayers(); + }); + } + + /// Updates the suggested players list based on current selection. + void _updateSuggestedPlayers() { + suggestedPlayers = allPlayers + .where((player) => !selectedPlayers.contains(player)) + .toList(); + } + + /// Displays a snackbar message at the bottom of the screen. + /// [message] - The message to display in the snackbar. + void showSnackBarMessage(String message) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: Center( + child: Text(message, style: const TextStyle(color: Colors.white)), + ), + ), + ); + } + /// Determines the appropriate info text to display when no players /// are available in the suggested players list. String _getInfoText(BuildContext context) { diff --git a/lib/presentation/widgets/text_input/custom_search_bar.dart b/lib/presentation/widgets/text_input/custom_search_bar.dart index 35c11e1..bf7971a 100644 --- a/lib/presentation/widgets/text_input/custom_search_bar.dart +++ b/lib/presentation/widgets/text_input/custom_search_bar.dart @@ -1,16 +1,16 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A custom search bar widget that encapsulates a [SearchBar] with additional customization options. +/// - [controller]: The controller for the search bar's text input. +/// - [hintText]: The hint text displayed in the search bar when it is empty. +/// - [trailingButtonShown]: Whether to show the trailing button. +/// - [trailingButtonicon]: The icon for the trailing button. +/// - [trailingButtonEnabled]: Whether the trailing button is in enabled state. +/// - [onTrailingButtonPressed]: The callback invoked when the trailing button is pressed. +/// - [onChanged]: The callback invoked when the text in the search bar changes. +/// - [constraints]: The constraints for the search bar. class CustomSearchBar extends StatelessWidget { - final TextEditingController controller; - final String hintText; - final ValueChanged? onChanged; - final BoxConstraints? constraints; - final bool trailingButtonShown; - final bool trailingButtonEnabled; - final VoidCallback? onTrailingButtonPressed; - final IconData trailingButtonicon; - const CustomSearchBar({ super.key, required this.controller, @@ -23,6 +23,30 @@ class CustomSearchBar extends StatelessWidget { this.constraints, }); + /// The controller for the search bar's text input. + final TextEditingController controller; + + /// The hint text displayed in the search bar when it is empty. + final String hintText; + + /// Whether to show the trailing button. + final bool trailingButtonShown; + + /// The icon for the trailing button. + final IconData trailingButtonicon; + + /// Whether the trailing button is in enabled state. + final bool trailingButtonEnabled; + + /// The callback invoked when the trailing button is pressed. + final VoidCallback? onTrailingButtonPressed; + + /// The callback invoked when the text in the search bar changes. + final ValueChanged? onChanged; + + /// The constraints for the search bar. + final BoxConstraints? constraints; + @override Widget build(BuildContext context) { return SearchBar( diff --git a/lib/presentation/widgets/text_input/text_input_field.dart b/lib/presentation/widgets/text_input/text_input_field.dart index 6cd9d75..a409c68 100644 --- a/lib/presentation/widgets/text_input/text_input_field.dart +++ b/lib/presentation/widgets/text_input/text_input_field.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A custom text input field widget that encapsulates a [TextField] with specific styling. +/// - [controller]: The controller for the text input field. +/// - [onChanged]: The callback invoked when the text in the field changes. +/// - [hintText]: The hint text displayed in the text input field when it is empty class TextInputField extends StatelessWidget { - final TextEditingController controller; - final ValueChanged? onChanged; - final String hintText; - const TextInputField({ super.key, required this.controller, @@ -13,6 +13,15 @@ class TextInputField extends StatelessWidget { this.onChanged, }); + /// The controller for the text input field. + final TextEditingController controller; + + /// The callback invoked when the text in the field changes. + final ValueChanged? onChanged; + + /// The hint text displayed in the text input field when it is empty. + final String hintText; + @override Widget build(BuildContext context) { return TextField( diff --git a/lib/presentation/widgets/tiles/choose_tile.dart b/lib/presentation/widgets/tiles/choose_tile.dart index 10a695d..ba12c3d 100644 --- a/lib/presentation/widgets/tiles/choose_tile.dart +++ b/lib/presentation/widgets/tiles/choose_tile.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A tile widget that allows users to choose an option by tapping on it. +/// - [title]: The title text displayed on the tile. +/// - [trailingText]: Optional trailing text displayed on the tile. +/// - [onPressed]: The callback invoked when the tile is tapped. class ChooseTile extends StatefulWidget { - final String title; - final VoidCallback? onPressed; - final String? trailingText; const ChooseTile({ super.key, required this.title, @@ -12,6 +13,15 @@ class ChooseTile extends StatefulWidget { this.onPressed, }); + /// The title text displayed on the tile. + final String title; + + /// The callback invoked when the tile is tapped. + final VoidCallback? onPressed; + + /// Optional trailing text displayed on the tile. + final String? trailingText; + @override State createState() => _ChooseTileState(); } diff --git a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart index 11e8b40..d76cf3f 100644 --- a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart +++ b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A custom radio list tile widget that encapsulates a [Radio] button with additional styling and functionality. +/// - [text]: The text to display next to the radio button. +/// - [value]: The value associated with the radio button. +/// - [onContainerTap]: The callback invoked when the container is tapped. class CustomRadioListTile extends StatelessWidget { - final String text; - final T value; - final ValueChanged onContainerTap; - const CustomRadioListTile({ super.key, required this.text, @@ -13,6 +13,15 @@ class CustomRadioListTile extends StatelessWidget { required this.onContainerTap, }); + /// The text to display next to the radio button. + final String text; + + /// The value associated with the radio button. + final T value; + + /// The callback invoked when the container is tapped. + final ValueChanged onContainerTap; + @override Widget build(BuildContext context) { return GestureDetector( diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index 5f870de..8dee1cd 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -3,10 +3,16 @@ import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; +/// A tile widget that displays information about a group, including its name and members. +/// - [group]: The group data to be displayed. +/// - [isHighlighted]: Whether the tile should be highlighted. class GroupTile extends StatelessWidget { const GroupTile({super.key, required this.group, this.isHighlighted = false}); + /// The group data to be displayed. final Group group; + + /// Whether the tile should be highlighted. final bool isHighlighted; @override diff --git a/lib/presentation/widgets/tiles/info_tile.dart b/lib/presentation/widgets/tiles/info_tile.dart index ff73e59..3e11679 100644 --- a/lib/presentation/widgets/tiles/info_tile.dart +++ b/lib/presentation/widgets/tiles/info_tile.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A tile widget that displays a title with an icon and some content below it. +/// - [title]: The title text displayed on the tile. +/// - [icon]: The icon displayed next to the title. +/// - [content]: The content widget displayed below the title. +/// - [padding]: Optional padding for the tile content. +/// - [height]: Optional height for the tile. +/// - [width]: Optional width for the tile. class InfoTile extends StatefulWidget { - final String title; - final IconData icon; - final Widget content; - final EdgeInsets? padding; - final double? height; - final double? width; const InfoTile({ super.key, required this.title, @@ -18,6 +19,24 @@ class InfoTile extends StatefulWidget { this.width, }); + /// The title text displayed on the tile. + final String title; + + /// The icon displayed next to the title. + final IconData icon; + + /// The content widget displayed below the title. + final Widget content; + + /// Optional padding for the tile content. + final EdgeInsets? padding; + + /// Optional height for the tile. + final double? height; + + /// Optional width for the tile. + final double? width; + @override State createState() => _InfoTileState(); } diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index c455949..d764dd9 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -1,26 +1,41 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/dto/match.dart'; +import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:intl/intl.dart'; +/// A tile widget that displays information about a match, including its name, +/// creation date, associated group, winner, and players. +/// - [match]: The match data to be displayed. +/// - [onTap]: The callback invoked when the tile is tapped. class MatchTile extends StatefulWidget { - final Match match; - final VoidCallback onTap; - const MatchTile({super.key, required this.match, required this.onTap}); + /// The match data to be displayed. + final Match match; + + /// The callback invoked when the tile is tapped. + final VoidCallback onTap; + @override State createState() => _MatchTileState(); } class _MatchTileState extends State { + late final List _allPlayers; + + @override + void initState() { + super.initState(); + _allPlayers = _getCombinedPlayers(); + } + @override Widget build(BuildContext context) { final group = widget.match.group; final winner = widget.match.winner; - final allPlayers = _getAllPlayers(); final loc = AppLocalizations.of(context); return GestureDetector( @@ -114,7 +129,7 @@ class _MatchTileState extends State { const SizedBox(height: 12), ], - if (allPlayers.isNotEmpty) ...[ + if (_allPlayers.isNotEmpty) ...[ Text( loc.players, style: const TextStyle( @@ -127,7 +142,7 @@ class _MatchTileState extends State { Wrap( spacing: 6, runSpacing: 6, - children: allPlayers.map((player) { + children: _allPlayers.map((player) { return TextIconTile(text: player.name, iconEnabled: false); }).toList(), ), @@ -138,6 +153,8 @@ class _MatchTileState extends State { ); } + /// Formats the given [dateTime] into a human-readable string based on its + /// difference from the current date. String _formatDate(DateTime dateTime, BuildContext context) { final now = DateTime.now(); final difference = now.difference(dateTime); @@ -158,8 +175,10 @@ class _MatchTileState extends State { } } - List _getAllPlayers() { - final allPlayers = []; + /// Retrieves all unique players associated with the match, + /// combining players from both the match and its group. + List _getCombinedPlayers() { + final allPlayers = []; final playerIds = {}; // Add players from game.players diff --git a/lib/presentation/widgets/tiles/quick_info_tile.dart b/lib/presentation/widgets/tiles/quick_info_tile.dart index d360aba..839f6c3 100644 --- a/lib/presentation/widgets/tiles/quick_info_tile.dart +++ b/lib/presentation/widgets/tiles/quick_info_tile.dart @@ -1,13 +1,14 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A tile widget that displays a title with an icon and a numeric value below it. +/// - [title]: The title text displayed on the tile. +/// - [icon]: The icon displayed next to the title. +/// - [value]: The numeric value displayed below the title. +/// - [height]: Optional height for the tile. +/// - [width]: Optional width for the tile. +/// - [padding]: Optional padding for the tile content. class QuickInfoTile extends StatefulWidget { - final String title; - final IconData icon; - final int value; - final double? height; - final double? width; - final EdgeInsets? padding; const QuickInfoTile({ super.key, required this.title, @@ -18,6 +19,24 @@ class QuickInfoTile extends StatefulWidget { this.padding, }); + /// The title text displayed on the tile. + final String title; + + /// The icon displayed next to the title. + final IconData icon; + + /// The numeric value displayed below the title. + final int value; + + /// Optional height for the tile. + final double? height; + + /// Optional width for the tile. + final double? width; + + /// Optional padding for the tile content. + final EdgeInsets? padding; + @override State createState() => _QuickInfoTileState(); } diff --git a/lib/presentation/widgets/tiles/settings_list_tile.dart b/lib/presentation/widgets/tiles/settings_list_tile.dart index 6b43557..7fb0f80 100644 --- a/lib/presentation/widgets/tiles/settings_list_tile.dart +++ b/lib/presentation/widgets/tiles/settings_list_tile.dart @@ -1,19 +1,32 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A customizable settings list tile widget that displays an icon, title, and an optional suffix widget. +/// - [icon]: The icon displayed on the left side of the tile. +/// - [title]: The title text displayed next to the icon. +/// - [suffixWidget]: An optional widget displayed on the right side of the tile. +/// - [onPressed]: The callback invoked when the tile is tapped. class SettingsListTile extends StatelessWidget { - final VoidCallback? onPressed; - final IconData icon; - final String title; - final Widget? suffixWidget; const SettingsListTile({ super.key, - required this.title, required this.icon, + required this.title, this.suffixWidget, this.onPressed, }); + /// The icon displayed on the left side of the tile. + final IconData icon; + + /// The title text displayed next to the icon. + final String title; + + /// An optional widget displayed on the right side of the tile. + final Widget? suffixWidget; + + /// The callback invoked when the tile is tapped. + final VoidCallback? onPressed; + @override Widget build(BuildContext context) { return Padding( diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 598fad0..57ceb04 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -4,6 +4,13 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; +/// A tile widget that displays statistical data using horizontal bars. +/// - [icon]: The icon displayed next to the title. +/// - [title]: The title text displayed on the tile. +/// - [width]: The width of the tile. +/// - [values]: A list of tuples containing labels and their corresponding numeric values. +/// - [itemCount]: The maximum number of items to display. +/// - [barColor]: The color of the bars representing the values. class StatisticsTile extends StatelessWidget { const StatisticsTile({ super.key, @@ -15,11 +22,22 @@ class StatisticsTile extends StatelessWidget { required this.barColor, }); + /// The icon displayed next to the title. final IconData icon; + + /// The title text displayed on the tile. final String title; + + /// The width of the tile. final double width; + + /// A list of tuples containing labels and their corresponding numeric values. final List<(String, num)> values; + + /// The maximum number of items to display. final int itemCount; + + /// The color of the bars representing the values. final Color barColor; @override diff --git a/lib/presentation/widgets/tiles/text_icon_list_tile.dart b/lib/presentation/widgets/tiles/text_icon_list_tile.dart index b23ef75..7d3fe1c 100644 --- a/lib/presentation/widgets/tiles/text_icon_list_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_list_tile.dart @@ -1,18 +1,27 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A list tile widget that displays text with an optional icon button. +/// - [text]: The text to display in the tile. +/// - [onPressed]: The callback to be invoked when the icon is pressed. +/// - [iconEnabled]: A boolean to determine if the icon should be displayed. class TextIconListTile extends StatelessWidget { - final String text; - final VoidCallback? onPressed; - final bool iconEnabled; - const TextIconListTile({ super.key, required this.text, - this.onPressed, this.iconEnabled = true, + this.onPressed, }); + /// The text to display in the tile. + final String text; + + /// A boolean to determine if the icon should be displayed. + final bool iconEnabled; + + /// The callback to be invoked when the icon is pressed. + final VoidCallback? onPressed; + @override Widget build(BuildContext context) { return Container( diff --git a/lib/presentation/widgets/tiles/text_icon_tile.dart b/lib/presentation/widgets/tiles/text_icon_tile.dart index 2544837..7142b27 100644 --- a/lib/presentation/widgets/tiles/text_icon_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_tile.dart @@ -1,18 +1,27 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A tile widget that displays text with an optional icon that can be tapped. +/// - [text]: The text to display in the tile. +/// - [iconEnabled]: A boolean to determine if the icon should be displayed. +/// - [onIconTap]: The callback to be invoked when the icon is tapped. class TextIconTile extends StatelessWidget { - final String text; - final bool iconEnabled; - final VoidCallback? onIconTap; - const TextIconTile({ super.key, required this.text, - this.onIconTap, this.iconEnabled = true, + this.onIconTap, }); + /// The text to display in the tile. + final String text; + + /// A boolean to determine if the icon should be displayed. + final bool iconEnabled; + + /// The callback to be invoked when the icon is tapped. + final VoidCallback? onIconTap; + @override Widget build(BuildContext context) { return Container( diff --git a/lib/presentation/widgets/tiles/title_description_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart index 465c94d..781149e 100644 --- a/lib/presentation/widgets/tiles/title_description_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; +/// A list tile widget that displays a title and description, with optional highlighting and badge. +/// - [title]: The title text displayed on the tile. +/// - [description]: The description text displayed below the title. +/// - [onPressed]: The callback invoked when the tile is tapped. +/// - [isHighlighted]: A boolean to determine if the tile should be highlighted. +/// - [badgeText]: Optional text to display in a badge on the right side of the title. +/// - [badgeColor]: Optional color for the badge background. class TitleDescriptionListTile extends StatelessWidget { - final String title; - final String description; - final VoidCallback? onPressed; - final bool isHighlighted; - final String? badgeText; - final Color? badgeColor; - const TitleDescriptionListTile({ super.key, required this.title, @@ -19,6 +19,24 @@ class TitleDescriptionListTile extends StatelessWidget { this.badgeColor, }); + /// The title text displayed on the tile. + final String title; + + /// The description text displayed below the title. + final String description; + + /// The callback invoked when the tile is tapped. + final VoidCallback? onPressed; + + /// A boolean to determine if the tile should be highlighted. + final bool isHighlighted; + + /// Optional text to display in a badge on the right side of the title. + final String? badgeText; + + /// Optional color for the badge background. + final Color? badgeColor; + @override Widget build(BuildContext context) { return GestureDetector( diff --git a/lib/presentation/widgets/top_centered_message.dart b/lib/presentation/widgets/top_centered_message.dart index a5deea2..c15c93d 100644 --- a/lib/presentation/widgets/top_centered_message.dart +++ b/lib/presentation/widgets/top_centered_message.dart @@ -1,5 +1,9 @@ import 'package:flutter/material.dart'; +/// A widget that displays a message centered at the top of the screen with an icon, title, and message. +/// - [icon]: The icon to display above the title. +/// - [title]: The title text to display. +/// - [message]: The message text to display below the title. class TopCenteredMessage extends StatelessWidget { const TopCenteredMessage({ super.key, @@ -8,10 +12,15 @@ class TopCenteredMessage extends StatelessWidget { required this.message, }); - final String title; - final String message; + /// The icon to display above the title. final IconData icon; + /// The title text to display. + final String title; + + /// The message text to display below the title. + final String message; + @override Widget build(BuildContext context) { return Container( From 0f824bb30a3feb48804f5f00342f7e465b9810c4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:10:09 +0100 Subject: [PATCH 145/222] Optimized statistics tile --- .../widgets/tiles/statistics_tile.dart | 90 ++++++++++--------- 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 57ceb04..2c0ced0 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -42,7 +42,6 @@ class StatisticsTile extends StatelessWidget { @override Widget build(BuildContext context) { - final maxBarWidth = MediaQuery.of(context).size.width * 0.65; final loc = AppLocalizations.of(context); return InfoTile( @@ -57,38 +56,56 @@ class StatisticsTile extends StatelessWidget { heightFactor: 4, child: Text(loc.no_data_available), ), - child: Column( - children: List.generate(min(values.length, itemCount), (index) { - /// The maximum wins among all players - final maxMatches = values.isNotEmpty ? values[0].$2 : 0; + child: LayoutBuilder( + builder: (context, constraints) { + final maxBarWidth = constraints.maxWidth * 0.65; + return Column( + children: List.generate(min(values.length, itemCount), (index) { + /// The maximum wins among all players + final maxMatches = values.isNotEmpty ? values[0].$2 : 0; - /// Fraction of wins - final double fraction = (maxMatches > 0) - ? (values[index].$2 / maxMatches) - : 0.0; + /// Fraction of wins + final double fraction = (maxMatches > 0) + ? (values[index].$2 / maxMatches) + : 0.0; - /// Calculated width for current the bar - final double barWidth = maxBarWidth * fraction; + /// Calculated width for current the bar + final double barWidth = maxBarWidth * fraction; - return Padding( - padding: const EdgeInsets.symmetric(vertical: 2.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Stack( + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, children: [ - Container( - height: 24, - width: barWidth, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(4), - color: barColor, - ), + Stack( + children: [ + Container( + height: 24, + width: barWidth, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: barColor, + ), + ), + Padding( + padding: const EdgeInsets.only(left: 4.0), + child: Text( + values[index].$1, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ], ), - Padding( - padding: const EdgeInsets.only(left: 4.0), + const Spacer(), + Center( child: Text( - values[index].$1, + values[index].$2 <= 1 && values[index].$2 is double + ? values[index].$2.toStringAsFixed(2) + : values[index].$2.toString(), + textAlign: TextAlign.center, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, @@ -97,23 +114,10 @@ class StatisticsTile extends StatelessWidget { ), ], ), - const Spacer(), - Center( - child: Text( - values[index].$2 <= 1 && values[index].$2 is double - ? values[index].$2.toStringAsFixed(2) - : values[index].$2.toString(), - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), + ); + }), ); - }), + }, ), ), ), From 4b1d3923a094cdc977db2011dcbb55fbe2022d30 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:11:14 +0100 Subject: [PATCH 146/222] Optimized ruleset touples --- .../create_match/create_match_view.dart | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 775f29d..2bc56e6 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -67,6 +67,9 @@ class _CreateMatchViewState extends State { /// The currently selected players List? selectedPlayers; + /// List of available rulesets with their localized string representations + late final List<(Ruleset, String)> _rulesets; + @override void initState() { super.initState(); @@ -88,9 +91,11 @@ class _CreateMatchViewState extends State { }); } - List<(Ruleset, String)> _getRulesets(BuildContext context) { + @override + void didChangeDependencies() { + super.didChangeDependencies(); final loc = AppLocalizations.of(context); - return [ + _rulesets = [ (Ruleset.singleWinner, loc.ruleset_single_winner), (Ruleset.singleLoser, loc.ruleset_single_loser), (Ruleset.mostPoints, loc.ruleset_most_points), @@ -147,9 +152,9 @@ class _CreateMatchViewState extends State { if (selectedGameIndex != -1) { hintText = games[selectedGameIndex].$1; selectedRuleset = games[selectedGameIndex].$3; - selectedRulesetIndex = _getRulesets( - context, - ).indexWhere((r) => r.$1 == selectedRuleset); + selectedRulesetIndex = _rulesets.indexWhere( + (r) => r.$1 == selectedRuleset, + ); } else { hintText = 'Match Name'; selectedRuleset = null; @@ -163,17 +168,16 @@ class _CreateMatchViewState extends State { ? loc.none : translateRulesetToString(selectedRuleset!, context), onPressed: () async { - final rulesets = _getRulesets(context); selectedRuleset = await Navigator.of(context).push( MaterialPageRoute( builder: (context) => ChooseRulesetView( - rulesets: rulesets, + rulesets: _rulesets, initialRulesetIndex: selectedRulesetIndex, ), ), ); if (!mounted) return; - selectedRulesetIndex = rulesets.indexWhere( + selectedRulesetIndex = _rulesets.indexWhere( (r) => r.$1 == selectedRuleset, ); selectedGameIndex = -1; From 2811ea892e013359e4a1f4572437feafa621f189 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:15:23 +0100 Subject: [PATCH 147/222] Added dispose & formatting --- .../match_view/create_match/create_match_view.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 2bc56e6..fe1e8f5 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -91,6 +91,12 @@ class _CreateMatchViewState extends State { }); } + @override + void dispose() { + _matchNameController.dispose(); + super.dispose(); + } + @override void didChangeDependencies() { super.didChangeDependencies(); @@ -262,8 +268,8 @@ class _CreateMatchViewState extends State { /// Determines whether the "Create Game" button should be enabled based on /// the current state of the input fields. bool _enableCreateGameButton() { - return selectedGroup != null || - (selectedPlayers != null && selectedPlayers!.length > 1) && - selectedRuleset != null; + return (selectedGroup != null || + (selectedPlayers != null && selectedPlayers!.length > 1)) && + selectedRuleset != null; } } From aa936a938d71a8b6d9df6392d73b9f8ceacd45ca Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:23:47 +0100 Subject: [PATCH 148/222] Corrected translation --- lib/l10n/arb/app_de.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 89354bd..14e3213 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -64,7 +64,7 @@ "search_for_groups": "Nach Gruppen suchen", "search_for_players": "Nach Spieler:innen suchen", "select_winner": "Gewinner:in wählen:", - "selected_players": "Ausgewählte Spieler:in: {count}", + "selected_players": "Ausgewählte Spieler:innen: {count}", "settings": "Einstellungen", "single_loser": "Ein:e Verlierer:in", "single_winner": "Ein:e Gewinner:in", From bc3beac866f6ec95bea480a58ebf6646db2e068e Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:25:14 +0100 Subject: [PATCH 149/222] Replaced Matches with Spiele --- lib/l10n/arb/app_de.arb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 14e3213..de7db12 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -2,16 +2,16 @@ "@@locale": "de", "all_players": "Alle Spieler:innen:", "all_players_selected": "Alle Spieler:innen ausgewählt", - "amount_of_matches": "Anzahl der Matches", + "amount_of_matches": "Anzahl der Spiele", "cancel": "Abbrechen", "choose_game": "Spielvorlage wählen", "choose_group": "Gruppe wählen", "choose_ruleset": "Regelwerk wählen", "could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden", "create_group": "Gruppe erstellen", - "create_match": "Match erstellen", + "create_match": "Spiel erstellen", "create_new_group": "Neue Gruppe erstellen", - "create_new_match": "Neues Match erstellen", + "create_new_match": "Neues Spiel erstellen", "data_successfully_deleted": "Daten erfolgreich gelöscht", "data_successfully_exported": "Daten erfolgreich exportiert", "data_successfully_imported": "Daten erfolgreich importiert", @@ -34,19 +34,19 @@ "info": "Info", "invalid_schema": "Ungültiges Schema", "least_points": "Niedrigste Punkte", - "match_in_progress": "Match läuft...", - "match_name": "Matchname", - "matches": "Matches", + "match_in_progress": "Spiel läuft...", + "match_name": "Spieltitel", + "matches": "Spiele", "menu": "Menü", "most_points": "Höchste Punkte", "no_data_available": "Keine Daten verfügbar", "no_groups_created_yet": "Noch keine Gruppen erstellt", - "no_matches_created_yet": "Noch keine Matches erstellt", + "no_matches_created_yet": "Noch keine Spiele erstellt", "no_players_created_yet": "Noch keine Spieler:in erstellt", "no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden", "no_players_selected": "Keine Spieler:in ausgewählt", - "no_recent_matches_available": "Keine letzten Matches verfügbar", - "no_second_match_available": "Kein zweites Match verfügbar", + "no_recent_matches_available": "Keine letzten Spiele verfügbar", + "no_second_match_available": "Kein zweites Spiel verfügbar", "no_statistics_available": "Keine Statistiken verfügbar", "none": "Kein", "none_group": "Keine", @@ -55,7 +55,7 @@ "players": "Spieler:in", "players_count": "{count} Spieler", "quick_create": "Schnellzugriff", - "recent_matches": "Letzte Matches", + "recent_matches": "Letzte Spiele", "ruleset": "Regelwerk", "ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.", "ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.", From e196d6e5ef12a69d15fde17d105b52b857ff1960 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:32:34 +0100 Subject: [PATCH 150/222] Corrected strings --- lib/l10n/arb/app_de.arb | 4 +- lib/l10n/generated/app_localizations.dart | 732 +++++++++---------- lib/l10n/generated/app_localizations_de.dart | 306 ++++---- lib/l10n/generated/app_localizations_en.dart | 388 +++++----- 4 files changed, 713 insertions(+), 717 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index de7db12..f55668e 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -52,7 +52,7 @@ "none_group": "Keine", "not_available": "Nicht verfügbar", "player_name": "Spieler:innenname", - "players": "Spieler:in", + "players": "Spieler:innen", "players_count": "{count} Spieler", "quick_create": "Schnellzugriff", "recent_matches": "Letzte Spiele", @@ -76,7 +76,7 @@ "today_at": "Heute um {time}", "undo": "Rückgängig", "unknown_exception": "Unbekannter Fehler (siehe Konsole)", - "winner": "Gewinner:in: {winnerName}", + "winner": "Gewinner*in", "winrate": "Siegquote", "wins": "Siege", "yesterday_at": "Gestern um {time}" diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 951ff22..1743997 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -98,23 +98,29 @@ abstract class AppLocalizations { Locale('en'), ]; - /// Label for choosing a group + /// Label for all players list /// /// In en, this message translates to: - /// **'Choose Group'** - String get choose_group; + /// **'All players:'** + String get all_players; - /// Button text to create a new match + /// Message when all players are added to selection /// /// In en, this message translates to: - /// **'Create new match'** - String get create_new_match; + /// **'All players selected'** + String get all_players_selected; - /// Label for choosing a ruleset + /// Label for amount of matches statistic /// /// In en, this message translates to: - /// **'Choose Ruleset'** - String get choose_ruleset; + /// **'Amount of Matches'** + String get amount_of_matches; + + /// Cancel button text + /// + /// In en, this message translates to: + /// **'Cancel'** + String get cancel; /// Label for choosing a game /// @@ -122,11 +128,125 @@ abstract class AppLocalizations { /// **'Choose Game'** String get choose_game; - /// Label to select the winner + /// Label for choosing a group /// /// In en, this message translates to: - /// **'Select Winner:'** - String get select_winner; + /// **'Choose Group'** + String get choose_group; + + /// Label for choosing a ruleset + /// + /// In en, this message translates to: + /// **'Choose Ruleset'** + String get choose_ruleset; + + /// Error message when adding a player fails + /// + /// In en, this message translates to: + /// **'Could not add player {playerName}'** + String could_not_add_player(String playerName); + + /// Button text to create a group + /// + /// In en, this message translates to: + /// **'Create Group'** + String get create_group; + + /// Button text to create a match + /// + /// In en, this message translates to: + /// **'Create match'** + String get create_match; + + /// Button text to create a new group + /// + /// In en, this message translates to: + /// **'Create new group'** + String get create_new_group; + + /// Button text to create a new match + /// + /// In en, this message translates to: + /// **'Create new match'** + String get create_new_match; + + /// Success message after deleting data + /// + /// In en, this message translates to: + /// **'Data successfully deleted'** + String get data_successfully_deleted; + + /// Success message after exporting data + /// + /// In en, this message translates to: + /// **'Data successfully exported'** + String get data_successfully_exported; + + /// Success message after importing data + /// + /// In en, this message translates to: + /// **'Data successfully imported'** + String get data_successfully_imported; + + /// Date format for days ago + /// + /// In en, this message translates to: + /// **'{count} days ago'** + String days_ago(int count); + + /// Delete button text + /// + /// In en, this message translates to: + /// **'Delete'** + String get delete; + + /// Confirmation dialog for deleting all data + /// + /// In en, this message translates to: + /// **'Delete all data?'** + String get delete_all_data; + + /// Error message when group creation fails + /// + /// In en, this message translates to: + /// **'Error while creating group, please try again'** + String get error_creating_group; + + /// Error message when file cannot be read + /// + /// In en, this message translates to: + /// **'Error reading file'** + String get error_reading_file; + + /// Message when export is canceled + /// + /// In en, this message translates to: + /// **'Export canceled'** + String get export_canceled; + + /// Export data menu item + /// + /// In en, this message translates to: + /// **'Export data'** + String get export_data; + + /// Error message for format exceptions + /// + /// In en, this message translates to: + /// **'Format Exception (see console)'** + String get format_exception; + + /// Game label + /// + /// In en, this message translates to: + /// **'Game'** + String get game; + + /// Placeholder for game name search + /// + /// In en, this message translates to: + /// **'Game Name'** + String get game_name; /// App Name /// @@ -134,6 +254,126 @@ abstract class AppLocalizations { /// **'Game Tracker'** String get game_tracker; + /// Group label + /// + /// In en, this message translates to: + /// **'Group'** + String get group; + + /// Placeholder for group name input + /// + /// In en, this message translates to: + /// **'Group name'** + String get group_name; + + /// Label for groups + /// + /// In en, this message translates to: + /// **'Groups'** + String get groups; + + /// Home tab label + /// + /// In en, this message translates to: + /// **'Home'** + String get home; + + /// Message when import is canceled + /// + /// In en, this message translates to: + /// **'Import canceled'** + String get import_canceled; + + /// Import data menu item + /// + /// In en, this message translates to: + /// **'Import data'** + String get import_data; + + /// Info label + /// + /// In en, this message translates to: + /// **'Info'** + String get info; + + /// Error message for invalid schema + /// + /// In en, this message translates to: + /// **'Invalid Schema'** + String get invalid_schema; + + /// Title for least points ruleset + /// + /// In en, this message translates to: + /// **'Least Points'** + String get least_points; + + /// Message when match is in progress + /// + /// In en, this message translates to: + /// **'Match in progress...'** + String get match_in_progress; + + /// Placeholder for match name input + /// + /// In en, this message translates to: + /// **'Match name'** + String get match_name; + + /// Label for matches + /// + /// In en, this message translates to: + /// **'Matches'** + String get matches; + + /// Menu label + /// + /// In en, this message translates to: + /// **'Menu'** + String get menu; + + /// Title for most points ruleset + /// + /// In en, this message translates to: + /// **'Most Points'** + String get most_points; + + /// Message when no data in the statistic tiles is given + /// + /// In en, this message translates to: + /// **'No data available'** + String get no_data_available; + + /// Message when no groups exist + /// + /// In en, this message translates to: + /// **'No groups created yet'** + String get no_groups_created_yet; + + /// Message when no matches exist + /// + /// In en, this message translates to: + /// **'No matches created yet'** + String get no_matches_created_yet; + + /// Message when no players exist + /// + /// In en, this message translates to: + /// **'No players created yet'** + String get no_players_created_yet; + + /// Message when search returns no results + /// + /// In en, this message translates to: + /// **'No players found with that name'** + String get no_players_found_with_that_name; + + /// Message when no players are selected + /// + /// In en, this message translates to: + /// **'No players selected'** + String get no_players_selected; + /// Message when no recent matches exist /// /// In en, this message translates to: @@ -146,294 +386,12 @@ abstract class AppLocalizations { /// **'No second match available'** String get no_second_match_available; - /// Confirmation dialog for deleting all data - /// - /// In en, this message translates to: - /// **'Delete all data?'** - String get delete_all_data; - - /// Cancel button text - /// - /// In en, this message translates to: - /// **'Cancel'** - String get cancel; - - /// Delete button text - /// - /// In en, this message translates to: - /// **'Delete'** - String get delete; - - /// Button text to create a new group - /// - /// In en, this message translates to: - /// **'Create new group'** - String get create_new_group; - - /// Error message when group creation fails - /// - /// In en, this message translates to: - /// **'Error while creating group, please try again'** - String get error_creating_group; - - /// Shows the number of selected players - /// - /// In en, this message translates to: - /// **'Selected players: {count}'** - String selected_players(int count); - - /// Message when no players are selected - /// - /// In en, this message translates to: - /// **'No players selected'** - String get no_players_selected; - - /// Label for all players list - /// - /// In en, this message translates to: - /// **'All players:'** - String get all_players; - - /// Success message when adding a player - /// - /// In en, this message translates to: - /// **'Successfully added player {playerName}'** - String successfully_added_player(String playerName); - - /// Error message when adding a player fails - /// - /// In en, this message translates to: - /// **'Could not add player {playerName}'** - String could_not_add_player(String playerName); - - /// Shows the winner's name - /// - /// In en, this message translates to: - /// **'Winner: {winnerName}'** - String winner(String winnerName); - - /// Players label - /// - /// In en, this message translates to: - /// **'Players'** - String get players; - /// Message when no statistics are available, because no matches were played yet /// /// In en, this message translates to: /// **'No statistics available'** String get no_statistics_available; - /// Message when no data in the statistic tiles is given - /// - /// In en, this message translates to: - /// **'No data available'** - String get no_data_available; - - /// Label for matches - /// - /// In en, this message translates to: - /// **'Matches'** - String get matches; - - /// Label for groups - /// - /// In en, this message translates to: - /// **'Groups'** - String get groups; - - /// Title for recent matches section - /// - /// In en, this message translates to: - /// **'Recent Matches'** - String get recent_matches; - - /// Title for quick create section - /// - /// In en, this message translates to: - /// **'Quick Create'** - String get quick_create; - - /// Message when match is in progress - /// - /// In en, this message translates to: - /// **'Match in progress...'** - String get match_in_progress; - - /// Menu label - /// - /// In en, this message translates to: - /// **'Menu'** - String get menu; - - /// Settings label - /// - /// In en, this message translates to: - /// **'Settings'** - String get settings; - - /// Export data menu item - /// - /// In en, this message translates to: - /// **'Export data'** - String get export_data; - - /// Import data menu item - /// - /// In en, this message translates to: - /// **'Import data'** - String get import_data; - - /// Warning message for irreversible actions - /// - /// In en, this message translates to: - /// **'This can\'t be undone'** - String get this_cannot_be_undone; - - /// Success message after deleting data - /// - /// In en, this message translates to: - /// **'Data successfully deleted'** - String get data_successfully_deleted; - - /// Success message after importing data - /// - /// In en, this message translates to: - /// **'Data successfully imported'** - String get data_successfully_imported; - - /// Error message for invalid schema - /// - /// In en, this message translates to: - /// **'Invalid Schema'** - String get invalid_schema; - - /// Error message when file cannot be read - /// - /// In en, this message translates to: - /// **'Error reading file'** - String get error_reading_file; - - /// Message when import is canceled - /// - /// In en, this message translates to: - /// **'Import canceled'** - String get import_canceled; - - /// Error message for format exceptions - /// - /// In en, this message translates to: - /// **'Format Exception (see console)'** - String get format_exception; - - /// Error message for unknown exceptions - /// - /// In en, this message translates to: - /// **'Unknown Exception (see console)'** - String get unknown_exception; - - /// Success message after exporting data - /// - /// In en, this message translates to: - /// **'Data successfully exported'** - String get data_successfully_exported; - - /// Message when export is canceled - /// - /// In en, this message translates to: - /// **'Export canceled'** - String get export_canceled; - - /// Undo button text - /// - /// In en, this message translates to: - /// **'Undo'** - String get undo; - - /// Label for wins statistic - /// - /// In en, this message translates to: - /// **'Wins'** - String get wins; - - /// Label for winrate statistic - /// - /// In en, this message translates to: - /// **'Winrate'** - String get winrate; - - /// Label for amount of matches statistic - /// - /// In en, this message translates to: - /// **'Amount of Matches'** - String get amount_of_matches; - - /// Info label - /// - /// In en, this message translates to: - /// **'Info'** - String get info; - - /// Message when no groups exist - /// - /// In en, this message translates to: - /// **'No groups created yet'** - String get no_groups_created_yet; - - /// Message when no players exist - /// - /// In en, this message translates to: - /// **'No players created yet'** - String get no_players_created_yet; - - /// Button text to create a group - /// - /// In en, this message translates to: - /// **'Create Group'** - String get create_group; - - /// Placeholder for group name input - /// - /// In en, this message translates to: - /// **'Group name'** - String get group_name; - - /// Placeholder for player name input - /// - /// In en, this message translates to: - /// **'Player name'** - String get player_name; - - /// Message when no matches exist - /// - /// In en, this message translates to: - /// **'No matches created yet'** - String get no_matches_created_yet; - - /// Placeholder for match name input - /// - /// In en, this message translates to: - /// **'Match name'** - String get match_name; - - /// Game label - /// - /// In en, this message translates to: - /// **'Game'** - String get game; - - /// Ruleset label - /// - /// In en, this message translates to: - /// **'Ruleset'** - String get ruleset; - - /// Group label - /// - /// In en, this message translates to: - /// **'Group'** - String get group; - /// None option label /// /// In en, this message translates to: @@ -446,47 +404,113 @@ abstract class AppLocalizations { /// **'None'** String get none_group; - /// Button text to create a match + /// Abbreviation for not available /// /// In en, this message translates to: - /// **'Create match'** - String get create_match; + /// **'Not available'** + String get not_available; - /// Message when search returns no results + /// Placeholder for player name input /// /// In en, this message translates to: - /// **'No players found with that name'** - String get no_players_found_with_that_name; + /// **'Player name'** + String get player_name; - /// Message when all players are added to selection + /// Players label /// /// In en, this message translates to: - /// **'All players selected'** - String get all_players_selected; + /// **'Players'** + String get players; - /// Date format for today + /// Shows the number of players /// /// In en, this message translates to: - /// **'Today at {time}'** - String today_at(String time); + /// **'{count} Players'** + String players_count(int count); - /// Date format for yesterday + /// Title for quick create section /// /// In en, this message translates to: - /// **'Yesterday at {time}'** - String yesterday_at(String time); + /// **'Quick Create'** + String get quick_create; - /// Date format for days ago + /// Title for recent matches section /// /// In en, this message translates to: - /// **'{count} days ago'** - String days_ago(int count); + /// **'Recent Matches'** + String get recent_matches; - /// Home tab label + /// Ruleset label /// /// In en, this message translates to: - /// **'Home'** - String get home; + /// **'Ruleset'** + String get ruleset; + + /// Description for least points ruleset + /// + /// In en, this message translates to: + /// **'Inverse scoring: the player with the fewest points wins.'** + String get ruleset_least_points; + + /// Description for most points ruleset + /// + /// In en, this message translates to: + /// **'Traditional ruleset: the player with the most points wins.'** + String get ruleset_most_points; + + /// Description for single loser ruleset + /// + /// In en, this message translates to: + /// **'Exactly one loser is determined; last place receives the penalty or consequence.'** + String get ruleset_single_loser; + + /// Description for single winner ruleset + /// + /// In en, this message translates to: + /// **'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'** + String get ruleset_single_winner; + + /// Hint text for group search input field + /// + /// In en, this message translates to: + /// **'Search for groups'** + String get search_for_groups; + + /// Hint text for player search input field + /// + /// In en, this message translates to: + /// **'Search for players'** + String get search_for_players; + + /// Label to select the winner + /// + /// In en, this message translates to: + /// **'Select Winner:'** + String get select_winner; + + /// Shows the number of selected players + /// + /// In en, this message translates to: + /// **'Selected players: {count}'** + String selected_players(int count); + + /// Settings label + /// + /// In en, this message translates to: + /// **'Settings'** + String get settings; + + /// Title for single loser ruleset + /// + /// In en, this message translates to: + /// **'Single Loser'** + String get single_loser; + + /// Title for single winner ruleset + /// + /// In en, this message translates to: + /// **'Single Winner'** + String get single_winner; /// Statistics tab label /// @@ -500,11 +524,11 @@ abstract class AppLocalizations { /// **'Stats'** String get stats; - /// Shows the number of players + /// Success message when adding a player /// /// In en, this message translates to: - /// **'{count} Players'** - String players_count(int count); + /// **'Successfully added player {playerName}'** + String successfully_added_player(String playerName); /// Message when search returns no groups /// @@ -512,77 +536,53 @@ abstract class AppLocalizations { /// **'There is no group matching your search'** String get there_is_no_group_matching_your_search; - /// Placeholder for game name search + /// Warning message for irreversible actions /// /// In en, this message translates to: - /// **'Game Name'** - String get game_name; + /// **'This can\'t be undone'** + String get this_cannot_be_undone; - /// Description for single winner ruleset + /// Date format for today /// /// In en, this message translates to: - /// **'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'** - String get ruleset_single_winner; + /// **'Today at {time}'** + String today_at(String time); - /// Description for single loser ruleset + /// Undo button text /// /// In en, this message translates to: - /// **'Exactly one loser is determined; last place receives the penalty or consequence.'** - String get ruleset_single_loser; + /// **'Undo'** + String get undo; - /// Description for most points ruleset + /// Error message for unknown exceptions /// /// In en, this message translates to: - /// **'Traditional ruleset: the player with the most points wins.'** - String get ruleset_most_points; + /// **'Unknown Exception (see console)'** + String get unknown_exception; - /// Description for least points ruleset + /// Winner label /// /// In en, this message translates to: - /// **'Inverse scoring: the player with the fewest points wins.'** - String get ruleset_least_points; + /// **'Winner'** + String get winner; - /// Title for single winner ruleset + /// Label for winrate statistic /// /// In en, this message translates to: - /// **'Single Winner'** - String get single_winner; + /// **'Winrate'** + String get winrate; - /// Title for single loser ruleset + /// Label for wins statistic /// /// In en, this message translates to: - /// **'Single Loser'** - String get single_loser; + /// **'Wins'** + String get wins; - /// Title for most points ruleset + /// Date format for yesterday /// /// In en, this message translates to: - /// **'Most Points'** - String get most_points; - - /// Title for least points ruleset - /// - /// In en, this message translates to: - /// **'Least Points'** - String get least_points; - - /// Hint text for player search input field - /// - /// In en, this message translates to: - /// **'Search for players'** - String get search_for_players; - - /// Hint text for group search input field - /// - /// In en, this message translates to: - /// **'Search for groups'** - String get search_for_groups; - - /// Abbreviation for not available - /// - /// In en, this message translates to: - /// **'Not available'** - String get not_available; + /// **'Yesterday at {time}'** + String yesterday_at(String time); } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 3f3e36e..4421fd1 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -9,65 +9,25 @@ class AppLocalizationsDe extends AppLocalizations { AppLocalizationsDe([String locale = 'de']) : super(locale); @override - String get choose_group => 'Gruppe wählen'; + String get all_players => 'Alle Spieler:innen:'; @override - String get create_new_match => 'Neues Match erstellen'; + String get all_players_selected => 'Alle Spieler:innen ausgewählt'; @override - String get choose_ruleset => 'Regelwerk wählen'; - - @override - String get choose_game => 'Spielvorlage wählen'; - - @override - String get select_winner => 'Gewinner:in wählen:'; - - @override - String get game_tracker => 'Game Tracker'; - - @override - String get no_recent_matches_available => 'Keine letzten Matches verfügbar'; - - @override - String get no_second_match_available => 'Kein zweites Match verfügbar'; - - @override - String get delete_all_data => 'Alle Daten löschen?'; + String get amount_of_matches => 'Anzahl der Spiele'; @override String get cancel => 'Abbrechen'; @override - String get delete => 'Löschen'; + String get choose_game => 'Spielvorlage wählen'; @override - String get create_new_group => 'Neue Gruppe erstellen'; + String get choose_group => 'Gruppe wählen'; @override - String get error_creating_group => - 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; - - @override - String selected_players(int count) { - final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( - locale: localeName, - ); - final String countString = countNumberFormat.format(count); - - return 'Ausgewählte Spieler:in: $countString'; - } - - @override - String get no_players_selected => 'Keine Spieler:in ausgewählt'; - - @override - String get all_players => 'Alle Spieler:innen:'; - - @override - String successfully_added_player(String playerName) { - return 'Spieler:in $playerName erfolgreich hinzugefügt'; - } + String get choose_ruleset => 'Regelwerk wählen'; @override String could_not_add_player(String playerName) { @@ -75,121 +35,131 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String winner(String winnerName) { - return 'Gewinner:in: $winnerName'; - } + String get create_group => 'Gruppe erstellen'; @override - String get players => 'Spieler:in'; + String get create_match => 'Spiel erstellen'; @override - String get no_statistics_available => 'Keine Statistiken verfügbar'; + String get create_new_group => 'Neue Gruppe erstellen'; @override - String get no_data_available => 'Keine Daten verfügbar'; - - @override - String get matches => 'Matches'; - - @override - String get groups => 'Gruppen'; - - @override - String get recent_matches => 'Letzte Matches'; - - @override - String get quick_create => 'Schnellzugriff'; - - @override - String get match_in_progress => 'Match läuft...'; - - @override - String get menu => 'Menü'; - - @override - String get settings => 'Einstellungen'; - - @override - String get export_data => 'Daten exportieren'; - - @override - String get import_data => 'Daten importieren'; - - @override - String get this_cannot_be_undone => - 'Dies kann nicht rückgängig gemacht werden'; + String get create_new_match => 'Neues Spiel erstellen'; @override String get data_successfully_deleted => 'Daten erfolgreich gelöscht'; + @override + String get data_successfully_exported => 'Daten erfolgreich exportiert'; + @override String get data_successfully_imported => 'Daten erfolgreich importiert'; @override - String get invalid_schema => 'Ungültiges Schema'; + String days_ago(int count) { + return 'vor $count Tagen'; + } + + @override + String get delete => 'Löschen'; + + @override + String get delete_all_data => 'Alle Daten löschen?'; + + @override + String get error_creating_group => + 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; @override String get error_reading_file => 'Fehler beim Lesen der Datei'; @override - String get import_canceled => 'Import abgebrochen'; + String get export_canceled => 'Export abgebrochen'; + + @override + String get export_data => 'Daten exportieren'; @override String get format_exception => 'Formatfehler (siehe Konsole)'; @override - String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; + String get game => 'Spielvorlage'; @override - String get data_successfully_exported => 'Daten erfolgreich exportiert'; + String get game_name => 'Spielvorlagenname'; @override - String get export_canceled => 'Export abgebrochen'; + String get game_tracker => 'Game Tracker'; @override - String get undo => 'Rückgängig'; - - @override - String get wins => 'Siege'; - - @override - String get winrate => 'Siegquote'; - - @override - String get amount_of_matches => 'Anzahl der Matches'; - - @override - String get info => 'Info'; - - @override - String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; - - @override - String get no_players_created_yet => 'Noch keine Spieler:in erstellt'; - - @override - String get create_group => 'Gruppe erstellen'; + String get group => 'Gruppe'; @override String get group_name => 'Gruppenname'; @override - String get player_name => 'Spieler:innenname'; + String get groups => 'Gruppen'; @override - String get no_matches_created_yet => 'Noch keine Matches erstellt'; + String get home => 'Startseite'; @override - String get match_name => 'Matchname'; + String get import_canceled => 'Import abgebrochen'; @override - String get game => 'Spielvorlage'; + String get import_data => 'Daten importieren'; @override - String get ruleset => 'Regelwerk'; + String get info => 'Info'; @override - String get group => 'Gruppe'; + String get invalid_schema => 'Ungültiges Schema'; + + @override + String get least_points => 'Niedrigste Punkte'; + + @override + String get match_in_progress => 'Spiel läuft...'; + + @override + String get match_name => 'Spieltitel'; + + @override + String get matches => 'Spiele'; + + @override + String get menu => 'Menü'; + + @override + String get most_points => 'Höchste Punkte'; + + @override + String get no_data_available => 'Keine Daten verfügbar'; + + @override + String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; + + @override + String get no_matches_created_yet => 'Noch keine Spiele erstellt'; + + @override + String get no_players_created_yet => 'Noch keine Spieler:in erstellt'; + + @override + String get no_players_found_with_that_name => + 'Keine Spieler:in mit diesem Namen gefunden'; + + @override + String get no_players_selected => 'Keine Spieler:in ausgewählt'; + + @override + String get no_recent_matches_available => 'Keine letzten Spiele verfügbar'; + + @override + String get no_second_match_available => 'Kein zweites Spiel verfügbar'; + + @override + String get no_statistics_available => 'Keine Statistiken verfügbar'; @override String get none => 'Kein'; @@ -198,32 +168,71 @@ class AppLocalizationsDe extends AppLocalizations { String get none_group => 'Keine'; @override - String get create_match => 'Match erstellen'; + String get not_available => 'Nicht verfügbar'; @override - String get no_players_found_with_that_name => - 'Keine Spieler:in mit diesem Namen gefunden'; + String get player_name => 'Spieler:innenname'; @override - String get all_players_selected => 'Alle Spieler:innen ausgewählt'; + String get players => 'Spieler:innen'; @override - String today_at(String time) { - return 'Heute um $time'; + String players_count(int count) { + return '$count Spieler'; } @override - String yesterday_at(String time) { - return 'Gestern um $time'; + String get quick_create => 'Schnellzugriff'; + + @override + String get recent_matches => 'Letzte Spiele'; + + @override + String get ruleset => 'Regelwerk'; + + @override + String get ruleset_least_points => + 'Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.'; + + @override + String get ruleset_most_points => + 'Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.'; + + @override + String get ruleset_single_loser => + 'Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; + + @override + String get ruleset_single_winner => + 'Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; + + @override + String get search_for_groups => 'Nach Gruppen suchen'; + + @override + String get search_for_players => 'Nach Spieler:innen suchen'; + + @override + String get select_winner => 'Gewinner:in wählen:'; + + @override + String selected_players(int count) { + final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( + locale: localeName, + ); + final String countString = countNumberFormat.format(count); + + return 'Ausgewählte Spieler:innen: $countString'; } @override - String days_ago(int count) { - return 'vor $count Tagen'; - } + String get settings => 'Einstellungen'; @override - String get home => 'Startseite'; + String get single_loser => 'Ein:e Verlierer:in'; + + @override + String get single_winner => 'Ein:e Gewinner:in'; @override String get statistics => 'Statistiken'; @@ -232,8 +241,8 @@ class AppLocalizationsDe extends AppLocalizations { String get stats => 'Statistiken'; @override - String players_count(int count) { - return '$count Spieler'; + String successfully_added_player(String playerName) { + return 'Spieler:in $playerName erfolgreich hinzugefügt'; } @override @@ -241,42 +250,31 @@ class AppLocalizationsDe extends AppLocalizations { 'Es gibt keine Gruppe, die deiner Suche entspricht'; @override - String get game_name => 'Spielvorlagenname'; + String get this_cannot_be_undone => + 'Dies kann nicht rückgängig gemacht werden'; @override - String get ruleset_single_winner => - 'Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; + String today_at(String time) { + return 'Heute um $time'; + } @override - String get ruleset_single_loser => - 'Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; + String get undo => 'Rückgängig'; @override - String get ruleset_most_points => - 'Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.'; + String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; @override - String get ruleset_least_points => - 'Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.'; + String get winner => 'Gewinner*in'; @override - String get single_winner => 'Ein:e Gewinner:in'; + String get winrate => 'Siegquote'; @override - String get single_loser => 'Ein:e Verlierer:in'; + String get wins => 'Siege'; @override - String get most_points => 'Höchste Punkte'; - - @override - String get least_points => 'Niedrigste Punkte'; - - @override - String get search_for_players => 'Nach Spieler:innen suchen'; - - @override - String get search_for_groups => 'Nach Gruppen suchen'; - - @override - String get not_available => 'Nicht verfügbar'; + String yesterday_at(String time) { + return 'Gestern um $time'; + } } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 263714c..0cd8842 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -9,23 +9,149 @@ class AppLocalizationsEn extends AppLocalizations { AppLocalizationsEn([String locale = 'en']) : super(locale); @override - String get choose_group => 'Choose Group'; + String get all_players => 'All players:'; @override - String get create_new_match => 'Create new match'; + String get all_players_selected => 'All players selected'; @override - String get choose_ruleset => 'Choose Ruleset'; + String get amount_of_matches => 'Amount of Matches'; + + @override + String get cancel => 'Cancel'; @override String get choose_game => 'Choose Game'; @override - String get select_winner => 'Select Winner:'; + String get choose_group => 'Choose Group'; + + @override + String get choose_ruleset => 'Choose Ruleset'; + + @override + String could_not_add_player(String playerName) { + return 'Could not add player $playerName'; + } + + @override + String get create_group => 'Create Group'; + + @override + String get create_match => 'Create match'; + + @override + String get create_new_group => 'Create new group'; + + @override + String get create_new_match => 'Create new match'; + + @override + String get data_successfully_deleted => 'Data successfully deleted'; + + @override + String get data_successfully_exported => 'Data successfully exported'; + + @override + String get data_successfully_imported => 'Data successfully imported'; + + @override + String days_ago(int count) { + return '$count days ago'; + } + + @override + String get delete => 'Delete'; + + @override + String get delete_all_data => 'Delete all data?'; + + @override + String get error_creating_group => + 'Error while creating group, please try again'; + + @override + String get error_reading_file => 'Error reading file'; + + @override + String get export_canceled => 'Export canceled'; + + @override + String get export_data => 'Export data'; + + @override + String get format_exception => 'Format Exception (see console)'; + + @override + String get game => 'Game'; + + @override + String get game_name => 'Game Name'; @override String get game_tracker => 'Game Tracker'; + @override + String get group => 'Group'; + + @override + String get group_name => 'Group name'; + + @override + String get groups => 'Groups'; + + @override + String get home => 'Home'; + + @override + String get import_canceled => 'Import canceled'; + + @override + String get import_data => 'Import data'; + + @override + String get info => 'Info'; + + @override + String get invalid_schema => 'Invalid Schema'; + + @override + String get least_points => 'Least Points'; + + @override + String get match_in_progress => 'Match in progress...'; + + @override + String get match_name => 'Match name'; + + @override + String get matches => 'Matches'; + + @override + String get menu => 'Menu'; + + @override + String get most_points => 'Most Points'; + + @override + String get no_data_available => 'No data available'; + + @override + String get no_groups_created_yet => 'No groups created yet'; + + @override + String get no_matches_created_yet => 'No matches created yet'; + + @override + String get no_players_created_yet => 'No players created yet'; + + @override + String get no_players_found_with_that_name => + 'No players found with that name'; + + @override + String get no_players_selected => 'No players selected'; + @override String get no_recent_matches_available => 'No recent matches available'; @@ -33,20 +159,61 @@ class AppLocalizationsEn extends AppLocalizations { String get no_second_match_available => 'No second match available'; @override - String get delete_all_data => 'Delete all data?'; + String get no_statistics_available => 'No statistics available'; @override - String get cancel => 'Cancel'; + String get none => 'None'; @override - String get delete => 'Delete'; + String get none_group => 'None'; @override - String get create_new_group => 'Create new group'; + String get not_available => 'Not available'; @override - String get error_creating_group => - 'Error while creating group, please try again'; + String get player_name => 'Player name'; + + @override + String get players => 'Players'; + + @override + String players_count(int count) { + return '$count Players'; + } + + @override + String get quick_create => 'Quick Create'; + + @override + String get recent_matches => 'Recent Matches'; + + @override + String get ruleset => 'Ruleset'; + + @override + String get ruleset_least_points => + 'Inverse scoring: the player with the fewest points wins.'; + + @override + String get ruleset_most_points => + 'Traditional ruleset: the player with the most points wins.'; + + @override + String get ruleset_single_loser => + 'Exactly one loser is determined; last place receives the penalty or consequence.'; + + @override + String get ruleset_single_winner => + 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'; + + @override + String get search_for_groups => 'Search for groups'; + + @override + String get search_for_players => 'Search for players'; + + @override + String get select_winner => 'Select Winner:'; @override String selected_players(int count) { @@ -58,171 +225,14 @@ class AppLocalizationsEn extends AppLocalizations { return 'Selected players: $countString'; } - @override - String get no_players_selected => 'No players selected'; - - @override - String get all_players => 'All players:'; - - @override - String successfully_added_player(String playerName) { - return 'Successfully added player $playerName'; - } - - @override - String could_not_add_player(String playerName) { - return 'Could not add player $playerName'; - } - - @override - String winner(String winnerName) { - return 'Winner: $winnerName'; - } - - @override - String get players => 'Players'; - - @override - String get no_statistics_available => 'No statistics available'; - - @override - String get no_data_available => 'No data available'; - - @override - String get matches => 'Matches'; - - @override - String get groups => 'Groups'; - - @override - String get recent_matches => 'Recent Matches'; - - @override - String get quick_create => 'Quick Create'; - - @override - String get match_in_progress => 'Match in progress...'; - - @override - String get menu => 'Menu'; - @override String get settings => 'Settings'; @override - String get export_data => 'Export data'; + String get single_loser => 'Single Loser'; @override - String get import_data => 'Import data'; - - @override - String get this_cannot_be_undone => 'This can\'t be undone'; - - @override - String get data_successfully_deleted => 'Data successfully deleted'; - - @override - String get data_successfully_imported => 'Data successfully imported'; - - @override - String get invalid_schema => 'Invalid Schema'; - - @override - String get error_reading_file => 'Error reading file'; - - @override - String get import_canceled => 'Import canceled'; - - @override - String get format_exception => 'Format Exception (see console)'; - - @override - String get unknown_exception => 'Unknown Exception (see console)'; - - @override - String get data_successfully_exported => 'Data successfully exported'; - - @override - String get export_canceled => 'Export canceled'; - - @override - String get undo => 'Undo'; - - @override - String get wins => 'Wins'; - - @override - String get winrate => 'Winrate'; - - @override - String get amount_of_matches => 'Amount of Matches'; - - @override - String get info => 'Info'; - - @override - String get no_groups_created_yet => 'No groups created yet'; - - @override - String get no_players_created_yet => 'No players created yet'; - - @override - String get create_group => 'Create Group'; - - @override - String get group_name => 'Group name'; - - @override - String get player_name => 'Player name'; - - @override - String get no_matches_created_yet => 'No matches created yet'; - - @override - String get match_name => 'Match name'; - - @override - String get game => 'Game'; - - @override - String get ruleset => 'Ruleset'; - - @override - String get group => 'Group'; - - @override - String get none => 'None'; - - @override - String get none_group => 'None'; - - @override - String get create_match => 'Create match'; - - @override - String get no_players_found_with_that_name => - 'No players found with that name'; - - @override - String get all_players_selected => 'All players selected'; - - @override - String today_at(String time) { - return 'Today at $time'; - } - - @override - String yesterday_at(String time) { - return 'Yesterday at $time'; - } - - @override - String days_ago(int count) { - return '$count days ago'; - } - - @override - String get home => 'Home'; + String get single_winner => 'Single Winner'; @override String get statistics => 'Statistics'; @@ -231,8 +241,8 @@ class AppLocalizationsEn extends AppLocalizations { String get stats => 'Stats'; @override - String players_count(int count) { - return '$count Players'; + String successfully_added_player(String playerName) { + return 'Successfully added player $playerName'; } @override @@ -240,42 +250,30 @@ class AppLocalizationsEn extends AppLocalizations { 'There is no group matching your search'; @override - String get game_name => 'Game Name'; + String get this_cannot_be_undone => 'This can\'t be undone'; @override - String get ruleset_single_winner => - 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'; + String today_at(String time) { + return 'Today at $time'; + } @override - String get ruleset_single_loser => - 'Exactly one loser is determined; last place receives the penalty or consequence.'; + String get undo => 'Undo'; @override - String get ruleset_most_points => - 'Traditional ruleset: the player with the most points wins.'; + String get unknown_exception => 'Unknown Exception (see console)'; @override - String get ruleset_least_points => - 'Inverse scoring: the player with the fewest points wins.'; + String get winner => 'Winner'; @override - String get single_winner => 'Single Winner'; + String get winrate => 'Winrate'; @override - String get single_loser => 'Single Loser'; + String get wins => 'Wins'; @override - String get most_points => 'Most Points'; - - @override - String get least_points => 'Least Points'; - - @override - String get search_for_players => 'Search for players'; - - @override - String get search_for_groups => 'Search for groups'; - - @override - String get not_available => 'Not available'; + String yesterday_at(String time) { + return 'Yesterday at $time'; + } } From cd2770be263ce47fafca0583f6276eaa4b3d67de Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:33:52 +0100 Subject: [PATCH 151/222] Removed unnessecary setStates --- .../views/main_menu/group_view/create_group_view.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index 022a4b5..8037de4 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -64,9 +64,6 @@ class _CreateGroupViewState extends State { child: TextInputField( controller: _groupNameController, hintText: loc.group_name, - onChanged: (value) { - setState(() {}); - }, ), ), Expanded( @@ -111,7 +108,6 @@ class _CreateGroupViewState extends State { ), ); } - setState(() {}); }, ), const SizedBox(height: 20), From fdd0e7579ac6e486ad57cfb4fb74d108e081f7b7 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:34:37 +0100 Subject: [PATCH 152/222] Updated game title process --- .../create_match/create_match_view.dart | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index fe1e8f5..7b7deb0 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -32,7 +32,7 @@ class _CreateMatchViewState extends State { final TextEditingController _matchNameController = TextEditingController(); /// Hint text for the match name input field - String hintText = 'Match Name'; + String? hintText; /// List of all groups from the database List groupsList = []; @@ -101,6 +101,7 @@ class _CreateMatchViewState extends State { void didChangeDependencies() { super.didChangeDependencies(); final loc = AppLocalizations.of(context); + hintText ??= loc.match_name; _rulesets = [ (Ruleset.singleWinner, loc.ruleset_single_winner), (Ruleset.singleLoser, loc.ruleset_single_loser), @@ -137,7 +138,7 @@ class _CreateMatchViewState extends State { margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), child: TextInputField( controller: _matchNameController, - hintText: hintText, + hintText: hintText ?? '', ), ), ChooseTile( @@ -162,7 +163,7 @@ class _CreateMatchViewState extends State { (r) => r.$1 == selectedRuleset, ); } else { - hintText = 'Match Name'; + hintText = AppLocalizations.of(context).match_name; selectedRuleset = null; } }); @@ -237,7 +238,7 @@ class _CreateMatchViewState extends State { ? () async { Match match = Match( name: _matchNameController.text.isEmpty - ? hintText + ? (hintText ?? '') : _matchNameController.text.trim(), createdAt: DateTime.now(), group: selectedGroup, @@ -265,8 +266,11 @@ class _CreateMatchViewState extends State { ); } - /// Determines whether the "Create Game" button should be enabled based on - /// the current state of the input fields. + /// Determines whether the "Create Match" button should be enabled. + /// + /// Returns `true` if: + /// - A ruleset is selected AND + /// - Either a group is selected OR at least 2 players are selected bool _enableCreateGameButton() { return (selectedGroup != null || (selectedPlayers != null && selectedPlayers!.length > 1)) && From 02d79574dd024194221b0df8f8a5c7a24b18a406 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:43:13 +0100 Subject: [PATCH 153/222] Simplified app bar logic in views --- lib/core/custom_theme.dart | 3 +++ .../main_menu/group_view/create_group_view.dart | 10 +--------- .../match_view/create_match/choose_game_view.dart | 8 +------- .../match_view/create_match/choose_group_view.dart | 8 +------- .../match_view/create_match/choose_ruleset_view.dart | 8 +------- .../match_view/create_match/create_match_view.dart | 10 +--------- .../main_menu/match_view/match_result_view.dart | 12 +----------- 7 files changed, 9 insertions(+), 50 deletions(-) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index 5930901..a9a31b2 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -25,10 +25,13 @@ class CustomTheme { backgroundColor: backgroundColor, foregroundColor: Colors.white, elevation: 0, + scrolledUnderElevation: 0, + centerTitle: true, titleTextStyle: const TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, + overflow: TextOverflow.ellipsis, ), iconTheme: const IconThemeData(color: Colors.white), ); diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index 8037de4..3d09561 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -46,15 +46,7 @@ class _CreateGroupViewState extends State { final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, - appBar: AppBar( - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, - title: Text( - loc.create_new_group, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - centerTitle: true, - ), + appBar: AppBar(title: Text(loc.create_new_group)), body: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.start, diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 01981e2..5976f72 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -38,19 +38,13 @@ class _ChooseGameViewState extends State { return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () { Navigator.of(context).pop(selectedGameIndex); }, ), - title: Text( - loc.choose_game, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - centerTitle: true, + title: Text(loc.choose_game), ), body: PopScope( // This fixes that the Android Back Gesture didn't return the diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 011b819..97fbcef 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -38,8 +38,6 @@ class _ChooseGroupViewState extends State { return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () { @@ -52,11 +50,7 @@ class _ChooseGroupViewState extends State { ); }, ), - title: Text( - loc.choose_group, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - centerTitle: true, + title: Text(loc.choose_group), ), body: PopScope( // This fixes that the Android Back Gesture didn't return the diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart index 73be471..ca021af 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart @@ -37,8 +37,6 @@ class _ChooseRulesetViewState extends State { child: Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () { @@ -49,11 +47,7 @@ class _ChooseRulesetViewState extends State { ); }, ), - title: Text( - loc.choose_ruleset, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - centerTitle: true, + title: Text(loc.choose_ruleset), ), body: PopScope( // This fixes that the Android Back Gesture didn't return the diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 7b7deb0..e99dfc1 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -121,15 +121,7 @@ class _CreateMatchViewState extends State { final loc = AppLocalizations.of(context); return Scaffold( backgroundColor: CustomTheme.backgroundColor, - appBar: AppBar( - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, - title: Text( - loc.create_new_match, - style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), - ), - centerTitle: true, - ), + appBar: AppBar(title: Text(loc.create_new_match)), body: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.start, diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index 93ebbc6..0d624f0 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -51,17 +51,7 @@ class _MatchResultViewState extends State { Navigator.of(context).pop(); }, ), - backgroundColor: CustomTheme.backgroundColor, - scrolledUnderElevation: 0, - title: Text( - widget.match.name, - style: const TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - overflow: TextOverflow.ellipsis, - ), - ), - centerTitle: true, + title: Text(widget.match.name), ), body: SafeArea( child: Column( From aef12bd65ab3b68140b6174e636a6a3e347a93ab Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 14:54:53 +0100 Subject: [PATCH 154/222] Refactored several theme settings into custom theme --- lib/core/custom_theme.dart | 31 ++++++++++++++++--- .../group_view/create_group_view.dart | 2 +- .../create_match/create_match_view.dart | 2 +- .../views/main_menu/statistics_view.dart | 4 +-- .../widgets/buttons/custom_width_button.dart | 4 +-- .../widgets/buttons/quick_create_button.dart | 4 ++- .../widgets/player_selection.dart | 2 +- .../widgets/tiles/choose_tile.dart | 4 +-- .../widgets/tiles/custom_radio_list_tile.dart | 2 +- .../widgets/tiles/group_tile.dart | 2 +- .../widgets/tiles/match_tile.dart | 6 ++-- 11 files changed, 43 insertions(+), 20 deletions(-) diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index a9a31b2..a6c6376 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -1,38 +1,59 @@ import 'package:flutter/material.dart'; class CustomTheme { + CustomTheme._(); // Private constructor to prevent instantiation + + // ==================== Colors ==================== static Color primaryColor = const Color(0xFF7505E4); static Color secondaryColor = const Color(0xFFAFA2FF); static Color backgroundColor = const Color(0xFF0B0B0B); static Color boxColor = const Color(0xFF101010); static Color onBoxColor = const Color(0xFF181818); static Color boxBorder = const Color(0xFF272727); + static const Color textColor = Colors.white; + // ==================== Border Radius ==================== + static const double standardBorderRadius = 12.0; + static BorderRadius get standardBorderRadiusAll => + BorderRadius.circular(standardBorderRadius); + + // ==================== Padding & Margins ==================== + static const EdgeInsets standardMargin = EdgeInsets.symmetric( + horizontal: 12, + vertical: 10, + ); + static const EdgeInsets tileMargin = EdgeInsets.symmetric( + horizontal: 12, + vertical: 5, + ); + + // ==================== Decorations ==================== static BoxDecoration standardBoxDecoration = BoxDecoration( color: boxColor, border: Border.all(color: boxBorder), - borderRadius: BorderRadius.circular(12), + borderRadius: standardBorderRadiusAll, ); static BoxDecoration highlightedBoxDecoration = BoxDecoration( color: boxColor, border: Border.all(color: primaryColor), - borderRadius: BorderRadius.circular(12), + borderRadius: standardBorderRadiusAll, boxShadow: [BoxShadow(color: primaryColor.withAlpha(120), blurRadius: 12)], ); + // ==================== App Bar Theme ==================== static AppBarTheme appBarTheme = AppBarTheme( backgroundColor: backgroundColor, - foregroundColor: Colors.white, + foregroundColor: textColor, elevation: 0, scrolledUnderElevation: 0, centerTitle: true, titleTextStyle: const TextStyle( - color: Colors.white, + color: textColor, fontSize: 20, fontWeight: FontWeight.bold, overflow: TextOverflow.ellipsis, ), - iconTheme: const IconThemeData(color: Colors.white), + iconTheme: const IconThemeData(color: textColor), ); } diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index 3d09561..f92df0f 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -52,7 +52,7 @@ class _CreateGroupViewState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + margin: CustomTheme.standardMargin, child: TextInputField( controller: _groupNameController, hintText: loc.group_name, diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index e99dfc1..0cc25d0 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -127,7 +127,7 @@ class _CreateMatchViewState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), + margin: CustomTheme.tileMargin, child: TextInputField( controller: _matchNameController, hintText: hintText ?? '', diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index a60b854..53569ad 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -57,7 +57,7 @@ class _StatisticsViewState extends State { width: constraints.maxWidth * 0.95, values: winCounts, itemCount: 3, - barColor: Colors.blue, + barColor: Colors.green, ), SizedBox(height: constraints.maxHeight * 0.02), StatisticsTile( @@ -75,7 +75,7 @@ class _StatisticsViewState extends State { width: constraints.maxWidth * 0.95, values: matchCounts, itemCount: 10, - barColor: Colors.green, + barColor: Colors.blue, ), ], ), diff --git a/lib/presentation/widgets/buttons/custom_width_button.dart b/lib/presentation/widgets/buttons/custom_width_button.dart index e27f009..7e52648 100644 --- a/lib/presentation/widgets/buttons/custom_width_button.dart +++ b/lib/presentation/widgets/buttons/custom_width_button.dart @@ -60,7 +60,7 @@ class CustomWidthButton extends StatelessWidget { 60, ), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: CustomTheme.standardBorderRadiusAll, ), ), child: Text( @@ -91,7 +91,7 @@ class CustomWidthButton extends StatelessWidget { ), side: BorderSide(color: borderSideColor, width: 2), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: CustomTheme.standardBorderRadiusAll, ), ), child: Text( diff --git a/lib/presentation/widgets/buttons/quick_create_button.dart b/lib/presentation/widgets/buttons/quick_create_button.dart index 2f61805..40ebeab 100644 --- a/lib/presentation/widgets/buttons/quick_create_button.dart +++ b/lib/presentation/widgets/buttons/quick_create_button.dart @@ -29,7 +29,9 @@ class _QuickCreateButtonState extends State { style: ElevatedButton.styleFrom( minimumSize: const Size(140, 45), backgroundColor: CustomTheme.primaryColor, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + shape: RoundedRectangleBorder( + borderRadius: CustomTheme.standardBorderRadiusAll, + ), ), child: Text( widget.text, diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 99b1e1c..a582427 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -77,7 +77,7 @@ class _PlayerSelectionState extends State { Widget build(BuildContext context) { final loc = AppLocalizations.of(context); return Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + margin: CustomTheme.standardMargin, padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), decoration: CustomTheme.standardBoxDecoration, child: Column( diff --git a/lib/presentation/widgets/tiles/choose_tile.dart b/lib/presentation/widgets/tiles/choose_tile.dart index ba12c3d..f6ec940 100644 --- a/lib/presentation/widgets/tiles/choose_tile.dart +++ b/lib/presentation/widgets/tiles/choose_tile.dart @@ -32,8 +32,8 @@ class _ChooseTileState extends State { return GestureDetector( onTap: widget.onPressed, child: Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), + margin: CustomTheme.tileMargin, + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: CustomTheme.standardBoxDecoration, child: Row( children: [ diff --git a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart index d76cf3f..706aabb 100644 --- a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart +++ b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart @@ -32,7 +32,7 @@ class CustomRadioListTile extends StatelessWidget { decoration: BoxDecoration( color: CustomTheme.boxColor, border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), + borderRadius: CustomTheme.standardBorderRadiusAll, ), child: Row( children: [ diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index 8dee1cd..64d9caa 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -18,7 +18,7 @@ class GroupTile extends StatelessWidget { @override Widget build(BuildContext context) { return AnimatedContainer( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + margin: CustomTheme.standardMargin, padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), decoration: isHighlighted ? CustomTheme.highlightedBoxDecoration diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index d764dd9..bc349d3 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -41,8 +41,8 @@ class _MatchTileState extends State { return GestureDetector( onTap: widget.onTap, child: Container( - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - padding: const EdgeInsets.all(16), + margin: CustomTheme.tileMargin, + padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: CustomTheme.boxColor, border: Border.all(color: CustomTheme.boxBorder), @@ -118,7 +118,7 @@ class _MatchTileState extends State { style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, - color: Colors.white, + color: CustomTheme.textColor, ), overflow: TextOverflow.ellipsis, ), From 13be75a65b2032c6eff4ef4127d4ff7e98bc6636 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Wed, 7 Jan 2026 15:12:12 +0100 Subject: [PATCH 155/222] change settings icons to filled and rounded --- lib/presentation/views/main_menu/settings_view.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 8f1e68a..374c463 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -58,7 +58,7 @@ class _SettingsViewState extends State { ), SettingsListTile( title: loc.export_data, - icon: Icons.upload_outlined, + icon: Icons.upload_rounded, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { final String json = @@ -73,7 +73,7 @@ class _SettingsViewState extends State { ), SettingsListTile( title: loc.import_data, - icon: Icons.download_outlined, + icon: Icons.download_rounded, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { final result = await DataTransferService.importData( @@ -85,7 +85,7 @@ class _SettingsViewState extends State { ), SettingsListTile( title: loc.delete_all_data, - icon: Icons.download_outlined, + icon: Icons.delete_rounded, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () { showDialog( From 803cf8a97269c1096993f2a57f1b4c411d075eec Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Wed, 7 Jan 2026 15:17:13 +0100 Subject: [PATCH 156/222] update app_localizations --- lib/l10n/generated/app_localizations.dart | 732 +++++++++---------- lib/l10n/generated/app_localizations_de.dart | 392 +++++----- lib/l10n/generated/app_localizations_en.dart | 390 +++++----- 3 files changed, 757 insertions(+), 757 deletions(-) diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 951ff22..aea4457 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -98,23 +98,29 @@ abstract class AppLocalizations { Locale('en'), ]; - /// Label for choosing a group + /// Label for all players list /// /// In en, this message translates to: - /// **'Choose Group'** - String get choose_group; + /// **'All players:'** + String get all_players; - /// Button text to create a new match + /// Message when all players are added to selection /// /// In en, this message translates to: - /// **'Create new match'** - String get create_new_match; + /// **'All players selected'** + String get all_players_selected; - /// Label for choosing a ruleset + /// Label for amount of matches statistic /// /// In en, this message translates to: - /// **'Choose Ruleset'** - String get choose_ruleset; + /// **'Amount of Matches'** + String get amount_of_matches; + + /// Cancel button text + /// + /// In en, this message translates to: + /// **'Cancel'** + String get cancel; /// Label for choosing a game /// @@ -122,11 +128,125 @@ abstract class AppLocalizations { /// **'Choose Game'** String get choose_game; - /// Label to select the winner + /// Label for choosing a group /// /// In en, this message translates to: - /// **'Select Winner:'** - String get select_winner; + /// **'Choose Group'** + String get choose_group; + + /// Label for choosing a ruleset + /// + /// In en, this message translates to: + /// **'Choose Ruleset'** + String get choose_ruleset; + + /// Error message when adding a player fails + /// + /// In en, this message translates to: + /// **'Could not add player {playerName}'** + String could_not_add_player(String playerName); + + /// Button text to create a group + /// + /// In en, this message translates to: + /// **'Create Group'** + String get create_group; + + /// Button text to create a match + /// + /// In en, this message translates to: + /// **'Create match'** + String get create_match; + + /// Button text to create a new group + /// + /// In en, this message translates to: + /// **'Create new group'** + String get create_new_group; + + /// Button text to create a new match + /// + /// In en, this message translates to: + /// **'Create new match'** + String get create_new_match; + + /// Success message after deleting data + /// + /// In en, this message translates to: + /// **'Data successfully deleted'** + String get data_successfully_deleted; + + /// Success message after exporting data + /// + /// In en, this message translates to: + /// **'Data successfully exported'** + String get data_successfully_exported; + + /// Success message after importing data + /// + /// In en, this message translates to: + /// **'Data successfully imported'** + String get data_successfully_imported; + + /// Date format for days ago + /// + /// In en, this message translates to: + /// **'{count} days ago'** + String days_ago(int count); + + /// Delete button text + /// + /// In en, this message translates to: + /// **'Delete'** + String get delete; + + /// Confirmation dialog for deleting all data + /// + /// In en, this message translates to: + /// **'Delete all data?'** + String get delete_all_data; + + /// Error message when group creation fails + /// + /// In en, this message translates to: + /// **'Error while creating group, please try again'** + String get error_creating_group; + + /// Error message when file cannot be read + /// + /// In en, this message translates to: + /// **'Error reading file'** + String get error_reading_file; + + /// Message when export is canceled + /// + /// In en, this message translates to: + /// **'Export canceled'** + String get export_canceled; + + /// Export data menu item + /// + /// In en, this message translates to: + /// **'Export data'** + String get export_data; + + /// Error message for format exceptions + /// + /// In en, this message translates to: + /// **'Format Exception (see console)'** + String get format_exception; + + /// Game label + /// + /// In en, this message translates to: + /// **'Game'** + String get game; + + /// Placeholder for game name search + /// + /// In en, this message translates to: + /// **'Game Name'** + String get game_name; /// App Name /// @@ -134,6 +254,126 @@ abstract class AppLocalizations { /// **'Game Tracker'** String get game_tracker; + /// Group label + /// + /// In en, this message translates to: + /// **'Group'** + String get group; + + /// Placeholder for group name input + /// + /// In en, this message translates to: + /// **'Group name'** + String get group_name; + + /// Label for groups + /// + /// In en, this message translates to: + /// **'Groups'** + String get groups; + + /// Home tab label + /// + /// In en, this message translates to: + /// **'Home'** + String get home; + + /// Message when import is canceled + /// + /// In en, this message translates to: + /// **'Import canceled'** + String get import_canceled; + + /// Import data menu item + /// + /// In en, this message translates to: + /// **'Import data'** + String get import_data; + + /// Info label + /// + /// In en, this message translates to: + /// **'Info'** + String get info; + + /// Error message for invalid schema + /// + /// In en, this message translates to: + /// **'Invalid Schema'** + String get invalid_schema; + + /// Title for least points ruleset + /// + /// In en, this message translates to: + /// **'Least Points'** + String get least_points; + + /// Message when match is in progress + /// + /// In en, this message translates to: + /// **'Match in progress...'** + String get match_in_progress; + + /// Placeholder for match name input + /// + /// In en, this message translates to: + /// **'Match name'** + String get match_name; + + /// Label for matches + /// + /// In en, this message translates to: + /// **'Matches'** + String get matches; + + /// Menu label + /// + /// In en, this message translates to: + /// **'Menu'** + String get menu; + + /// Title for most points ruleset + /// + /// In en, this message translates to: + /// **'Most Points'** + String get most_points; + + /// Message when no data in the statistic tiles is given + /// + /// In en, this message translates to: + /// **'No data available'** + String get no_data_available; + + /// Message when no groups exist + /// + /// In en, this message translates to: + /// **'No groups created yet'** + String get no_groups_created_yet; + + /// Message when no matches exist + /// + /// In en, this message translates to: + /// **'No matches created yet'** + String get no_matches_created_yet; + + /// Message when no players exist + /// + /// In en, this message translates to: + /// **'No players created yet'** + String get no_players_created_yet; + + /// Message when search returns no results + /// + /// In en, this message translates to: + /// **'No players found with that name'** + String get no_players_found_with_that_name; + + /// Message when no players are selected + /// + /// In en, this message translates to: + /// **'No players selected'** + String get no_players_selected; + /// Message when no recent matches exist /// /// In en, this message translates to: @@ -146,294 +386,12 @@ abstract class AppLocalizations { /// **'No second match available'** String get no_second_match_available; - /// Confirmation dialog for deleting all data - /// - /// In en, this message translates to: - /// **'Delete all data?'** - String get delete_all_data; - - /// Cancel button text - /// - /// In en, this message translates to: - /// **'Cancel'** - String get cancel; - - /// Delete button text - /// - /// In en, this message translates to: - /// **'Delete'** - String get delete; - - /// Button text to create a new group - /// - /// In en, this message translates to: - /// **'Create new group'** - String get create_new_group; - - /// Error message when group creation fails - /// - /// In en, this message translates to: - /// **'Error while creating group, please try again'** - String get error_creating_group; - - /// Shows the number of selected players - /// - /// In en, this message translates to: - /// **'Selected players: {count}'** - String selected_players(int count); - - /// Message when no players are selected - /// - /// In en, this message translates to: - /// **'No players selected'** - String get no_players_selected; - - /// Label for all players list - /// - /// In en, this message translates to: - /// **'All players:'** - String get all_players; - - /// Success message when adding a player - /// - /// In en, this message translates to: - /// **'Successfully added player {playerName}'** - String successfully_added_player(String playerName); - - /// Error message when adding a player fails - /// - /// In en, this message translates to: - /// **'Could not add player {playerName}'** - String could_not_add_player(String playerName); - - /// Shows the winner's name - /// - /// In en, this message translates to: - /// **'Winner: {winnerName}'** - String winner(String winnerName); - - /// Players label - /// - /// In en, this message translates to: - /// **'Players'** - String get players; - /// Message when no statistics are available, because no matches were played yet /// /// In en, this message translates to: /// **'No statistics available'** String get no_statistics_available; - /// Message when no data in the statistic tiles is given - /// - /// In en, this message translates to: - /// **'No data available'** - String get no_data_available; - - /// Label for matches - /// - /// In en, this message translates to: - /// **'Matches'** - String get matches; - - /// Label for groups - /// - /// In en, this message translates to: - /// **'Groups'** - String get groups; - - /// Title for recent matches section - /// - /// In en, this message translates to: - /// **'Recent Matches'** - String get recent_matches; - - /// Title for quick create section - /// - /// In en, this message translates to: - /// **'Quick Create'** - String get quick_create; - - /// Message when match is in progress - /// - /// In en, this message translates to: - /// **'Match in progress...'** - String get match_in_progress; - - /// Menu label - /// - /// In en, this message translates to: - /// **'Menu'** - String get menu; - - /// Settings label - /// - /// In en, this message translates to: - /// **'Settings'** - String get settings; - - /// Export data menu item - /// - /// In en, this message translates to: - /// **'Export data'** - String get export_data; - - /// Import data menu item - /// - /// In en, this message translates to: - /// **'Import data'** - String get import_data; - - /// Warning message for irreversible actions - /// - /// In en, this message translates to: - /// **'This can\'t be undone'** - String get this_cannot_be_undone; - - /// Success message after deleting data - /// - /// In en, this message translates to: - /// **'Data successfully deleted'** - String get data_successfully_deleted; - - /// Success message after importing data - /// - /// In en, this message translates to: - /// **'Data successfully imported'** - String get data_successfully_imported; - - /// Error message for invalid schema - /// - /// In en, this message translates to: - /// **'Invalid Schema'** - String get invalid_schema; - - /// Error message when file cannot be read - /// - /// In en, this message translates to: - /// **'Error reading file'** - String get error_reading_file; - - /// Message when import is canceled - /// - /// In en, this message translates to: - /// **'Import canceled'** - String get import_canceled; - - /// Error message for format exceptions - /// - /// In en, this message translates to: - /// **'Format Exception (see console)'** - String get format_exception; - - /// Error message for unknown exceptions - /// - /// In en, this message translates to: - /// **'Unknown Exception (see console)'** - String get unknown_exception; - - /// Success message after exporting data - /// - /// In en, this message translates to: - /// **'Data successfully exported'** - String get data_successfully_exported; - - /// Message when export is canceled - /// - /// In en, this message translates to: - /// **'Export canceled'** - String get export_canceled; - - /// Undo button text - /// - /// In en, this message translates to: - /// **'Undo'** - String get undo; - - /// Label for wins statistic - /// - /// In en, this message translates to: - /// **'Wins'** - String get wins; - - /// Label for winrate statistic - /// - /// In en, this message translates to: - /// **'Winrate'** - String get winrate; - - /// Label for amount of matches statistic - /// - /// In en, this message translates to: - /// **'Amount of Matches'** - String get amount_of_matches; - - /// Info label - /// - /// In en, this message translates to: - /// **'Info'** - String get info; - - /// Message when no groups exist - /// - /// In en, this message translates to: - /// **'No groups created yet'** - String get no_groups_created_yet; - - /// Message when no players exist - /// - /// In en, this message translates to: - /// **'No players created yet'** - String get no_players_created_yet; - - /// Button text to create a group - /// - /// In en, this message translates to: - /// **'Create Group'** - String get create_group; - - /// Placeholder for group name input - /// - /// In en, this message translates to: - /// **'Group name'** - String get group_name; - - /// Placeholder for player name input - /// - /// In en, this message translates to: - /// **'Player name'** - String get player_name; - - /// Message when no matches exist - /// - /// In en, this message translates to: - /// **'No matches created yet'** - String get no_matches_created_yet; - - /// Placeholder for match name input - /// - /// In en, this message translates to: - /// **'Match name'** - String get match_name; - - /// Game label - /// - /// In en, this message translates to: - /// **'Game'** - String get game; - - /// Ruleset label - /// - /// In en, this message translates to: - /// **'Ruleset'** - String get ruleset; - - /// Group label - /// - /// In en, this message translates to: - /// **'Group'** - String get group; - /// None option label /// /// In en, this message translates to: @@ -446,47 +404,113 @@ abstract class AppLocalizations { /// **'None'** String get none_group; - /// Button text to create a match + /// Abbreviation for not available /// /// In en, this message translates to: - /// **'Create match'** - String get create_match; + /// **'Not available'** + String get not_available; - /// Message when search returns no results + /// Placeholder for player name input /// /// In en, this message translates to: - /// **'No players found with that name'** - String get no_players_found_with_that_name; + /// **'Player name'** + String get player_name; - /// Message when all players are added to selection + /// Players label /// /// In en, this message translates to: - /// **'All players selected'** - String get all_players_selected; + /// **'Players'** + String get players; - /// Date format for today + /// Shows the number of players /// /// In en, this message translates to: - /// **'Today at {time}'** - String today_at(String time); + /// **'{count} Players'** + String players_count(int count); - /// Date format for yesterday + /// Title for quick create section /// /// In en, this message translates to: - /// **'Yesterday at {time}'** - String yesterday_at(String time); + /// **'Quick Create'** + String get quick_create; - /// Date format for days ago + /// Title for recent matches section /// /// In en, this message translates to: - /// **'{count} days ago'** - String days_ago(int count); + /// **'Recent Matches'** + String get recent_matches; - /// Home tab label + /// Ruleset label /// /// In en, this message translates to: - /// **'Home'** - String get home; + /// **'Ruleset'** + String get ruleset; + + /// Description for least points ruleset + /// + /// In en, this message translates to: + /// **'Inverse scoring: the player with the fewest points wins.'** + String get ruleset_least_points; + + /// Description for most points ruleset + /// + /// In en, this message translates to: + /// **'Traditional ruleset: the player with the most points wins.'** + String get ruleset_most_points; + + /// Description for single loser ruleset + /// + /// In en, this message translates to: + /// **'Exactly one loser is determined; last place receives the penalty or consequence.'** + String get ruleset_single_loser; + + /// Description for single winner ruleset + /// + /// In en, this message translates to: + /// **'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'** + String get ruleset_single_winner; + + /// Hint text for group search input field + /// + /// In en, this message translates to: + /// **'Search for groups'** + String get search_for_groups; + + /// Hint text for player search input field + /// + /// In en, this message translates to: + /// **'Search for players'** + String get search_for_players; + + /// Label to select the winner + /// + /// In en, this message translates to: + /// **'Select Winner:'** + String get select_winner; + + /// Shows the number of selected players + /// + /// In en, this message translates to: + /// **'Selected players: {count}'** + String selected_players(int count); + + /// Settings label + /// + /// In en, this message translates to: + /// **'Settings'** + String get settings; + + /// Title for single loser ruleset + /// + /// In en, this message translates to: + /// **'Single Loser'** + String get single_loser; + + /// Title for single winner ruleset + /// + /// In en, this message translates to: + /// **'Single Winner'** + String get single_winner; /// Statistics tab label /// @@ -500,11 +524,11 @@ abstract class AppLocalizations { /// **'Stats'** String get stats; - /// Shows the number of players + /// Success message when adding a player /// /// In en, this message translates to: - /// **'{count} Players'** - String players_count(int count); + /// **'Successfully added player {playerName}'** + String successfully_added_player(String playerName); /// Message when search returns no groups /// @@ -512,77 +536,53 @@ abstract class AppLocalizations { /// **'There is no group matching your search'** String get there_is_no_group_matching_your_search; - /// Placeholder for game name search + /// Warning message for irreversible actions /// /// In en, this message translates to: - /// **'Game Name'** - String get game_name; + /// **'This can\'t be undone'** + String get this_cannot_be_undone; - /// Description for single winner ruleset + /// Date format for today /// /// In en, this message translates to: - /// **'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'** - String get ruleset_single_winner; + /// **'Today at {time}'** + String today_at(String time); - /// Description for single loser ruleset + /// Undo button text /// /// In en, this message translates to: - /// **'Exactly one loser is determined; last place receives the penalty or consequence.'** - String get ruleset_single_loser; + /// **'Undo'** + String get undo; - /// Description for most points ruleset + /// Error message for unknown exceptions /// /// In en, this message translates to: - /// **'Traditional ruleset: the player with the most points wins.'** - String get ruleset_most_points; + /// **'Unknown Exception (see console)'** + String get unknown_exception; - /// Description for least points ruleset + /// Winner label /// /// In en, this message translates to: - /// **'Inverse scoring: the player with the fewest points wins.'** - String get ruleset_least_points; + /// **'Winner'** + String winner(Object winnerName); - /// Title for single winner ruleset + /// Label for winrate statistic /// /// In en, this message translates to: - /// **'Single Winner'** - String get single_winner; + /// **'Winrate'** + String get winrate; - /// Title for single loser ruleset + /// Label for wins statistic /// /// In en, this message translates to: - /// **'Single Loser'** - String get single_loser; + /// **'Wins'** + String get wins; - /// Title for most points ruleset + /// Date format for yesterday /// /// In en, this message translates to: - /// **'Most Points'** - String get most_points; - - /// Title for least points ruleset - /// - /// In en, this message translates to: - /// **'Least Points'** - String get least_points; - - /// Hint text for player search input field - /// - /// In en, this message translates to: - /// **'Search for players'** - String get search_for_players; - - /// Hint text for group search input field - /// - /// In en, this message translates to: - /// **'Search for groups'** - String get search_for_groups; - - /// Abbreviation for not available - /// - /// In en, this message translates to: - /// **'Not available'** - String get not_available; + /// **'Yesterday at {time}'** + String yesterday_at(String time); } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 3f3e36e..bfb9870 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -9,23 +9,149 @@ class AppLocalizationsDe extends AppLocalizations { AppLocalizationsDe([String locale = 'de']) : super(locale); @override - String get choose_group => 'Gruppe wählen'; + String get all_players => 'Alle Spieler:innen:'; @override - String get create_new_match => 'Neues Match erstellen'; + String get all_players_selected => 'Alle Spieler:innen ausgewählt'; @override - String get choose_ruleset => 'Regelwerk wählen'; + String get amount_of_matches => 'Anzahl der Matches'; + + @override + String get cancel => 'Abbrechen'; @override String get choose_game => 'Spielvorlage wählen'; @override - String get select_winner => 'Gewinner:in wählen:'; + String get choose_group => 'Gruppe wählen'; + + @override + String get choose_ruleset => 'Regelwerk wählen'; + + @override + String could_not_add_player(String playerName) { + return 'Spieler:in $playerName konnte nicht hinzugefügt werden'; + } + + @override + String get create_group => 'Gruppe erstellen'; + + @override + String get create_match => 'Match erstellen'; + + @override + String get create_new_group => 'Neue Gruppe erstellen'; + + @override + String get create_new_match => 'Neues Match erstellen'; + + @override + String get data_successfully_deleted => 'Daten erfolgreich gelöscht'; + + @override + String get data_successfully_exported => 'Daten erfolgreich exportiert'; + + @override + String get data_successfully_imported => 'Daten erfolgreich importiert'; + + @override + String days_ago(int count) { + return 'vor $count Tagen'; + } + + @override + String get delete => 'Löschen'; + + @override + String get delete_all_data => 'Alle Daten löschen?'; + + @override + String get error_creating_group => + 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; + + @override + String get error_reading_file => 'Fehler beim Lesen der Datei'; + + @override + String get export_canceled => 'Export abgebrochen'; + + @override + String get export_data => 'Daten exportieren'; + + @override + String get format_exception => 'Formatfehler (siehe Konsole)'; + + @override + String get game => 'Spielvorlage'; + + @override + String get game_name => 'Spielvorlagenname'; @override String get game_tracker => 'Game Tracker'; + @override + String get group => 'Gruppe'; + + @override + String get group_name => 'Gruppenname'; + + @override + String get groups => 'Gruppen'; + + @override + String get home => 'Startseite'; + + @override + String get import_canceled => 'Import abgebrochen'; + + @override + String get import_data => 'Daten importieren'; + + @override + String get info => 'Info'; + + @override + String get invalid_schema => 'Ungültiges Schema'; + + @override + String get least_points => 'Niedrigste Punkte'; + + @override + String get match_in_progress => 'Match läuft...'; + + @override + String get match_name => 'Matchname'; + + @override + String get matches => 'Matches'; + + @override + String get menu => 'Menü'; + + @override + String get most_points => 'Höchste Punkte'; + + @override + String get no_data_available => 'Keine Daten verfügbar'; + + @override + String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; + + @override + String get no_matches_created_yet => 'Noch keine Matches erstellt'; + + @override + String get no_players_created_yet => 'Noch keine Spieler:in erstellt'; + + @override + String get no_players_found_with_that_name => + 'Keine Spieler:in mit diesem Namen gefunden'; + + @override + String get no_players_selected => 'Keine Spieler:in ausgewählt'; + @override String get no_recent_matches_available => 'Keine letzten Matches verfügbar'; @@ -33,20 +159,61 @@ class AppLocalizationsDe extends AppLocalizations { String get no_second_match_available => 'Kein zweites Match verfügbar'; @override - String get delete_all_data => 'Alle Daten löschen?'; + String get no_statistics_available => 'Keine Statistiken verfügbar'; @override - String get cancel => 'Abbrechen'; + String get none => 'Kein'; @override - String get delete => 'Löschen'; + String get none_group => 'Keine'; @override - String get create_new_group => 'Neue Gruppe erstellen'; + String get not_available => 'Nicht verfügbar'; @override - String get error_creating_group => - 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; + String get player_name => 'Spieler:innenname'; + + @override + String get players => 'Spieler:in'; + + @override + String players_count(int count) { + return '$count Spieler'; + } + + @override + String get quick_create => 'Schnellzugriff'; + + @override + String get recent_matches => 'Letzte Matches'; + + @override + String get ruleset => 'Regelwerk'; + + @override + String get ruleset_least_points => + 'Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.'; + + @override + String get ruleset_most_points => + 'Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.'; + + @override + String get ruleset_single_loser => + 'Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; + + @override + String get ruleset_single_winner => + 'Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; + + @override + String get search_for_groups => 'Nach Gruppen suchen'; + + @override + String get search_for_players => 'Nach Spieler:innen suchen'; + + @override + String get select_winner => 'Gewinner:in wählen:'; @override String selected_players(int count) { @@ -58,172 +225,14 @@ class AppLocalizationsDe extends AppLocalizations { return 'Ausgewählte Spieler:in: $countString'; } - @override - String get no_players_selected => 'Keine Spieler:in ausgewählt'; - - @override - String get all_players => 'Alle Spieler:innen:'; - - @override - String successfully_added_player(String playerName) { - return 'Spieler:in $playerName erfolgreich hinzugefügt'; - } - - @override - String could_not_add_player(String playerName) { - return 'Spieler:in $playerName konnte nicht hinzugefügt werden'; - } - - @override - String winner(String winnerName) { - return 'Gewinner:in: $winnerName'; - } - - @override - String get players => 'Spieler:in'; - - @override - String get no_statistics_available => 'Keine Statistiken verfügbar'; - - @override - String get no_data_available => 'Keine Daten verfügbar'; - - @override - String get matches => 'Matches'; - - @override - String get groups => 'Gruppen'; - - @override - String get recent_matches => 'Letzte Matches'; - - @override - String get quick_create => 'Schnellzugriff'; - - @override - String get match_in_progress => 'Match läuft...'; - - @override - String get menu => 'Menü'; - @override String get settings => 'Einstellungen'; @override - String get export_data => 'Daten exportieren'; + String get single_loser => 'Ein:e Verlierer:in'; @override - String get import_data => 'Daten importieren'; - - @override - String get this_cannot_be_undone => - 'Dies kann nicht rückgängig gemacht werden'; - - @override - String get data_successfully_deleted => 'Daten erfolgreich gelöscht'; - - @override - String get data_successfully_imported => 'Daten erfolgreich importiert'; - - @override - String get invalid_schema => 'Ungültiges Schema'; - - @override - String get error_reading_file => 'Fehler beim Lesen der Datei'; - - @override - String get import_canceled => 'Import abgebrochen'; - - @override - String get format_exception => 'Formatfehler (siehe Konsole)'; - - @override - String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; - - @override - String get data_successfully_exported => 'Daten erfolgreich exportiert'; - - @override - String get export_canceled => 'Export abgebrochen'; - - @override - String get undo => 'Rückgängig'; - - @override - String get wins => 'Siege'; - - @override - String get winrate => 'Siegquote'; - - @override - String get amount_of_matches => 'Anzahl der Matches'; - - @override - String get info => 'Info'; - - @override - String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; - - @override - String get no_players_created_yet => 'Noch keine Spieler:in erstellt'; - - @override - String get create_group => 'Gruppe erstellen'; - - @override - String get group_name => 'Gruppenname'; - - @override - String get player_name => 'Spieler:innenname'; - - @override - String get no_matches_created_yet => 'Noch keine Matches erstellt'; - - @override - String get match_name => 'Matchname'; - - @override - String get game => 'Spielvorlage'; - - @override - String get ruleset => 'Regelwerk'; - - @override - String get group => 'Gruppe'; - - @override - String get none => 'Kein'; - - @override - String get none_group => 'Keine'; - - @override - String get create_match => 'Match erstellen'; - - @override - String get no_players_found_with_that_name => - 'Keine Spieler:in mit diesem Namen gefunden'; - - @override - String get all_players_selected => 'Alle Spieler:innen ausgewählt'; - - @override - String today_at(String time) { - return 'Heute um $time'; - } - - @override - String yesterday_at(String time) { - return 'Gestern um $time'; - } - - @override - String days_ago(int count) { - return 'vor $count Tagen'; - } - - @override - String get home => 'Startseite'; + String get single_winner => 'Ein:e Gewinner:in'; @override String get statistics => 'Statistiken'; @@ -232,8 +241,8 @@ class AppLocalizationsDe extends AppLocalizations { String get stats => 'Statistiken'; @override - String players_count(int count) { - return '$count Spieler'; + String successfully_added_player(String playerName) { + return 'Spieler:in $playerName erfolgreich hinzugefügt'; } @override @@ -241,42 +250,33 @@ class AppLocalizationsDe extends AppLocalizations { 'Es gibt keine Gruppe, die deiner Suche entspricht'; @override - String get game_name => 'Spielvorlagenname'; + String get this_cannot_be_undone => + 'Dies kann nicht rückgängig gemacht werden'; @override - String get ruleset_single_winner => - 'Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.'; + String today_at(String time) { + return 'Heute um $time'; + } @override - String get ruleset_single_loser => - 'Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.'; + String get undo => 'Rückgängig'; @override - String get ruleset_most_points => - 'Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.'; + String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; @override - String get ruleset_least_points => - 'Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.'; + String winner(Object winnerName) { + return 'Gewinner:in: $winnerName'; + } @override - String get single_winner => 'Ein:e Gewinner:in'; + String get winrate => 'Siegquote'; @override - String get single_loser => 'Ein:e Verlierer:in'; + String get wins => 'Siege'; @override - String get most_points => 'Höchste Punkte'; - - @override - String get least_points => 'Niedrigste Punkte'; - - @override - String get search_for_players => 'Nach Spieler:innen suchen'; - - @override - String get search_for_groups => 'Nach Gruppen suchen'; - - @override - String get not_available => 'Nicht verfügbar'; + String yesterday_at(String time) { + return 'Gestern um $time'; + } } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 263714c..38ea20f 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -9,23 +9,149 @@ class AppLocalizationsEn extends AppLocalizations { AppLocalizationsEn([String locale = 'en']) : super(locale); @override - String get choose_group => 'Choose Group'; + String get all_players => 'All players:'; @override - String get create_new_match => 'Create new match'; + String get all_players_selected => 'All players selected'; @override - String get choose_ruleset => 'Choose Ruleset'; + String get amount_of_matches => 'Amount of Matches'; + + @override + String get cancel => 'Cancel'; @override String get choose_game => 'Choose Game'; @override - String get select_winner => 'Select Winner:'; + String get choose_group => 'Choose Group'; + + @override + String get choose_ruleset => 'Choose Ruleset'; + + @override + String could_not_add_player(String playerName) { + return 'Could not add player $playerName'; + } + + @override + String get create_group => 'Create Group'; + + @override + String get create_match => 'Create match'; + + @override + String get create_new_group => 'Create new group'; + + @override + String get create_new_match => 'Create new match'; + + @override + String get data_successfully_deleted => 'Data successfully deleted'; + + @override + String get data_successfully_exported => 'Data successfully exported'; + + @override + String get data_successfully_imported => 'Data successfully imported'; + + @override + String days_ago(int count) { + return '$count days ago'; + } + + @override + String get delete => 'Delete'; + + @override + String get delete_all_data => 'Delete all data?'; + + @override + String get error_creating_group => + 'Error while creating group, please try again'; + + @override + String get error_reading_file => 'Error reading file'; + + @override + String get export_canceled => 'Export canceled'; + + @override + String get export_data => 'Export data'; + + @override + String get format_exception => 'Format Exception (see console)'; + + @override + String get game => 'Game'; + + @override + String get game_name => 'Game Name'; @override String get game_tracker => 'Game Tracker'; + @override + String get group => 'Group'; + + @override + String get group_name => 'Group name'; + + @override + String get groups => 'Groups'; + + @override + String get home => 'Home'; + + @override + String get import_canceled => 'Import canceled'; + + @override + String get import_data => 'Import data'; + + @override + String get info => 'Info'; + + @override + String get invalid_schema => 'Invalid Schema'; + + @override + String get least_points => 'Least Points'; + + @override + String get match_in_progress => 'Match in progress...'; + + @override + String get match_name => 'Match name'; + + @override + String get matches => 'Matches'; + + @override + String get menu => 'Menu'; + + @override + String get most_points => 'Most Points'; + + @override + String get no_data_available => 'No data available'; + + @override + String get no_groups_created_yet => 'No groups created yet'; + + @override + String get no_matches_created_yet => 'No matches created yet'; + + @override + String get no_players_created_yet => 'No players created yet'; + + @override + String get no_players_found_with_that_name => + 'No players found with that name'; + + @override + String get no_players_selected => 'No players selected'; + @override String get no_recent_matches_available => 'No recent matches available'; @@ -33,20 +159,61 @@ class AppLocalizationsEn extends AppLocalizations { String get no_second_match_available => 'No second match available'; @override - String get delete_all_data => 'Delete all data?'; + String get no_statistics_available => 'No statistics available'; @override - String get cancel => 'Cancel'; + String get none => 'None'; @override - String get delete => 'Delete'; + String get none_group => 'None'; @override - String get create_new_group => 'Create new group'; + String get not_available => 'Not available'; @override - String get error_creating_group => - 'Error while creating group, please try again'; + String get player_name => 'Player name'; + + @override + String get players => 'Players'; + + @override + String players_count(int count) { + return '$count Players'; + } + + @override + String get quick_create => 'Quick Create'; + + @override + String get recent_matches => 'Recent Matches'; + + @override + String get ruleset => 'Ruleset'; + + @override + String get ruleset_least_points => + 'Inverse scoring: the player with the fewest points wins.'; + + @override + String get ruleset_most_points => + 'Traditional ruleset: the player with the most points wins.'; + + @override + String get ruleset_single_loser => + 'Exactly one loser is determined; last place receives the penalty or consequence.'; + + @override + String get ruleset_single_winner => + 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'; + + @override + String get search_for_groups => 'Search for groups'; + + @override + String get search_for_players => 'Search for players'; + + @override + String get select_winner => 'Select Winner:'; @override String selected_players(int count) { @@ -58,171 +225,14 @@ class AppLocalizationsEn extends AppLocalizations { return 'Selected players: $countString'; } - @override - String get no_players_selected => 'No players selected'; - - @override - String get all_players => 'All players:'; - - @override - String successfully_added_player(String playerName) { - return 'Successfully added player $playerName'; - } - - @override - String could_not_add_player(String playerName) { - return 'Could not add player $playerName'; - } - - @override - String winner(String winnerName) { - return 'Winner: $winnerName'; - } - - @override - String get players => 'Players'; - - @override - String get no_statistics_available => 'No statistics available'; - - @override - String get no_data_available => 'No data available'; - - @override - String get matches => 'Matches'; - - @override - String get groups => 'Groups'; - - @override - String get recent_matches => 'Recent Matches'; - - @override - String get quick_create => 'Quick Create'; - - @override - String get match_in_progress => 'Match in progress...'; - - @override - String get menu => 'Menu'; - @override String get settings => 'Settings'; @override - String get export_data => 'Export data'; + String get single_loser => 'Single Loser'; @override - String get import_data => 'Import data'; - - @override - String get this_cannot_be_undone => 'This can\'t be undone'; - - @override - String get data_successfully_deleted => 'Data successfully deleted'; - - @override - String get data_successfully_imported => 'Data successfully imported'; - - @override - String get invalid_schema => 'Invalid Schema'; - - @override - String get error_reading_file => 'Error reading file'; - - @override - String get import_canceled => 'Import canceled'; - - @override - String get format_exception => 'Format Exception (see console)'; - - @override - String get unknown_exception => 'Unknown Exception (see console)'; - - @override - String get data_successfully_exported => 'Data successfully exported'; - - @override - String get export_canceled => 'Export canceled'; - - @override - String get undo => 'Undo'; - - @override - String get wins => 'Wins'; - - @override - String get winrate => 'Winrate'; - - @override - String get amount_of_matches => 'Amount of Matches'; - - @override - String get info => 'Info'; - - @override - String get no_groups_created_yet => 'No groups created yet'; - - @override - String get no_players_created_yet => 'No players created yet'; - - @override - String get create_group => 'Create Group'; - - @override - String get group_name => 'Group name'; - - @override - String get player_name => 'Player name'; - - @override - String get no_matches_created_yet => 'No matches created yet'; - - @override - String get match_name => 'Match name'; - - @override - String get game => 'Game'; - - @override - String get ruleset => 'Ruleset'; - - @override - String get group => 'Group'; - - @override - String get none => 'None'; - - @override - String get none_group => 'None'; - - @override - String get create_match => 'Create match'; - - @override - String get no_players_found_with_that_name => - 'No players found with that name'; - - @override - String get all_players_selected => 'All players selected'; - - @override - String today_at(String time) { - return 'Today at $time'; - } - - @override - String yesterday_at(String time) { - return 'Yesterday at $time'; - } - - @override - String days_ago(int count) { - return '$count days ago'; - } - - @override - String get home => 'Home'; + String get single_winner => 'Single Winner'; @override String get statistics => 'Statistics'; @@ -231,8 +241,8 @@ class AppLocalizationsEn extends AppLocalizations { String get stats => 'Stats'; @override - String players_count(int count) { - return '$count Players'; + String successfully_added_player(String playerName) { + return 'Successfully added player $playerName'; } @override @@ -240,42 +250,32 @@ class AppLocalizationsEn extends AppLocalizations { 'There is no group matching your search'; @override - String get game_name => 'Game Name'; + String get this_cannot_be_undone => 'This can\'t be undone'; @override - String get ruleset_single_winner => - 'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'; + String today_at(String time) { + return 'Today at $time'; + } @override - String get ruleset_single_loser => - 'Exactly one loser is determined; last place receives the penalty or consequence.'; + String get undo => 'Undo'; @override - String get ruleset_most_points => - 'Traditional ruleset: the player with the most points wins.'; + String get unknown_exception => 'Unknown Exception (see console)'; @override - String get ruleset_least_points => - 'Inverse scoring: the player with the fewest points wins.'; + String winner(Object winnerName) { + return 'Winner'; + } @override - String get single_winner => 'Single Winner'; + String get winrate => 'Winrate'; @override - String get single_loser => 'Single Loser'; + String get wins => 'Wins'; @override - String get most_points => 'Most Points'; - - @override - String get least_points => 'Least Points'; - - @override - String get search_for_players => 'Search for players'; - - @override - String get search_for_groups => 'Search for groups'; - - @override - String get not_available => 'Not available'; + String yesterday_at(String time) { + return 'Yesterday at $time'; + } } From d5ee6449b05461b87992410d851fd2c1a2ac7065 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 17:02:06 +0100 Subject: [PATCH 157/222] Updated localizations --- lib/l10n/generated/app_localizations.dart | 2 +- lib/l10n/generated/app_localizations_de.dart | 28 +++++++++----------- lib/l10n/generated/app_localizations_en.dart | 4 +-- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index aea4457..1743997 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -564,7 +564,7 @@ abstract class AppLocalizations { /// /// In en, this message translates to: /// **'Winner'** - String winner(Object winnerName); + String get winner; /// Label for winrate statistic /// diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index bfb9870..4421fd1 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -15,7 +15,7 @@ class AppLocalizationsDe extends AppLocalizations { String get all_players_selected => 'Alle Spieler:innen ausgewählt'; @override - String get amount_of_matches => 'Anzahl der Matches'; + String get amount_of_matches => 'Anzahl der Spiele'; @override String get cancel => 'Abbrechen'; @@ -38,13 +38,13 @@ class AppLocalizationsDe extends AppLocalizations { String get create_group => 'Gruppe erstellen'; @override - String get create_match => 'Match erstellen'; + String get create_match => 'Spiel erstellen'; @override String get create_new_group => 'Neue Gruppe erstellen'; @override - String get create_new_match => 'Neues Match erstellen'; + String get create_new_match => 'Neues Spiel erstellen'; @override String get data_successfully_deleted => 'Daten erfolgreich gelöscht'; @@ -119,13 +119,13 @@ class AppLocalizationsDe extends AppLocalizations { String get least_points => 'Niedrigste Punkte'; @override - String get match_in_progress => 'Match läuft...'; + String get match_in_progress => 'Spiel läuft...'; @override - String get match_name => 'Matchname'; + String get match_name => 'Spieltitel'; @override - String get matches => 'Matches'; + String get matches => 'Spiele'; @override String get menu => 'Menü'; @@ -140,7 +140,7 @@ class AppLocalizationsDe extends AppLocalizations { String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; @override - String get no_matches_created_yet => 'Noch keine Matches erstellt'; + String get no_matches_created_yet => 'Noch keine Spiele erstellt'; @override String get no_players_created_yet => 'Noch keine Spieler:in erstellt'; @@ -153,10 +153,10 @@ class AppLocalizationsDe extends AppLocalizations { String get no_players_selected => 'Keine Spieler:in ausgewählt'; @override - String get no_recent_matches_available => 'Keine letzten Matches verfügbar'; + String get no_recent_matches_available => 'Keine letzten Spiele verfügbar'; @override - String get no_second_match_available => 'Kein zweites Match verfügbar'; + String get no_second_match_available => 'Kein zweites Spiel verfügbar'; @override String get no_statistics_available => 'Keine Statistiken verfügbar'; @@ -174,7 +174,7 @@ class AppLocalizationsDe extends AppLocalizations { String get player_name => 'Spieler:innenname'; @override - String get players => 'Spieler:in'; + String get players => 'Spieler:innen'; @override String players_count(int count) { @@ -185,7 +185,7 @@ class AppLocalizationsDe extends AppLocalizations { String get quick_create => 'Schnellzugriff'; @override - String get recent_matches => 'Letzte Matches'; + String get recent_matches => 'Letzte Spiele'; @override String get ruleset => 'Regelwerk'; @@ -222,7 +222,7 @@ class AppLocalizationsDe extends AppLocalizations { ); final String countString = countNumberFormat.format(count); - return 'Ausgewählte Spieler:in: $countString'; + return 'Ausgewählte Spieler:innen: $countString'; } @override @@ -265,9 +265,7 @@ class AppLocalizationsDe extends AppLocalizations { String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; @override - String winner(Object winnerName) { - return 'Gewinner:in: $winnerName'; - } + String get winner => 'Gewinner*in'; @override String get winrate => 'Siegquote'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 38ea20f..0cd8842 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -264,9 +264,7 @@ class AppLocalizationsEn extends AppLocalizations { String get unknown_exception => 'Unknown Exception (see console)'; @override - String winner(Object winnerName) { - return 'Winner'; - } + String get winner => 'Winner'; @override String get winrate => 'Winrate'; From 76121eb4fb5d02db5c9bf27fd43a38bf35d059ab Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 7 Jan 2026 17:30:19 +0100 Subject: [PATCH 158/222] Updated localizations --- lib/l10n/arb/app_de.arb | 2 +- lib/l10n/generated/app_localizations_de.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index f55668e..4adbfa2 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -76,7 +76,7 @@ "today_at": "Heute um {time}", "undo": "Rückgängig", "unknown_exception": "Unbekannter Fehler (siehe Konsole)", - "winner": "Gewinner*in", + "winner": "Gewinner:in", "winrate": "Siegquote", "wins": "Siege", "yesterday_at": "Gestern um {time}" diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 4421fd1..88374a1 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -265,7 +265,7 @@ class AppLocalizationsDe extends AppLocalizations { String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; @override - String get winner => 'Gewinner*in'; + String get winner => 'Gewinner:in'; @override String get winrate => 'Siegquote'; From d741990f2fad4e11c68c2b009fd8ffc172bf6fde Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 8 Jan 2026 21:07:30 +0100 Subject: [PATCH 159/222] Fixed navbar issue --- lib/main.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index c1ed977..8219fcb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -35,8 +35,6 @@ class GameTracker extends StatelessWidget { }, debugShowCheckedModeBanner: false, onGenerateTitle: (context) => AppLocalizations.of(context).game_tracker, - darkTheme: ThemeData.dark(), - themeMode: ThemeMode.dark, // forces dark mode theme: ThemeData( primaryColor: CustomTheme.primaryColor, From cfb07bfe28b9af85b32b2b9c60ec15312b3009cc Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 8 Jan 2026 21:13:24 +0100 Subject: [PATCH 160/222] Updated localizations --- lib/l10n/arb/app_de.arb | 4 +- lib/l10n/arb/app_en.arb | 712 +++++++++--------- lib/l10n/generated/app_localizations.dart | 24 +- lib/l10n/generated/app_localizations_de.dart | 16 +- lib/l10n/generated/app_localizations_en.dart | 18 +- lib/main.dart | 2 +- .../widgets/tiles/match_tile.dart | 8 +- 7 files changed, 377 insertions(+), 407 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 4adbfa2..4ed0997 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -73,11 +73,11 @@ "successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt", "there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht", "this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden", - "today_at": "Heute um {time}", + "today_at": "Heute um", "undo": "Rückgängig", "unknown_exception": "Unbekannter Fehler (siehe Konsole)", "winner": "Gewinner:in", "winrate": "Siegquote", "wins": "Siege", - "yesterday_at": "Gestern um {time}" + "yesterday_at": "Gestern um" } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index d567f50..dd1e593 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1,367 +1,349 @@ { - "@@locale": "en", - "@all_players": { - "description": "Label for all players list" - }, - "@all_players_selected": { - "description": "Message when all players are added to selection" - }, - "@amount_of_matches": { - "description": "Label for amount of matches statistic" - }, - "@cancel": { - "description": "Cancel button text" - }, - "@choose_game": { - "description": "Label for choosing a game" - }, - "@choose_group": { - "description": "Label for choosing a group" - }, - "@choose_ruleset": { - "description": "Label for choosing a ruleset" - }, - "@could_not_add_player": { - "description": "Error message when adding a player fails", - "placeholders": { - "playerName": { - "type": "String", - "example": "John" - } - } - }, - "@create_group": { - "description": "Button text to create a group" - }, - "@create_match": { - "description": "Button text to create a match" - }, - "@create_new_group": { - "description": "Button text to create a new group" - }, - "@create_new_match": { - "description": "Button text to create a new match" - }, - "@data_successfully_deleted": { - "description": "Success message after deleting data" - }, - "@data_successfully_exported": { - "description": "Success message after exporting data" - }, - "@data_successfully_imported": { - "description": "Success message after importing data" - }, - "@days_ago": { - "description": "Date format for days ago", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "@delete": { - "description": "Delete button text" - }, - "@delete_all_data": { - "description": "Confirmation dialog for deleting all data" - }, - "@error_creating_group": { - "description": "Error message when group creation fails" - }, - "@error_reading_file": { - "description": "Error message when file cannot be read" - }, - "@export_canceled": { - "description": "Message when export is canceled" - }, - "@export_data": { - "description": "Export data menu item" - }, - "@format_exception": { - "description": "Error message for format exceptions" - }, - "@game": { - "description": "Game label" - }, - "@game_name": { - "description": "Placeholder for game name search" - }, - "@game_tracker": { - "description": "App Name" - }, - "@group": { - "description": "Group label" - }, - "@group_name": { - "description": "Placeholder for group name input" - }, - "@groups": { - "description": "Label for groups" - }, - "@home": { - "description": "Home tab label" - }, - "@import_canceled": { - "description": "Message when import is canceled" - }, - "@import_data": { - "description": "Import data menu item" - }, - "@info": { - "description": "Info label" - }, - "@invalid_schema": { - "description": "Error message for invalid schema" - }, - "@least_points": { - "description": "Title for least points ruleset" - }, - "@match_in_progress": { - "description": "Message when match is in progress" - }, - "@match_name": { - "description": "Placeholder for match name input" - }, - "@matches": { - "description": "Label for matches" - }, - "@menu": { - "description": "Menu label" - }, - "@most_points": { - "description": "Title for most points ruleset" - }, - "@no_data_available": { - "description": "Message when no data in the statistic tiles is given" - }, - "@no_groups_created_yet": { - "description": "Message when no groups exist" - }, - "@no_matches_created_yet": { - "description": "Message when no matches exist" - }, - "@no_players_created_yet": { - "description": "Message when no players exist" - }, - "@no_players_found_with_that_name": { - "description": "Message when search returns no results" - }, - "@no_players_selected": { - "description": "Message when no players are selected" - }, - "@no_recent_matches_available": { - "description": "Message when no recent matches exist" - }, - "@no_second_match_available": { - "description": "Message when no second match exists" - }, - "@no_statistics_available": { - "description": "Message when no statistics are available, because no matches were played yet" - }, - "@none": { - "description": "None option label" - }, - "@none_group": { - "description": "None group option label" - }, - "@not_available": { - "description": "Abbreviation for not available" - }, - "@player_name": { - "description": "Placeholder for player name input" - }, - "@players": { - "description": "Players label" - }, - "@players_count": { - "description": "Shows the number of players", - "placeholders": { - "count": { - "type": "int" - } - } - }, - "@quick_create": { - "description": "Title for quick create section" - }, - "@recent_matches": { - "description": "Title for recent matches section" - }, - "@ruleset": { - "description": "Ruleset label" - }, - "@ruleset_least_points": { - "description": "Description for least points ruleset" - }, - "@ruleset_most_points": { - "description": "Description for most points ruleset" - }, - "@ruleset_single_loser": { - "description": "Description for single loser ruleset" - }, - "@ruleset_single_winner": { - "description": "Description for single winner ruleset" - }, - "@search_for_groups": { - "description": "Hint text for group search input field" - }, - "@search_for_players": { - "description": "Hint text for player search input field" - }, - "@select_winner": { - "description": "Label to select the winner" - }, - "@selected_players": { - "description": "Shows the number of selected players", - "placeholders": { - "count": { - "type": "int", - "format": "compact" - } - } - }, - "@settings": { - "description": "Settings label" - }, - "@single_loser": { - "description": "Title for single loser ruleset" - }, - "@single_winner": { - "description": "Title for single winner ruleset" - }, - "@statistics": { - "description": "Statistics tab label" - }, - "@stats": { - "description": "Stats tab label (short)" - }, - "@successfully_added_player": { - "description": "Success message when adding a player", - "placeholders": { - "playerName": { - "type": "String", - "example": "John" - } - } - }, - "@there_is_no_group_matching_your_search": { - "description": "Message when search returns no groups" - }, - "@this_cannot_be_undone": { - "description": "Warning message for irreversible actions" - }, - "@today_at": { - "description": "Date format for today", - "placeholders": { - "time": { - "type": "String", - "example": "14:30" - } - } - }, - "@undo": { - "description": "Undo button text" - }, - "@unknown_exception": { - "description": "Error message for unknown exceptions" - }, - "@winner": { - "description": "Winner label" - }, - "@winrate": { - "description": "Label for winrate statistic" - }, - "@wins": { - "description": "Label for wins statistic" - }, - "@yesterday_at": { - "description": "Date format for yesterday", - "placeholders": { - "time": { - "type": "String", - "example": "14:30" - } - } - }, - "all_players": "All players:", - "all_players_selected": "All players selected", - "amount_of_matches": "Amount of Matches", - "cancel": "Cancel", - "choose_game": "Choose Game", - "choose_group": "Choose Group", - "choose_ruleset": "Choose Ruleset", - "could_not_add_player": "Could not add player {playerName}", - "create_group": "Create Group", - "create_match": "Create match", - "create_new_group": "Create new group", - "create_new_match": "Create new match", - "data_successfully_deleted": "Data successfully deleted", - "data_successfully_exported": "Data successfully exported", - "data_successfully_imported": "Data successfully imported", - "days_ago": "{count} days ago", - "delete": "Delete", - "delete_all_data": "Delete all data?", - "error_creating_group": "Error while creating group, please try again", - "error_reading_file": "Error reading file", - "export_canceled": "Export canceled", - "export_data": "Export data", - "format_exception": "Format Exception (see console)", - "game": "Game", - "game_name": "Game Name", - "game_tracker": "Game Tracker", - "group": "Group", - "group_name": "Group name", - "groups": "Groups", - "home": "Home", - "import_canceled": "Import canceled", - "import_data": "Import data", - "info": "Info", - "invalid_schema": "Invalid Schema", - "least_points": "Least Points", - "match_in_progress": "Match in progress...", - "match_name": "Match name", - "matches": "Matches", - "menu": "Menu", - "most_points": "Most Points", - "no_data_available": "No data available", - "no_groups_created_yet": "No groups created yet", - "no_matches_created_yet": "No matches created yet", - "no_players_created_yet": "No players created yet", - "no_players_found_with_that_name": "No players found with that name", - "no_players_selected": "No players selected", - "no_recent_matches_available": "No recent matches available", - "no_second_match_available": "No second match available", - "no_statistics_available": "No statistics available", - "none": "None", - "none_group": "None", - "not_available": "Not available", - "player_name": "Player name", - "players": "Players", - "players_count": "{count} Players", - "quick_create": "Quick Create", - "recent_matches": "Recent Matches", - "ruleset": "Ruleset", - "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", - "ruleset_most_points": "Traditional ruleset: the player with the most points wins.", - "ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.", - "ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", - "search_for_groups": "Search for groups", - "search_for_players": "Search for players", - "select_winner": "Select Winner:", - "selected_players": "Selected players: {count}", - "settings": "Settings", - "single_loser": "Single Loser", - "single_winner": "Single Winner", - "statistics": "Statistics", - "stats": "Stats", - "successfully_added_player": "Successfully added player {playerName}", - "there_is_no_group_matching_your_search": "There is no group matching your search", - "this_cannot_be_undone": "This can't be undone", - "today_at": "Today at {time}", - "undo": "Undo", - "unknown_exception": "Unknown Exception (see console)", - "winner": "Winner", - "winrate": "Winrate", - "wins": "Wins", - "yesterday_at": "Yesterday at {time}" + "@@locale": "en", + "@all_players": { + "description": "Label for all players list" + }, + "@all_players_selected": { + "description": "Message when all players are added to selection" + }, + "@amount_of_matches": { + "description": "Label for amount of matches statistic" + }, + "@app_name": { + "description": "The name of the App" + }, + "@cancel": { + "description": "Cancel button text" + }, + "@choose_game": { + "description": "Label for choosing a game" + }, + "@choose_group": { + "description": "Label for choosing a group" + }, + "@choose_ruleset": { + "description": "Label for choosing a ruleset" + }, + "@could_not_add_player": { + "description": "Error message when adding a player fails" + }, + "@create_group": { + "description": "Button text to create a group" + }, + "@create_match": { + "description": "Button text to create a match" + }, + "@create_new_group": { + "description": "Button text to create a new group" + }, + "@create_new_match": { + "description": "Button text to create a new match" + }, + "@data_successfully_deleted": { + "description": "Success message after deleting data" + }, + "@data_successfully_exported": { + "description": "Success message after exporting data" + }, + "@data_successfully_imported": { + "description": "Success message after importing data" + }, + "@days_ago": { + "description": "Date format for days ago", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "@delete": { + "description": "Delete button text" + }, + "@delete_all_data": { + "description": "Confirmation dialog for deleting all data" + }, + "@error_creating_group": { + "description": "Error message when group creation fails" + }, + "@error_reading_file": { + "description": "Error message when file cannot be read" + }, + "@export_canceled": { + "description": "Message when export is canceled" + }, + "@export_data": { + "description": "Export data menu item" + }, + "@format_exception": { + "description": "Error message for format exceptions" + }, + "@game": { + "description": "Game label" + }, + "@game_name": { + "description": "Placeholder for game name search" + }, + "@group": { + "description": "Group label" + }, + "@group_name": { + "description": "Placeholder for group name input" + }, + "@groups": { + "description": "Label for groups" + }, + "@home": { + "description": "Home tab label" + }, + "@import_canceled": { + "description": "Message when import is canceled" + }, + "@import_data": { + "description": "Import data menu item" + }, + "@info": { + "description": "Info label" + }, + "@invalid_schema": { + "description": "Error message for invalid schema" + }, + "@least_points": { + "description": "Title for least points ruleset" + }, + "@match_in_progress": { + "description": "Message when match is in progress" + }, + "@match_name": { + "description": "Placeholder for match name input" + }, + "@matches": { + "description": "Label for matches" + }, + "@menu": { + "description": "Menu label" + }, + "@most_points": { + "description": "Title for most points ruleset" + }, + "@no_data_available": { + "description": "Message when no data in the statistic tiles is given" + }, + "@no_groups_created_yet": { + "description": "Message when no groups exist" + }, + "@no_matches_created_yet": { + "description": "Message when no matches exist" + }, + "@no_players_created_yet": { + "description": "Message when no players exist" + }, + "@no_players_found_with_that_name": { + "description": "Message when search returns no results" + }, + "@no_players_selected": { + "description": "Message when no players are selected" + }, + "@no_recent_matches_available": { + "description": "Message when no recent matches exist" + }, + "@no_second_match_available": { + "description": "Message when no second match exists" + }, + "@no_statistics_available": { + "description": "Message when no statistics are available, because no matches were played yet" + }, + "@none": { + "description": "None option label" + }, + "@none_group": { + "description": "None group option label" + }, + "@not_available": { + "description": "Abbreviation for not available" + }, + "@player_name": { + "description": "Placeholder for player name input" + }, + "@players": { + "description": "Players label" + }, + "@players_count": { + "description": "Shows the number of players", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "@quick_create": { + "description": "Title for quick create section" + }, + "@recent_matches": { + "description": "Title for recent matches section" + }, + "@ruleset": { + "description": "Ruleset label" + }, + "@ruleset_least_points": { + "description": "Description for least points ruleset" + }, + "@ruleset_most_points": { + "description": "Description for most points ruleset" + }, + "@ruleset_single_loser": { + "description": "Description for single loser ruleset" + }, + "@ruleset_single_winner": { + "description": "Description for single winner ruleset" + }, + "@search_for_groups": { + "description": "Hint text for group search input field" + }, + "@search_for_players": { + "description": "Hint text for player search input field" + }, + "@select_winner": { + "description": "Label to select the winner" + }, + "@selected_players": { + "description": "Shows the number of selected players", + "placeholders": { + "count": { + "type": "int", + "format": "compact" + } + } + }, + "@settings": { + "description": "Settings label" + }, + "@single_loser": { + "description": "Title for single loser ruleset" + }, + "@single_winner": { + "description": "Title for single winner ruleset" + }, + "@statistics": { + "description": "Statistics tab label" + }, + "@stats": { + "description": "Stats tab label (short)" + }, + "@successfully_added_player": { + "description": "Success message when adding a player", + "placeholders": { + "playerName": { + "type": "String", + "example": "John" + } + } + }, + "@there_is_no_group_matching_your_search": { + "description": "Message when search returns no groups" + }, + "@this_cannot_be_undone": { + "description": "Warning message for irreversible actions" + }, + "@today_at": { + "description": "Date format for today" + }, + "@undo": { + "description": "Undo button text" + }, + "@unknown_exception": { + "description": "Error message for unknown exceptions" + }, + "@winner": { + "description": "Winner label" + }, + "@winrate": { + "description": "Label for winrate statistic" + }, + "@wins": { + "description": "Label for wins statistic" + }, + "@yesterday_at": { + "description": "Date format for yesterday" + }, + "all_players": "All players:", + "all_players_selected": "All players selected", + "amount_of_matches": "Amount of Matches", + "app_name": "Game Tracker", + "cancel": "Cancel", + "choose_game": "Choose Game", + "choose_group": "Choose Group", + "choose_ruleset": "Choose Ruleset", + "could_not_add_player": "Could not add player", + "create_group": "Create Group", + "create_match": "Create match", + "create_new_group": "Create new group", + "create_new_match": "Create new match", + "data_successfully_deleted": "Data successfully deleted", + "data_successfully_exported": "Data successfully exported", + "data_successfully_imported": "Data successfully imported", + "days_ago": "{count} days ago", + "delete": "Delete", + "delete_all_data": "Delete all data?", + "error_creating_group": "Error while creating group, please try again", + "error_reading_file": "Error reading file", + "export_canceled": "Export canceled", + "export_data": "Export data", + "format_exception": "Format Exception (see console)", + "game": "Game", + "game_name": "Game Name", + "group": "Group", + "group_name": "Group name", + "groups": "Groups", + "home": "Home", + "import_canceled": "Import canceled", + "import_data": "Import data", + "info": "Info", + "invalid_schema": "Invalid Schema", + "least_points": "Least Points", + "match_in_progress": "Match in progress...", + "match_name": "Match name", + "matches": "Matches", + "menu": "Menu", + "most_points": "Most Points", + "no_data_available": "No data available", + "no_groups_created_yet": "No groups created yet", + "no_matches_created_yet": "No matches created yet", + "no_players_created_yet": "No players created yet", + "no_players_found_with_that_name": "No players found with that name", + "no_players_selected": "No players selected", + "no_recent_matches_available": "No recent matches available", + "no_second_match_available": "No second match available", + "no_statistics_available": "No statistics available", + "none": "None", + "none_group": "None", + "not_available": "Not available", + "player_name": "Player name", + "players": "Players", + "players_count": "{count} Players", + "quick_create": "Quick Create", + "recent_matches": "Recent Matches", + "ruleset": "Ruleset", + "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", + "ruleset_most_points": "Traditional ruleset: the player with the most points wins.", + "ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.", + "ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", + "search_for_groups": "Search for groups", + "search_for_players": "Search for players", + "select_winner": "Select Winner:", + "selected_players": "Selected players: {count}", + "settings": "Settings", + "single_loser": "Single Loser", + "single_winner": "Single Winner", + "statistics": "Statistics", + "stats": "Stats", + "successfully_added_player": "Successfully added player {playerName}", + "there_is_no_group_matching_your_search": "There is no group matching your search", + "this_cannot_be_undone": "This can't be undone", + "today_at": "Today at", + "undo": "Undo", + "unknown_exception": "Unknown Exception (see console)", + "winner": "Winner", + "winrate": "Winrate", + "wins": "Wins", + "yesterday_at": "Yesterday at" } \ No newline at end of file diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 1743997..79ae804 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -116,6 +116,12 @@ abstract class AppLocalizations { /// **'Amount of Matches'** String get amount_of_matches; + /// The name of the App + /// + /// In en, this message translates to: + /// **'Game Tracker'** + String get app_name; + /// Cancel button text /// /// In en, this message translates to: @@ -143,8 +149,8 @@ abstract class AppLocalizations { /// Error message when adding a player fails /// /// In en, this message translates to: - /// **'Could not add player {playerName}'** - String could_not_add_player(String playerName); + /// **'Could not add player'** + String could_not_add_player(Object playerName); /// Button text to create a group /// @@ -248,12 +254,6 @@ abstract class AppLocalizations { /// **'Game Name'** String get game_name; - /// App Name - /// - /// In en, this message translates to: - /// **'Game Tracker'** - String get game_tracker; - /// Group label /// /// In en, this message translates to: @@ -545,8 +545,8 @@ abstract class AppLocalizations { /// Date format for today /// /// In en, this message translates to: - /// **'Today at {time}'** - String today_at(String time); + /// **'Today at'** + String get today_at; /// Undo button text /// @@ -581,8 +581,8 @@ abstract class AppLocalizations { /// Date format for yesterday /// /// In en, this message translates to: - /// **'Yesterday at {time}'** - String yesterday_at(String time); + /// **'Yesterday at'** + String get yesterday_at; } class _AppLocalizationsDelegate diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 88374a1..17c459b 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -17,6 +17,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get amount_of_matches => 'Anzahl der Spiele'; + @override + String get app_name => 'Game Tracker'; + @override String get cancel => 'Abbrechen'; @@ -30,7 +33,7 @@ class AppLocalizationsDe extends AppLocalizations { String get choose_ruleset => 'Regelwerk wählen'; @override - String could_not_add_player(String playerName) { + String could_not_add_player(Object playerName) { return 'Spieler:in $playerName konnte nicht hinzugefügt werden'; } @@ -88,9 +91,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get game_name => 'Spielvorlagenname'; - @override - String get game_tracker => 'Game Tracker'; - @override String get group => 'Gruppe'; @@ -254,9 +254,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Dies kann nicht rückgängig gemacht werden'; @override - String today_at(String time) { - return 'Heute um $time'; - } + String get today_at => 'Heute um'; @override String get undo => 'Rückgängig'; @@ -274,7 +272,5 @@ class AppLocalizationsDe extends AppLocalizations { String get wins => 'Siege'; @override - String yesterday_at(String time) { - return 'Gestern um $time'; - } + String get yesterday_at => 'Gestern um'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 0cd8842..1bf24ca 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -17,6 +17,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get amount_of_matches => 'Amount of Matches'; + @override + String get app_name => 'Game Tracker'; + @override String get cancel => 'Cancel'; @@ -30,8 +33,8 @@ class AppLocalizationsEn extends AppLocalizations { String get choose_ruleset => 'Choose Ruleset'; @override - String could_not_add_player(String playerName) { - return 'Could not add player $playerName'; + String could_not_add_player(Object playerName) { + return 'Could not add player'; } @override @@ -88,9 +91,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get game_name => 'Game Name'; - @override - String get game_tracker => 'Game Tracker'; - @override String get group => 'Group'; @@ -253,9 +253,7 @@ class AppLocalizationsEn extends AppLocalizations { String get this_cannot_be_undone => 'This can\'t be undone'; @override - String today_at(String time) { - return 'Today at $time'; - } + String get today_at => 'Today at'; @override String get undo => 'Undo'; @@ -273,7 +271,5 @@ class AppLocalizationsEn extends AppLocalizations { String get wins => 'Wins'; @override - String yesterday_at(String time) { - return 'Yesterday at $time'; - } + String get yesterday_at => 'Yesterday at'; } diff --git a/lib/main.dart b/lib/main.dart index 8219fcb..656db90 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -34,7 +34,7 @@ class GameTracker extends StatelessWidget { ); }, debugShowCheckedModeBanner: false, - onGenerateTitle: (context) => AppLocalizations.of(context).game_tracker, + onGenerateTitle: (context) => AppLocalizations.of(context).app_name, themeMode: ThemeMode.dark, // forces dark mode theme: ThemeData( primaryColor: CustomTheme.primaryColor, diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index bc349d3..55d81c3 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -161,13 +161,9 @@ class _MatchTileState extends State { final loc = AppLocalizations.of(context); if (difference.inDays == 0) { - return AppLocalizations.of( - context, - ).today_at(DateFormat('HH:mm').format(dateTime)); + return "${loc.today_at} ${DateFormat('HH:mm').format(dateTime)}"; } else if (difference.inDays == 1) { - return AppLocalizations.of( - context, - ).yesterday_at(DateFormat('HH:mm').format(dateTime)); + return "${loc.yesterday_at} ${DateFormat('HH:mm').format(dateTime)}"; } else if (difference.inDays < 7) { return loc.days_ago(difference.inDays); } else { From 2a1573ee2da3a3e5f4f0103462bcbd83bde77233 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 8 Jan 2026 21:15:01 +0100 Subject: [PATCH 161/222] Remove Whitespace --- lib/main.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 656db90..1dee10b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -40,13 +40,11 @@ class GameTracker extends StatelessWidget { primaryColor: CustomTheme.primaryColor, scaffoldBackgroundColor: CustomTheme.backgroundColor, appBarTheme: CustomTheme.appBarTheme, - colorScheme: ColorScheme.fromSeed( seedColor: CustomTheme.primaryColor, brightness: Brightness.dark, ).copyWith(surface: CustomTheme.backgroundColor), ), - home: const CustomNavigationBar(), ); } From 88766652b964320cf89c9f8158e2b68425c204d5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Thu, 8 Jan 2026 21:21:09 +0100 Subject: [PATCH 162/222] Small loc corrections --- lib/l10n/arb/app_de.arb | 6 +++--- lib/l10n/arb/app_en.arb | 12 +++--------- lib/l10n/generated/app_localizations.dart | 6 +++--- lib/l10n/generated/app_localizations_de.dart | 13 +++---------- lib/l10n/generated/app_localizations_en.dart | 11 ++--------- .../match_view/create_match/create_match_view.dart | 2 +- lib/presentation/widgets/player_selection.dart | 4 +--- 7 files changed, 16 insertions(+), 38 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 4ed0997..4d86460 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -1,6 +1,6 @@ { "@@locale": "de", - "all_players": "Alle Spieler:innen:", + "all_players": "Alle Spieler:innen", "all_players_selected": "Alle Spieler:innen ausgewählt", "amount_of_matches": "Anzahl der Spiele", "cancel": "Abbrechen", @@ -44,7 +44,7 @@ "no_matches_created_yet": "Noch keine Spiele erstellt", "no_players_created_yet": "Noch keine Spieler:in erstellt", "no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden", - "no_players_selected": "Keine Spieler:in ausgewählt", + "no_players_selected": "Keine Spieler:innen ausgewählt", "no_recent_matches_available": "Keine letzten Spiele verfügbar", "no_second_match_available": "Kein zweites Spiel verfügbar", "no_statistics_available": "Keine Statistiken verfügbar", @@ -64,7 +64,7 @@ "search_for_groups": "Nach Gruppen suchen", "search_for_players": "Nach Spieler:innen suchen", "select_winner": "Gewinner:in wählen:", - "selected_players": "Ausgewählte Spieler:innen: {count}", + "selected_players": "Ausgewählte Spieler:innen", "settings": "Einstellungen", "single_loser": "Ein:e Verlierer:in", "single_winner": "Ein:e Gewinner:in", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index dd1e593..17c3b06 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -206,13 +206,7 @@ "description": "Label to select the winner" }, "@selected_players": { - "description": "Shows the number of selected players", - "placeholders": { - "count": { - "type": "int", - "format": "compact" - } - } + "description": "Shows the number of selected players" }, "@settings": { "description": "Settings label" @@ -265,7 +259,7 @@ "@yesterday_at": { "description": "Date format for yesterday" }, - "all_players": "All players:", + "all_players": "All players", "all_players_selected": "All players selected", "amount_of_matches": "Amount of Matches", "app_name": "Game Tracker", @@ -330,7 +324,7 @@ "search_for_groups": "Search for groups", "search_for_players": "Search for players", "select_winner": "Select Winner:", - "selected_players": "Selected players: {count}", + "selected_players": "Selected players", "settings": "Settings", "single_loser": "Single Loser", "single_winner": "Single Winner", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 79ae804..5080ff3 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -101,7 +101,7 @@ abstract class AppLocalizations { /// Label for all players list /// /// In en, this message translates to: - /// **'All players:'** + /// **'All players'** String get all_players; /// Message when all players are added to selection @@ -491,8 +491,8 @@ abstract class AppLocalizations { /// Shows the number of selected players /// /// In en, this message translates to: - /// **'Selected players: {count}'** - String selected_players(int count); + /// **'Selected players'** + String get selected_players; /// Settings label /// diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 17c459b..c720941 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -9,7 +9,7 @@ class AppLocalizationsDe extends AppLocalizations { AppLocalizationsDe([String locale = 'de']) : super(locale); @override - String get all_players => 'Alle Spieler:innen:'; + String get all_players => 'Alle Spieler:innen'; @override String get all_players_selected => 'Alle Spieler:innen ausgewählt'; @@ -150,7 +150,7 @@ class AppLocalizationsDe extends AppLocalizations { 'Keine Spieler:in mit diesem Namen gefunden'; @override - String get no_players_selected => 'Keine Spieler:in ausgewählt'; + String get no_players_selected => 'Keine Spieler:innen ausgewählt'; @override String get no_recent_matches_available => 'Keine letzten Spiele verfügbar'; @@ -216,14 +216,7 @@ class AppLocalizationsDe extends AppLocalizations { String get select_winner => 'Gewinner:in wählen:'; @override - String selected_players(int count) { - final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( - locale: localeName, - ); - final String countString = countNumberFormat.format(count); - - return 'Ausgewählte Spieler:innen: $countString'; - } + String get selected_players => 'Ausgewählte Spieler:innen'; @override String get settings => 'Einstellungen'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 1bf24ca..cd71035 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -9,7 +9,7 @@ class AppLocalizationsEn extends AppLocalizations { AppLocalizationsEn([String locale = 'en']) : super(locale); @override - String get all_players => 'All players:'; + String get all_players => 'All players'; @override String get all_players_selected => 'All players selected'; @@ -216,14 +216,7 @@ class AppLocalizationsEn extends AppLocalizations { String get select_winner => 'Select Winner:'; @override - String selected_players(int count) { - final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact( - locale: localeName, - ); - final String countString = countNumberFormat.format(count); - - return 'Selected players: $countString'; - } + String get selected_players => 'Selected players'; @override String get settings => 'Settings'; diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 0cc25d0..dc6690b 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -155,7 +155,7 @@ class _CreateMatchViewState extends State { (r) => r.$1 == selectedRuleset, ); } else { - hintText = AppLocalizations.of(context).match_name; + hintText = loc.match_name; selectedRuleset = null; } }); diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index a582427..9280ae0 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -119,9 +119,7 @@ class _PlayerSelectionState extends State { ), const SizedBox(height: 10), Text( - AppLocalizations.of( - context, - ).selected_players(selectedPlayers.length), + loc.selected_players, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), ), const SizedBox(height: 10), From 349ff948defb9b668d6783511fcdadd048a09a20 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 9 Jan 2026 19:50:44 +0100 Subject: [PATCH 163/222] Modified match tile --- .../main_menu/match_view/match_view.dart | 29 ++++++++++--------- .../widgets/tiles/match_tile.dart | 12 ++++++-- pubspec.yaml | 7 +---- 3 files changed, 27 insertions(+), 21 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 45b957f..108b592 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -78,20 +78,23 @@ class _MatchViewState extends State { height: MediaQuery.paddingOf(context).bottom - 20, ); } - return MatchTile( - onTap: () async { - Navigator.push( - context, - CupertinoPageRoute( - fullscreenDialog: true, - builder: (context) => MatchResultView( - match: matches[index], - onWinnerChanged: loadGames, + return Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: MatchTile( + onTap: () async { + Navigator.push( + context, + CupertinoPageRoute( + fullscreenDialog: true, + builder: (context) => MatchResultView( + match: matches[index], + onWinnerChanged: loadGames, + ), ), - ), - ); - }, - match: matches[index], + ); + }, + match: matches[index], + ), ); }, ), diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 55d81c3..21e24c0 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -11,7 +11,12 @@ import 'package:intl/intl.dart'; /// - [match]: The match data to be displayed. /// - [onTap]: The callback invoked when the tile is tapped. class MatchTile extends StatefulWidget { - const MatchTile({super.key, required this.match, required this.onTap}); + const MatchTile({ + super.key, + required this.match, + required this.onTap, + this.width, + }); /// The match data to be displayed. final Match match; @@ -19,6 +24,8 @@ class MatchTile extends StatefulWidget { /// The callback invoked when the tile is tapped. final VoidCallback onTap; + final double? width; + @override State createState() => _MatchTileState(); } @@ -41,7 +48,8 @@ class _MatchTileState extends State { return GestureDetector( onTap: widget.onTap, child: Container( - margin: CustomTheme.tileMargin, + margin: EdgeInsets.zero, + width: widget.width, padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: CustomTheme.boxColor, diff --git a/pubspec.yaml b/pubspec.yaml index e79ca17..993ef78 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.1+21 +version: 0.0.1+23 environment: sdk: ^3.8.1 @@ -9,11 +9,6 @@ environment: dependencies: flutter: sdk: flutter - material_symbols_icons: ^4.2815.1 - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.8 drift: ^2.27.0 drift_flutter: ^0.2.4 path_provider: ^2.1.5 From a9d2325eee9c9f4ed4ab83cb0d8367bc864df987 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 9 Jan 2026 19:50:51 +0100 Subject: [PATCH 164/222] Implemented match tile in home view --- .../views/main_menu/home_view.dart | 132 +++++++++++------- 1 file changed, 84 insertions(+), 48 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 170adb4..0761f08 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -5,10 +5,11 @@ import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; +import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; -import 'package:game_tracker/presentation/widgets/tiles/match_summary_tile.dart'; +import 'package:game_tracker/presentation/widgets/tiles/match_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; import 'package:provider/provider.dart'; @@ -86,6 +87,38 @@ class _HomeViewState extends State { ], ), Padding( + padding: const EdgeInsets.only(top: 8.0), + child: MatchTile( + width: constraints.maxWidth * 0.95, + match: recentMatches[0], + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + fullscreenDialog: true, + builder: (context) => + MatchResultView(match: recentMatches[0]), + ), + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: MatchTile( + width: constraints.maxWidth * 0.95, + match: recentMatches[1], + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + fullscreenDialog: true, + builder: (context) => + MatchResultView(match: recentMatches[1]), + ), + ); + }, + ), + ), + /*Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), child: InfoTile( width: constraints.maxWidth * 0.95, @@ -156,53 +189,56 @@ class _HomeViewState extends State { ), ), ), - ), - InfoTile( - width: constraints.maxWidth * 0.95, - title: loc.quick_create, - icon: Icons.add_box_rounded, - content: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - QuickCreateButton( - text: 'Category 1', - onPressed: () {}, - ), - QuickCreateButton( - text: 'Category 2', - onPressed: () {}, - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - QuickCreateButton( - text: 'Category 3', - onPressed: () {}, - ), - QuickCreateButton( - text: 'Category 4', - onPressed: () {}, - ), - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - QuickCreateButton( - text: 'Category 5', - onPressed: () {}, - ), - QuickCreateButton( - text: 'Category 6', - onPressed: () {}, - ), - ], - ), - ], + ),*/ + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: InfoTile( + width: constraints.maxWidth * 0.95, + title: loc.quick_create, + icon: Icons.add_box_rounded, + content: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + QuickCreateButton( + text: 'Category 1', + onPressed: () {}, + ), + QuickCreateButton( + text: 'Category 2', + onPressed: () {}, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + QuickCreateButton( + text: 'Category 3', + onPressed: () {}, + ), + QuickCreateButton( + text: 'Category 4', + onPressed: () {}, + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + QuickCreateButton( + text: 'Category 5', + onPressed: () {}, + ), + QuickCreateButton( + text: 'Category 6', + onPressed: () {}, + ), + ], + ), + ], + ), ), ), ], From 3d510d5b3d5ea1efccdc40030da73a839adeeb67 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 9 Jan 2026 20:04:10 +0100 Subject: [PATCH 165/222] Implemented compact mode for match tiles --- .../views/main_menu/home_view.dart | 2 + .../widgets/tiles/match_tile.dart | 63 ++++++++++++++++++- 2 files changed, 63 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 0761f08..6599b98 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -89,6 +89,7 @@ class _HomeViewState extends State { Padding( padding: const EdgeInsets.only(top: 8.0), child: MatchTile( + compact: true, width: constraints.maxWidth * 0.95, match: recentMatches[0], onTap: () { @@ -105,6 +106,7 @@ class _HomeViewState extends State { Padding( padding: const EdgeInsets.only(top: 8.0), child: MatchTile( + compact: true, width: constraints.maxWidth * 0.95, match: recentMatches[1], onTap: () { diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 21e24c0..59ee0b9 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -10,12 +10,15 @@ import 'package:intl/intl.dart'; /// creation date, associated group, winner, and players. /// - [match]: The match data to be displayed. /// - [onTap]: The callback invoked when the tile is tapped. +/// - [width]: Optional width for the tile. +/// - [compact]: Whether to display the tile in a compact mode class MatchTile extends StatefulWidget { const MatchTile({ super.key, required this.match, required this.onTap, this.width, + this.compact = false, }); /// The match data to be displayed. @@ -24,8 +27,12 @@ class MatchTile extends StatefulWidget { /// The callback invoked when the tile is tapped. final VoidCallback onTap; + /// Optional width for the tile. final double? width; + /// Whether to display the tile in a compact mode + final bool compact; + @override State createState() => _MatchTileState(); } @@ -88,7 +95,22 @@ class _MatchTileState extends State { const SizedBox(width: 6), Expanded( child: Text( - group.name, + '${group.name}${widget.match.players != null ? ' + ${widget.match.players?.length}' : ''}', + style: const TextStyle(fontSize: 14, color: Colors.grey), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 12), + ] else if (widget.compact) ...[ + Row( + children: [ + const Icon(Icons.person, size: 16, color: Colors.grey), + const SizedBox(width: 6), + Expanded( + child: Text( + '${widget.match.players!.length} ${loc.players}', style: const TextStyle(fontSize: 14, color: Colors.grey), overflow: TextOverflow.ellipsis, ), @@ -135,9 +157,46 @@ class _MatchTileState extends State { ), ), const SizedBox(height: 12), + ] else ...[ + Container( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 12, + ), + decoration: BoxDecoration( + color: Colors.amber.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: Colors.amber.withValues(alpha: 0.3), + width: 1, + ), + ), + child: Row( + children: [ + const Icon( + Icons.watch_later, + size: 20, + color: Colors.amber, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + loc.match_in_progress, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: CustomTheme.textColor, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + const SizedBox(height: 12), ], - if (_allPlayers.isNotEmpty) ...[ + if (_allPlayers.isNotEmpty && widget.compact == false) ...[ Text( loc.players, style: const TextStyle( From afb7a5f1d4c18be253b15b6acb2d8187b9ae9b00 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 9 Jan 2026 20:15:50 +0100 Subject: [PATCH 166/222] MatchTiles only show when recent games are available --- .../views/main_menu/home_view.dart | 66 ++++++++++--------- pubspec.yaml | 2 +- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 6599b98..a94eae4 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -86,40 +86,42 @@ class _HomeViewState extends State { ), ], ), - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: MatchTile( - compact: true, - width: constraints.maxWidth * 0.95, - match: recentMatches[0], - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - fullscreenDialog: true, - builder: (context) => - MatchResultView(match: recentMatches[0]), - ), - ); - }, + if (recentMatches.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: MatchTile( + compact: true, + width: constraints.maxWidth * 0.95, + match: recentMatches[0], + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + fullscreenDialog: true, + builder: (context) => + MatchResultView(match: recentMatches[0]), + ), + ); + }, + ), ), - ), - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: MatchTile( - compact: true, - width: constraints.maxWidth * 0.95, - match: recentMatches[1], - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - fullscreenDialog: true, - builder: (context) => - MatchResultView(match: recentMatches[1]), - ), - ); - }, + if (recentMatches.length > 1) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: MatchTile( + compact: true, + width: constraints.maxWidth * 0.95, + match: recentMatches[1], + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + fullscreenDialog: true, + builder: (context) => + MatchResultView(match: recentMatches[1]), + ), + ); + }, + ), ), - ), /*Padding( padding: const EdgeInsets.symmetric(vertical: 16.0), child: InfoTile( diff --git a/pubspec.yaml b/pubspec.yaml index 993ef78..835cabf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.1+23 +version: 0.0.1+43 environment: sdk: ^3.8.1 From 644728a9dfffb7d0c1ec26f77379a475fe9d49a5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 9 Jan 2026 20:18:52 +0100 Subject: [PATCH 167/222] Removed unneccesary code --- .../views/main_menu/home_view.dart | 92 ------------------- pubspec.yaml | 2 +- 2 files changed, 1 insertion(+), 93 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index a94eae4..c9825c8 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -122,78 +122,6 @@ class _HomeViewState extends State { }, ), ), - /*Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: InfoTile( - width: constraints.maxWidth * 0.95, - title: loc.recent_matches, - icon: Icons.timer, - content: Padding( - padding: const EdgeInsets.symmetric(horizontal: 40.0), - child: Visibility( - visible: !isLoading && loadedRecentMatches.isNotEmpty, - replacement: Center( - heightFactor: 12, - child: Text( - AppLocalizations.of( - context, - ).no_recent_matches_available, - ), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - MatchSummaryTile( - matchTitle: recentMatches[0].name, - game: 'Winner', - ruleset: 'Ruleset', - players: _getPlayerText( - recentMatches[0], - context, - ), - winner: recentMatches[0].winner == null - ? AppLocalizations.of( - context, - ).match_in_progress - : recentMatches[0].winner!.name, - ), - const Padding( - padding: EdgeInsets.symmetric(vertical: 8.0), - child: Divider(), - ), - if (loadedRecentMatches.length > 1) ...[ - MatchSummaryTile( - matchTitle: recentMatches[1].name, - game: 'Winner', - ruleset: 'Ruleset', - players: _getPlayerText( - recentMatches[1], - context, - ), - winner: recentMatches[1].winner == null - ? AppLocalizations.of( - context, - ).match_in_progress - : recentMatches[1].winner!.name, - ), - const SizedBox(height: 8), - ] else ...[ - Center( - heightFactor: 5.35, - child: Text( - AppLocalizations.of( - context, - ).no_second_match_available, - ), - ), - ], - ], - ), - ), - ), - ), - ),*/ Padding( padding: const EdgeInsets.only(top: 8.0), child: InfoTile( @@ -271,11 +199,6 @@ class _HomeViewState extends State { ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) .take(2) .toList(); - if (loadedRecentMatches.length < 2) { - recentMatches.add( - Match(name: 'Dummy Match', winner: null, group: null, players: null), - ); - } if (mounted) { setState(() { isLoading = false; @@ -283,19 +206,4 @@ class _HomeViewState extends State { } }); } - - /// Generates a text representation of the players in the match. - /// If the match has a group, it returns the group name and the number of additional players. - /// If there is no group, it returns the count of players. - String _getPlayerText(Match game, context) { - final loc = AppLocalizations.of(context); - if (game.group == null) { - final playerCount = game.players?.length ?? 0; - return loc.players_count(playerCount); - } - if (game.players == null || game.players!.isEmpty) { - return game.group!.name; - } - return '${game.group!.name} + ${game.players!.length}'; - } } diff --git a/pubspec.yaml b/pubspec.yaml index 835cabf..5887957 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.1+43 +version: 0.0.2+47 environment: sdk: ^3.8.1 From 799c849570685334cdcb82279b694c96e7b2bf7f Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 9 Jan 2026 21:55:58 +0100 Subject: [PATCH 168/222] wrap every view that uses a snackbar in ScaffoldMessenger --- .../group_view/create_group_view.dart | 114 ++++---- .../create_match/create_match_view.dart | 254 +++++++++--------- .../views/main_menu/settings_view.dart | 196 +++++++------- 3 files changed, 285 insertions(+), 279 deletions(-) diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index f92df0f..8192c6b 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -44,66 +44,68 @@ class _CreateGroupViewState extends State { @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context); - return Scaffold( - backgroundColor: CustomTheme.backgroundColor, - appBar: AppBar(title: Text(loc.create_new_group)), - body: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Container( - margin: CustomTheme.standardMargin, - child: TextInputField( - controller: _groupNameController, - hintText: loc.group_name, + return ScaffoldMessenger( + child: Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar(title: Text(loc.create_new_group)), + body: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + margin: CustomTheme.standardMargin, + child: TextInputField( + controller: _groupNameController, + hintText: loc.group_name, + ), ), - ), - Expanded( - child: PlayerSelection( - onChanged: (value) { - setState(() { - selectedPlayers = [...value]; - }); - }, + Expanded( + child: PlayerSelection( + onChanged: (value) { + setState(() { + selectedPlayers = [...value]; + }); + }, + ), ), - ), - CustomWidthButton( - text: loc.create_group, - sizeRelativeToWidth: 0.95, - buttonType: ButtonType.primary, - onPressed: - (_groupNameController.text.isEmpty || - (selectedPlayers.length < 2)) - ? null - : () async { - bool success = await db.groupDao.addGroup( - group: Group( - name: _groupNameController.text.trim(), - members: selectedPlayers, - ), - ); - if (!context.mounted) return; - if (success) { - Navigator.pop(context); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - backgroundColor: CustomTheme.boxColor, - content: Center( - child: Text( - AppLocalizations.of( - context, - ).error_creating_group, - style: const TextStyle(color: Colors.white), - ), - ), + CustomWidthButton( + text: loc.create_group, + sizeRelativeToWidth: 0.95, + buttonType: ButtonType.primary, + onPressed: + (_groupNameController.text.isEmpty || + (selectedPlayers.length < 2)) + ? null + : () async { + bool success = await db.groupDao.addGroup( + group: Group( + name: _groupNameController.text.trim(), + members: selectedPlayers, ), ); - } - }, - ), - const SizedBox(height: 20), - ], + if (!context.mounted) return; + if (success) { + Navigator.pop(context); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + backgroundColor: CustomTheme.boxColor, + content: Center( + child: Text( + AppLocalizations.of( + context, + ).error_creating_group, + style: const TextStyle(color: Colors.white), + ), + ), + ), + ); + } + }, + ), + const SizedBox(height: 20), + ], + ), ), ), ); diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index dc6690b..518fd24 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -119,140 +119,142 @@ class _CreateMatchViewState extends State { @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context); - return Scaffold( - backgroundColor: CustomTheme.backgroundColor, - appBar: AppBar(title: Text(loc.create_new_match)), - body: SafeArea( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Container( - margin: CustomTheme.tileMargin, - child: TextInputField( - controller: _matchNameController, - hintText: hintText ?? '', + return ScaffoldMessenger( + child: Scaffold( + backgroundColor: CustomTheme.backgroundColor, + appBar: AppBar(title: Text(loc.create_new_match)), + body: SafeArea( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + margin: CustomTheme.tileMargin, + child: TextInputField( + controller: _matchNameController, + hintText: hintText ?? '', + ), ), - ), - ChooseTile( - title: loc.game, - trailingText: selectedGameIndex == -1 - ? loc.none - : games[selectedGameIndex].$1, - onPressed: () async { - selectedGameIndex = await Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => ChooseGameView( - games: games, - initialGameIndex: selectedGameIndex, + ChooseTile( + title: loc.game, + trailingText: selectedGameIndex == -1 + ? loc.none + : games[selectedGameIndex].$1, + onPressed: () async { + selectedGameIndex = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ChooseGameView( + games: games, + initialGameIndex: selectedGameIndex, + ), ), - ), - ); - setState(() { - if (selectedGameIndex != -1) { - hintText = games[selectedGameIndex].$1; - selectedRuleset = games[selectedGameIndex].$3; - selectedRulesetIndex = _rulesets.indexWhere( - (r) => r.$1 == selectedRuleset, - ); - } else { - hintText = loc.match_name; - selectedRuleset = null; - } - }); - }, - ), - ChooseTile( - title: loc.ruleset, - trailingText: selectedRuleset == null - ? loc.none - : translateRulesetToString(selectedRuleset!, context), - onPressed: () async { - selectedRuleset = await Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => ChooseRulesetView( - rulesets: _rulesets, - initialRulesetIndex: selectedRulesetIndex, - ), - ), - ); - if (!mounted) return; - selectedRulesetIndex = _rulesets.indexWhere( - (r) => r.$1 == selectedRuleset, - ); - selectedGameIndex = -1; - setState(() {}); - }, - ), - ChooseTile( - title: loc.group, - trailingText: selectedGroup == null - ? loc.none_group - : selectedGroup!.name, - onPressed: () async { - selectedGroup = await Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => ChooseGroupView( - groups: groupsList, - initialGroupId: selectedGroupId, - ), - ), - ); - selectedGroupId = selectedGroup?.id ?? ''; - if (selectedGroup != null) { - filteredPlayerList = playerList - .where( - (p) => !selectedGroup!.members.any((m) => m.id == p.id), - ) - .toList(); - } else { - filteredPlayerList = List.from(playerList); - } - setState(() {}); - }, - ), - Expanded( - child: PlayerSelection( - key: ValueKey(selectedGroup?.id ?? 'no_group'), - initialSelectedPlayers: selectedPlayers ?? [], - availablePlayers: filteredPlayerList, - onChanged: (value) { + ); setState(() { - selectedPlayers = value; + if (selectedGameIndex != -1) { + hintText = games[selectedGameIndex].$1; + selectedRuleset = games[selectedGameIndex].$3; + selectedRulesetIndex = _rulesets.indexWhere( + (r) => r.$1 == selectedRuleset, + ); + } else { + hintText = loc.match_name; + selectedRuleset = null; + } }); }, ), - ), - CustomWidthButton( - text: loc.create_match, - sizeRelativeToWidth: 0.95, - buttonType: ButtonType.primary, - onPressed: _enableCreateGameButton() - ? () async { - Match match = Match( - name: _matchNameController.text.isEmpty - ? (hintText ?? '') - : _matchNameController.text.trim(), - createdAt: DateTime.now(), - group: selectedGroup, - players: selectedPlayers, - ); - await db.matchDao.addMatch(match: match); - if (context.mounted) { - Navigator.pushReplacement( - context, - CupertinoPageRoute( - fullscreenDialog: true, - builder: (context) => MatchResultView( - match: match, - onWinnerChanged: widget.onWinnerChanged, - ), - ), + ChooseTile( + title: loc.ruleset, + trailingText: selectedRuleset == null + ? loc.none + : translateRulesetToString(selectedRuleset!, context), + onPressed: () async { + selectedRuleset = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ChooseRulesetView( + rulesets: _rulesets, + initialRulesetIndex: selectedRulesetIndex, + ), + ), + ); + if (!mounted) return; + selectedRulesetIndex = _rulesets.indexWhere( + (r) => r.$1 == selectedRuleset, + ); + selectedGameIndex = -1; + setState(() {}); + }, + ), + ChooseTile( + title: loc.group, + trailingText: selectedGroup == null + ? loc.none_group + : selectedGroup!.name, + onPressed: () async { + selectedGroup = await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => ChooseGroupView( + groups: groupsList, + initialGroupId: selectedGroupId, + ), + ), + ); + selectedGroupId = selectedGroup?.id ?? ''; + if (selectedGroup != null) { + filteredPlayerList = playerList + .where( + (p) => !selectedGroup!.members.any((m) => m.id == p.id), + ) + .toList(); + } else { + filteredPlayerList = List.from(playerList); + } + setState(() {}); + }, + ), + Expanded( + child: PlayerSelection( + key: ValueKey(selectedGroup?.id ?? 'no_group'), + initialSelectedPlayers: selectedPlayers ?? [], + availablePlayers: filteredPlayerList, + onChanged: (value) { + setState(() { + selectedPlayers = value; + }); + }, + ), + ), + CustomWidthButton( + text: loc.create_match, + sizeRelativeToWidth: 0.95, + buttonType: ButtonType.primary, + onPressed: _enableCreateGameButton() + ? () async { + Match match = Match( + name: _matchNameController.text.isEmpty + ? (hintText ?? '') + : _matchNameController.text.trim(), + createdAt: DateTime.now(), + group: selectedGroup, + players: selectedPlayers, ); + await db.matchDao.addMatch(match: match); + if (context.mounted) { + Navigator.pushReplacement( + context, + CupertinoPageRoute( + fullscreenDialog: true, + builder: (context) => MatchResultView( + match: match, + onWinnerChanged: widget.onWinnerChanged, + ), + ), + ); + } } - } - : null, - ), - ], + : null, + ), + ], + ), ), ), ); diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 374c463..c5fcfa2 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -21,105 +21,107 @@ class _SettingsViewState extends State { @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context); - return Scaffold( - appBar: AppBar(backgroundColor: CustomTheme.backgroundColor), - backgroundColor: CustomTheme.backgroundColor, - body: LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) => - SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(24, 0, 24, 10), - child: Text( - textAlign: TextAlign.start, - loc.menu, - style: const TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 10, - ), - child: Text( - textAlign: TextAlign.start, - loc.settings, - style: const TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - ), - ), - ), - SettingsListTile( - title: loc.export_data, - icon: Icons.upload_rounded, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () async { - final String json = - await DataTransferService.getAppDataAsJson(context); - final result = await DataTransferService.exportData( - json, - 'game_tracker-data', - ); - if (!context.mounted) return; - showExportSnackBar(context: context, result: result); - }, - ), - SettingsListTile( - title: loc.import_data, - icon: Icons.download_rounded, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () async { - final result = await DataTransferService.importData( - context, - ); - if (!context.mounted) return; - showImportSnackBar(context: context, result: result); - }, - ), - SettingsListTile( - title: loc.delete_all_data, - icon: Icons.delete_rounded, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text(loc.delete_all_data), - content: Text(loc.this_cannot_be_undone), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(false), - child: Text(loc.cancel), - ), - TextButton( - onPressed: () => Navigator.of(context).pop(true), - child: Text(loc.delete), - ), - ], + return ScaffoldMessenger( + child: Scaffold( + appBar: AppBar(backgroundColor: CustomTheme.backgroundColor), + backgroundColor: CustomTheme.backgroundColor, + body: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) => + SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(24, 0, 24, 10), + child: Text( + textAlign: TextAlign.start, + loc.menu, + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, ), - ).then((confirmed) { - if (confirmed == true && context.mounted) { - DataTransferService.deleteAllData(context); - showSnackbar( - context: context, - message: AppLocalizations.of( - context, - ).data_successfully_deleted, - ); - } - }); - }, - ), - ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24, + vertical: 10, + ), + child: Text( + textAlign: TextAlign.start, + loc.settings, + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + ), + ), + ), + SettingsListTile( + title: loc.export_data, + icon: Icons.upload_rounded, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () async { + final String json = + await DataTransferService.getAppDataAsJson(context); + final result = await DataTransferService.exportData( + json, + 'game_tracker-data', + ); + if (!context.mounted) return; + showExportSnackBar(context: context, result: result); + }, + ), + SettingsListTile( + title: loc.import_data, + icon: Icons.download_rounded, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () async { + final result = await DataTransferService.importData( + context, + ); + if (!context.mounted) return; + showImportSnackBar(context: context, result: result); + }, + ), + SettingsListTile( + title: loc.delete_all_data, + icon: Icons.delete_rounded, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(loc.delete_all_data), + content: Text(loc.this_cannot_be_undone), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text(loc.cancel), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text(loc.delete), + ), + ], + ), + ).then((confirmed) { + if (confirmed == true && context.mounted) { + DataTransferService.deleteAllData(context); + showSnackbar( + context: context, + message: AppLocalizations.of( + context, + ).data_successfully_deleted, + ); + } + }); + }, + ), + ], + ), ), - ), + ), ), ); } From a4ef9705f950042bd4a73d0b6df4f15327314424 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Fri, 9 Jan 2026 22:22:26 +0100 Subject: [PATCH 169/222] made PageRoutes adapt to os, default to MaterialPageRoute --- .../main_menu/custom_navigation_bar.dart | 5 +++- .../main_menu/group_view/groups_view.dart | 9 +++++++- .../create_match/create_match_view.dart | 23 ++++++++++++++++--- .../main_menu/match_view/match_view.dart | 5 +++- 4 files changed, 36 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index a8b18c8..17eeb41 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -1,3 +1,6 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; @@ -56,7 +59,7 @@ class _CustomNavigationBarState extends State onPressed: () async { await Navigator.push( context, - MaterialPageRoute(builder: (_) => const SettingsView()), + Platform.isIOS ? CupertinoPageRoute(builder: (_) => const SettingsView()) : MaterialPageRoute(builder: (_) => const SettingsView()), ); setState(() { tabKeyCount++; diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index 57d05a4..d19028f 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -1,3 +1,6 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/custom_theme.dart'; @@ -85,7 +88,11 @@ class _GroupsViewState extends State { onPressed: () async { await Navigator.push( context, - MaterialPageRoute( + Platform.isIOS ? CupertinoPageRoute( + builder: (context) { + return const CreateGroupView(); + }, + ) : MaterialPageRoute( builder: (context) { return const CreateGroupView(); }, diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index dc6690b..cff066d 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; @@ -140,7 +142,12 @@ class _CreateMatchViewState extends State { : games[selectedGameIndex].$1, onPressed: () async { selectedGameIndex = await Navigator.of(context).push( - MaterialPageRoute( + Platform.isIOS ? CupertinoPageRoute( + builder: (context) => ChooseGameView( + games: games, + initialGameIndex: selectedGameIndex, + ), + ) : MaterialPageRoute( builder: (context) => ChooseGameView( games: games, initialGameIndex: selectedGameIndex, @@ -168,7 +175,12 @@ class _CreateMatchViewState extends State { : translateRulesetToString(selectedRuleset!, context), onPressed: () async { selectedRuleset = await Navigator.of(context).push( - MaterialPageRoute( + Platform.isIOS ? CupertinoPageRoute( + builder: (context) => ChooseRulesetView( + rulesets: _rulesets, + initialRulesetIndex: selectedRulesetIndex, + ), + ) : MaterialPageRoute( builder: (context) => ChooseRulesetView( rulesets: _rulesets, initialRulesetIndex: selectedRulesetIndex, @@ -190,7 +202,12 @@ class _CreateMatchViewState extends State { : selectedGroup!.name, onPressed: () async { selectedGroup = await Navigator.of(context).push( - MaterialPageRoute( + Platform.isIOS ? CupertinoPageRoute( + builder: (context) => ChooseGroupView( + groups: groupsList, + initialGroupId: selectedGroupId, + ), + ): MaterialPageRoute( builder: (context) => ChooseGroupView( groups: groupsList, initialGroupId: selectedGroupId, diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 45b957f..55d35c6 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -1,4 +1,5 @@ import 'dart:core' hide Match; +import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -105,7 +106,9 @@ class _MatchViewState extends State { onPressed: () async { Navigator.push( context, - MaterialPageRoute( + Platform.isIOS ? CupertinoPageRoute( + builder: (context) => + CreateMatchView(onWinnerChanged: loadGames)) : MaterialPageRoute( builder: (context) => CreateMatchView(onWinnerChanged: loadGames), ), From 2c4cef76d8fb0e8fe96ecd6b7213fd1235467500 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Fri, 9 Jan 2026 23:55:29 +0100 Subject: [PATCH 170/222] Reimplemented info tile & match tile into it --- .../views/main_menu/home_view.dart | 80 ++++++++++--------- pubspec.yaml | 2 +- 2 files changed, 42 insertions(+), 40 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index c9825c8..118c0e1 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -34,7 +34,7 @@ class _HomeViewState extends State { /// Recent matches to display, initially filled with skeleton matches List recentMatches = List.filled( - 2, + 3, Match( name: 'Skeleton Match', group: Group( @@ -44,7 +44,6 @@ class _HomeViewState extends State { Player(name: 'Skeleton Player 2'), ], ), - winner: Player(name: 'Skeleton Player 1'), ), ); @@ -86,44 +85,46 @@ class _HomeViewState extends State { ), ], ), - if (recentMatches.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: MatchTile( - compact: true, - width: constraints.maxWidth * 0.95, - match: recentMatches[0], - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - fullscreenDialog: true, - builder: (context) => - MatchResultView(match: recentMatches[0]), - ), - ); - }, - ), - ), - if (recentMatches.length > 1) - Padding( - padding: const EdgeInsets.only(top: 8.0), - child: MatchTile( - compact: true, - width: constraints.maxWidth * 0.95, - match: recentMatches[1], - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - fullscreenDialog: true, - builder: (context) => - MatchResultView(match: recentMatches[1]), - ), - ); - }, - ), - ), Padding( - padding: const EdgeInsets.only(top: 8.0), + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: InfoTile( + width: constraints.maxWidth * 0.95, + title: loc.recent_matches, + icon: Icons.history_rounded, + content: Column( + children: [ + if (recentMatches.isNotEmpty) + for (Match match in recentMatches) + Padding( + padding: const EdgeInsets.symmetric( + vertical: 6.0, + ), + child: MatchTile( + compact: true, + width: constraints.maxWidth * 0.9, + match: match, + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + fullscreenDialog: true, + builder: (context) => + MatchResultView(match: match), + ), + ); + }, + ), + ) + else + Center( + heightFactor: 5, + child: Text(loc.no_recent_matches_available), + ), + ], + ), + ), + ), + Padding( + padding: EdgeInsets.zero, child: InfoTile( width: constraints.maxWidth * 0.95, title: loc.quick_create, @@ -173,6 +174,7 @@ class _HomeViewState extends State { ), ), ), + SizedBox(height: MediaQuery.paddingOf(context).bottom), ], ), ), diff --git a/pubspec.yaml b/pubspec.yaml index 5887957..2474f78 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.2+47 +version: 0.0.2+57 environment: sdk: ^3.8.1 From db3e8215fa48308b8a892309d53e6e7f890e91c3 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 13:42:09 +0100 Subject: [PATCH 171/222] Added ios swipe back gesture --- lib/main.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index 1dee10b..8889755 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -44,6 +44,9 @@ class GameTracker extends StatelessWidget { seedColor: CustomTheme.primaryColor, brightness: Brightness.dark, ).copyWith(surface: CustomTheme.backgroundColor), + pageTransitionsTheme: const PageTransitionsTheme( + builders: {TargetPlatform.iOS: CupertinoPageTransitionsBuilder()}, + ), ), home: const CustomNavigationBar(), ); From 1be86bc3c5ec15ffd4d030a9906728cc4d950aa2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 13:57:46 +0100 Subject: [PATCH 172/222] Added theme box design --- lib/presentation/widgets/tiles/match_tile.dart | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 59ee0b9..88ae1f1 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -58,11 +58,7 @@ class _MatchTileState extends State { margin: EdgeInsets.zero, width: widget.width, padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), - borderRadius: BorderRadius.circular(12), - ), + decoration: CustomTheme.standardBoxDecoration, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ From d22855fc1a2d3a109a1e645fdbf005723d74364c Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 13:57:54 +0100 Subject: [PATCH 173/222] Fixed match view tile width --- .../main_menu/match_view/match_view.dart | 33 ++++++++++--------- pubspec.yaml | 2 +- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 108b592..a0b88d0 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -78,22 +78,25 @@ class _MatchViewState extends State { height: MediaQuery.paddingOf(context).bottom - 20, ); } - return Padding( - padding: const EdgeInsets.only(bottom: 12.0), - child: MatchTile( - onTap: () async { - Navigator.push( - context, - CupertinoPageRoute( - fullscreenDialog: true, - builder: (context) => MatchResultView( - match: matches[index], - onWinnerChanged: loadGames, + return Center( + child: Padding( + padding: const EdgeInsets.only(bottom: 12.0), + child: MatchTile( + width: MediaQuery.sizeOf(context).width * 0.95, + onTap: () async { + Navigator.push( + context, + CupertinoPageRoute( + fullscreenDialog: true, + builder: (context) => MatchResultView( + match: matches[index], + onWinnerChanged: loadGames, + ), ), - ), - ); - }, - match: matches[index], + ); + }, + match: matches[index], + ), ), ); }, diff --git a/pubspec.yaml b/pubspec.yaml index 2474f78..ff49b8d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.2+57 +version: 0.0.2+70 environment: sdk: ^3.8.1 From 66b90aac25871ac5afea2b7db3c91f2f7ddf283d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 14:09:08 +0100 Subject: [PATCH 174/222] Fixed issue with android page transition --- lib/main.dart | 5 ++++- pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 8889755..8fdd4e4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -45,7 +45,10 @@ class GameTracker extends StatelessWidget { brightness: Brightness.dark, ).copyWith(surface: CustomTheme.backgroundColor), pageTransitionsTheme: const PageTransitionsTheme( - builders: {TargetPlatform.iOS: CupertinoPageTransitionsBuilder()}, + builders: { + TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), + TargetPlatform.android: FadeForwardsPageTransitionsBuilder(), + }, ), ), home: const CustomNavigationBar(), diff --git a/pubspec.yaml b/pubspec.yaml index e79ca17..4944deb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.1+21 +version: 0.0.1+33 environment: sdk: ^3.8.1 From 6faafe9fab327d4e4cfd782dbec8954cc31441a0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 14:16:16 +0100 Subject: [PATCH 175/222] Changed android page transition to Android U transition --- lib/main.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 8fdd4e4..2f64e2e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -47,7 +47,7 @@ class GameTracker extends StatelessWidget { pageTransitionsTheme: const PageTransitionsTheme( builders: { TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), - TargetPlatform.android: FadeForwardsPageTransitionsBuilder(), + TargetPlatform.android: PredictiveBackPageTransitionsBuilder(), }, ), ), diff --git a/pubspec.yaml b/pubspec.yaml index 4944deb..26253fd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.1+33 +version: 0.0.1+37 environment: sdk: ^3.8.1 From 595cf6ead07a7548fd1e073518ab30e55568eb65 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 14:20:09 +0100 Subject: [PATCH 176/222] Changed skeleton data from 3 to 2 tiles --- lib/presentation/views/main_menu/home_view.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 118c0e1..29da229 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -34,7 +34,7 @@ class _HomeViewState extends State { /// Recent matches to display, initially filled with skeleton matches List recentMatches = List.filled( - 3, + 2, Match( name: 'Skeleton Match', group: Group( diff --git a/pubspec.yaml b/pubspec.yaml index ff49b8d..1255a27 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.2+70 +version: 0.0.2+73 environment: sdk: ^3.8.1 From d67972624e25227ce6fd7a64cb9fad7b3bfac1d4 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 10 Jan 2026 14:44:33 +0100 Subject: [PATCH 177/222] replaced ternary operator solution with custom adaptive_page_route.dart --- lib/core/adaptive_page_route.dart | 19 ++++++++++++++ lib/main.dart | 3 ++- .../main_menu/custom_navigation_bar.dart | 5 ++-- .../main_menu/group_view/groups_view.dart | 9 ++----- .../create_match/create_match_view.dart | 26 ++++--------------- .../main_menu/match_view/match_view.dart | 12 +++------ 6 files changed, 34 insertions(+), 40 deletions(-) create mode 100644 lib/core/adaptive_page_route.dart diff --git a/lib/core/adaptive_page_route.dart b/lib/core/adaptive_page_route.dart new file mode 100644 index 0000000..631f000 --- /dev/null +++ b/lib/core/adaptive_page_route.dart @@ -0,0 +1,19 @@ +import 'dart:io'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +Route AdaptivePageRoute({ + required Widget Function(BuildContext) builder, + bool fullscreenDialog = false, +}) { + if (Platform.isIOS) { + return CupertinoPageRoute( + builder: builder, + fullscreenDialog: fullscreenDialog, + ); + } + return MaterialPageRoute( + builder: builder, + fullscreenDialog: fullscreenDialog, + ); +} diff --git a/lib/main.dart b/lib/main.dart index 8889755..9b9683d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -45,7 +45,8 @@ class GameTracker extends StatelessWidget { brightness: Brightness.dark, ).copyWith(surface: CustomTheme.backgroundColor), pageTransitionsTheme: const PageTransitionsTheme( - builders: {TargetPlatform.iOS: CupertinoPageTransitionsBuilder()}, + builders: {TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), TargetPlatform.android: PredictiveBackPageTransitionsBuilder() + }, ), ), home: const CustomNavigationBar(), diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 17eeb41..4850e5a 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -1,7 +1,6 @@ -import 'dart:io'; - import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:game_tracker/core/adaptive_page_route.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/group_view/groups_view.dart'; @@ -59,7 +58,7 @@ class _CustomNavigationBarState extends State onPressed: () async { await Navigator.push( context, - Platform.isIOS ? CupertinoPageRoute(builder: (_) => const SettingsView()) : MaterialPageRoute(builder: (_) => const SettingsView()), + AdaptivePageRoute(builder: (_) => const SettingsView()), ); setState(() { tabKeyCount++; diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index d19028f..ce9f697 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -1,7 +1,6 @@ -import 'dart:io'; - import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:game_tracker/core/adaptive_page_route.dart'; import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; @@ -88,11 +87,7 @@ class _GroupsViewState extends State { onPressed: () async { await Navigator.push( context, - Platform.isIOS ? CupertinoPageRoute( - builder: (context) { - return const CreateGroupView(); - }, - ) : MaterialPageRoute( + AdaptivePageRoute( builder: (context) { return const CreateGroupView(); }, diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index cff066d..77fe6b9 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -1,7 +1,6 @@ -import 'dart:io'; - import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:game_tracker/core/adaptive_page_route.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/data/db/database.dart'; @@ -142,12 +141,7 @@ class _CreateMatchViewState extends State { : games[selectedGameIndex].$1, onPressed: () async { selectedGameIndex = await Navigator.of(context).push( - Platform.isIOS ? CupertinoPageRoute( - builder: (context) => ChooseGameView( - games: games, - initialGameIndex: selectedGameIndex, - ), - ) : MaterialPageRoute( + AdaptivePageRoute( builder: (context) => ChooseGameView( games: games, initialGameIndex: selectedGameIndex, @@ -175,12 +169,7 @@ class _CreateMatchViewState extends State { : translateRulesetToString(selectedRuleset!, context), onPressed: () async { selectedRuleset = await Navigator.of(context).push( - Platform.isIOS ? CupertinoPageRoute( - builder: (context) => ChooseRulesetView( - rulesets: _rulesets, - initialRulesetIndex: selectedRulesetIndex, - ), - ) : MaterialPageRoute( + AdaptivePageRoute( builder: (context) => ChooseRulesetView( rulesets: _rulesets, initialRulesetIndex: selectedRulesetIndex, @@ -202,12 +191,7 @@ class _CreateMatchViewState extends State { : selectedGroup!.name, onPressed: () async { selectedGroup = await Navigator.of(context).push( - Platform.isIOS ? CupertinoPageRoute( - builder: (context) => ChooseGroupView( - groups: groupsList, - initialGroupId: selectedGroupId, - ), - ): MaterialPageRoute( + AdaptivePageRoute( builder: (context) => ChooseGroupView( groups: groupsList, initialGroupId: selectedGroupId, @@ -257,7 +241,7 @@ class _CreateMatchViewState extends State { if (context.mounted) { Navigator.pushReplacement( context, - CupertinoPageRoute( + AdaptivePageRoute( fullscreenDialog: true, builder: (context) => MatchResultView( match: match, diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 55d35c6..6797ffc 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -1,8 +1,7 @@ import 'dart:core' hide Match; -import 'dart:io'; - import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:game_tracker/core/adaptive_page_route.dart'; import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/data/db/database.dart'; @@ -83,7 +82,7 @@ class _MatchViewState extends State { onTap: () async { Navigator.push( context, - CupertinoPageRoute( + AdaptivePageRoute( fullscreenDialog: true, builder: (context) => MatchResultView( match: matches[index], @@ -106,12 +105,9 @@ class _MatchViewState extends State { onPressed: () async { Navigator.push( context, - Platform.isIOS ? CupertinoPageRoute( + AdaptivePageRoute( builder: (context) => - CreateMatchView(onWinnerChanged: loadGames)) : MaterialPageRoute( - builder: (context) => - CreateMatchView(onWinnerChanged: loadGames), - ), + CreateMatchView(onWinnerChanged: loadGames)) ); }, ), From 97ca62b083760766c588f80c766073cebee31c10 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 10 Jan 2026 14:52:59 +0100 Subject: [PATCH 178/222] refactor: rename AdaptivePageRoute to adaptivePageRoute for lowerCamelCase --- lib/core/adaptive_page_route.dart | 2 +- .../views/main_menu/custom_navigation_bar.dart | 3 +-- .../views/main_menu/group_view/groups_view.dart | 3 +-- .../match_view/create_match/create_match_view.dart | 9 ++++----- .../views/main_menu/match_view/match_view.dart | 5 ++--- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/core/adaptive_page_route.dart b/lib/core/adaptive_page_route.dart index 631f000..ba68557 100644 --- a/lib/core/adaptive_page_route.dart +++ b/lib/core/adaptive_page_route.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -Route AdaptivePageRoute({ +Route adaptivePageRoute({ required Widget Function(BuildContext) builder, bool fullscreenDialog = false, }) { diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 4850e5a..0693189 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -1,4 +1,3 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:game_tracker/core/adaptive_page_route.dart'; import 'package:game_tracker/core/custom_theme.dart'; @@ -58,7 +57,7 @@ class _CustomNavigationBarState extends State onPressed: () async { await Navigator.push( context, - AdaptivePageRoute(builder: (_) => const SettingsView()), + adaptivePageRoute(builder: (_) => const SettingsView()), ); setState(() { tabKeyCount++; diff --git a/lib/presentation/views/main_menu/group_view/groups_view.dart b/lib/presentation/views/main_menu/group_view/groups_view.dart index ce9f697..239aa23 100644 --- a/lib/presentation/views/main_menu/group_view/groups_view.dart +++ b/lib/presentation/views/main_menu/group_view/groups_view.dart @@ -1,4 +1,3 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:game_tracker/core/adaptive_page_route.dart'; import 'package:game_tracker/core/constants.dart'; @@ -87,7 +86,7 @@ class _GroupsViewState extends State { onPressed: () async { await Navigator.push( context, - AdaptivePageRoute( + adaptivePageRoute( builder: (context) { return const CreateGroupView(); }, diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 77fe6b9..281928e 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -1,4 +1,3 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:game_tracker/core/adaptive_page_route.dart'; import 'package:game_tracker/core/custom_theme.dart'; @@ -141,7 +140,7 @@ class _CreateMatchViewState extends State { : games[selectedGameIndex].$1, onPressed: () async { selectedGameIndex = await Navigator.of(context).push( - AdaptivePageRoute( + adaptivePageRoute( builder: (context) => ChooseGameView( games: games, initialGameIndex: selectedGameIndex, @@ -169,7 +168,7 @@ class _CreateMatchViewState extends State { : translateRulesetToString(selectedRuleset!, context), onPressed: () async { selectedRuleset = await Navigator.of(context).push( - AdaptivePageRoute( + adaptivePageRoute( builder: (context) => ChooseRulesetView( rulesets: _rulesets, initialRulesetIndex: selectedRulesetIndex, @@ -191,7 +190,7 @@ class _CreateMatchViewState extends State { : selectedGroup!.name, onPressed: () async { selectedGroup = await Navigator.of(context).push( - AdaptivePageRoute( + adaptivePageRoute( builder: (context) => ChooseGroupView( groups: groupsList, initialGroupId: selectedGroupId, @@ -241,7 +240,7 @@ class _CreateMatchViewState extends State { if (context.mounted) { Navigator.pushReplacement( context, - AdaptivePageRoute( + adaptivePageRoute( fullscreenDialog: true, builder: (context) => MatchResultView( match: match, diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 6797ffc..1781302 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -1,5 +1,4 @@ import 'dart:core' hide Match; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:game_tracker/core/adaptive_page_route.dart'; import 'package:game_tracker/core/constants.dart'; @@ -82,7 +81,7 @@ class _MatchViewState extends State { onTap: () async { Navigator.push( context, - AdaptivePageRoute( + adaptivePageRoute( fullscreenDialog: true, builder: (context) => MatchResultView( match: matches[index], @@ -105,7 +104,7 @@ class _MatchViewState extends State { onPressed: () async { Navigator.push( context, - AdaptivePageRoute( + adaptivePageRoute( builder: (context) => CreateMatchView(onWinnerChanged: loadGames)) ); From 54ec865f04298ee0dfa3652dd5ab8f81c95405b7 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 10 Jan 2026 15:00:33 +0100 Subject: [PATCH 179/222] fix merge issues --- .../match_view/create_match/create_match_view.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 134906e..9740b92 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -142,7 +142,7 @@ class _CreateMatchViewState extends State { : games[selectedGameIndex].$1, onPressed: () async { selectedGameIndex = await Navigator.of(context).push( - MaterialPageRoute( + adaptivePageRoute( builder: (context) => ChooseGameView( games: games, initialGameIndex: selectedGameIndex, @@ -170,7 +170,7 @@ class _CreateMatchViewState extends State { : translateRulesetToString(selectedRuleset!, context), onPressed: () async { selectedRuleset = await Navigator.of(context).push( - MaterialPageRoute( + adaptivePageRoute( builder: (context) => ChooseRulesetView( rulesets: _rulesets, initialRulesetIndex: selectedRulesetIndex, @@ -192,7 +192,7 @@ class _CreateMatchViewState extends State { : selectedGroup!.name, onPressed: () async { selectedGroup = await Navigator.of(context).push( - MaterialPageRoute( + adaptivePageRoute( builder: (context) => ChooseGroupView( groups: groupsList, initialGroupId: selectedGroupId, @@ -242,7 +242,7 @@ class _CreateMatchViewState extends State { if (context.mounted) { Navigator.pushReplacement( context, - CupertinoPageRoute( + adaptivePageRoute( fullscreenDialog: true, builder: (context) => MatchResultView( match: match, From 9eb9c0eb7f4493f546cd74b37ec8c2ff8f400e30 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 10 Jan 2026 15:02:01 +0100 Subject: [PATCH 180/222] remove unneccessary import --- .../main_menu/match_view/create_match/create_match_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 9740b92..b9885a4 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -1,4 +1,3 @@ -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:game_tracker/core/adaptive_page_route.dart'; import 'package:game_tracker/core/custom_theme.dart'; From c4f67498827285fabe69a72ae8ea47f6b17faf7f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 15:13:56 +0100 Subject: [PATCH 181/222] Fixed state issue in home view --- lib/presentation/views/main_menu/home_view.dart | 5 +++-- pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 29da229..f59831c 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -103,14 +103,15 @@ class _HomeViewState extends State { compact: true, width: constraints.maxWidth * 0.9, match: match, - onTap: () { - Navigator.of(context).push( + onTap: () async { + await Navigator.of(context).push( MaterialPageRoute( fullscreenDialog: true, builder: (context) => MatchResultView(match: match), ), ); + loadHomeViewData(); }, ), ) diff --git a/pubspec.yaml b/pubspec.yaml index 1255a27..2e4273b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.2+73 +version: 0.0.2+75 environment: sdk: ^3.8.1 From 830a64b5dda0767b0b26de73893195888513a6bd Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 15:15:52 +0100 Subject: [PATCH 182/222] Small changes --- lib/presentation/views/main_menu/match_view/match_view.dart | 6 ++++-- pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index 94dbb77..ecfa9ca 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -1,4 +1,5 @@ import 'dart:core' hide Match; + import 'package:flutter/material.dart'; import 'package:game_tracker/core/adaptive_page_route.dart'; import 'package:game_tracker/core/constants.dart'; @@ -111,8 +112,9 @@ class _MatchViewState extends State { Navigator.push( context, adaptivePageRoute( - builder: (context) => - CreateMatchView(onWinnerChanged: loadGames)) + builder: (context) => + CreateMatchView(onWinnerChanged: loadGames), + ), ); }, ), diff --git a/pubspec.yaml b/pubspec.yaml index 2e4273b..629a028 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.2+75 +version: 0.0.2+92 environment: sdk: ^3.8.1 From 1c07346aaf8fec6a17e4567606a03446c36efddc Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 15:16:09 +0100 Subject: [PATCH 183/222] Updated version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 629a028..b6f2ed3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.2+92 +version: 0.0.3+92 environment: sdk: ^3.8.1 From 6f0e5ba5c2adb3ca4afe1153f323a64f8a5e2531 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sat, 10 Jan 2026 15:19:18 +0100 Subject: [PATCH 184/222] add ability to search for groups members in choose_group_view.dart --- .../match_view/create_match/choose_group_view.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 97fbcef..9e34460 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -140,7 +140,11 @@ class _ChooseGroupViewState extends State { filteredGroups.clear(); filteredGroups.addAll( widget.groups.where( - (group) => group.name.toLowerCase().contains(query.toLowerCase()), + (group) => + group.name.toLowerCase().contains(query.toLowerCase()) || + group.members.any( + (player) => player.name.toLowerCase().contains(query.toLowerCase()), + ), ), ); } From 2ef8eb65346f66aa67ac53fa56ff0fe31062f71f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 15:53:31 +0100 Subject: [PATCH 185/222] Made winner field non final --- lib/data/dto/match.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/data/dto/match.dart b/lib/data/dto/match.dart index fcb4dae..9570f66 100644 --- a/lib/data/dto/match.dart +++ b/lib/data/dto/match.dart @@ -9,7 +9,7 @@ class Match { final String name; final List? players; final Group? group; - final Player? winner; + Player? winner; Match({ String? id, From 2ef671884d4cfd30b60fa48b6b22351105a387e8 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 15:54:11 +0100 Subject: [PATCH 186/222] New method for specific winner refreshing --- .../views/main_menu/home_view.dart | 18 ++++++++++++++++-- pubspec.yaml | 2 +- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index f59831c..1b07839 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -111,7 +111,7 @@ class _HomeViewState extends State { MatchResultView(match: match), ), ); - loadHomeViewData(); + await updatedWinnerinRecentMatches(match.id); }, ), ) @@ -186,7 +186,7 @@ class _HomeViewState extends State { /// Loads the data for the HomeView from the database. /// This includes the match count, group count, and recent matches. - void loadHomeViewData() { + Future loadHomeViewData() async { final db = Provider.of(context, listen: false); Future.wait([ db.matchDao.getMatchCount(), @@ -209,4 +209,18 @@ class _HomeViewState extends State { } }); } + + /// Updates the winner information for a specific match in the recent matches list. + Future updatedWinnerinRecentMatches(String matchId) async { + final db = Provider.of(context, listen: false); + final winner = await db.matchDao.getWinner(matchId: matchId); + print('Winner for match $matchId: ${winner?.name}'); + + final matchIndex = recentMatches.indexWhere((match) => match.id == matchId); + if (matchIndex != -1) { + setState(() { + recentMatches[matchIndex].winner = winner; + }); + } + } } diff --git a/pubspec.yaml b/pubspec.yaml index b6f2ed3..7ace12f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.3+92 +version: 0.0.3+93 environment: sdk: ^3.8.1 From d34990ed50b537fdeefd07f7e6ca0cd9eac2bc95 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 15:54:41 +0100 Subject: [PATCH 187/222] Removed print --- lib/presentation/views/main_menu/home_view.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 1b07839..29d6d67 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -214,8 +214,6 @@ class _HomeViewState extends State { Future updatedWinnerinRecentMatches(String matchId) async { final db = Provider.of(context, listen: false); final winner = await db.matchDao.getWinner(matchId: matchId); - print('Winner for match $matchId: ${winner?.name}'); - final matchIndex = recentMatches.indexWhere((match) => match.id == matchId); if (matchIndex != -1) { setState(() { From f2a4327166f26107f6ac39743eedc8626d9ff1bf Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 15:55:19 +0100 Subject: [PATCH 188/222] Implemented adaptive page route --- lib/presentation/views/main_menu/home_view.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index 29d6d67..affbe92 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:game_tracker/core/adaptive_page_route.dart'; import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/dto/group.dart'; @@ -105,7 +106,7 @@ class _HomeViewState extends State { match: match, onTap: () async { await Navigator.of(context).push( - MaterialPageRoute( + adaptivePageRoute( fullscreenDialog: true, builder: (context) => MatchResultView(match: match), From 7aa41abe61629a2864a33e35b5c83fa53e96b2b0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 16:20:21 +0100 Subject: [PATCH 189/222] Implemented version number --- .../views/main_menu/settings_view.dart | 210 ++++++++++-------- pubspec.yaml | 3 +- 2 files changed, 118 insertions(+), 95 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index c5fcfa2..9c61d8d 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -4,6 +4,7 @@ import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; import 'package:game_tracker/services/data_transfer_service.dart'; +import 'package:package_info_plus/package_info_plus.dart'; class SettingsView extends StatefulWidget { const SettingsView({super.key}); @@ -13,9 +14,18 @@ class SettingsView extends StatefulWidget { } class _SettingsViewState extends State { + PackageInfo _packageInfo = PackageInfo( + appName: 'Unknown', + packageName: 'Unknown', + version: 'Unknown', + buildNumber: 'Unknown', + buildSignature: 'Unknown', + installerStore: 'Unknown', + ); @override void initState() { super.initState(); + _initPackageInfo(); } @override @@ -25,102 +35,107 @@ class _SettingsViewState extends State { child: Scaffold( appBar: AppBar(backgroundColor: CustomTheme.backgroundColor), backgroundColor: CustomTheme.backgroundColor, - body: LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) => - SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.fromLTRB(24, 0, 24, 10), - child: Text( - textAlign: TextAlign.start, - loc.menu, - style: const TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 10, - ), - child: Text( - textAlign: TextAlign.start, - loc.settings, - style: const TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - ), - ), - ), - SettingsListTile( - title: loc.export_data, - icon: Icons.upload_rounded, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () async { - final String json = - await DataTransferService.getAppDataAsJson(context); - final result = await DataTransferService.exportData( - json, - 'game_tracker-data', - ); - if (!context.mounted) return; - showExportSnackBar(context: context, result: result); - }, - ), - SettingsListTile( - title: loc.import_data, - icon: Icons.download_rounded, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () async { - final result = await DataTransferService.importData( - context, - ); - if (!context.mounted) return; - showImportSnackBar(context: context, result: result); - }, - ), - SettingsListTile( - title: loc.delete_all_data, - icon: Icons.delete_rounded, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text(loc.delete_all_data), - content: Text(loc.this_cannot_be_undone), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(false), - child: Text(loc.cancel), - ), - TextButton( - onPressed: () => Navigator.of(context).pop(true), - child: Text(loc.delete), - ), - ], - ), - ).then((confirmed) { - if (confirmed == true && context.mounted) { - DataTransferService.deleteAllData(context); - showSnackbar( - context: context, - message: AppLocalizations.of( - context, - ).data_successfully_deleted, - ); - } - }); - }, - ), - ], + body: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(24, 0, 24, 10), + child: Text( + textAlign: TextAlign.start, + loc.menu, + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, ), ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10), + child: Text( + textAlign: TextAlign.start, + loc.settings, + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + ), + ), + ), + SettingsListTile( + title: loc.export_data, + icon: Icons.upload_rounded, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () async { + final String json = await DataTransferService.getAppDataAsJson( + context, + ); + final result = await DataTransferService.exportData( + json, + 'game_tracker-data', + ); + if (!context.mounted) return; + showExportSnackBar(context: context, result: result); + }, + ), + SettingsListTile( + title: loc.import_data, + icon: Icons.download_rounded, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () async { + final result = await DataTransferService.importData(context); + if (!context.mounted) return; + showImportSnackBar(context: context, result: result); + }, + ), + SettingsListTile( + title: loc.delete_all_data, + icon: Icons.delete_rounded, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(loc.delete_all_data), + content: Text(loc.this_cannot_be_undone), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text(loc.cancel), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text(loc.delete), + ), + ], + ), + ).then((confirmed) { + if (confirmed == true && context.mounted) { + DataTransferService.deleteAllData(context); + showSnackbar( + context: context, + message: AppLocalizations.of( + context, + ).data_successfully_deleted, + ); + } + }); + }, + ), + const Spacer(), + Padding( + padding: const EdgeInsets.all(20), + child: Center( + child: Text( + 'Version ${_packageInfo.version} (${_packageInfo.buildNumber})', + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ), + ), + ], ), ), ); @@ -196,4 +211,11 @@ class _SettingsViewState extends State { ), ); } + + Future _initPackageInfo() async { + final info = await PackageInfo.fromPlatform(); + setState(() { + _packageInfo = info; + }); + } } diff --git a/pubspec.yaml b/pubspec.yaml index 7ace12f..67fd5c5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.3+93 +version: 0.0.4+97 environment: sdk: ^3.8.1 @@ -22,6 +22,7 @@ dependencies: intl: any flutter_localizations: sdk: flutter + package_info_plus: ^9.0.0 dev_dependencies: flutter_test: From 2a72332bcd9e5e0f5ac4de997657fbd2edcd4d60 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 21:23:28 +0100 Subject: [PATCH 190/222] Updated error string --- lib/presentation/views/main_menu/settings_view.dart | 10 ++++------ pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 9c61d8d..2019891 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -15,12 +15,10 @@ class SettingsView extends StatefulWidget { class _SettingsViewState extends State { PackageInfo _packageInfo = PackageInfo( - appName: 'Unknown', - packageName: 'Unknown', - version: 'Unknown', - buildNumber: 'Unknown', - buildSignature: 'Unknown', - installerStore: 'Unknown', + appName: 'n.A.', + packageName: 'n.A.', + version: 'n.A.', + buildNumber: 'n.A.', ); @override void initState() { diff --git a/pubspec.yaml b/pubspec.yaml index 67fd5c5..970eff4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.4+97 +version: 0.0.4+99 environment: sdk: ^3.8.1 From 497f30421d8b358dfc6511cb2f7d2b7270e730d4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 21:29:45 +0100 Subject: [PATCH 191/222] Updated string --- lib/l10n/arb/app_de.arb | 2 +- lib/l10n/arb/app_en.arb | 2 +- lib/l10n/generated/app_localizations.dart | 2 +- lib/l10n/generated/app_localizations_de.dart | 2 +- lib/l10n/generated/app_localizations_en.dart | 2 +- lib/presentation/views/main_menu/settings_view.dart | 2 +- pubspec.yaml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 4d86460..947c31c 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -17,7 +17,7 @@ "data_successfully_imported": "Daten erfolgreich importiert", "days_ago": "vor {count} Tagen", "delete": "Löschen", - "delete_all_data": "Alle Daten löschen?", + "delete_all_data": "Alle Daten löschen", "error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", "error_reading_file": "Fehler beim Lesen der Datei", "export_canceled": "Export abgebrochen", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 17c3b06..7bae9db 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -277,7 +277,7 @@ "data_successfully_imported": "Data successfully imported", "days_ago": "{count} days ago", "delete": "Delete", - "delete_all_data": "Delete all data?", + "delete_all_data": "Delete all data", "error_creating_group": "Error while creating group, please try again", "error_reading_file": "Error reading file", "export_canceled": "Export canceled", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 5080ff3..1da83f8 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -209,7 +209,7 @@ abstract class AppLocalizations { /// Confirmation dialog for deleting all data /// /// In en, this message translates to: - /// **'Delete all data?'** + /// **'Delete all data'** String get delete_all_data; /// Error message when group creation fails diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index c720941..75a1325 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -67,7 +67,7 @@ class AppLocalizationsDe extends AppLocalizations { String get delete => 'Löschen'; @override - String get delete_all_data => 'Alle Daten löschen?'; + String get delete_all_data => 'Alle Daten löschen'; @override String get error_creating_group => diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index cd71035..00b933b 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -67,7 +67,7 @@ class AppLocalizationsEn extends AppLocalizations { String get delete => 'Delete'; @override - String get delete_all_data => 'Delete all data?'; + String get delete_all_data => 'Delete all data'; @override String get error_creating_group => diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 2019891..0fa7085 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -93,7 +93,7 @@ class _SettingsViewState extends State { showDialog( context: context, builder: (context) => AlertDialog( - title: Text(loc.delete_all_data), + title: Text('${loc.delete_all_data}?'), content: Text(loc.this_cannot_be_undone), actions: [ TextButton( diff --git a/pubspec.yaml b/pubspec.yaml index 970eff4..dbb26c3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.4+99 +version: 0.0.4+101 environment: sdk: ^3.8.1 From a491427a1de08ab331d7074b5e35a6e64c0395a1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 23:37:33 +0100 Subject: [PATCH 192/222] Added license tile, social media icons & copyright --- .../views/main_menu/settings_view.dart | 71 +++++++++++++++++-- pubspec.yaml | 4 +- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view.dart index 0fa7085..7285394 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view.dart @@ -1,10 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; import 'package:game_tracker/services/data_transfer_service.dart'; +import 'package:intl/intl.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}); @@ -119,17 +122,71 @@ class _SettingsViewState extends State { }); }, ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10), + child: Text( + textAlign: TextAlign.start, + 'App', + style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold), + ), + ), + SettingsListTile( + title: 'Lizenzen', + icon: Icons.insert_drive_file, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () {}, + ), const Spacer(), Padding( padding: const EdgeInsets.all(20), child: Center( - child: Text( - 'Version ${_packageInfo.version} (${_packageInfo.buildNumber})', - style: TextStyle( - color: Colors.grey.shade600, - fontSize: 14, - fontWeight: FontWeight.w600, - ), + child: Column( + spacing: 6, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 40, + children: [ + GestureDetector( + child: const Icon(Icons.language), + onTap: () => { + launchUrl(Uri.parse('https://liquid-dev.de')), + }, + ), + GestureDetector( + child: const FaIcon(FontAwesomeIcons.github), + onTap: () => { + launchUrl( + Uri.parse( + 'https://github.com/liquiddevelopmentde', + ), + ), + }, + ), + GestureDetector( + child: const Icon(Icons.email), + onTap: () => + launchUrl(Uri.parse('mailto:hi@liquid-dev.de')), + ), + ], + ), + Text( + '© ${DateFormat('yyyy').format(DateTime.now())} Liquid Development', + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + Text( + 'Version ${_packageInfo.version} (${_packageInfo.buildNumber})', + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], ), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index dbb26c3..6ce90a7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.4+101 +version: 0.0.4+109 environment: sdk: ^3.8.1 @@ -23,6 +23,8 @@ dependencies: flutter_localizations: sdk: flutter package_info_plus: ^9.0.0 + font_awesome_flutter: ^10.12.0 + url_launcher: ^6.3.2 dev_dependencies: flutter_test: From 1e4fd2a1645fc123016fefe590e24372c75f1dfa Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 23:37:47 +0100 Subject: [PATCH 193/222] Added ios permissions for web --- ios/Runner/Info.plist | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index d6b8994..8db75d8 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -24,6 +24,11 @@ $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + LSApplicationQueriesSchemes + + https + http + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile @@ -44,4 +49,4 @@ UIApplicationSupportsIndirectInputEvents - + \ No newline at end of file From e384230a0b3b44b64e645aeba57a74b8fd02a9fb Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sat, 10 Jan 2026 23:37:54 +0100 Subject: [PATCH 194/222] Updated gradle version --- android/settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts index ab39a10..43394ed 100644 --- a/android/settings.gradle.kts +++ b/android/settings.gradle.kts @@ -18,7 +18,7 @@ pluginManagement { plugins { id("dev.flutter.flutter-plugin-loader") version "1.0.0" - id("com.android.application") version "8.7.3" apply false + id("com.android.application") version "8.9.1" apply false id("org.jetbrains.kotlin.android") version "2.1.0" apply false } From c5fa540c5f8cbff5c1f1d62df63c47fa462d4d47 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 11 Jan 2026 15:43:11 +0100 Subject: [PATCH 195/222] Added licenses to settings --- lib/l10n/arb/app_de.arb | 5 + lib/l10n/arb/app_en.arb | 16 + .../main_menu/custom_navigation_bar.dart | 2 +- .../settings_view/license_detail_view.dart | 100 + .../settings_view/licenses_view.dart | 32 + .../main_menu/settings_view/oss_licenses.dart | 7270 +++++++++++++++++ .../{ => settings_view}/settings_view.dart | 9 +- .../widgets/tiles/license_tile.dart | 53 + pubspec.yaml | 3 +- 9 files changed, 7486 insertions(+), 4 deletions(-) create mode 100644 lib/presentation/views/main_menu/settings_view/license_detail_view.dart create mode 100644 lib/presentation/views/main_menu/settings_view/licenses_view.dart create mode 100644 lib/presentation/views/main_menu/settings_view/oss_licenses.dart rename lib/presentation/views/main_menu/{ => settings_view}/settings_view.dart (96%) create mode 100644 lib/presentation/widgets/tiles/license_tile.dart diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 947c31c..58870c5 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -3,6 +3,7 @@ "all_players": "Alle Spieler:innen", "all_players_selected": "Alle Spieler:innen ausgewählt", "amount_of_matches": "Anzahl der Spiele", + "app_name": "Game Tracker", "cancel": "Abbrechen", "choose_game": "Spielvorlage wählen", "choose_group": "Gruppe wählen", @@ -33,6 +34,10 @@ "import_data": "Daten importieren", "info": "Info", "invalid_schema": "Ungültiges Schema", + "licenses": "Lizenzen", + "no_licenses_found": "Keine Lizenzen gefunden", + "no_license_text_available": "Kein Lizenztext verfügbar", + "error": "Fehler", "least_points": "Niedrigste Punkte", "match_in_progress": "Spiel läuft...", "match_name": "Spieltitel", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 7bae9db..309893c 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -107,6 +107,18 @@ "@invalid_schema": { "description": "Error message for invalid schema" }, + "@licenses": { + "description": "Licenses menu item" + }, + "@no_licenses_found": { + "description": "Message when no licenses are found" + }, + "@no_license_text_available": { + "description": "Message when no license text is available" + }, + "@error": { + "description": "Error label" + }, "@least_points": { "description": "Title for least points ruleset" }, @@ -293,6 +305,10 @@ "import_data": "Import data", "info": "Info", "invalid_schema": "Invalid Schema", + "licenses": "Licenses", + "no_licenses_found": "No licenses found", + "no_license_text_available": "No license text available", + "error": "Error", "least_points": "Least Points", "match_in_progress": "Match in progress...", "match_name": "Match name", diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 0693189..f6eb381 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -5,7 +5,7 @@ import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/group_view/groups_view.dart'; import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/match_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/settings_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/settings_view/settings_view.dart'; import 'package:game_tracker/presentation/views/main_menu/statistics_view.dart'; import 'package:game_tracker/presentation/widgets/navbar_item.dart'; diff --git a/lib/presentation/views/main_menu/settings_view/license_detail_view.dart b/lib/presentation/views/main_menu/settings_view/license_detail_view.dart new file mode 100644 index 0000000..8684787 --- /dev/null +++ b/lib/presentation/views/main_menu/settings_view/license_detail_view.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; +import 'package:game_tracker/presentation/views/main_menu/settings_view/oss_licenses.dart'; + +class LicenseDetailView extends StatelessWidget { + final Package package; + + const LicenseDetailView({super.key, required this.package}); + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); + + return Scaffold( + appBar: AppBar(title: const Text('Lizenzdetails')), + body: SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Column( + children: [ + Text( + package.name, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + + if (package.version != null) ...[ + const SizedBox(height: 8), + Text( + 'Version ${package.version}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade300, + fontWeight: FontWeight.bold, + ), + ), + ], + if (package.authors.isNotEmpty) ...[ + const SizedBox(height: 8), + Text( + package.authors.join(', '), + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade500, + ), + ), + ], + if (package.homepage != null && + package.homepage!.isNotEmpty) ...[ + const SizedBox(height: 4), + Text( + package.homepage!, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade500, + ), + ), + ], + ], + ), + ), + const SizedBox(height: 24), + Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), + decoration: CustomTheme.standardBoxDecoration, + child: (package.license != null && package.license!.isNotEmpty) + ? SelectableText( + package.license!, + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade300, + height: 1.5, + fontFamily: 'monospace', + ), + ) + : Center( + heightFactor: 25, + child: Text( + loc.no_license_text_available, + style: TextStyle(color: Colors.grey.shade500), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/presentation/views/main_menu/settings_view/licenses_view.dart b/lib/presentation/views/main_menu/settings_view/licenses_view.dart new file mode 100644 index 0000000..a4699f1 --- /dev/null +++ b/lib/presentation/views/main_menu/settings_view/licenses_view.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/l10n/generated/app_localizations.dart'; +import 'package:game_tracker/presentation/views/main_menu/settings_view/oss_licenses.dart'; +import 'package:game_tracker/presentation/widgets/tiles/license_tile.dart'; + +class LicensesView extends StatelessWidget { + const LicensesView({super.key}); + + @override + Widget build(BuildContext context) { + final loc = AppLocalizations.of(context); + + return Scaffold( + appBar: AppBar( + title: Text(loc.licenses), + backgroundColor: CustomTheme.backgroundColor, + ), + backgroundColor: CustomTheme.backgroundColor, + body: ListView.builder( + itemCount: allDependencies.length, + itemBuilder: (context, index) { + final package = allDependencies[index]; + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 12), + child: LicenseTile(package: package), + ); + }, + ), + ); + } +} diff --git a/lib/presentation/views/main_menu/settings_view/oss_licenses.dart b/lib/presentation/views/main_menu/settings_view/oss_licenses.dart new file mode 100644 index 0000000..06f46a4 --- /dev/null +++ b/lib/presentation/views/main_menu/settings_view/oss_licenses.dart @@ -0,0 +1,7270 @@ +// dart format off +// cSpell:disable +// ignore_for_file: always_put_required_named_parameters_first +// ignore_for_file: constant_identifier_names +// ignore_for_file: sort_constructors_first + +// This code was generated by dart_pubspec_licenses +// https://pub.dev/packages/dart_pubspec_licenses + +/// This package. +const thisPackage = _game_tracker; + +/// All dependencies including transitive dependencies. +const allDependencies = [ + __fe_analyzer_shared, + _analyzer, + _args, + _async, + _boolean_selector, + _build, + _build_config, + _build_daemon, + _build_resolvers, + _build_runner, + _build_runner_core, + _built_collection, + _built_value, + _characters, + _charcode, + _checked_yaml, + _cli_config, + _cli_util, + _clock, + _code_builder, + _collection, + _convert, + _coverage, + _cross_file, + _crypto, + _csslib, + _dart_pubspec_licenses, + _dart_style, + _dbus, + _dio, + _dio_web_adapter, + _drift, + _drift_dev, + _drift_flutter, + _fake_async, + _ffi, + _file, + _file_picker, + _file_saver, + _fixnum, + _flutter, + _flutter_lints, + _flutter_plugin_android_lifecycle, + _flutter_web_plugins, + _font_awesome_flutter, + _frontend_server_client, + _glob, + _graphs, + _html, + _http, + _http_multi_server, + _http_parser, + _intl, + _io, + _js, + _json_annotation, + _json_schema, + _leak_tracker, + _leak_tracker_flutter_testing, + _leak_tracker_testing, + _lints, + _logging, + _markdown, + _matcher, + _material_color_utilities, + _meta, + _mime, + _nested, + _node_preamble, + _package_config, + _package_info_plus, + _package_info_plus_platform_interface, + _pana, + _path, + _path_provider, + _path_provider_android, + _path_provider_foundation, + _path_provider_linux, + _path_provider_platform_interface, + _path_provider_windows, + _petitparser, + _platform, + _plugin_platform_interface, + _pool, + _provider, + _pub_semver, + _pubspec_parse, + _quiver, + _recase, + _retry, + _rfc_6901, + _safe_url_check, + _shelf, + _shelf_packages_handler, + _shelf_static, + _shelf_web_socket, + _skeletonizer, + _source_gen, + _source_map_stack_trace, + _source_maps, + _source_span, + _sqlite3, + _sqlite3_flutter_libs, + _sqlparser, + _stack_trace, + _stream_channel, + _stream_transform, + _string_scanner, + _tar, + _term_glyph, + _test, + _test_api, + _test_core, + _timing, + _typed_data, + _uri, + _url_launcher, + _url_launcher_android, + _url_launcher_ios, + _url_launcher_linux, + _url_launcher_macos, + _url_launcher_platform_interface, + _url_launcher_web, + _url_launcher_windows, + _uuid, + _vector_math, + _vm_service, + _watcher, + _web, + _web_socket, + _web_socket_channel, + _webkit_inspection_protocol, + _win32, + _xdg_directories, + _xml, + _yaml +]; + +/// Direct `dependencies`. +const dependencies = [ + _flutter, + _drift, + _drift_flutter, + _path_provider, + _provider, + _skeletonizer, + _uuid, + _file_picker, + _json_schema, + _file_saver, + _clock, + _intl, + _package_info_plus, + _font_awesome_flutter, + _url_launcher +]; + +/// Direct `dev_dependencies`. +const devDependencies = [ + _dart_pubspec_licenses, + _flutter_lints, + _drift_dev, + _build_runner +]; + +/// Package license definition. +class Package { + /// Package name + final String name; + /// Description + final String description; + /// Authors + final List authors; + /// Whether the license is in markdown format or not (plain text). + final bool isMarkdown; + /// Whether the package is included in the SDK or not. + final bool isSdk; + /// Direct dependencies + final List dependencies; + /// Direct devDependencies + final List devDependencies; + /// Website URL + final String? homepage; + /// Repository URL + final String? repository; + /// Version + final String? version; + /// License + final String? license; + /// The [SPDX](https://spdx.org/licenses/) license identifiers, if detected. + final List spdxIdentifiers; + + const Package({ + required this.name, + required this.description, + required this.authors, + required this.isMarkdown, + required this.isSdk, + required this.dependencies, + required this.devDependencies, + this.homepage, + this.repository, + this.version, + this.license, + this.spdxIdentifiers = const [], + }); +} + +class PackageRef { + final String name; + + const PackageRef(this.name); + + Package resolve() => allDependencies.firstWhere((d) => d.name == name); +} + +/// _fe_analyzer_shared 85.0.0 +const __fe_analyzer_shared = Package( + name: '_fe_analyzer_shared', + description: 'Logic that is shared between the front_end and analyzer packages.', + repository: 'https://github.com/dart-lang/sdk/tree/main/pkg/_fe_analyzer_shared', + authors: [], + version: '85.0.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('meta')], + devDependencies: [PackageRef('collection'), PackageRef('test')], + license: '''Copyright 2019, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// analyzer 7.7.1 +const _analyzer = Package( + name: 'analyzer', + description: 'This package provides a library that performs static analysis of Dart code.', + repository: 'https://github.com/dart-lang/sdk/tree/main/pkg/analyzer', + authors: [], + version: '7.7.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('_fe_analyzer_shared'), PackageRef('collection'), PackageRef('convert'), PackageRef('crypto'), PackageRef('glob'), PackageRef('meta'), PackageRef('package_config'), PackageRef('path'), PackageRef('pub_semver'), PackageRef('source_span'), PackageRef('watcher'), PackageRef('yaml')], + devDependencies: [PackageRef('args'), PackageRef('async'), PackageRef('lints'), PackageRef('matcher'), PackageRef('test'), PackageRef('vm_service')], + license: '''Copyright 2013, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// args 2.7.0 +const _args = Package( + name: 'args', + description: 'Library for defining parsers for parsing raw command-line arguments into a set of options and values using GNU and POSIX style options.', + repository: 'https://github.com/dart-lang/core/tree/main/pkgs/args', + authors: [], + version: '2.7.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('test')], + license: '''Copyright 2013, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// async 2.13.0 +const _async = Package( + name: 'async', + description: "Utility functions and classes related to the 'dart:async' library.", + repository: 'https://github.com/dart-lang/core/tree/main/pkgs/async', + authors: [], + version: '2.13.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('collection'), PackageRef('meta')], + devDependencies: [PackageRef('fake_async'), PackageRef('stack_trace'), PackageRef('test')], + license: '''Copyright 2015, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// boolean_selector 2.1.2 +const _boolean_selector = Package( + name: 'boolean_selector', + description: "A flexible syntax for boolean expressions, based on a simplified version of Dart's expression syntax.", + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/boolean_selector', + authors: [], + version: '2.1.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('source_span'), PackageRef('string_scanner')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2016, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// build 3.1.0 +const _build = Package( + name: 'build', + description: 'A package for authoring build_runner compatible code generators.', + repository: 'https://github.com/dart-lang/build/tree/master/build', + authors: [], + version: '3.1.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('analyzer'), PackageRef('async'), PackageRef('build_runner_core'), PackageRef('convert'), PackageRef('crypto'), PackageRef('glob'), PackageRef('logging'), PackageRef('package_config'), PackageRef('path')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2016, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// build_config 1.2.0 +const _build_config = Package( + name: 'build_config', + description: 'Format definition and support for parsing `build.yaml` configuration.', + repository: 'https://github.com/dart-lang/build/tree/master/build_config', + authors: [], + version: '1.2.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('checked_yaml'), PackageRef('json_annotation'), PackageRef('path'), PackageRef('pubspec_parse')], + devDependencies: [PackageRef('build_runner'), PackageRef('term_glyph'), PackageRef('test')], + license: '''Copyright 2017, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// build_daemon 4.1.0 +const _build_daemon = Package( + name: 'build_daemon', + description: 'A daemon for running Dart builds.', + repository: 'https://github.com/dart-lang/build/tree/master/build_daemon', + authors: [], + version: '4.1.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('built_collection'), PackageRef('built_value'), PackageRef('crypto'), PackageRef('http_multi_server'), PackageRef('logging'), PackageRef('path'), PackageRef('pool'), PackageRef('shelf'), PackageRef('shelf_web_socket'), PackageRef('stream_transform'), PackageRef('watcher'), PackageRef('web_socket_channel')], + devDependencies: [PackageRef('build_runner'), PackageRef('test')], + license: '''Copyright 2019, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// build_resolvers 3.0.3 +const _build_resolvers = Package( + name: 'build_resolvers', + description: 'Resolve Dart code in a Builder', + repository: 'https://github.com/dart-lang/build/tree/master/build_resolvers', + authors: [], + version: '3.0.3', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('analyzer'), PackageRef('async'), PackageRef('build'), PackageRef('build_runner_core'), PackageRef('collection'), PackageRef('package_config'), PackageRef('path'), PackageRef('pool'), PackageRef('pub_semver')], + devDependencies: [PackageRef('logging'), PackageRef('test')], + license: '''Copyright 2018, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// build_runner 2.7.1 +const _build_runner = Package( + name: 'build_runner', + description: 'A build system for Dart code generation and modular compilation.', + repository: 'https://github.com/dart-lang/build/tree/master/build_runner', + authors: [], + version: '2.7.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('args'), PackageRef('async'), PackageRef('build'), PackageRef('build_config'), PackageRef('build_daemon'), PackageRef('build_runner_core'), PackageRef('built_collection'), PackageRef('code_builder'), PackageRef('crypto'), PackageRef('dart_style'), PackageRef('frontend_server_client'), PackageRef('glob'), PackageRef('graphs'), PackageRef('http_multi_server'), PackageRef('io'), PackageRef('logging'), PackageRef('meta'), PackageRef('mime'), PackageRef('path'), PackageRef('pub_semver'), PackageRef('shelf'), PackageRef('shelf_web_socket'), PackageRef('stack_trace'), PackageRef('stream_transform'), PackageRef('watcher'), PackageRef('web_socket_channel'), PackageRef('yaml')], + devDependencies: [PackageRef('http'), PackageRef('package_config'), PackageRef('stream_channel'), PackageRef('test'), PackageRef('web')], + license: '''Copyright 2016, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// build_runner_core 9.3.1 +const _build_runner_core = Package( + name: 'build_runner_core', + description: 'Core tools to organize the structure of a build and run Builders.', + repository: 'https://github.com/dart-lang/build/tree/master/build_runner_core', + authors: [], + version: '9.3.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('analyzer'), PackageRef('async'), PackageRef('build'), PackageRef('build_config'), PackageRef('build_resolvers'), PackageRef('build_runner'), PackageRef('built_collection'), PackageRef('built_value'), PackageRef('collection'), PackageRef('convert'), PackageRef('crypto'), PackageRef('glob'), PackageRef('graphs'), PackageRef('json_annotation'), PackageRef('logging'), PackageRef('meta'), PackageRef('package_config'), PackageRef('path'), PackageRef('pool'), PackageRef('timing'), PackageRef('watcher'), PackageRef('yaml')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2018, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// built_collection 5.1.1 +const _built_collection = Package( + name: 'built_collection', + description: '''Immutable collections based on the SDK collections. Each SDK collection class is split into a new immutable collection class and a corresponding mutable builder class. +''', + homepage: 'https://github.com/google/built_collection.dart', + authors: [], + version: '5.1.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('test')], + license: '''Copyright 2015, Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// built_value 8.12.0 +const _built_value = Package( + name: 'built_value', + description: '''Value types with builders, Dart classes as enums, and serialization. This library is the runtime dependency. +''', + repository: 'https://github.com/google/built_value.dart/tree/master/built_value', + authors: [], + version: '8.12.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('built_collection'), PackageRef('collection'), PackageRef('fixnum'), PackageRef('meta')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2015, Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// characters 1.4.0 +const _characters = Package( + name: 'characters', + description: 'String replacement with operations that are Unicode/grapheme cluster aware.', + repository: 'https://github.com/dart-lang/core/tree/main/pkgs/characters', + authors: [], + version: '1.4.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('test')], + license: '''Copyright 2019, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// charcode 1.4.0 +const _charcode = Package( + name: 'charcode', + description: 'Constants for ASCII and common non-ASCII character codes. Integer constants corresponding to the code points of individual characters.', + repository: 'https://github.com/lrhn/charcode', + authors: [], + version: '1.4.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('test'), PackageRef('lints')], + license: '''Copyright 2014, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// checked_yaml 2.0.4 +const _checked_yaml = Package( + name: 'checked_yaml', + description: 'Generate more helpful exceptions when decoding YAML documents using package:json_serializable and package:yaml.', + repository: 'https://github.com/google/json_serializable.dart/tree/master/checked_yaml', + authors: [], + version: '2.0.4', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('json_annotation'), PackageRef('source_span'), PackageRef('yaml')], + devDependencies: [PackageRef('build_runner'), PackageRef('path'), PackageRef('test')], + license: '''Copyright 2019, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// cli_config 0.2.0 +const _cli_config = Package( + name: 'cli_config', + description: 'A library to take config values from configuration files, CLI arguments, and environment variables.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/cli_config', + authors: [], + version: '0.2.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('args'), PackageRef('yaml')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2023, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// cli_util 0.4.2 +const _cli_util = Package( + name: 'cli_util', + description: 'A library to help in building Dart command-line apps.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/cli_util', + authors: [], + version: '0.4.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('meta'), PackageRef('path')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2015, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// clock 1.1.2 +const _clock = Package( + name: 'clock', + description: 'A fakeable wrapper for dart:core clock APIs.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/clock', + authors: [], + version: '1.1.2', + spdxIdentifiers: ['Apache-2.0'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('test')], + license: '''Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.''', + ); + +/// code_builder 4.11.0 +const _code_builder = Package( + name: 'code_builder', + description: 'A fluent, builder-based library for generating valid Dart code.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/code_builder', + authors: [], + version: '4.11.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('built_collection'), PackageRef('built_value'), PackageRef('collection'), PackageRef('matcher'), PackageRef('meta')], + devDependencies: [PackageRef('build'), PackageRef('build_runner'), PackageRef('dart_style'), PackageRef('source_gen'), PackageRef('test')], + license: '''Copyright 2016, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// collection 1.19.1 +const _collection = Package( + name: 'collection', + description: 'Collections and utilities functions and classes related to collections.', + repository: 'https://github.com/dart-lang/core/tree/main/pkgs/collection', + authors: [], + version: '1.19.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('test')], + license: '''Copyright 2015, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// convert 3.1.2 +const _convert = Package( + name: 'convert', + description: 'Utilities for converting between data representations. Provides a number of Sink, Codec, Decoder, and Encoder types.', + repository: 'https://github.com/dart-lang/core/tree/main/pkgs/convert', + authors: [], + version: '3.1.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('typed_data')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2015, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// coverage 1.15.0 +const _coverage = Package( + name: 'coverage', + description: 'Coverage data manipulation and formatting', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/coverage', + authors: [], + version: '1.15.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('args'), PackageRef('cli_config'), PackageRef('glob'), PackageRef('logging'), PackageRef('meta'), PackageRef('package_config'), PackageRef('path'), PackageRef('source_maps'), PackageRef('stack_trace'), PackageRef('vm_service'), PackageRef('yaml')], + devDependencies: [PackageRef('build_runner'), PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// cross_file 0.3.5+1 +const _cross_file = Package( + name: 'cross_file', + description: 'An abstraction to allow working with files across multiple platforms.', + repository: 'https://github.com/flutter/packages/tree/main/packages/cross_file', + authors: [], + version: '0.3.5+1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('meta'), PackageRef('web')], + devDependencies: [PackageRef('path'), PackageRef('test')], + license: '''Copyright 2013 The Flutter Authors + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// crypto 3.0.7 +const _crypto = Package( + name: 'crypto', + description: 'Implementations of SHA, MD5, and HMAC cryptographic functions.', + repository: 'https://github.com/dart-lang/core/tree/main/pkgs/crypto', + authors: [], + version: '3.0.7', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('typed_data')], + devDependencies: [PackageRef('convert'), PackageRef('test')], + license: '''Copyright 2015, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// csslib 1.0.2 +const _csslib = Package( + name: 'csslib', + description: 'A library for parsing and analyzing CSS (Cascading Style Sheets).', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/csslib', + authors: [], + version: '1.0.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('source_span')], + devDependencies: [PackageRef('path'), PackageRef('term_glyph'), PackageRef('test')], + license: '''Copyright 2013, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// dart_pubspec_licenses 3.0.15 +const _dart_pubspec_licenses = Package( + name: 'dart_pubspec_licenses', + description: 'A library to make it easy to extract OSS license information from Dart packages using pubspec.yaml', + homepage: 'https://github.com/espresso3389/flutter_oss_licenses/tree/master/packages/dart_pubspec_licenses', + repository: 'https://github.com/espresso3389/flutter_oss_licenses', + authors: [], + version: '3.0.15', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('yaml'), PackageRef('path'), PackageRef('json_annotation'), PackageRef('args'), PackageRef('pana')], + devDependencies: [PackageRef('lints'), PackageRef('build_runner')], + license: '''MIT License + +Copyright (c) 2019 Takashi Kawasaki + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + ); + +/// dart_style 3.1.1 +const _dart_style = Package( + name: 'dart_style', + description: 'Opinionated, automatic Dart source code formatter. Provides an API and a CLI tool.', + repository: 'https://github.com/dart-lang/dart_style', + authors: [], + version: '3.1.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('analyzer'), PackageRef('args'), PackageRef('collection'), PackageRef('package_config'), PackageRef('path'), PackageRef('pub_semver'), PackageRef('source_span'), PackageRef('yaml')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// dbus 0.7.11 +const _dbus = Package( + name: 'dbus', + description: 'A native Dart implementation of the D-Bus message bus client. This package allows Dart applications to directly access services on the Linux desktop.', + homepage: 'https://github.com/canonical/dbus.dart', + authors: [], + version: '0.7.11', + spdxIdentifiers: ['MPL-2.0'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('args'), PackageRef('ffi'), PackageRef('meta'), PackageRef('xml')], + devDependencies: [PackageRef('lints'), PackageRef('test')], + license: '''Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0.''', + ); + +/// dio 5.9.0 +const _dio = Package( + name: 'dio', + description: '''A powerful HTTP networking package, +supports Interceptors, +Aborting and canceling a request, +Custom adapters, Transformers, etc. +''', + homepage: 'https://github.com/cfug/dio', + repository: 'https://github.com/cfug/dio/blob/main/dio', + authors: [], + version: '5.9.0', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('async'), PackageRef('collection'), PackageRef('http_parser'), PackageRef('meta'), PackageRef('mime'), PackageRef('path'), PackageRef('dio_web_adapter')], + devDependencies: [PackageRef('lints'), PackageRef('test'), PackageRef('build_runner'), PackageRef('coverage'), PackageRef('crypto')], + license: '''MIT License + +Copyright (c) 2018 Wen Du (wendux) +Copyright (c) 2022 The CFUG Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + ); + +/// dio_web_adapter 2.1.1 +const _dio_web_adapter = Package( + name: 'dio_web_adapter', + description: 'An adapter that supports Dio on Web.', + homepage: 'https://github.com/cfug/dio', + repository: 'https://github.com/cfug/dio/blob/main/plugins/web_adapter', + authors: [], + version: '2.1.1', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('dio'), PackageRef('http_parser'), PackageRef('meta'), PackageRef('web')], + devDependencies: [PackageRef('lints'), PackageRef('test')], + license: '''MIT License + +Copyright (c) 2018 Wen Du (wendux) +Copyright (c) 2022 The CFUG Team + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + ); + +/// drift 2.29.0 +const _drift = Package( + name: 'drift', + description: 'Drift is a reactive library to store relational data in Dart and Flutter applications.', + homepage: 'https://drift.simonbinder.eu/', + repository: 'https://github.com/simolus3/drift', + authors: [], + version: '2.29.0', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('async'), PackageRef('convert'), PackageRef('collection'), PackageRef('meta'), PackageRef('stream_channel'), PackageRef('sqlite3'), PackageRef('path'), PackageRef('stack_trace'), PackageRef('web')], + devDependencies: [PackageRef('analyzer'), PackageRef('drift_dev'), PackageRef('http'), PackageRef('lints'), PackageRef('uuid'), PackageRef('build_runner'), PackageRef('test'), PackageRef('shelf'), PackageRef('vm_service'), PackageRef('build_daemon'), PackageRef('fake_async')], + license: '''MIT License + +Copyright (c) 2021 Simon Binder + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + ); + +/// drift_dev 2.29.0 +const _drift_dev = Package( + name: 'drift_dev', + description: 'Dev-dependency for users of drift. Contains the generator and development tools.', + homepage: 'https://drift.simonbinder.eu/', + repository: 'https://github.com/simolus3/drift', + authors: [], + version: '2.29.0', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('charcode'), PackageRef('collection'), PackageRef('recase'), PackageRef('meta'), PackageRef('path'), PackageRef('json_annotation'), PackageRef('stream_transform'), PackageRef('args'), PackageRef('logging'), PackageRef('cli_util'), PackageRef('yaml'), PackageRef('io'), PackageRef('drift'), PackageRef('sqlite3'), PackageRef('sqlparser'), PackageRef('analyzer'), PackageRef('source_span'), PackageRef('package_config'), PackageRef('pub_semver'), PackageRef('build'), PackageRef('build_config'), PackageRef('dart_style'), PackageRef('source_gen'), PackageRef('string_scanner')], + devDependencies: [PackageRef('lints'), PackageRef('checked_yaml'), PackageRef('test'), PackageRef('build_runner'), PackageRef('crypto'), PackageRef('glob')], + license: '''MIT License + +Copyright (c) 2021 Simon Binder + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + ); + +/// drift_flutter 0.2.7 +const _drift_flutter = Package( + name: 'drift_flutter', + description: 'Easily set up drift databases across platforms in Flutter apps.', + homepage: 'https://drift.simonbinder.eu/', + repository: 'https://github.com/simolus3/drift', + authors: [], + version: '0.2.7', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('drift'), PackageRef('flutter'), PackageRef('meta'), PackageRef('path'), PackageRef('path_provider'), PackageRef('sqlite3'), PackageRef('sqlite3_flutter_libs')], + devDependencies: [PackageRef('build_runner'), PackageRef('drift_dev'), PackageRef('lints'), PackageRef('test'), PackageRef('async')], + license: '''MIT License + +Copyright (c) 2024 Simon Binder + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + ); + +/// fake_async 1.3.3 +const _fake_async = Package( + name: 'fake_async', + description: 'Fake asynchronous events such as timers and microtasks for deterministic testing.', + repository: 'https://github.com/dart-lang/test/tree/master/pkgs/fake_async', + authors: [], + version: '1.3.3', + spdxIdentifiers: ['Apache-2.0'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('clock'), PackageRef('collection')], + devDependencies: [PackageRef('async'), PackageRef('test')], + license: '''Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.''', + ); + +/// ffi 2.1.4 +const _ffi = Package( + name: 'ffi', + description: 'Utilities for working with Foreign Function Interface (FFI) code.', + repository: 'https://github.com/dart-lang/native/tree/main/pkgs/ffi', + authors: [], + version: '2.1.4', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('test')], + license: '''Copyright 2019, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// file 7.0.1 +const _file = Package( + name: 'file', + description: 'A pluggable, mockable file system abstraction for Dart. Supports local file system access, as well as in-memory file systems, record-replay file systems, and chroot file systems.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/file', + authors: [], + version: '7.0.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('meta'), PackageRef('path')], + devDependencies: [PackageRef('lints'), PackageRef('test')], + license: '''Copyright 2017, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// file_picker 10.3.7 +const _file_picker = Package( + name: 'file_picker', + description: 'A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extension filtering support.', + homepage: 'https://github.com/miguelpruivo/plugins_flutter_file_picker', + repository: 'https://github.com/miguelpruivo/flutter_file_picker', + authors: [], + version: '10.3.7', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter'), PackageRef('flutter_web_plugins'), PackageRef('flutter_plugin_android_lifecycle'), PackageRef('plugin_platform_interface'), PackageRef('ffi'), PackageRef('path'), PackageRef('win32'), PackageRef('cross_file'), PackageRef('web'), PackageRef('dbus')], + devDependencies: [PackageRef('flutter_lints')], + license: '''MIT License + +Copyright (c) 2018 Miguel Ruivo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + ); + +/// file_saver 0.3.1 +const _file_saver = Package( + name: 'file_saver', + description: 'A Flutter plugin for saving files across all platforms (Android, iOS, Web, Windows, macOS, Linux). Save files from bytes, File objects, file paths, or download from URLs with a single method call. Features include MIME type support, Dio integration, and platform-specific save locations. Supports saveAs() dialog for user-selected locations on supported platforms.', + homepage: 'https://hassanansari.dev', + repository: 'https://github.com/incrediblezayed/file_saver', + authors: [], + version: '0.3.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('collection'), PackageRef('dio'), PackageRef('flutter'), PackageRef('flutter_web_plugins'), PackageRef('path_provider'), PackageRef('path_provider_linux'), PackageRef('path_provider_windows'), PackageRef('web')], + devDependencies: [PackageRef('flutter_lints')], + license: '''BSD 3-Clause License + +Copyright (c) 2021, Hassan Ansari +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// fixnum 1.1.1 +const _fixnum = Package( + name: 'fixnum', + description: 'Library for 32- and 64-bit signed fixed-width integers with consistent behavior between native and JS runtimes.', + repository: 'https://github.com/dart-lang/core/tree/main/pkgs/fixnum', + authors: [], + version: '1.1.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// flutter 3.38.4 +const _flutter = Package( + name: 'flutter', + description: 'A framework for writing Flutter applications', + homepage: 'https://flutter.dev', + authors: [], + version: '3.38.4', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: true, + dependencies: [PackageRef('characters'), PackageRef('collection'), PackageRef('material_color_utilities'), PackageRef('meta'), PackageRef('vector_math')], + devDependencies: [PackageRef('fake_async'), PackageRef('leak_tracker_flutter_testing'), PackageRef('leak_tracker_testing'), PackageRef('leak_tracker'), PackageRef('web'), PackageRef('clock'), PackageRef('file'), PackageRef('path'), PackageRef('platform')], + license: '''Copyright 2014 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// flutter_lints 5.0.0 +const _flutter_lints = Package( + name: 'flutter_lints', + description: 'Recommended lints for Flutter apps, packages, and plugins to encourage good coding practices.', + repository: 'https://github.com/flutter/packages/tree/main/packages/flutter_lints', + authors: [], + version: '5.0.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('lints')], + devDependencies: [], + license: '''Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// flutter_plugin_android_lifecycle 2.0.33 +const _flutter_plugin_android_lifecycle = Package( + name: 'flutter_plugin_android_lifecycle', + description: 'Flutter plugin for accessing an Android Lifecycle within other plugins.', + repository: 'https://github.com/flutter/packages/tree/main/packages/flutter_plugin_android_lifecycle', + authors: [], + version: '2.0.33', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter')], + devDependencies: [], + license: '''Copyright 2013 The Flutter Authors + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// flutter_web_plugins null +const _flutter_web_plugins = Package( + name: 'flutter_web_plugins', + description: 'Library to register Flutter Web plugins', + homepage: 'https://flutter.dev', + authors: [], + spdxIdentifiers: [], + isMarkdown: false, + isSdk: true, + dependencies: [PackageRef('flutter')], + devDependencies: [], + ); + +/// font_awesome_flutter 10.12.0 +const _font_awesome_flutter = Package( + name: 'font_awesome_flutter', + description: 'The Font Awesome Icon pack available as Flutter Icons. Provides 2000 additional icons to use in your apps.', + repository: 'https://github.com/fluttercommunity/font_awesome_flutter', + authors: [], + version: '10.12.0', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter')], + devDependencies: [PackageRef('recase'), PackageRef('args'), PackageRef('flutter_lints'), PackageRef('pub_semver')], + license: '''MIT License + +Copyright (c) 2017 Brian Egan +Copyright (c) 2020 Michael Spiss +Font Awesome Icons by @fontawesome - https://fontawesome.com +License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + ); + +/// frontend_server_client 4.0.0 +const _frontend_server_client = Package( + name: 'frontend_server_client', + description: 'Client code to start and interact with the frontend_server compiler from the Dart SDK.', + repository: 'https://github.com/dart-lang/webdev/tree/master/frontend_server_client', + authors: [], + version: '4.0.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('async'), PackageRef('path')], + devDependencies: [PackageRef('package_config'), PackageRef('shelf'), PackageRef('shelf_packages_handler'), PackageRef('shelf_static'), PackageRef('test'), PackageRef('vm_service')], + license: '''Copyright 2020, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// glob 2.1.3 +const _glob = Package( + name: 'glob', + description: 'A library to perform Bash-style file and directory globbing.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/glob', + authors: [], + version: '2.1.3', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('async'), PackageRef('collection'), PackageRef('file'), PackageRef('path'), PackageRef('string_scanner')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// graphs 2.3.2 +const _graphs = Package( + name: 'graphs', + description: 'Graph algorithms that operate on graphs in any representation.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/graphs', + authors: [], + version: '2.3.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('collection')], + devDependencies: [PackageRef('test'), PackageRef('analyzer'), PackageRef('path'), PackageRef('pool')], + license: '''Copyright 2017, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// html 0.15.6 +const _html = Package( + name: 'html', + description: 'APIs for parsing and manipulating HTML content outside the browser.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/html', + authors: [], + version: '0.15.6', + spdxIdentifiers: [], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('csslib'), PackageRef('source_span')], + devDependencies: [PackageRef('dart_style'), PackageRef('path'), PackageRef('test')], + license: '''Copyright (c) 2006-2012 The Authors + +Contributors: +James Graham - jg307@cam.ac.uk +Anne van Kesteren - annevankesteren@gmail.com +Lachlan Hunt - lachlan.hunt@lachy.id.au +Matt McDonald - kanashii@kanashii.ca +Sam Ruby - rubys@intertwingly.net +Ian Hickson (Google) - ian@hixie.ch +Thomas Broyer - t.broyer@ltgt.net +Jacques Distler - distler@golem.ph.utexas.edu +Henri Sivonen - hsivonen@iki.fi +Adam Barth - abarth@webkit.org +Eric Seidel - eric@webkit.org +The Mozilla Foundation (contributions from Henri Sivonen since 2008) +David Flanagan (Mozilla) - dflanagan@mozilla.com +Google LLC (contributed the Dart port) - misc@dartlang.org + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', + ); + +/// http 1.6.0 +const _http = Package( + name: 'http', + description: 'A composable, multi-platform, Future-based API for HTTP requests.', + repository: 'https://github.com/dart-lang/http/tree/master/pkgs/http', + authors: [], + version: '1.6.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('async'), PackageRef('http_parser'), PackageRef('meta'), PackageRef('web')], + devDependencies: [PackageRef('fake_async'), PackageRef('shelf'), PackageRef('stream_channel'), PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// http_multi_server 3.2.2 +const _http_multi_server = Package( + name: 'http_multi_server', + description: 'A dart:io HttpServer wrapper that handles requests from multiple servers.', + repository: 'https://github.com/dart-lang/http/tree/master/pkgs/http_multi_server', + authors: [], + version: '3.2.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('async')], + devDependencies: [PackageRef('http'), PackageRef('shelf'), PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// http_parser 4.1.2 +const _http_parser = Package( + name: 'http_parser', + description: 'A platform-independent package for parsing and serializing HTTP formats.', + repository: 'https://github.com/dart-lang/http/tree/master/pkgs/http_parser', + authors: [], + version: '4.1.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('collection'), PackageRef('source_span'), PackageRef('string_scanner'), PackageRef('typed_data')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// intl 0.20.2 +const _intl = Package( + name: 'intl', + description: 'Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and other internationalization issues.', + repository: 'https://github.com/dart-lang/i18n/tree/main/pkgs/intl', + authors: [], + version: '0.20.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('clock'), PackageRef('meta'), PackageRef('path')], + devDependencies: [PackageRef('ffi'), PackageRef('fixnum'), PackageRef('lints'), PackageRef('test')], + license: '''Copyright 2013, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// io 1.0.5 +const _io = Package( + name: 'io', + description: 'Utilities for the Dart VM Runtime including support for ANSI colors, file copying, and standard exit code values.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/io', + authors: [], + version: '1.0.5', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('meta'), PackageRef('path'), PackageRef('string_scanner')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2017, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// js 0.7.2 +const _js = Package( + name: 'js', + description: 'Annotations to create static Dart interfaces for JavaScript APIs.', + repository: 'https://github.com/dart-lang/sdk/tree/main/pkg/js', + authors: [], + version: '0.7.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('lints')], + license: '''Copyright 2012, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// json_annotation 4.9.0 +const _json_annotation = Package( + name: 'json_annotation', + description: 'Classes and helper functions that support JSON code generation via the `json_serializable` package.', + repository: 'https://github.com/google/json_serializable.dart/tree/master/json_annotation', + authors: [], + version: '4.9.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('meta')], + devDependencies: [], + license: '''Copyright 2017, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// json_schema 5.2.2 +const _json_schema = Package( + name: 'json_schema', + description: 'JSON Schema implementation in Dart', + homepage: 'https://github.com/workiva/json_schema', + authors: [], + version: '5.2.2', + spdxIdentifiers: [], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('collection'), PackageRef('http'), PackageRef('logging'), PackageRef('rfc_6901'), PackageRef('uri')], + devDependencies: [PackageRef('path'), PackageRef('shelf'), PackageRef('shelf_static'), PackageRef('test')], + license: '''Copyright 2013-2022 Workiva Inc. + +Licensed under the Boost Software License (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.boost.org/LICENSE_1_0.txt + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +This software or document includes material copied from or derived +from JSON-Schema-Test-Suite (https://github.com/json-schema-org/JSON-Schema-Test-Suite), +Copyright (c) 2012 Julian Berman, which is licensed under the following terms: + + Copyright (c) 2012 Julian Berman + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE.''', + ); + +/// leak_tracker 11.0.2 +const _leak_tracker = Package( + name: 'leak_tracker', + description: 'A framework for memory leak tracking for Dart and Flutter applications.', + repository: 'https://github.com/dart-lang/leak_tracker/tree/main/pkgs/leak_tracker', + authors: [], + version: '11.0.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('clock'), PackageRef('collection'), PackageRef('meta'), PackageRef('path'), PackageRef('vm_service')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2022, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// leak_tracker_flutter_testing 3.0.10 +const _leak_tracker_flutter_testing = Package( + name: 'leak_tracker_flutter_testing', + description: 'An internal package to test leak tracking with Flutter.', + repository: 'https://github.com/dart-lang/leak_tracker/tree/main/pkgs/leak_tracker_flutter_testing', + authors: [], + version: '3.0.10', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter'), PackageRef('leak_tracker'), PackageRef('leak_tracker_testing'), PackageRef('matcher'), PackageRef('meta')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2022, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// leak_tracker_testing 3.0.2 +const _leak_tracker_testing = Package( + name: 'leak_tracker_testing', + description: 'Leak tracking code intended for usage in tests.', + repository: 'https://github.com/dart-lang/leak_tracker/tree/main/pkgs/leak_tracker_testing', + authors: [], + version: '3.0.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('leak_tracker'), PackageRef('matcher'), PackageRef('meta')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2022, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// lints 5.1.1 +const _lints = Package( + name: 'lints', + description: """Official Dart lint rules. Defines the 'core' and 'recommended' set of lints suggested by the Dart team. +""", + repository: 'https://github.com/dart-lang/core/tree/main/pkgs/lints', + authors: [], + version: '5.1.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('http'), PackageRef('path'), PackageRef('yaml')], + license: '''Copyright 2021, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// logging 1.3.0 +const _logging = Package( + name: 'logging', + description: 'Provides APIs for debugging and error logging, similar to loggers in other languages, such as the Closure JS Logger and java.util.logging.Logger.', + repository: 'https://github.com/dart-lang/core/tree/main/pkgs/logging', + authors: [], + version: '1.3.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('test')], + license: '''Copyright 2013, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// markdown 7.3.0 +const _markdown = Package( + name: 'markdown', + description: 'A portable Markdown library written in Dart that can parse Markdown into HTML.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/markdown', + authors: [], + version: '7.3.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('args'), PackageRef('meta')], + devDependencies: [PackageRef('build_runner'), PackageRef('collection'), PackageRef('html'), PackageRef('http'), PackageRef('io'), PackageRef('path'), PackageRef('pool'), PackageRef('tar'), PackageRef('test'), PackageRef('web'), PackageRef('yaml')], + license: '''Copyright 2012, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// matcher 0.12.17 +const _matcher = Package( + name: 'matcher', + description: 'Support for specifying test expectations via an extensible Matcher class. Also includes a number of built-in Matcher implementations for common cases.', + repository: 'https://github.com/dart-lang/test/tree/master/pkgs/matcher', + authors: [], + version: '0.12.17', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('async'), PackageRef('meta'), PackageRef('stack_trace'), PackageRef('term_glyph'), PackageRef('test_api')], + devDependencies: [PackageRef('fake_async'), PackageRef('lints'), PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// material_color_utilities 0.11.1 +const _material_color_utilities = Package( + name: 'material_color_utilities', + description: 'Algorithms and utilities that power the Material Design 3 color system, including choosing theme colors from images and creating tones of colors; all in a new color space.', + repository: 'https://github.com/material-foundation/material-color-utilities/tree/main/dart', + authors: [], + version: '0.11.1', + spdxIdentifiers: ['Apache-2.0'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('collection')], + devDependencies: [PackageRef('matcher'), PackageRef('lints'), PackageRef('test')], + license: '''Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 Google LLC + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.''', + ); + +/// meta 1.17.0 +const _meta = Package( + name: 'meta', + description: "Annotations used to express developer intentions that can't otherwise be deduced by statically analyzing source code.", + repository: 'https://github.com/dart-lang/sdk/tree/main/pkg/meta', + authors: [], + version: '1.17.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('lints')], + license: '''Copyright 2016, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// mime 2.0.0 +const _mime = Package( + name: 'mime', + description: 'Utilities for handling media (MIME) types, including determining a type from a file extension and file contents.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/mime', + authors: [], + version: '2.0.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('test')], + license: '''Copyright 2015, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// nested 1.0.0 +const _nested = Package( + name: 'nested', + description: 'A Flutter Widget which helps nest multiple widgets without needing to manually nest them.', + repository: 'https://github.com/rrousselGit/nested', + authors: [], + version: '1.0.0', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter')], + devDependencies: [], + license: '''MIT License + +Copyright (c) 2019 Remi Rousselet + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + ); + +/// node_preamble 2.0.2 +const _node_preamble = Package( + name: 'node_preamble', + description: 'Better node.js preamble for dart2js, use it in your build system.', + homepage: 'https://github.com/mbullington/node_preamble.dart', + authors: ['Michael Bullington '], + version: '2.0.2', + spdxIdentifiers: ['BSD-3-Clause', 'MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [], + license: '''The MIT License (MIT) + +Copyright (c) 2015 Michael Bullington + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +=== + +Copyright 2012, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// package_config 2.2.0 +const _package_config = Package( + name: 'package_config', + description: 'Support for reading and writing Dart Package Configuration files.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/package_config', + authors: [], + version: '2.2.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('path')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2019, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// package_info_plus 9.0.0 +const _package_info_plus = Package( + name: 'package_info_plus', + description: 'Flutter plugin for querying information about the application package, such as CFBundleVersion on iOS or versionCode on Android.', + homepage: 'https://github.com/fluttercommunity/plus_plugins', + repository: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/package_info_plus/package_info_plus', + authors: [], + version: '9.0.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('ffi'), PackageRef('flutter'), PackageRef('flutter_web_plugins'), PackageRef('http'), PackageRef('meta'), PackageRef('path'), PackageRef('package_info_plus_platform_interface'), PackageRef('web'), PackageRef('win32'), PackageRef('clock')], + devDependencies: [PackageRef('flutter_lints'), PackageRef('test')], + license: '''Copyright 2017 The Chromium Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// package_info_plus_platform_interface 3.2.1 +const _package_info_plus_platform_interface = Package( + name: 'package_info_plus_platform_interface', + description: 'A common platform interface for the package_info_plus plugin.', + homepage: 'https://github.com/fluttercommunity/plus_plugins', + repository: 'https://github.com/fluttercommunity/plus_plugins/tree/main/packages/', + authors: [], + version: '3.2.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter'), PackageRef('meta'), PackageRef('plugin_platform_interface')], + devDependencies: [PackageRef('flutter_lints')], + license: '''Copyright 2017 The Chromium Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// pana 0.22.22 +const _pana = Package( + name: 'pana', + description: 'PAckage aNAlyzer - produce a report summarizing the health and quality of a Dart package.', + repository: 'https://github.com/dart-lang/pana', + authors: [], + version: '0.22.22', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('analyzer'), PackageRef('args'), PackageRef('async'), PackageRef('cli_util'), PackageRef('collection'), PackageRef('crypto'), PackageRef('html'), PackageRef('http'), PackageRef('io'), PackageRef('json_annotation'), PackageRef('lints'), PackageRef('logging'), PackageRef('markdown'), PackageRef('meta'), PackageRef('path'), PackageRef('pub_semver'), PackageRef('pubspec_parse'), PackageRef('retry'), PackageRef('safe_url_check'), PackageRef('source_span'), PackageRef('string_scanner'), PackageRef('tar'), PackageRef('test'), PackageRef('yaml')], + devDependencies: [PackageRef('build'), PackageRef('build_config'), PackageRef('build_runner'), PackageRef('shelf'), PackageRef('source_gen')], + license: '''Copyright 2017, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// path 1.9.1 +const _path = Package( + name: 'path', + description: 'A string-based path manipulation library. All of the path operations you know and love, with solid support for Windows, POSIX (Linux and Mac OS X), and the web.', + repository: 'https://github.com/dart-lang/core/tree/main/pkgs/path', + authors: [], + version: '1.9.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// path_provider 2.1.5 +const _path_provider = Package( + name: 'path_provider', + description: 'Flutter plugin for getting commonly used locations on host platform file systems, such as the temp and app data directories.', + repository: 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider', + authors: [], + version: '2.1.5', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter'), PackageRef('path_provider_android'), PackageRef('path_provider_foundation'), PackageRef('path_provider_linux'), PackageRef('path_provider_platform_interface'), PackageRef('path_provider_windows')], + devDependencies: [PackageRef('plugin_platform_interface'), PackageRef('test')], + license: '''Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// path_provider_android 2.2.20 +const _path_provider_android = Package( + name: 'path_provider_android', + description: 'Android implementation of the path_provider plugin.', + repository: 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_android', + authors: [], + version: '2.2.20', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter'), PackageRef('path_provider_platform_interface')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2013 The Flutter Authors + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// path_provider_foundation 2.4.3 +const _path_provider_foundation = Package( + name: 'path_provider_foundation', + description: 'iOS and macOS implementation of the path_provider plugin', + repository: 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_foundation', + authors: [], + version: '2.4.3', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter'), PackageRef('path_provider_platform_interface')], + devDependencies: [PackageRef('build_runner'), PackageRef('path')], + license: '''Copyright 2013 The Flutter Authors + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// path_provider_linux 2.2.1 +const _path_provider_linux = Package( + name: 'path_provider_linux', + description: 'Linux implementation of the path_provider plugin', + repository: 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_linux', + authors: [], + version: '2.2.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('ffi'), PackageRef('flutter'), PackageRef('path'), PackageRef('path_provider_platform_interface'), PackageRef('xdg_directories')], + devDependencies: [], + license: '''Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// path_provider_platform_interface 2.1.2 +const _path_provider_platform_interface = Package( + name: 'path_provider_platform_interface', + description: 'A common platform interface for the path_provider plugin.', + repository: 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_platform_interface', + authors: [], + version: '2.1.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter'), PackageRef('platform'), PackageRef('plugin_platform_interface')], + devDependencies: [], + license: '''Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// path_provider_windows 2.3.0 +const _path_provider_windows = Package( + name: 'path_provider_windows', + description: 'Windows implementation of the path_provider plugin', + repository: 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_windows', + authors: [], + version: '2.3.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('ffi'), PackageRef('flutter'), PackageRef('path'), PackageRef('path_provider_platform_interface')], + devDependencies: [], + license: '''Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// petitparser 7.0.1 +const _petitparser = Package( + name: 'petitparser', + description: 'A dynamic parser framework to build efficient grammars and parsers quickly.', + homepage: 'https://petitparser.github.io', + repository: 'https://github.com/petitparser/dart-petitparser', + authors: [], + version: '7.0.1', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('meta'), PackageRef('collection')], + devDependencies: [PackageRef('lints'), PackageRef('test')], + license: '''The MIT License + +Copyright (c) 2006-2024 Lukas Renggli. +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.''', + ); + +/// platform 3.1.6 +const _platform = Package( + name: 'platform', + description: 'A pluggable, mockable platform information abstraction for Dart.', + repository: 'https://github.com/dart-lang/core/tree/main/pkgs/platform', + authors: [], + version: '3.1.6', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('test')], + license: '''Copyright 2017, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// plugin_platform_interface 2.1.8 +const _plugin_platform_interface = Package( + name: 'plugin_platform_interface', + description: 'Reusable base class for platform interfaces of Flutter federated plugins, to help enforce best practices.', + repository: 'https://github.com/flutter/packages/tree/main/packages/plugin_platform_interface', + authors: [], + version: '2.1.8', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('meta')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// pool 1.5.2 +const _pool = Package( + name: 'pool', + description: 'Manage a finite pool of resources. Useful for controlling concurrent file system or network requests.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/pool', + authors: [], + version: '1.5.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('async'), PackageRef('stack_trace')], + devDependencies: [PackageRef('fake_async'), PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// provider 6.1.5+1 +const _provider = Package( + name: 'provider', + description: 'A wrapper around InheritedWidget to make them easier to use and more reusable.', + repository: 'https://github.com/rrousselGit/provider', + authors: [], + version: '6.1.5+1', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('collection'), PackageRef('flutter'), PackageRef('nested')], + devDependencies: [PackageRef('leak_tracker'), PackageRef('test')], + license: '''MIT License + +Copyright (c) 2019 Remi Rousselet + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + ); + +/// pub_semver 2.2.0 +const _pub_semver = Package( + name: 'pub_semver', + description: "Versions and version constraints implementing pub's versioning policy. This is very similar to vanilla semver, with a few corner cases.", + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/pub_semver', + authors: [], + version: '2.2.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('collection')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// pubspec_parse 1.5.0 +const _pubspec_parse = Package( + name: 'pubspec_parse', + description: 'Simple package for parsing pubspec.yaml files with a type-safe API and rich error reporting.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/pubspec_parse', + authors: [], + version: '1.5.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('checked_yaml'), PackageRef('collection'), PackageRef('json_annotation'), PackageRef('pub_semver'), PackageRef('yaml')], + devDependencies: [PackageRef('build_runner'), PackageRef('path'), PackageRef('source_gen'), PackageRef('stack_trace'), PackageRef('test')], + license: '''Copyright 2018, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// quiver 3.2.2 +const _quiver = Package( + name: 'quiver', + description: 'Quiver is a set of utility libraries for Dart that makes using many Dart libraries easier and more convenient, or adds additional functionality.', + repository: 'https://github.com/google/quiver-dart', + authors: [], + version: '3.2.2', + spdxIdentifiers: ['Apache-2.0'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('matcher')], + devDependencies: [PackageRef('lints'), PackageRef('path'), PackageRef('test')], + license: '''Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.''', + ); + +/// recase 4.1.0 +const _recase = Package( + name: 'recase', + description: 'Changes the case of the input text to the desire case convention.', + homepage: 'https://github.com/techniboogie-dart/recase', + authors: [], + version: '4.1.0', + spdxIdentifiers: ['BSD-2-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('test')], + license: '''Copyright 2017 Keith Elliott + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// retry 3.1.2 +const _retry = Package( + name: 'retry', + description: 'Utility for wrapping an asynchronous function in automatic retry logic with exponential back-off, useful when making requests over network.', + homepage: 'https://github.com/google/dart-neats/tree/master/retry', + repository: 'https://github.com/google/dart-neats.git', + authors: [], + version: '3.1.2', + spdxIdentifiers: ['Apache-2.0'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('test'), PackageRef('lints')], + license: '''Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.''', + ); + +/// rfc_6901 0.2.1 +const _rfc_6901 = Package( + name: 'rfc_6901', + description: 'JSON Pointer (RFC 6901). Reads/writes referred values in JSON documents.', + homepage: 'https://github.com/f3ath/rfc-6901-dart', + authors: [], + version: '0.2.1', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('lints'), PackageRef('test')], + license: '''MIT License + +Copyright (c) 2021 The Конь + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + ); + +/// safe_url_check 1.1.2 +const _safe_url_check = Package( + name: 'safe_url_check', + description: 'Check if an untrusted URL is broken, without allowing connections to a private IP address.', + homepage: 'https://github.com/google/dart-neats/tree/master/safe_url_check', + repository: 'https://github.com/google/dart-neats.git', + authors: [], + version: '1.1.2', + spdxIdentifiers: ['Apache-2.0'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('meta'), PackageRef('retry')], + devDependencies: [PackageRef('test'), PackageRef('lints'), PackageRef('build_runner')], + license: '''Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.''', + ); + +/// shelf 1.4.2 +const _shelf = Package( + name: 'shelf', + description: '''A model for web server middleware that encourages composition and easy reuse. +''', + repository: 'https://github.com/dart-lang/shelf/tree/master/pkgs/shelf', + authors: [], + version: '1.4.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('async'), PackageRef('collection'), PackageRef('http_parser'), PackageRef('path'), PackageRef('stack_trace'), PackageRef('stream_channel')], + devDependencies: [PackageRef('http'), PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// shelf_packages_handler 3.0.2 +const _shelf_packages_handler = Package( + name: 'shelf_packages_handler', + description: 'A shelf handler for serving a `packages/` directory.', + repository: 'https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_packages_handler', + authors: [], + version: '3.0.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('path'), PackageRef('shelf'), PackageRef('shelf_static')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2016, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// shelf_static 1.1.3 +const _shelf_static = Package( + name: 'shelf_static', + description: 'Static file server support for the shelf package and ecosystem.', + repository: 'https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_static', + authors: [], + version: '1.1.3', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('convert'), PackageRef('http_parser'), PackageRef('mime'), PackageRef('path'), PackageRef('shelf')], + devDependencies: [PackageRef('args'), PackageRef('test')], + license: '''Copyright 2015, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// shelf_web_socket 3.0.0 +const _shelf_web_socket = Package( + name: 'shelf_web_socket', + description: 'A shelf handler that wires up a listener for every connection.', + repository: 'https://github.com/dart-lang/shelf/tree/master/pkgs/shelf_web_socket', + authors: [], + version: '3.0.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('shelf'), PackageRef('stream_channel'), PackageRef('web_socket_channel')], + devDependencies: [PackageRef('http'), PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// skeletonizer 2.1.1 +const _skeletonizer = Package( + name: 'skeletonizer', + description: 'Converts already built widgets into skeleton loaders with no extra effort.', + homepage: 'https://github.com/Milad-Akarie/skeletonizer', + authors: [], + version: '2.1.1', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter')], + devDependencies: [PackageRef('flutter_lints')], + license: '''MIT License + +Copyright (c) 2023 Milad Akarie + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + ); + +/// source_gen 4.0.0 +const _source_gen = Package( + name: 'source_gen', + description: 'Source code generation builders and utilities for the Dart build system', + repository: 'https://github.com/dart-lang/source_gen/tree/master/source_gen', + authors: [], + version: '4.0.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('analyzer'), PackageRef('async'), PackageRef('build'), PackageRef('dart_style'), PackageRef('glob'), PackageRef('path'), PackageRef('pub_semver'), PackageRef('source_span'), PackageRef('yaml')], + devDependencies: [PackageRef('logging'), PackageRef('term_glyph'), PackageRef('test')], + license: '''Copyright 2015, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// source_map_stack_trace 2.1.2 +const _source_map_stack_trace = Package( + name: 'source_map_stack_trace', + description: 'A package for applying source maps to stack traces.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/source_map_stack_trace', + authors: [], + version: '2.1.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('path'), PackageRef('source_maps'), PackageRef('stack_trace')], + devDependencies: [PackageRef('source_span'), PackageRef('test')], + license: '''Copyright 2015, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// source_maps 0.10.13 +const _source_maps = Package( + name: 'source_maps', + description: 'A library to programmatically manipulate source map files.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/source_maps', + authors: [], + version: '0.10.13', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('source_span')], + devDependencies: [PackageRef('term_glyph'), PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// source_span 1.10.1 +const _source_span = Package( + name: 'source_span', + description: 'Provides a standard representation for source code locations and spans.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/source_span', + authors: [], + version: '1.10.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('collection'), PackageRef('path'), PackageRef('term_glyph')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// sqlite3 2.9.4 +const _sqlite3 = Package( + name: 'sqlite3', + description: 'Provides lightweight yet convenient bindings to SQLite by using dart:ffi', + homepage: 'https://github.com/simolus3/sqlite3.dart/tree/main/sqlite3', + authors: [], + version: '2.9.4', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('collection'), PackageRef('ffi'), PackageRef('meta'), PackageRef('path'), PackageRef('web'), PackageRef('typed_data')], + devDependencies: [PackageRef('analyzer'), PackageRef('build_daemon'), PackageRef('build_runner'), PackageRef('dart_style'), PackageRef('http'), PackageRef('lints'), PackageRef('shelf'), PackageRef('shelf_static'), PackageRef('stream_channel'), PackageRef('test'), PackageRef('pub_semver'), PackageRef('convert')], + license: '''MIT License + +Copyright (c) 2020 Simon Binder + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + ); + +/// sqlite3_flutter_libs 0.5.40 +const _sqlite3_flutter_libs = Package( + name: 'sqlite3_flutter_libs', + description: 'Flutter plugin to include native sqlite3 libraries with your app', + homepage: 'https://github.com/simolus3/sqlite3.dart/tree/main/sqlite3_flutter_libs', + authors: [], + version: '0.5.40', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter')], + devDependencies: [], + license: '''MIT License + +Copyright (c) 2020 Simon Binder + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + ); + +/// sqlparser 0.42.0 +const _sqlparser = Package( + name: 'sqlparser', + description: 'Parses sqlite statements and performs static analysis on them', + homepage: 'https://github.com/simolus3/drift/tree/develop/sqlparser', + repository: 'https://github.com/simolus3/drift', + authors: [], + version: '0.42.0', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('meta'), PackageRef('collection'), PackageRef('source_span'), PackageRef('charcode')], + devDependencies: [PackageRef('lints'), PackageRef('test'), PackageRef('path'), PackageRef('ffi'), PackageRef('sqlite3')], + license: '''MIT License + +Copyright (c) 2019 Simon Binder + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + ); + +/// stack_trace 1.12.1 +const _stack_trace = Package( + name: 'stack_trace', + description: 'A package for manipulating stack traces and printing them readably.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/stack_trace', + authors: [], + version: '1.12.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('path')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// stream_channel 2.1.4 +const _stream_channel = Package( + name: 'stream_channel', + description: 'An abstraction for two-way communication channels based on the Dart Stream class.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/stream_channel', + authors: [], + version: '2.1.4', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('async')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2015, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// stream_transform 2.1.1 +const _stream_transform = Package( + name: 'stream_transform', + description: 'A collection of utilities to transform and manipulate streams.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/stream_transform', + authors: [], + version: '2.1.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('async'), PackageRef('fake_async'), PackageRef('test')], + license: '''Copyright 2017, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// string_scanner 1.4.1 +const _string_scanner = Package( + name: 'string_scanner', + description: 'A class for parsing strings using a sequence of patterns.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/string_scanner', + authors: [], + version: '1.4.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('source_span')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// tar 2.0.2 +const _tar = Package( + name: 'tar', + description: 'Memory-efficient, streaming implementation of the tar file format', + repository: 'https://github.com/simolus3/tar/', + authors: [], + version: '2.0.2', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('async'), PackageRef('meta'), PackageRef('typed_data')], + devDependencies: [PackageRef('charcode'), PackageRef('lints'), PackageRef('file'), PackageRef('path'), PackageRef('test')], + license: '''MIT License + +Copyright (c) 2021 Simon Binder + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + ); + +/// term_glyph 1.2.2 +const _term_glyph = Package( + name: 'term_glyph', + description: 'Useful Unicode glyphs and ASCII substitutes.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/term_glyph', + authors: [], + version: '1.2.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('dart_style'), PackageRef('test')], + license: '''Copyright 2017, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// test 1.26.3 +const _test = Package( + name: 'test', + description: 'A full featured library for writing and running Dart tests across platforms.', + repository: 'https://github.com/dart-lang/test/tree/master/pkgs/test', + authors: [], + version: '1.26.3', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('analyzer'), PackageRef('async'), PackageRef('boolean_selector'), PackageRef('collection'), PackageRef('coverage'), PackageRef('http_multi_server'), PackageRef('io'), PackageRef('js'), PackageRef('matcher'), PackageRef('node_preamble'), PackageRef('package_config'), PackageRef('path'), PackageRef('pool'), PackageRef('shelf'), PackageRef('shelf_packages_handler'), PackageRef('shelf_static'), PackageRef('shelf_web_socket'), PackageRef('source_span'), PackageRef('stack_trace'), PackageRef('stream_channel'), PackageRef('test_api'), PackageRef('test_core'), PackageRef('typed_data'), PackageRef('web_socket_channel'), PackageRef('webkit_inspection_protocol'), PackageRef('yaml')], + devDependencies: [PackageRef('fake_async'), PackageRef('glob')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// test_api 0.7.7 +const _test_api = Package( + name: 'test_api', + description: 'The user facing API for structuring Dart tests and checking expectations.', + repository: 'https://github.com/dart-lang/test/tree/master/pkgs/test_api', + authors: [], + version: '0.7.7', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('async'), PackageRef('boolean_selector'), PackageRef('collection'), PackageRef('meta'), PackageRef('source_span'), PackageRef('stack_trace'), PackageRef('stream_channel'), PackageRef('string_scanner'), PackageRef('term_glyph')], + devDependencies: [PackageRef('analyzer'), PackageRef('fake_async'), PackageRef('glob'), PackageRef('graphs'), PackageRef('path'), PackageRef('test'), PackageRef('test_core')], + license: '''Copyright 2018, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// test_core 0.6.12 +const _test_core = Package( + name: 'test_core', + description: 'A basic library for writing tests and running them on the VM.', + repository: 'https://github.com/dart-lang/test/tree/master/pkgs/test_core', + authors: [], + version: '0.6.12', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('analyzer'), PackageRef('args'), PackageRef('async'), PackageRef('boolean_selector'), PackageRef('collection'), PackageRef('coverage'), PackageRef('frontend_server_client'), PackageRef('glob'), PackageRef('io'), PackageRef('meta'), PackageRef('package_config'), PackageRef('path'), PackageRef('pool'), PackageRef('source_map_stack_trace'), PackageRef('source_maps'), PackageRef('source_span'), PackageRef('stack_trace'), PackageRef('stream_channel'), PackageRef('test_api'), PackageRef('vm_service'), PackageRef('yaml')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2018, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// timing 1.0.2 +const _timing = Package( + name: 'timing', + description: 'A simple package for tracking the performance of synchronous and asynchronous actions.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/timing', + authors: [], + version: '1.0.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('json_annotation')], + devDependencies: [PackageRef('build_runner'), PackageRef('test')], + license: '''Copyright 2018, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// typed_data 1.4.0 +const _typed_data = Package( + name: 'typed_data', + description: 'Utility functions and classes related to the dart:typed_data library.', + repository: 'https://github.com/dart-lang/core/tree/main/pkgs/typed_data', + authors: [], + version: '1.4.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('collection')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2015, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// uri 1.0.0 +const _uri = Package( + name: 'uri', + description: 'Utilities for building and parsing URIs, including support for parsing URI templates as defined in RFC 6570.', + repository: 'https://github.com/google/uri.dart', + authors: [], + version: '1.0.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('matcher'), PackageRef('quiver')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2013, the Dart project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// url_launcher 6.3.2 +const _url_launcher = Package( + name: 'url_launcher', + description: 'Flutter plugin for launching a URL. Supports web, phone, SMS, and email schemes.', + repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher', + authors: [], + version: '6.3.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter'), PackageRef('url_launcher_android'), PackageRef('url_launcher_ios'), PackageRef('url_launcher_linux'), PackageRef('url_launcher_macos'), PackageRef('url_launcher_platform_interface'), PackageRef('url_launcher_web'), PackageRef('url_launcher_windows')], + devDependencies: [PackageRef('plugin_platform_interface'), PackageRef('test')], + license: '''Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// url_launcher_android 6.3.28 +const _url_launcher_android = Package( + name: 'url_launcher_android', + description: 'Android implementation of the url_launcher plugin.', + repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_android', + authors: [], + version: '6.3.28', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter'), PackageRef('url_launcher_platform_interface')], + devDependencies: [PackageRef('plugin_platform_interface'), PackageRef('test')], + license: '''Copyright 2013 The Flutter Authors + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// url_launcher_ios 6.3.6 +const _url_launcher_ios = Package( + name: 'url_launcher_ios', + description: 'iOS implementation of the url_launcher plugin.', + repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_ios', + authors: [], + version: '6.3.6', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter'), PackageRef('url_launcher_platform_interface')], + devDependencies: [PackageRef('build_runner'), PackageRef('plugin_platform_interface'), PackageRef('test')], + license: '''Copyright 2013 The Flutter Authors + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// url_launcher_linux 3.2.2 +const _url_launcher_linux = Package( + name: 'url_launcher_linux', + description: 'Linux implementation of the url_launcher plugin.', + repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_linux', + authors: [], + version: '3.2.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter'), PackageRef('url_launcher_platform_interface')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2013 The Flutter Authors + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// url_launcher_macos 3.2.5 +const _url_launcher_macos = Package( + name: 'url_launcher_macos', + description: 'macOS implementation of the url_launcher plugin.', + repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_macos', + authors: [], + version: '3.2.5', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter'), PackageRef('url_launcher_platform_interface')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2013 The Flutter Authors + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// url_launcher_platform_interface 2.3.2 +const _url_launcher_platform_interface = Package( + name: 'url_launcher_platform_interface', + description: 'A common platform interface for the url_launcher plugin.', + repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_platform_interface', + authors: [], + version: '2.3.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter'), PackageRef('plugin_platform_interface')], + devDependencies: [], + license: '''Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// url_launcher_web 2.4.1 +const _url_launcher_web = Package( + name: 'url_launcher_web', + description: 'Web platform implementation of url_launcher', + repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_web', + authors: [], + version: '2.4.1', + spdxIdentifiers: ['Apache-2.0', 'BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter'), PackageRef('flutter_web_plugins'), PackageRef('url_launcher_platform_interface'), PackageRef('web')], + devDependencies: [], + license: '''url_launcher_web + +Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +-------------------------------------------------------------------------------- +platform_detect + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017 Workiva Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License.''', + ); + +/// url_launcher_windows 3.1.5 +const _url_launcher_windows = Package( + name: 'url_launcher_windows', + description: 'Windows implementation of the url_launcher plugin.', + repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_windows', + authors: [], + version: '3.1.5', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter'), PackageRef('url_launcher_platform_interface')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2013 The Flutter Authors + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// uuid 4.5.2 +const _uuid = Package( + name: 'uuid', + description: '''RFC4122 (v1, v4, v5, v6, v7, v8) UUID Generator and Parser for Dart +''', + repository: 'https://github.com/Daegalus/dart-uuid', + authors: [], + version: '4.5.2', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('crypto'), PackageRef('meta'), PackageRef('fixnum')], + devDependencies: [PackageRef('lints'), PackageRef('test')], + license: '''Copyright (c) 2021 Yulian Kuncheff + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', + ); + +/// vector_math 2.2.0 +const _vector_math = Package( + name: 'vector_math', + description: 'A Vector Math library for 2D and 3D applications.', + repository: 'https://github.com/google/vector_math.dart', + authors: [], + version: '2.2.0', + spdxIdentifiers: ['Zlib', 'BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('build_runner'), PackageRef('path'), PackageRef('test')], + license: '''Copyright 2015, Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Copyright (C) 2013 Andrew Magill + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution.''', + ); + +/// vm_service 15.0.2 +const _vm_service = Package( + name: 'vm_service', + description: 'A library to communicate with a service implementing the Dart VM service protocol.', + repository: 'https://github.com/dart-lang/sdk/tree/main/pkg/vm_service', + authors: [], + version: '15.0.2', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('async'), PackageRef('collection'), PackageRef('lints'), PackageRef('logging'), PackageRef('markdown'), PackageRef('path'), PackageRef('pub_semver'), PackageRef('stack_trace'), PackageRef('test')], + license: '''Copyright 2015, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// watcher 1.1.4 +const _watcher = Package( + name: 'watcher', + description: 'A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified.', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/watcher', + authors: [], + version: '1.1.4', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('async'), PackageRef('path')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2014, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// web 1.1.1 +const _web = Package( + name: 'web', + description: 'Lightweight browser API bindings built around JS interop.', + repository: 'https://github.com/dart-lang/web', + authors: [], + version: '1.1.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('path'), PackageRef('test')], + license: '''Copyright 2023, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// web_socket 1.0.1 +const _web_socket = Package( + name: 'web_socket', + description: 'Any easy-to-use library for communicating with WebSockets that has multiple implementations.', + repository: 'https://github.com/dart-lang/http/tree/master/pkgs/web_socket', + authors: [], + version: '1.0.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('web')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2024, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// web_socket_channel 3.0.3 +const _web_socket_channel = Package( + name: 'web_socket_channel', + description: 'StreamChannel wrappers for WebSockets. Provides a cross-platform WebSocketChannel API, a cross-platform implementation of that API that communicates over an underlying StreamChannel.', + repository: 'https://github.com/dart-lang/http/tree/master/pkgs/web_socket_channel', + authors: [], + version: '3.0.3', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('async'), PackageRef('crypto'), PackageRef('stream_channel'), PackageRef('web'), PackageRef('web_socket')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2016, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// webkit_inspection_protocol 1.2.1 +const _webkit_inspection_protocol = Package( + name: 'webkit_inspection_protocol', + description: '''A client for the Chrome DevTools Protocol (previously called the Webkit Inspection Protocol). +''', + repository: 'https://github.com/google/webkit_inspection_protocol.dart', + authors: [], + version: '1.2.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('logging')], + devDependencies: [PackageRef('args'), PackageRef('lints'), PackageRef('shelf_static'), PackageRef('shelf_web_socket'), PackageRef('shelf'), PackageRef('test'), PackageRef('web_socket_channel')], + license: '''Copyright 2013, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// win32 5.15.0 +const _win32 = Package( + name: 'win32', + description: '''Access common Win32 APIs directly from Dart using FFI — no C required! +''', + homepage: 'https://win32.pub', + repository: 'https://github.com/halildurmus/win32', + authors: [], + version: '5.15.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('ffi')], + devDependencies: [PackageRef('args'), PackageRef('path'), PackageRef('test')], + license: '''BSD 3-Clause License + +Copyright (c) 2024, Halil Durmus + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// xdg_directories 1.1.0 +const _xdg_directories = Package( + name: 'xdg_directories', + description: 'A Dart package for reading XDG directory configuration information on Linux.', + repository: 'https://github.com/flutter/packages/tree/main/packages/xdg_directories', + authors: [], + version: '1.1.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('meta'), PackageRef('path')], + devDependencies: [PackageRef('test')], + license: '''Copyright 2013 The Flutter Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// xml 6.6.1 +const _xml = Package( + name: 'xml', + description: 'A lightweight library for parsing, traversing, querying, transforming and building XML documents.', + homepage: 'https://github.com/renggli/dart-xml', + authors: [], + version: '6.6.1', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('collection'), PackageRef('meta'), PackageRef('petitparser')], + devDependencies: [PackageRef('args'), PackageRef('lints'), PackageRef('test')], + license: '''The MIT License + +Copyright (c) 2006-2025 Lukas Renggli. +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.''', + ); + +/// yaml 3.1.3 +const _yaml = Package( + name: 'yaml', + description: 'A parser for YAML, a human-friendly data serialization standard', + repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/yaml', + authors: [], + version: '3.1.3', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('collection'), PackageRef('source_span'), PackageRef('string_scanner')], + devDependencies: [PackageRef('path'), PackageRef('test')], + license: '''Copyright (c) 2014, the Dart project authors. +Copyright (c) 2006, Kirill Simonov. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.''', + ); + +/// game_tracker 0.0.4+111 +const _game_tracker = Package( + name: 'game_tracker', + description: 'Game Tracking App for Card Games', + authors: [], + version: '0.0.4+111', + spdxIdentifiers: [], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter'), PackageRef('drift'), PackageRef('drift_flutter'), PackageRef('path_provider'), PackageRef('provider'), PackageRef('skeletonizer'), PackageRef('uuid'), PackageRef('file_picker'), PackageRef('json_schema'), PackageRef('file_saver'), PackageRef('clock'), PackageRef('intl'), PackageRef('package_info_plus'), PackageRef('font_awesome_flutter'), PackageRef('url_launcher')], + devDependencies: [PackageRef('dart_pubspec_licenses'), PackageRef('flutter_lints'), PackageRef('drift_dev'), PackageRef('build_runner')], + ); + diff --git a/lib/presentation/views/main_menu/settings_view.dart b/lib/presentation/views/main_menu/settings_view/settings_view.dart similarity index 96% rename from lib/presentation/views/main_menu/settings_view.dart rename to lib/presentation/views/main_menu/settings_view/settings_view.dart index 7285394..e48b1f3 100644 --- a/lib/presentation/views/main_menu/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view/settings_view.dart @@ -3,6 +3,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; +import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses_view.dart'; import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; import 'package:game_tracker/services/data_transfer_service.dart'; import 'package:intl/intl.dart'; @@ -131,10 +132,14 @@ class _SettingsViewState extends State { ), ), SettingsListTile( - title: 'Lizenzen', + title: loc.licenses, icon: Icons.insert_drive_file, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () {}, + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => const LicensesView()), + ); + }, ), const Spacer(), Padding( diff --git a/lib/presentation/widgets/tiles/license_tile.dart b/lib/presentation/widgets/tiles/license_tile.dart new file mode 100644 index 0000000..bf2c4d5 --- /dev/null +++ b/lib/presentation/widgets/tiles/license_tile.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:game_tracker/core/custom_theme.dart'; +import 'package:game_tracker/presentation/views/main_menu/settings_view/license_detail_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/settings_view/oss_licenses.dart'; + +class LicenseTile extends StatelessWidget { + final Package package; + + const LicenseTile({super.key, required this.package}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => LicenseDetailView(package: package), + ), + ); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + decoration: CustomTheme.standardBoxDecoration, + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + package.name, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + const SizedBox(height: 4), + Text( + package.description, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 12, color: Colors.grey.shade400), + ), + ], + ), + ), + const Icon(Icons.arrow_forward_ios, size: 16), + ], + ), + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 6ce90a7..42df4a2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.4+109 +version: 0.0.4+118 environment: sdk: ^3.8.1 @@ -27,6 +27,7 @@ dependencies: url_launcher: ^6.3.2 dev_dependencies: + dart_pubspec_licenses: ^3.0.14 flutter_test: sdk: flutter flutter_lints: ^5.0.0 From c8b76ae0ea64c62cddc6b182b76f842ea44e07d0 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 11 Jan 2026 15:43:46 +0100 Subject: [PATCH 196/222] Updated l10n config --- .gitignore | 2 ++ l10n.yaml | 3 ++- lib/l10n/generated/app_localizations.dart | 24 ++++++++++++++++++++ lib/l10n/generated/app_localizations_de.dart | 12 ++++++++++ lib/l10n/generated/app_localizations_en.dart | 12 ++++++++++ 5 files changed, 52 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 72eb56e..1f715b9 100644 --- a/.gitignore +++ b/.gitignore @@ -196,3 +196,5 @@ app.*.map.json /android/app/profile /android/app/release /devtools_options.yaml + +untranslated_messages.json \ No newline at end of file diff --git a/l10n.yaml b/l10n.yaml index f7805c8..e72b308 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -2,4 +2,5 @@ arb-dir: lib/l10n/arb template-arb-file: app_en.arb output-localization-file: app_localizations.dart output-dir: lib/l10n/generated -nullable-getter: false \ No newline at end of file +nullable-getter: false +untranslated-messages-file: lib/l10n/untranslated_messages.json \ No newline at end of file diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 1da83f8..d91be37 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -302,6 +302,30 @@ abstract class AppLocalizations { /// **'Invalid Schema'** String get invalid_schema; + /// Licenses menu item + /// + /// In en, this message translates to: + /// **'Licenses'** + String get licenses; + + /// Message when no licenses are found + /// + /// In en, this message translates to: + /// **'No licenses found'** + String get no_licenses_found; + + /// Message when no license text is available + /// + /// In en, this message translates to: + /// **'No license text available'** + String get no_license_text_available; + + /// Error label + /// + /// In en, this message translates to: + /// **'Error'** + String get error; + /// Title for least points ruleset /// /// In en, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 75a1325..fa3010d 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -115,6 +115,18 @@ class AppLocalizationsDe extends AppLocalizations { @override String get invalid_schema => 'Ungültiges Schema'; + @override + String get licenses => 'Lizenzen'; + + @override + String get no_licenses_found => 'Keine Lizenzen gefunden'; + + @override + String get no_license_text_available => 'Kein Lizenztext verfügbar'; + + @override + String get error => 'Fehler'; + @override String get least_points => 'Niedrigste Punkte'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 00b933b..d6bfa25 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -115,6 +115,18 @@ class AppLocalizationsEn extends AppLocalizations { @override String get invalid_schema => 'Invalid Schema'; + @override + String get licenses => 'Licenses'; + + @override + String get no_licenses_found => 'No licenses found'; + + @override + String get no_license_text_available => 'No license text available'; + + @override + String get error => 'Error'; + @override String get least_points => 'Least Points'; From 5ea7797b3e2465038b549f5f68823a120b077614 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 11 Jan 2026 15:44:05 +0100 Subject: [PATCH 197/222] Moved files --- .../settings_view/{ => licenses}/license_detail_view.dart | 2 +- .../main_menu/settings_view/{ => licenses}/oss_licenses.dart | 0 .../views/main_menu/settings_view/licenses_view.dart | 2 +- lib/presentation/widgets/tiles/license_tile.dart | 4 ++-- 4 files changed, 4 insertions(+), 4 deletions(-) rename lib/presentation/views/main_menu/settings_view/{ => licenses}/license_detail_view.dart (98%) rename lib/presentation/views/main_menu/settings_view/{ => licenses}/oss_licenses.dart (100%) diff --git a/lib/presentation/views/main_menu/settings_view/license_detail_view.dart b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart similarity index 98% rename from lib/presentation/views/main_menu/settings_view/license_detail_view.dart rename to lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart index 8684787..50d9d23 100644 --- a/lib/presentation/views/main_menu/settings_view/license_detail_view.dart +++ b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/views/main_menu/settings_view/oss_licenses.dart'; +import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart'; class LicenseDetailView extends StatelessWidget { final Package package; diff --git a/lib/presentation/views/main_menu/settings_view/oss_licenses.dart b/lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart similarity index 100% rename from lib/presentation/views/main_menu/settings_view/oss_licenses.dart rename to lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart diff --git a/lib/presentation/views/main_menu/settings_view/licenses_view.dart b/lib/presentation/views/main_menu/settings_view/licenses_view.dart index a4699f1..63ec7b2 100644 --- a/lib/presentation/views/main_menu/settings_view/licenses_view.dart +++ b/lib/presentation/views/main_menu/settings_view/licenses_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/views/main_menu/settings_view/oss_licenses.dart'; +import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart'; import 'package:game_tracker/presentation/widgets/tiles/license_tile.dart'; class LicensesView extends StatelessWidget { diff --git a/lib/presentation/widgets/tiles/license_tile.dart b/lib/presentation/widgets/tiles/license_tile.dart index bf2c4d5..08aceba 100644 --- a/lib/presentation/widgets/tiles/license_tile.dart +++ b/lib/presentation/widgets/tiles/license_tile.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/views/main_menu/settings_view/license_detail_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/settings_view/oss_licenses.dart'; +import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart'; +import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart'; class LicenseTile extends StatelessWidget { final Package package; From 9a00e543d76890d38c17749bd0612ebd3c05d097 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 11 Jan 2026 16:00:19 +0100 Subject: [PATCH 198/222] Implemented adaptive mail icon --- .../settings_view/settings_view.dart | 60 +++++++++++-------- pubspec.yaml | 30 +++++----- 2 files changed, 51 insertions(+), 39 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view/settings_view.dart b/lib/presentation/views/main_menu/settings_view/settings_view.dart index e48b1f3..c3fa850 100644 --- a/lib/presentation/views/main_menu/settings_view/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view/settings_view.dart @@ -1,3 +1,6 @@ +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:game_tracker/core/custom_theme.dart'; @@ -146,34 +149,41 @@ class _SettingsViewState extends State { padding: const EdgeInsets.all(20), child: Center( child: Column( - spacing: 6, + spacing: 4, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - spacing: 40, - children: [ - GestureDetector( - child: const Icon(Icons.language), - onTap: () => { - launchUrl(Uri.parse('https://liquid-dev.de')), - }, - ), - GestureDetector( - child: const FaIcon(FontAwesomeIcons.github), - onTap: () => { - launchUrl( - Uri.parse( - 'https://github.com/liquiddevelopmentde', + Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 40, + children: [ + GestureDetector( + child: const Icon(Icons.language), + onTap: () => { + launchUrl(Uri.parse('https://liquid-dev.de')), + }, + ), + GestureDetector( + child: const FaIcon(FontAwesomeIcons.github), + onTap: () => { + launchUrl( + Uri.parse( + 'https://github.com/liquiddevelopmentde', + ), ), + }, + ), + GestureDetector( + child: Icon( + Platform.isIOS + ? CupertinoIcons.mail_solid + : Icons.email, ), - }, - ), - GestureDetector( - child: const Icon(Icons.email), - onTap: () => - launchUrl(Uri.parse('mailto:hi@liquid-dev.de')), - ), - ], + onTap: () => + launchUrl(Uri.parse('mailto:hi@liquid-dev.de')), + ), + ], + ), ), Text( '© ${DateFormat('yyyy').format(DateTime.now())} Liquid Development', diff --git a/pubspec.yaml b/pubspec.yaml index 42df4a2..d1c9538 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.4+118 +version: 0.0.4+122 environment: sdk: ^3.8.1 @@ -9,30 +9,32 @@ environment: dependencies: flutter: sdk: flutter + flutter_localizations: + sdk: flutter + clock: ^1.1.2 + cupertino_icons: ^1.0.6 drift: ^2.27.0 drift_flutter: ^0.2.4 + file_picker: ^10.3.6 + file_saver: ^0.3.1 + font_awesome_flutter: ^10.12.0 + intl: any + json_schema: ^5.2.2 + package_info_plus: ^9.0.0 path_provider: ^2.1.5 provider: ^6.1.5 skeletonizer: ^2.1.0+1 - uuid: ^4.5.2 - file_picker: ^10.3.6 - json_schema: ^5.2.2 - file_saver: ^0.3.1 - clock: ^1.1.2 - intl: any - flutter_localizations: - sdk: flutter - package_info_plus: ^9.0.0 - font_awesome_flutter: ^10.12.0 url_launcher: ^6.3.2 + uuid: ^4.5.2 + dev_dependencies: - dart_pubspec_licenses: ^3.0.14 flutter_test: sdk: flutter - flutter_lints: ^5.0.0 - drift_dev: ^2.27.0 build_runner: ^2.5.4 + dart_pubspec_licenses: ^3.0.14 + drift_dev: ^2.27.0 + flutter_lints: ^5.0.0 flutter: uses-material-design: true From 9248284292090399bed1ca20db254624eaba162f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 11 Jan 2026 16:32:19 +0100 Subject: [PATCH 199/222] Removed double patterns --- .gitignore | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/.gitignore b/.gitignore index 1f715b9..6faa982 100644 --- a/.gitignore +++ b/.gitignore @@ -150,25 +150,9 @@ app.*.symbols .gclient_previous_custom_vars .gclient_previous_sync_commits -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.build/ -.buildlog/ -.history -.svn/ .swiftpm/ migrate_working_dir/ -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line @@ -176,17 +160,8 @@ migrate_working_dir/ #.vscode/ # Flutter/Dart/Pub related -**/doc/api/ -**/ios/Flutter/.last_build_id -.dart_tool/ -.flutter-plugins -.flutter-plugins-dependencies -.pub-cache/ -.pub/ /build/ -# Symbolication related -app.*.symbols # Obfuscation related app.*.map.json From d7f08c5f50d8be2a6beed373da5de5516591dfcc Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 11 Jan 2026 16:33:56 +0100 Subject: [PATCH 200/222] Updated license tile and adjusted settings tile accordingly --- .../licenses/license_detail_view.dart | 66 ++++++++++------ .../settings_view/licenses_view.dart | 2 +- .../settings_view/settings_view.dart | 12 +-- .../widgets/tiles/license_tile.dart | 75 ++++++++++++++++--- .../widgets/tiles/settings_list_tile.dart | 15 +++- pubspec.yaml | 2 +- 6 files changed, 131 insertions(+), 41 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart index 50d9d23..423c8cf 100644 --- a/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart +++ b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart @@ -22,30 +22,54 @@ class LicenseDetailView extends StatelessWidget { Center( child: Column( children: [ - Text( - package.name, - textAlign: TextAlign.center, - style: const TextStyle( - fontSize: 24, - fontWeight: FontWeight.bold, - ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + + children: [ + Container( + margin: const EdgeInsetsGeometry.only(right: 15), + width: 60, + height: 60, + decoration: BoxDecoration( + color: CustomTheme.primaryColor.withAlpha(40), + borderRadius: BorderRadius.circular(10), + ), + child: Icon( + Icons.description, + color: CustomTheme.primaryColor, + size: 30, + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + package.name, + textAlign: TextAlign.left, + style: const TextStyle( + height: 0, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + if (package.version != null) ...[ + Text( + 'Version ${package.version}', + textAlign: TextAlign.left, + style: TextStyle( + fontSize: 14, + color: Colors.grey.shade300, + ), + ), + ], + ], + ), + ], ), - if (package.version != null) ...[ - const SizedBox(height: 8), - Text( - 'Version ${package.version}', - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 14, - color: Colors.grey.shade300, - fontWeight: FontWeight.bold, - ), - ), - ], if (package.authors.isNotEmpty) ...[ const SizedBox(height: 8), - Text( + SelectableText( package.authors.join(', '), textAlign: TextAlign.center, style: TextStyle( @@ -57,7 +81,7 @@ class LicenseDetailView extends StatelessWidget { if (package.homepage != null && package.homepage!.isNotEmpty) ...[ const SizedBox(height: 4), - Text( + SelectableText( package.homepage!, textAlign: TextAlign.center, style: TextStyle( diff --git a/lib/presentation/views/main_menu/settings_view/licenses_view.dart b/lib/presentation/views/main_menu/settings_view/licenses_view.dart index 63ec7b2..603162f 100644 --- a/lib/presentation/views/main_menu/settings_view/licenses_view.dart +++ b/lib/presentation/views/main_menu/settings_view/licenses_view.dart @@ -22,7 +22,7 @@ class LicensesView extends StatelessWidget { itemBuilder: (context, index) { final package = allDependencies[index]; return Padding( - padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 12), + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8), child: LicenseTile(package: package), ); }, diff --git a/lib/presentation/views/main_menu/settings_view/settings_view.dart b/lib/presentation/views/main_menu/settings_view/settings_view.dart index c3fa850..2a5c7d2 100644 --- a/lib/presentation/views/main_menu/settings_view/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view/settings_view.dart @@ -45,7 +45,7 @@ class _SettingsViewState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.fromLTRB(24, 0, 24, 10), + padding: const EdgeInsets.only(left: 16, bottom: 10), child: Text( textAlign: TextAlign.start, loc.menu, @@ -56,7 +56,7 @@ class _SettingsViewState extends State { ), ), Padding( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 10), + padding: const EdgeInsets.only(left: 16, top: 10, bottom: 10), child: Text( textAlign: TextAlign.start, loc.settings, @@ -68,7 +68,7 @@ class _SettingsViewState extends State { ), SettingsListTile( title: loc.export_data, - icon: Icons.upload_rounded, + icon: Icons.upload, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { final String json = await DataTransferService.getAppDataAsJson( @@ -84,7 +84,7 @@ class _SettingsViewState extends State { ), SettingsListTile( title: loc.import_data, - icon: Icons.download_rounded, + icon: Icons.download, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () async { final result = await DataTransferService.importData(context); @@ -94,7 +94,7 @@ class _SettingsViewState extends State { ), SettingsListTile( title: loc.delete_all_data, - icon: Icons.delete_rounded, + icon: Icons.delete, suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), onPressed: () { showDialog( @@ -127,7 +127,7 @@ class _SettingsViewState extends State { }, ), const Padding( - padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10), + padding: EdgeInsets.only(left: 16, top: 10, bottom: 10), child: Text( textAlign: TextAlign.start, 'App', diff --git a/lib/presentation/widgets/tiles/license_tile.dart b/lib/presentation/widgets/tiles/license_tile.dart index 08aceba..a5d4f0c 100644 --- a/lib/presentation/widgets/tiles/license_tile.dart +++ b/lib/presentation/widgets/tiles/license_tile.dart @@ -19,32 +19,85 @@ class LicenseTile extends StatelessWidget { ); }, child: Container( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), - decoration: CustomTheme.standardBoxDecoration, + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.all(16), + decoration: CustomTheme.standardBoxDecoration.copyWith( + borderRadius: BorderRadius.circular(12), + ), child: Row( children: [ + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: CustomTheme.primaryColor.withAlpha(40), + borderRadius: BorderRadius.circular(10), + ), + child: Icon( + Icons.description, + color: CustomTheme.primaryColor, + size: 24, + ), + ), + const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - package.name, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - ), + Row( + children: [ + Flexible( + child: Text( + package.name, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + ), + ), + ), + if (package.version != null && + package.version!.isNotEmpty) ...[ + const SizedBox(width: 12), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 2, + ), + decoration: BoxDecoration( + color: CustomTheme.onBoxColor, + borderRadius: BorderRadius.circular(6), + ), + child: Text( + 'v${package.version}', + style: TextStyle( + fontSize: 11, + color: Colors.grey.shade500, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ], ), const SizedBox(height: 4), Text( package.description, - maxLines: 1, + maxLines: 2, overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: 12, color: Colors.grey.shade400), + style: TextStyle( + fontSize: 13, + color: Colors.grey.shade400, + height: 1.3, + ), ), ], ), ), - const Icon(Icons.arrow_forward_ios, size: 16), + const SizedBox(width: 12), + // Arrow Icon + Icon(Icons.chevron_right, color: Colors.grey.shade600, size: 24), ], ), ), diff --git a/lib/presentation/widgets/tiles/settings_list_tile.dart b/lib/presentation/widgets/tiles/settings_list_tile.dart index 7fb0f80..d54f13f 100644 --- a/lib/presentation/widgets/tiles/settings_list_tile.dart +++ b/lib/presentation/widgets/tiles/settings_list_tile.dart @@ -47,13 +47,26 @@ class SettingsListTile extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: CustomTheme.primaryColor.withAlpha(40), + borderRadius: BorderRadius.circular(10), + ), + child: Icon( + icon, + size: 28, + color: CustomTheme.primaryColor.withGreen(40), + ), + ), + /* Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: CustomTheme.primaryColor, shape: BoxShape.circle, ), child: Icon(icon, size: 24), - ), + ),*/ const SizedBox(width: 16), Text(title, style: const TextStyle(fontSize: 18)), ], diff --git a/pubspec.yaml b/pubspec.yaml index d1c9538..bb8181d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.4+122 +version: 0.0.5+127 environment: sdk: ^3.8.1 From e51bf3eabb6c04599dc990c8ae146f5f9d8678c8 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 11 Jan 2026 16:47:57 +0100 Subject: [PATCH 201/222] Updated spacing --- .../main_menu/settings_view/licenses/license_detail_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart index 423c8cf..812e43e 100644 --- a/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart +++ b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart @@ -80,7 +80,7 @@ class LicenseDetailView extends StatelessWidget { ], if (package.homepage != null && package.homepage!.isNotEmpty) ...[ - const SizedBox(height: 4), + const SizedBox(height: 8), SelectableText( package.homepage!, textAlign: TextAlign.center, @@ -93,7 +93,7 @@ class LicenseDetailView extends StatelessWidget { ], ), ), - const SizedBox(height: 24), + const SizedBox(height: 20), Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16), From 86982ada0f5ec6588c99790f0c5b7c3f2b22e6cf Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 11 Jan 2026 16:48:08 +0100 Subject: [PATCH 202/222] Updated license file --- .../settings_view/licenses/oss_licenses.dart | 66 ++++++++++++++----- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart b/lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart index 06f46a4..ef1109c 100644 --- a/lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart +++ b/lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart @@ -38,6 +38,7 @@ const allDependencies = [ _cross_file, _crypto, _csslib, + _cupertino_icons, _dart_pubspec_licenses, _dart_style, _dbus, @@ -153,28 +154,29 @@ const allDependencies = [ /// Direct `dependencies`. const dependencies = [ _flutter, + _clock, + _cupertino_icons, _drift, _drift_flutter, + _file_picker, + _file_saver, + _font_awesome_flutter, + _intl, + _json_schema, + _package_info_plus, _path_provider, _provider, _skeletonizer, - _uuid, - _file_picker, - _json_schema, - _file_saver, - _clock, - _intl, - _package_info_plus, - _font_awesome_flutter, - _url_launcher + _url_launcher, + _uuid ]; /// Direct `dev_dependencies`. const devDependencies = [ + _build_runner, _dart_pubspec_licenses, - _flutter_lints, _drift_dev, - _build_runner + _flutter_lints ]; /// Package license definition. @@ -1468,6 +1470,40 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); +/// cupertino_icons 1.0.8 +const _cupertino_icons = Package( + name: 'cupertino_icons', + description: 'Default icons asset for Cupertino widgets based on Apple styled icons', + repository: 'https://github.com/flutter/packages/tree/main/third_party/packages/cupertino_icons', + authors: [], + version: '1.0.8', + spdxIdentifiers: ['MIT'], + isMarkdown: false, + isSdk: false, + dependencies: [], + devDependencies: [PackageRef('flutter')], + license: '''The MIT License (MIT) + +Copyright (c) 2016 Vladimir Kharlampidi + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', + ); + /// dart_pubspec_licenses 3.0.15 const _dart_pubspec_licenses = Package( name: 'dart_pubspec_licenses', @@ -7255,16 +7291,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', ); -/// game_tracker 0.0.4+111 +/// game_tracker 0.0.5+127 const _game_tracker = Package( name: 'game_tracker', description: 'Game Tracking App for Card Games', authors: [], - version: '0.0.4+111', + version: '0.0.5+127', spdxIdentifiers: [], isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('drift'), PackageRef('drift_flutter'), PackageRef('path_provider'), PackageRef('provider'), PackageRef('skeletonizer'), PackageRef('uuid'), PackageRef('file_picker'), PackageRef('json_schema'), PackageRef('file_saver'), PackageRef('clock'), PackageRef('intl'), PackageRef('package_info_plus'), PackageRef('font_awesome_flutter'), PackageRef('url_launcher')], - devDependencies: [PackageRef('dart_pubspec_licenses'), PackageRef('flutter_lints'), PackageRef('drift_dev'), PackageRef('build_runner')], + dependencies: [PackageRef('flutter'), PackageRef('clock'), PackageRef('cupertino_icons'), PackageRef('drift'), PackageRef('drift_flutter'), PackageRef('file_picker'), PackageRef('file_saver'), PackageRef('font_awesome_flutter'), PackageRef('intl'), PackageRef('json_schema'), PackageRef('package_info_plus'), PackageRef('path_provider'), PackageRef('provider'), PackageRef('skeletonizer'), PackageRef('url_launcher'), PackageRef('uuid')], + devDependencies: [PackageRef('build_runner'), PackageRef('dart_pubspec_licenses'), PackageRef('drift_dev'), PackageRef('flutter_lints')], ); From 857e05127dd5ef94b8308c9a4271bcf47b79afb4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 11 Jan 2026 16:55:28 +0100 Subject: [PATCH 203/222] Updated arb files --- lib/l10n/arb/app_de.arb | 5 ++-- lib/l10n/arb/app_en.arb | 20 ++++++------- lib/l10n/generated/app_localizations.dart | 30 ++++++++------------ lib/l10n/generated/app_localizations_de.dart | 15 ++++------ lib/l10n/generated/app_localizations_en.dart | 15 ++++------ pubspec.yaml | 2 +- 6 files changed, 35 insertions(+), 52 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 58870c5..407a5c4 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -35,9 +35,6 @@ "info": "Info", "invalid_schema": "Ungültiges Schema", "licenses": "Lizenzen", - "no_licenses_found": "Keine Lizenzen gefunden", - "no_license_text_available": "Kein Lizenztext verfügbar", - "error": "Fehler", "least_points": "Niedrigste Punkte", "match_in_progress": "Spiel läuft...", "match_name": "Spieltitel", @@ -46,6 +43,8 @@ "most_points": "Höchste Punkte", "no_data_available": "Keine Daten verfügbar", "no_groups_created_yet": "Noch keine Gruppen erstellt", + "no_licenses_found": "Keine Lizenzen gefunden", + "no_license_text_available": "Kein Lizenztext verfügbar", "no_matches_created_yet": "Noch keine Spiele erstellt", "no_players_created_yet": "Noch keine Spieler:in erstellt", "no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 309893c..a9c9e9a 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -110,15 +110,6 @@ "@licenses": { "description": "Licenses menu item" }, - "@no_licenses_found": { - "description": "Message when no licenses are found" - }, - "@no_license_text_available": { - "description": "Message when no license text is available" - }, - "@error": { - "description": "Error label" - }, "@least_points": { "description": "Title for least points ruleset" }, @@ -143,6 +134,12 @@ "@no_groups_created_yet": { "description": "Message when no groups exist" }, + "@no_licenses_found": { + "description": "Message when no licenses are found" + }, + "@no_license_text_available": { + "description": "Message when no license text is available" + }, "@no_matches_created_yet": { "description": "Message when no matches exist" }, @@ -306,9 +303,6 @@ "info": "Info", "invalid_schema": "Invalid Schema", "licenses": "Licenses", - "no_licenses_found": "No licenses found", - "no_license_text_available": "No license text available", - "error": "Error", "least_points": "Least Points", "match_in_progress": "Match in progress...", "match_name": "Match name", @@ -317,6 +311,8 @@ "most_points": "Most Points", "no_data_available": "No data available", "no_groups_created_yet": "No groups created yet", + "no_licenses_found": "No licenses found", + "no_license_text_available": "No license text available", "no_matches_created_yet": "No matches created yet", "no_players_created_yet": "No players created yet", "no_players_found_with_that_name": "No players found with that name", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index d91be37..f1399ab 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -308,24 +308,6 @@ abstract class AppLocalizations { /// **'Licenses'** String get licenses; - /// Message when no licenses are found - /// - /// In en, this message translates to: - /// **'No licenses found'** - String get no_licenses_found; - - /// Message when no license text is available - /// - /// In en, this message translates to: - /// **'No license text available'** - String get no_license_text_available; - - /// Error label - /// - /// In en, this message translates to: - /// **'Error'** - String get error; - /// Title for least points ruleset /// /// In en, this message translates to: @@ -374,6 +356,18 @@ abstract class AppLocalizations { /// **'No groups created yet'** String get no_groups_created_yet; + /// Message when no licenses are found + /// + /// In en, this message translates to: + /// **'No licenses found'** + String get no_licenses_found; + + /// Message when no license text is available + /// + /// In en, this message translates to: + /// **'No license text available'** + String get no_license_text_available; + /// Message when no matches exist /// /// In en, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index fa3010d..9ee0cb4 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -118,15 +118,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get licenses => 'Lizenzen'; - @override - String get no_licenses_found => 'Keine Lizenzen gefunden'; - - @override - String get no_license_text_available => 'Kein Lizenztext verfügbar'; - - @override - String get error => 'Fehler'; - @override String get least_points => 'Niedrigste Punkte'; @@ -151,6 +142,12 @@ class AppLocalizationsDe extends AppLocalizations { @override String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; + @override + String get no_licenses_found => 'Keine Lizenzen gefunden'; + + @override + String get no_license_text_available => 'Kein Lizenztext verfügbar'; + @override String get no_matches_created_yet => 'Noch keine Spiele erstellt'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index d6bfa25..59b7e3d 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -118,15 +118,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get licenses => 'Licenses'; - @override - String get no_licenses_found => 'No licenses found'; - - @override - String get no_license_text_available => 'No license text available'; - - @override - String get error => 'Error'; - @override String get least_points => 'Least Points'; @@ -151,6 +142,12 @@ class AppLocalizationsEn extends AppLocalizations { @override String get no_groups_created_yet => 'No groups created yet'; + @override + String get no_licenses_found => 'No licenses found'; + + @override + String get no_license_text_available => 'No license text available'; + @override String get no_matches_created_yet => 'No matches created yet'; diff --git a/pubspec.yaml b/pubspec.yaml index bb8181d..cbade29 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.5+127 +version: 0.0.5+128 environment: sdk: ^3.8.1 From 7fc4bbfb133230c662eadf41246539252ec29f42 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 11 Jan 2026 17:09:05 +0100 Subject: [PATCH 204/222] Added placeholder setting tiles for legal --- lib/l10n/arb/app_de.arb | 7 +- lib/l10n/arb/app_en.arb | 28 +- lib/l10n/generated/app_localizations.dart | 42 ++- lib/l10n/generated/app_localizations_de.dart | 19 +- lib/l10n/generated/app_localizations_en.dart | 19 +- .../settings_view/settings_view.dart | 339 ++++++++++-------- pubspec.yaml | 2 +- 7 files changed, 265 insertions(+), 191 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 407a5c4..3a2685e 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -13,6 +13,7 @@ "create_match": "Spiel erstellen", "create_new_group": "Neue Gruppe erstellen", "create_new_match": "Neues Spiel erstellen", + "data": "Daten", "data_successfully_deleted": "Daten erfolgreich gelöscht", "data_successfully_exported": "Daten erfolgreich exportiert", "data_successfully_imported": "Daten erfolgreich importiert", @@ -34,8 +35,10 @@ "import_data": "Daten importieren", "info": "Info", "invalid_schema": "Ungültiges Schema", - "licenses": "Lizenzen", "least_points": "Niedrigste Punkte", + "legal": "Rechtliches", + "legal_notice": "Impressum", + "licenses": "Lizenzen", "match_in_progress": "Spiel läuft...", "match_name": "Spieltitel", "matches": "Spiele", @@ -58,6 +61,7 @@ "player_name": "Spieler:innenname", "players": "Spieler:innen", "players_count": "{count} Spieler", + "privacy_policy": "Datenschutzerklärung", "quick_create": "Schnellzugriff", "recent_matches": "Letzte Spiele", "ruleset": "Regelwerk", @@ -69,7 +73,6 @@ "search_for_players": "Nach Spieler:innen suchen", "select_winner": "Gewinner:in wählen:", "selected_players": "Ausgewählte Spieler:innen", - "settings": "Einstellungen", "single_loser": "Ein:e Verlierer:in", "single_winner": "Ein:e Gewinner:in", "statistics": "Statistiken", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index a9c9e9a..c080532 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -39,6 +39,9 @@ "@create_new_match": { "description": "Button text to create a new match" }, + "@data": { + "description": "Data label" + }, "@data_successfully_deleted": { "description": "Success message after deleting data" }, @@ -107,12 +110,18 @@ "@invalid_schema": { "description": "Error message for invalid schema" }, - "@licenses": { - "description": "Licenses menu item" - }, "@least_points": { "description": "Title for least points ruleset" }, + "@legal": { + "description": "Legal section header" + }, + "@legal_notice": { + "description": "Legal notice menu item" + }, + "@licenses": { + "description": "Licenses menu item" + }, "@match_in_progress": { "description": "Message when match is in progress" }, @@ -184,6 +193,9 @@ } } }, + "@privacy_policy": { + "description": "Privacy policy menu item" + }, "@quick_create": { "description": "Title for quick create section" }, @@ -217,9 +229,6 @@ "@selected_players": { "description": "Shows the number of selected players" }, - "@settings": { - "description": "Settings label" - }, "@single_loser": { "description": "Title for single loser ruleset" }, @@ -281,6 +290,7 @@ "create_match": "Create match", "create_new_group": "Create new group", "create_new_match": "Create new match", + "data": "Data", "data_successfully_deleted": "Data successfully deleted", "data_successfully_exported": "Data successfully exported", "data_successfully_imported": "Data successfully imported", @@ -302,8 +312,10 @@ "import_data": "Import data", "info": "Info", "invalid_schema": "Invalid Schema", - "licenses": "Licenses", "least_points": "Least Points", + "legal": "Legal", + "legal_notice": "Legal Notice", + "licenses": "Licenses", "match_in_progress": "Match in progress...", "match_name": "Match name", "matches": "Matches", @@ -326,6 +338,7 @@ "player_name": "Player name", "players": "Players", "players_count": "{count} Players", + "privacy_policy": "Privacy Policy", "quick_create": "Quick Create", "recent_matches": "Recent Matches", "ruleset": "Ruleset", @@ -337,7 +350,6 @@ "search_for_players": "Search for players", "select_winner": "Select Winner:", "selected_players": "Selected players", - "settings": "Settings", "single_loser": "Single Loser", "single_winner": "Single Winner", "statistics": "Statistics", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index f1399ab..30aa685 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -176,6 +176,12 @@ abstract class AppLocalizations { /// **'Create new match'** String get create_new_match; + /// Data label + /// + /// In en, this message translates to: + /// **'Data'** + String get data; + /// Success message after deleting data /// /// In en, this message translates to: @@ -302,18 +308,30 @@ abstract class AppLocalizations { /// **'Invalid Schema'** String get invalid_schema; - /// Licenses menu item - /// - /// In en, this message translates to: - /// **'Licenses'** - String get licenses; - /// Title for least points ruleset /// /// In en, this message translates to: /// **'Least Points'** String get least_points; + /// Legal section header + /// + /// In en, this message translates to: + /// **'Legal'** + String get legal; + + /// Legal notice menu item + /// + /// In en, this message translates to: + /// **'Legal Notice'** + String get legal_notice; + + /// Licenses menu item + /// + /// In en, this message translates to: + /// **'Licenses'** + String get licenses; + /// Message when match is in progress /// /// In en, this message translates to: @@ -446,6 +464,12 @@ abstract class AppLocalizations { /// **'{count} Players'** String players_count(int count); + /// Privacy policy menu item + /// + /// In en, this message translates to: + /// **'Privacy Policy'** + String get privacy_policy; + /// Title for quick create section /// /// In en, this message translates to: @@ -512,12 +536,6 @@ abstract class AppLocalizations { /// **'Selected players'** String get selected_players; - /// Settings label - /// - /// In en, this message translates to: - /// **'Settings'** - String get settings; - /// Title for single loser ruleset /// /// In en, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 9ee0cb4..def4859 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -49,6 +49,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get create_new_match => 'Neues Spiel erstellen'; + @override + String get data => 'Daten'; + @override String get data_successfully_deleted => 'Daten erfolgreich gelöscht'; @@ -116,10 +119,16 @@ class AppLocalizationsDe extends AppLocalizations { String get invalid_schema => 'Ungültiges Schema'; @override - String get licenses => 'Lizenzen'; + String get least_points => 'Niedrigste Punkte'; @override - String get least_points => 'Niedrigste Punkte'; + String get legal => 'Rechtliches'; + + @override + String get legal_notice => 'Impressum'; + + @override + String get licenses => 'Lizenzen'; @override String get match_in_progress => 'Spiel läuft...'; @@ -190,6 +199,9 @@ class AppLocalizationsDe extends AppLocalizations { return '$count Spieler'; } + @override + String get privacy_policy => 'Datenschutzerklärung'; + @override String get quick_create => 'Schnellzugriff'; @@ -227,9 +239,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get selected_players => 'Ausgewählte Spieler:innen'; - @override - String get settings => 'Einstellungen'; - @override String get single_loser => 'Ein:e Verlierer:in'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 59b7e3d..7419e24 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -49,6 +49,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get create_new_match => 'Create new match'; + @override + String get data => 'Data'; + @override String get data_successfully_deleted => 'Data successfully deleted'; @@ -116,10 +119,16 @@ class AppLocalizationsEn extends AppLocalizations { String get invalid_schema => 'Invalid Schema'; @override - String get licenses => 'Licenses'; + String get least_points => 'Least Points'; @override - String get least_points => 'Least Points'; + String get legal => 'Legal'; + + @override + String get legal_notice => 'Legal Notice'; + + @override + String get licenses => 'Licenses'; @override String get match_in_progress => 'Match in progress...'; @@ -190,6 +199,9 @@ class AppLocalizationsEn extends AppLocalizations { return '$count Players'; } + @override + String get privacy_policy => 'Privacy Policy'; + @override String get quick_create => 'Quick Create'; @@ -227,9 +239,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get selected_players => 'Selected players'; - @override - String get settings => 'Settings'; - @override String get single_loser => 'Single Loser'; diff --git a/lib/presentation/views/main_menu/settings_view/settings_view.dart b/lib/presentation/views/main_menu/settings_view/settings_view.dart index 2a5c7d2..3e1df91 100644 --- a/lib/presentation/views/main_menu/settings_view/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view/settings_view.dart @@ -40,172 +40,194 @@ class _SettingsViewState extends State { child: Scaffold( appBar: AppBar(backgroundColor: CustomTheme.backgroundColor), backgroundColor: CustomTheme.backgroundColor, - body: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(left: 16, bottom: 10), - child: Text( - textAlign: TextAlign.start, - loc.menu, - style: const TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - ), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 16, top: 10, bottom: 10), - child: Text( - textAlign: TextAlign.start, - loc.settings, - style: const TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - ), - ), - ), - SettingsListTile( - title: loc.export_data, - icon: Icons.upload, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () async { - final String json = await DataTransferService.getAppDataAsJson( - context, - ); - final result = await DataTransferService.exportData( - json, - 'game_tracker-data', - ); - if (!context.mounted) return; - showExportSnackBar(context: context, result: result); - }, - ), - SettingsListTile( - title: loc.import_data, - icon: Icons.download, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () async { - final result = await DataTransferService.importData(context); - if (!context.mounted) return; - showImportSnackBar(context: context, result: result); - }, - ), - SettingsListTile( - title: loc.delete_all_data, - icon: Icons.delete, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text('${loc.delete_all_data}?'), - content: Text(loc.this_cannot_be_undone), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(false), - child: Text(loc.cancel), - ), - TextButton( - onPressed: () => Navigator.of(context).pop(true), - child: Text(loc.delete), - ), - ], + body: SingleChildScrollView( + child: Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 16, bottom: 10), + child: Text( + textAlign: TextAlign.start, + loc.menu, + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + ), ), - ).then((confirmed) { - if (confirmed == true && context.mounted) { - DataTransferService.deleteAllData(context); - showSnackbar( - context: context, - message: AppLocalizations.of( - context, - ).data_successfully_deleted, + ), + Padding( + padding: const EdgeInsets.only(left: 16, top: 10, bottom: 10), + child: Text( + textAlign: TextAlign.start, + loc.data, + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + ), + ), + ), + SettingsListTile( + title: loc.export_data, + icon: Icons.upload, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () async { + final String json = + await DataTransferService.getAppDataAsJson(context); + final result = await DataTransferService.exportData( + json, + 'game_tracker-data', ); - } - }); - }, - ), - const Padding( - padding: EdgeInsets.only(left: 16, top: 10, bottom: 10), - child: Text( - textAlign: TextAlign.start, - 'App', - style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold), - ), - ), - SettingsListTile( - title: loc.licenses, - icon: Icons.insert_drive_file, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute(builder: (context) => const LicensesView()), - ); - }, - ), - const Spacer(), - Padding( - padding: const EdgeInsets.all(20), - child: Center( - child: Column( - spacing: 4, - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - spacing: 40, - children: [ - GestureDetector( - child: const Icon(Icons.language), - onTap: () => { - launchUrl(Uri.parse('https://liquid-dev.de')), - }, + if (!context.mounted) return; + showExportSnackBar(context: context, result: result); + }, + ), + SettingsListTile( + title: loc.import_data, + icon: Icons.download, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () async { + final result = await DataTransferService.importData( + context, + ); + if (!context.mounted) return; + showImportSnackBar(context: context, result: result); + }, + ), + SettingsListTile( + title: loc.delete_all_data, + icon: Icons.delete, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('${loc.delete_all_data}?'), + content: Text(loc.this_cannot_be_undone), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text(loc.cancel), ), - GestureDetector( - child: const FaIcon(FontAwesomeIcons.github), - onTap: () => { - launchUrl( - Uri.parse( - 'https://github.com/liquiddevelopmentde', - ), - ), - }, - ), - GestureDetector( - child: Icon( - Platform.isIOS - ? CupertinoIcons.mail_solid - : Icons.email, - ), - onTap: () => - launchUrl(Uri.parse('mailto:hi@liquid-dev.de')), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text(loc.delete), ), ], ), - ), - Text( - '© ${DateFormat('yyyy').format(DateTime.now())} Liquid Development', - style: TextStyle( - color: Colors.grey.shade600, - fontSize: 14, - fontWeight: FontWeight.w600, - ), - ), - Text( - 'Version ${_packageInfo.version} (${_packageInfo.buildNumber})', - style: TextStyle( - color: Colors.grey.shade600, - fontSize: 14, - fontWeight: FontWeight.w600, - ), - ), - ], + ).then((confirmed) { + if (confirmed == true && context.mounted) { + DataTransferService.deleteAllData(context); + showSnackbar( + context: context, + message: AppLocalizations.of( + context, + ).data_successfully_deleted, + ); + } + }); + }, ), - ), + Padding( + padding: const EdgeInsets.only(left: 16, top: 10, bottom: 10), + child: Text( + textAlign: TextAlign.start, + loc.legal, + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + ), + ), + ), + SettingsListTile( + title: loc.licenses, + icon: Icons.insert_drive_file, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const LicensesView(), + ), + ); + }, + ), + SettingsListTile( + title: loc.legal_notice, + icon: Icons.account_balance_sharp, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: null, + ), + SettingsListTile( + title: loc.privacy_policy, + icon: Icons.gpp_good_rounded, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: null, + ), + Padding( + padding: const EdgeInsets.only(top: 30, bottom: 20), + child: Center( + child: Column( + spacing: 4, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 40, + children: [ + GestureDetector( + child: const Icon(Icons.language), + onTap: () => { + launchUrl(Uri.parse('https://liquid-dev.de')), + }, + ), + GestureDetector( + child: const FaIcon(FontAwesomeIcons.github), + onTap: () => { + launchUrl( + Uri.parse( + 'https://github.com/liquiddevelopmentde', + ), + ), + }, + ), + GestureDetector( + child: Icon( + Platform.isIOS + ? CupertinoIcons.mail_solid + : Icons.email, + ), + onTap: () => launchUrl( + Uri.parse('mailto:hi@liquid-dev.de'), + ), + ), + ], + ), + ), + Text( + '© ${DateFormat('yyyy').format(DateTime.now())} Liquid Development', + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + Text( + 'Version ${_packageInfo.version} (${_packageInfo.buildNumber})', + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ], ), - ], + ), ), ), ); @@ -282,6 +304,7 @@ class _SettingsViewState extends State { ); } + /// Initializes the package information. Future _initPackageInfo() async { final info = await PackageInfo.fromPlatform(); setState(() { diff --git a/pubspec.yaml b/pubspec.yaml index cbade29..d15ae4e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.5+128 +version: 0.0.5+130 environment: sdk: ^3.8.1 From 758f1e6c3a72c7a1f66650c9e01fb9ecb77c0ab5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 11 Jan 2026 16:14:43 +0000 Subject: [PATCH 205/222] .gitea/PULL_REQUEST_TEMPLATE.md aktualisiert --- .gitea/PULL_REQUEST_TEMPLATE.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitea/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md index 7ae0f8a..63f6ad7 100644 --- a/.gitea/PULL_REQUEST_TEMPLATE.md +++ b/.gitea/PULL_REQUEST_TEMPLATE.md @@ -7,10 +7,10 @@ Closes `` *Eine klare und prägnante Übersicht über die vorgenommenen Änderungen. Erläutere nicht nur das was gemacht wurde, sondern auch warum.* ## Änderungen -- [ ] Neue Funktion X hinzugefügt -- [ ] Bug in Komponente Y behoben -- [ ] Modul Z für bessere Leistung refactored -- [ ] Dependencies aktualisiert +- Neue Funktion X hinzugefügt +- Bug in Komponente Y behoben +- Modul Z für bessere Leistung refactored +- Dependencies aktualisiert ## Zusätzliche Anmerkungen *Gibt es zusätzlichen Kontext, Einschränkungen oder Informationen, die Reviewer wissen sollten?* From 485ac87fdb83f158d86adb3831b86c3283fa7f71 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 11 Jan 2026 19:41:45 +0100 Subject: [PATCH 206/222] Added new yaml issue templates --- .gitea/ISSUE_TEMPLATE/BUG_REPORT.md | 35 ----------------- .gitea/ISSUE_TEMPLATE/ENHANCEMENT.md | 22 ----------- .gitea/ISSUE_TEMPLATE/FEATURE.md | 19 --------- .gitea/ISSUE_TEMPLATE/enhancement.yaml | 36 +++++++++++++++++ .gitea/issue_template/bug.yaml | 53 ++++++++++++++++++++++++++ .gitea/issue_template/feature.yaml | 36 +++++++++++++++++ 6 files changed, 125 insertions(+), 76 deletions(-) delete mode 100644 .gitea/ISSUE_TEMPLATE/BUG_REPORT.md delete mode 100644 .gitea/ISSUE_TEMPLATE/ENHANCEMENT.md delete mode 100644 .gitea/ISSUE_TEMPLATE/FEATURE.md create mode 100644 .gitea/ISSUE_TEMPLATE/enhancement.yaml create mode 100644 .gitea/issue_template/bug.yaml create mode 100644 .gitea/issue_template/feature.yaml diff --git a/.gitea/ISSUE_TEMPLATE/BUG_REPORT.md b/.gitea/ISSUE_TEMPLATE/BUG_REPORT.md deleted file mode 100644 index 10f8648..0000000 --- a/.gitea/ISSUE_TEMPLATE/BUG_REPORT.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: Bug report -about: Erstelle eine Meldung für etwas, das nicht Funktioniert, wie es soll. -title: '' -labels: 'Task/Bug' -assignees: '' - ---- - -# Bug Report - -## Beschreibung -[Eine klare und prägnante Beschreibung des Bugs] - -## Schritte zur Reproduktion -1. Schritt 1 -2. Schritt 2 -3. ... - -## Erwartetes Verhalten -[Was hätte passieren sollen] - -## Tatsächliches Verhalten -[Was tatsächlich passiert ist] - -## Screenshots/Protokolle -[Falls zutreffend, füge Screenshots, Error Logs oder Stack Traces hinzu] - -## Umgebung -- Plattform: Android, iOS, Web -- OS: [z. B. iOS 18.5, Android 14] -- Flutter Version: [z.B. 3.35.6] - -## Verwandte Issues -[Verweisen Sie auf ähnliche Issues oder PRs] \ No newline at end of file diff --git a/.gitea/ISSUE_TEMPLATE/ENHANCEMENT.md b/.gitea/ISSUE_TEMPLATE/ENHANCEMENT.md deleted file mode 100644 index 49442d1..0000000 --- a/.gitea/ISSUE_TEMPLATE/ENHANCEMENT.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -name: Enhancement -about: Enhancements for current features -title: '' -labels: 'Task\Enhancement' -assignees: '' - ---- - -# Enhancement - -## Aktuelles Verhalten -[Beschreibe die bestehende Funktionalität] - -## Einschränkungen/Probleme -[Was sind die aktuellen Mängel?] - -## Vorgeschlagene Verbesserung -[Wie kann das Problem bzw. die Einschränkung verbessert werden?] - -## Zugehörige Issues -[Links zu verwandten oder blockierenden Issues] \ No newline at end of file diff --git a/.gitea/ISSUE_TEMPLATE/FEATURE.md b/.gitea/ISSUE_TEMPLATE/FEATURE.md deleted file mode 100644 index 01e7a5f..0000000 --- a/.gitea/ISSUE_TEMPLATE/FEATURE.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Feature -about: Neues Feature für die App -title: '' -labels: 'Task\Feature' -assignees: '' - ---- - -# Feature - -## Beschreibung -[Ausführliche Erläuterung der vorgeschlagenen Funktion] - -## Vorgeschlagene Lösung -[Beschreibe, wie die Feature funktionieren soll] - -## Zugehörige Issues -[Links zu verwandten oder blockierenden Issues] \ No newline at end of file diff --git a/.gitea/ISSUE_TEMPLATE/enhancement.yaml b/.gitea/ISSUE_TEMPLATE/enhancement.yaml new file mode 100644 index 0000000..34c3551 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/enhancement.yaml @@ -0,0 +1,36 @@ +name: Enhancement +about: Erstelle ein Enhancement-Ticket +labels: 'Task/Enhancement' +title: '' +body: + - type: textarea + id: description + attributes: + label: Aktuelles Verhalten + description: Beschreibe, wie die Funktionalität aktuell gestaltet ist + placeholder: | + - Aktuell macht Button X folgendes ... + - Das Problem ist, dass ... + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Vorgeschlagene Verbesserung + description: Beschreibe, wie das Problem bzw. die Einschränkung verbessert werden kann + placeholder: | + - Button X ändern, sodass ... + - Funktion X so erweitern, dass ... + - Design anpassen, sodass ... + validations: + required: true + + - type: textarea + attributes: + label: Zugehörige Issues + description: Links zu verwandten oder blockierenden Issues + placeholder: | + - Knüpft an Issue #35 an + - Ersetzt Issue #12 + - Brauch Implementierung von #43 \ No newline at end of file diff --git a/.gitea/issue_template/bug.yaml b/.gitea/issue_template/bug.yaml new file mode 100644 index 0000000..b7b28d1 --- /dev/null +++ b/.gitea/issue_template/bug.yaml @@ -0,0 +1,53 @@ +name: Bug Report +about: Erstelle eine Bug Report +labels: 'Task/Bug' +title: '' +body: + - type: textarea + id: description + attributes: + label: Beschreibung + description: Beschreibe klar und pregnant das Fehlerverhalten + placeholder: | + - Welchen Zweck erfüllt das Feature? + - Welches Problem löst das Feature? + - Wer profitiert davon? + - Warum ist es wichtig? + validations: + required: true + + - type: textarea + id: reproduce + attributes: + label: Schritte zur Reproduktion + description: Beschreibe, wie der Fehler reproduziert werden kann + placeholder: | + - 1. Schritt 1 + - 2. Schritt 2 + - 3. ... + + - type: dropdown + id: enviroment + attributes: + label: Umgebung + description: Gebe an, auf welchen Platformen dieser Fehler auftritt + list: false + multiple: true + options: ['Android', 'iOS', 'Web'] + + - type: textarea + id: bahaviour + attributes: + label: Unerwünschtes Verhalten + description: Beschreibe, was passiert ist, obwohl es nicht passieren sollte + placeholder: Bei Verhalten X tritt folgendes Verhalten auf ... + + + - type: textarea + attributes: + label: Verwandte Issues + description: Verweise auf ähnliche Issues oder PRs + placeholder: | + - Knüpft an Issue #35 an + - Ersetzt Issue #12 + - Brauch Implementierung von #43 \ No newline at end of file diff --git a/.gitea/issue_template/feature.yaml b/.gitea/issue_template/feature.yaml new file mode 100644 index 0000000..ae3b592 --- /dev/null +++ b/.gitea/issue_template/feature.yaml @@ -0,0 +1,36 @@ +name: Feature +about: Erstelle ein Feature-Ticket +labels: 'Task/Feature' +title: '' +body: + - type: textarea + id: description + attributes: + label: Beschreibung + description: Ausführliche Erläuterung der vorgeschlagenen Funktion + placeholder: | + - Welchen Zweck erfüllt das Feature? + - Welches Problem löst das Feature? + - Wer profitiert davon? + - Warum ist es wichtig? + validations: + required: true + + - type: textarea + id: solution + attributes: + label: Vorgeschlagene Lösung + description: Beschreibe, wie das Feature funktionieren soll + placeholder: | + - Neues Widget, das folgendermaßen aussieht ... + - Neue Ansicht, die folgende Inhalte hat + - Neue Funktionsweise von Komponente XY + + - type: textarea + attributes: + label: Zugehörige Issues + description: Links zu verwandten oder blockierenden Issues + placeholder: | + - Knüpft an Issue #35 an + - Ersetzt Issue #12 + - Brauch Implementierung von #43 \ No newline at end of file From 9d3a45c01d51040bd3fd4da3994d5e67c6cf2e13 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 11 Jan 2026 19:56:04 +0100 Subject: [PATCH 207/222] Updated PR Template --- .gitea/PULL_REQUEST_TEMPLATE.md | 16 ----------- .gitea/pull_request_template.yaml | 47 +++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 16 deletions(-) delete mode 100644 .gitea/PULL_REQUEST_TEMPLATE.md create mode 100644 .gitea/pull_request_template.yaml diff --git a/.gitea/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md deleted file mode 100644 index 63f6ad7..0000000 --- a/.gitea/PULL_REQUEST_TEMPLATE.md +++ /dev/null @@ -1,16 +0,0 @@ -# [PR Titel] - -**Zugehörige Issue(s):** -Closes `` - -## Beschreibung -*Eine klare und prägnante Übersicht über die vorgenommenen Änderungen. Erläutere nicht nur das was gemacht wurde, sondern auch warum.* - -## Änderungen -- Neue Funktion X hinzugefügt -- Bug in Komponente Y behoben -- Modul Z für bessere Leistung refactored -- Dependencies aktualisiert - -## Zusätzliche Anmerkungen -*Gibt es zusätzlichen Kontext, Einschränkungen oder Informationen, die Reviewer wissen sollten?* diff --git a/.gitea/pull_request_template.yaml b/.gitea/pull_request_template.yaml new file mode 100644 index 0000000..f5f9642 --- /dev/null +++ b/.gitea/pull_request_template.yaml @@ -0,0 +1,47 @@ +name: Pull Request +about: Vorlage für Pull Requests +title: "WIP: [Name des Issues]" + +body: + - type: input + id: related_issue + attributes: + label: Zugehörige Issue(s) + description: Issues welche mit diesem Pull Request geschlossen werden sollen + placeholder: "Closes #123" + validations: + required: true + + - type: textarea + id: description + attributes: + label: Beschreibung + description: | + Eine klare und prägnante Zusammenfassung aller vorgenommenen Änderungen. + placeholder: | + Was wurde geändert? + validations: + required: true + + - type: textarea + id: changes + attributes: + label: Änderungen + description: Liste alle Änderungen detailiert auf, die in diesem Pull Request vorgenommen wurden. + placeholder: | + - Neue Funktion X hinzugefügt + - Bug in Komponente X behoben + - Modul X für bessere Leistung refactored + - Dependencies aktualisiert + + - type: textarea + id: additional_notes + attributes: + label: Zusätzliche Anmerkungen + description: | + Gibt es zusätzlichen Kontext, Einschränkungen oder Informationen, + die Reviewer wissen sollten? + placeholder: | + - Bekannte Einschränkungen + - Offene TODOs + - Hinweise für Reviewer From 5a5898787fe933a83b9601620263441b394905cc Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 11 Jan 2026 20:00:38 +0100 Subject: [PATCH 208/222] Folder fix 1/2 --- .gitea/{issue_template => issue_template1}/bug.yaml | 0 .gitea/{ISSUE_TEMPLATE => issue_template1}/enhancement.yaml | 0 .gitea/{issue_template => issue_template1}/feature.yaml | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename .gitea/{issue_template => issue_template1}/bug.yaml (100%) rename .gitea/{ISSUE_TEMPLATE => issue_template1}/enhancement.yaml (100%) rename .gitea/{issue_template => issue_template1}/feature.yaml (100%) diff --git a/.gitea/issue_template/bug.yaml b/.gitea/issue_template1/bug.yaml similarity index 100% rename from .gitea/issue_template/bug.yaml rename to .gitea/issue_template1/bug.yaml diff --git a/.gitea/ISSUE_TEMPLATE/enhancement.yaml b/.gitea/issue_template1/enhancement.yaml similarity index 100% rename from .gitea/ISSUE_TEMPLATE/enhancement.yaml rename to .gitea/issue_template1/enhancement.yaml diff --git a/.gitea/issue_template/feature.yaml b/.gitea/issue_template1/feature.yaml similarity index 100% rename from .gitea/issue_template/feature.yaml rename to .gitea/issue_template1/feature.yaml From c157644b4467b06d1d4422c1cd46dae4a9213747 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 11 Jan 2026 20:00:51 +0100 Subject: [PATCH 209/222] Folder fix 2/2 --- .gitea/{issue_template1 => issue_template}/bug.yaml | 0 .gitea/{issue_template1 => issue_template}/enhancement.yaml | 0 .gitea/{issue_template1 => issue_template}/feature.yaml | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename .gitea/{issue_template1 => issue_template}/bug.yaml (100%) rename .gitea/{issue_template1 => issue_template}/enhancement.yaml (100%) rename .gitea/{issue_template1 => issue_template}/feature.yaml (100%) diff --git a/.gitea/issue_template1/bug.yaml b/.gitea/issue_template/bug.yaml similarity index 100% rename from .gitea/issue_template1/bug.yaml rename to .gitea/issue_template/bug.yaml diff --git a/.gitea/issue_template1/enhancement.yaml b/.gitea/issue_template/enhancement.yaml similarity index 100% rename from .gitea/issue_template1/enhancement.yaml rename to .gitea/issue_template/enhancement.yaml diff --git a/.gitea/issue_template1/feature.yaml b/.gitea/issue_template/feature.yaml similarity index 100% rename from .gitea/issue_template1/feature.yaml rename to .gitea/issue_template/feature.yaml From fed5c55dd4ff3412b61d5f14def866b417ee9e21 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 12 Jan 2026 00:18:45 +0100 Subject: [PATCH 210/222] Updated placeholder --- .gitea/issue_template/bug.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.gitea/issue_template/bug.yaml b/.gitea/issue_template/bug.yaml index b7b28d1..20d8149 100644 --- a/.gitea/issue_template/bug.yaml +++ b/.gitea/issue_template/bug.yaml @@ -9,10 +9,8 @@ body: label: Beschreibung description: Beschreibe klar und pregnant das Fehlerverhalten placeholder: | - - Welchen Zweck erfüllt das Feature? - - Welches Problem löst das Feature? - - Wer profitiert davon? - - Warum ist es wichtig? + - Was genau ist das unerwünschte Verhalten? + - Welche Auswirkungen hat der Fehler? validations: required: true From 4bbbcdd93f52eafb902a51f697dadba3c2c56cf4 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 12 Jan 2026 00:47:43 +0100 Subject: [PATCH 211/222] Updated label --- lib/l10n/arb/app_de.arb | 2 +- lib/l10n/arb/app_en.arb | 8 ++++---- lib/l10n/generated/app_localizations.dart | 12 ++++++------ lib/l10n/generated/app_localizations_de.dart | 6 +++--- lib/l10n/generated/app_localizations_en.dart | 6 +++--- .../views/main_menu/settings_view/settings_view.dart | 2 +- pubspec.yaml | 2 +- 7 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 3a2685e..6aee6ee 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -42,7 +42,6 @@ "match_in_progress": "Spiel läuft...", "match_name": "Spieltitel", "matches": "Spiele", - "menu": "Menü", "most_points": "Höchste Punkte", "no_data_available": "Keine Daten verfügbar", "no_groups_created_yet": "Noch keine Gruppen erstellt", @@ -73,6 +72,7 @@ "search_for_players": "Nach Spieler:innen suchen", "select_winner": "Gewinner:in wählen:", "selected_players": "Ausgewählte Spieler:innen", + "settings": "Einstellungen", "single_loser": "Ein:e Verlierer:in", "single_winner": "Ein:e Gewinner:in", "statistics": "Statistiken", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index c080532..c311050 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -131,9 +131,6 @@ "@matches": { "description": "Label for matches" }, - "@menu": { - "description": "Menu label" - }, "@most_points": { "description": "Title for most points ruleset" }, @@ -229,6 +226,9 @@ "@selected_players": { "description": "Shows the number of selected players" }, + "@settings": { + "description": "Label for the App Settings" + }, "@single_loser": { "description": "Title for single loser ruleset" }, @@ -319,7 +319,6 @@ "match_in_progress": "Match in progress...", "match_name": "Match name", "matches": "Matches", - "menu": "Menu", "most_points": "Most Points", "no_data_available": "No data available", "no_groups_created_yet": "No groups created yet", @@ -350,6 +349,7 @@ "search_for_players": "Search for players", "select_winner": "Select Winner:", "selected_players": "Selected players", + "settings": "Settings", "single_loser": "Single Loser", "single_winner": "Single Winner", "statistics": "Statistics", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 30aa685..399dc85 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -350,12 +350,6 @@ abstract class AppLocalizations { /// **'Matches'** String get matches; - /// Menu label - /// - /// In en, this message translates to: - /// **'Menu'** - String get menu; - /// Title for most points ruleset /// /// In en, this message translates to: @@ -536,6 +530,12 @@ abstract class AppLocalizations { /// **'Selected players'** String get selected_players; + /// Label for the App Settings + /// + /// In en, this message translates to: + /// **'Settings'** + String get settings; + /// Title for single loser ruleset /// /// In en, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index def4859..f4d0f8c 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -139,9 +139,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get matches => 'Spiele'; - @override - String get menu => 'Menü'; - @override String get most_points => 'Höchste Punkte'; @@ -239,6 +236,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get selected_players => 'Ausgewählte Spieler:innen'; + @override + String get settings => 'Einstellungen'; + @override String get single_loser => 'Ein:e Verlierer:in'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 7419e24..6c4ac74 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -139,9 +139,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get matches => 'Matches'; - @override - String get menu => 'Menu'; - @override String get most_points => 'Most Points'; @@ -239,6 +236,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get selected_players => 'Selected players'; + @override + String get settings => 'Settings'; + @override String get single_loser => 'Single Loser'; diff --git a/lib/presentation/views/main_menu/settings_view/settings_view.dart b/lib/presentation/views/main_menu/settings_view/settings_view.dart index 3e1df91..8b9075a 100644 --- a/lib/presentation/views/main_menu/settings_view/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view/settings_view.dart @@ -50,7 +50,7 @@ class _SettingsViewState extends State { padding: const EdgeInsets.only(left: 16, bottom: 10), child: Text( textAlign: TextAlign.start, - loc.menu, + loc.settings, style: const TextStyle( fontSize: 28, fontWeight: FontWeight.bold, diff --git a/pubspec.yaml b/pubspec.yaml index d15ae4e..24d69c3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.5+130 +version: 0.0.5+132 environment: sdk: ^3.8.1 From 6d9871a5f018e5e8e0f5050da8af9bde83a570b2 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 12 Jan 2026 16:03:09 +0100 Subject: [PATCH 212/222] Removed Web --- .gitea/issue_template/bug.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/issue_template/bug.yaml b/.gitea/issue_template/bug.yaml index 20d8149..ec3b0eb 100644 --- a/.gitea/issue_template/bug.yaml +++ b/.gitea/issue_template/bug.yaml @@ -31,7 +31,7 @@ body: description: Gebe an, auf welchen Platformen dieser Fehler auftritt list: false multiple: true - options: ['Android', 'iOS', 'Web'] + options: ['Android', 'iOS'] - type: textarea id: bahaviour From 6aee055df216ee4258645337877531e6248f481a Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 12 Jan 2026 16:04:37 +0100 Subject: [PATCH 213/222] Removed whitespace --- .../main_menu/settings_view/licenses/license_detail_view.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart index 812e43e..f3cd1b8 100644 --- a/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart +++ b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart @@ -24,7 +24,6 @@ class LicenseDetailView extends StatelessWidget { children: [ Row( mainAxisAlignment: MainAxisAlignment.center, - children: [ Container( margin: const EdgeInsetsGeometry.only(right: 15), From cdafd4bb6f8dbefd82a9bf78b423cf46b1c0ba45 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 12 Jan 2026 16:16:59 +0100 Subject: [PATCH 214/222] Made links clickable --- .../licenses/license_detail_view.dart | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart index f3cd1b8..aa597e6 100644 --- a/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart +++ b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart'; +import 'package:url_launcher/url_launcher.dart'; class LicenseDetailView extends StatelessWidget { final Package package; @@ -80,12 +81,22 @@ class LicenseDetailView extends StatelessWidget { if (package.homepage != null && package.homepage!.isNotEmpty) ...[ const SizedBox(height: 8), - SelectableText( - package.homepage!, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - color: Colors.grey.shade500, + GestureDetector( + onTap: () async { + final uri = Uri.parse(package.homepage!); + if (await canLaunchUrl(uri)) { + launchUrl(uri); + } + }, + child: Text( + package.homepage!, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: CustomTheme.secondaryColor, + decoration: TextDecoration.underline, + decorationColor: CustomTheme.secondaryColor, + ), ), ), ], From 5da1b6eecbdb8461edc1a98f785087f1bc62df1b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 12 Jan 2026 16:17:18 +0100 Subject: [PATCH 215/222] Removed expanded widget --- .../settings_view/settings_view.dart | 348 +++++++++--------- pubspec.yaml | 2 +- 2 files changed, 173 insertions(+), 177 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view/settings_view.dart b/lib/presentation/views/main_menu/settings_view/settings_view.dart index 8b9075a..1843c90 100644 --- a/lib/presentation/views/main_menu/settings_view/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view/settings_view.dart @@ -41,192 +41,188 @@ class _SettingsViewState extends State { appBar: AppBar(backgroundColor: CustomTheme.backgroundColor), backgroundColor: CustomTheme.backgroundColor, body: SingleChildScrollView( - child: Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(left: 16, bottom: 10), - child: Text( - textAlign: TextAlign.start, - loc.settings, - style: const TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 16, bottom: 10), + child: Text( + textAlign: TextAlign.start, + loc.settings, + style: const TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, ), ), - Padding( - padding: const EdgeInsets.only(left: 16, top: 10, bottom: 10), - child: Text( - textAlign: TextAlign.start, - loc.data, - style: const TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - ), + ), + Padding( + padding: const EdgeInsets.only(left: 16, top: 10, bottom: 10), + child: Text( + textAlign: TextAlign.start, + loc.data, + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, ), ), - SettingsListTile( - title: loc.export_data, - icon: Icons.upload, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () async { - final String json = - await DataTransferService.getAppDataAsJson(context); - final result = await DataTransferService.exportData( - json, - 'game_tracker-data', - ); - if (!context.mounted) return; - showExportSnackBar(context: context, result: result); - }, - ), - SettingsListTile( - title: loc.import_data, - icon: Icons.download, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () async { - final result = await DataTransferService.importData( - context, - ); - if (!context.mounted) return; - showImportSnackBar(context: context, result: result); - }, - ), - SettingsListTile( - title: loc.delete_all_data, - icon: Icons.delete, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text('${loc.delete_all_data}?'), - content: Text(loc.this_cannot_be_undone), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(false), - child: Text(loc.cancel), - ), - TextButton( - onPressed: () => Navigator.of(context).pop(true), - child: Text(loc.delete), - ), - ], - ), - ).then((confirmed) { - if (confirmed == true && context.mounted) { - DataTransferService.deleteAllData(context); - showSnackbar( - context: context, - message: AppLocalizations.of( - context, - ).data_successfully_deleted, - ); - } - }); - }, - ), - Padding( - padding: const EdgeInsets.only(left: 16, top: 10, bottom: 10), - child: Text( - textAlign: TextAlign.start, - loc.legal, - style: const TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - ), - ), - ), - SettingsListTile( - title: loc.licenses, - icon: Icons.insert_drive_file, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => const LicensesView(), - ), - ); - }, - ), - SettingsListTile( - title: loc.legal_notice, - icon: Icons.account_balance_sharp, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: null, - ), - SettingsListTile( - title: loc.privacy_policy, - icon: Icons.gpp_good_rounded, - suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), - onPressed: null, - ), - Padding( - padding: const EdgeInsets.only(top: 30, bottom: 20), - child: Center( - child: Column( - spacing: 4, - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 12), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - spacing: 40, - children: [ - GestureDetector( - child: const Icon(Icons.language), - onTap: () => { - launchUrl(Uri.parse('https://liquid-dev.de')), - }, - ), - GestureDetector( - child: const FaIcon(FontAwesomeIcons.github), - onTap: () => { - launchUrl( - Uri.parse( - 'https://github.com/liquiddevelopmentde', - ), - ), - }, - ), - GestureDetector( - child: Icon( - Platform.isIOS - ? CupertinoIcons.mail_solid - : Icons.email, - ), - onTap: () => launchUrl( - Uri.parse('mailto:hi@liquid-dev.de'), - ), - ), - ], - ), + ), + SettingsListTile( + title: loc.export_data, + icon: Icons.upload, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () async { + final String json = + await DataTransferService.getAppDataAsJson(context); + final result = await DataTransferService.exportData( + json, + 'game_tracker-data', + ); + if (!context.mounted) return; + showExportSnackBar(context: context, result: result); + }, + ), + SettingsListTile( + title: loc.import_data, + icon: Icons.download, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () async { + final result = await DataTransferService.importData(context); + if (!context.mounted) return; + showImportSnackBar(context: context, result: result); + }, + ), + SettingsListTile( + title: loc.delete_all_data, + icon: Icons.delete, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text('${loc.delete_all_data}?'), + content: Text(loc.this_cannot_be_undone), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text(loc.cancel), ), - Text( - '© ${DateFormat('yyyy').format(DateTime.now())} Liquid Development', - style: TextStyle( - color: Colors.grey.shade600, - fontSize: 14, - fontWeight: FontWeight.w600, - ), - ), - Text( - 'Version ${_packageInfo.version} (${_packageInfo.buildNumber})', - style: TextStyle( - color: Colors.grey.shade600, - fontSize: 14, - fontWeight: FontWeight.w600, - ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text(loc.delete), ), ], ), + ).then((confirmed) { + if (confirmed == true && context.mounted) { + DataTransferService.deleteAllData(context); + showSnackbar( + context: context, + message: AppLocalizations.of( + context, + ).data_successfully_deleted, + ); + } + }); + }, + ), + Padding( + padding: const EdgeInsets.only(left: 16, top: 10, bottom: 10), + child: Text( + textAlign: TextAlign.start, + loc.legal, + style: const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, ), ), - ], - ), + ), + SettingsListTile( + title: loc.licenses, + icon: Icons.insert_drive_file, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const LicensesView(), + ), + ); + }, + ), + SettingsListTile( + title: loc.legal_notice, + icon: Icons.account_balance_sharp, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: null, + ), + SettingsListTile( + title: loc.privacy_policy, + icon: Icons.gpp_good_rounded, + suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16), + onPressed: null, + ), + Padding( + padding: const EdgeInsets.only(top: 30, bottom: 20), + child: Center( + child: Column( + spacing: 4, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 40, + children: [ + GestureDetector( + child: const Icon(Icons.language), + onTap: () => { + launchUrl(Uri.parse('https://liquid-dev.de')), + }, + ), + GestureDetector( + child: const FaIcon(FontAwesomeIcons.github), + onTap: () => { + launchUrl( + Uri.parse( + 'https://github.com/liquiddevelopmentde', + ), + ), + }, + ), + GestureDetector( + child: Icon( + Platform.isIOS + ? CupertinoIcons.mail_solid + : Icons.email, + ), + onTap: () => launchUrl( + Uri.parse('mailto:hi@liquid-dev.de'), + ), + ), + ], + ), + ), + Text( + '© ${DateFormat('yyyy').format(DateTime.now())} Liquid Development', + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + Text( + 'Version ${_packageInfo.version} (${_packageInfo.buildNumber})', + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + ), + ], ), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index 24d69c3..23cce4e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.5+132 +version: 0.0.5+137 environment: sdk: ^3.8.1 From 7bdad57cc893942a4bfb3a9aa5aa53578c3b4e59 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 12 Jan 2026 16:18:13 +0100 Subject: [PATCH 216/222] Removed old code --- lib/presentation/widgets/tiles/settings_list_tile.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/presentation/widgets/tiles/settings_list_tile.dart b/lib/presentation/widgets/tiles/settings_list_tile.dart index d54f13f..0655772 100644 --- a/lib/presentation/widgets/tiles/settings_list_tile.dart +++ b/lib/presentation/widgets/tiles/settings_list_tile.dart @@ -59,14 +59,6 @@ class SettingsListTile extends StatelessWidget { color: CustomTheme.primaryColor.withGreen(40), ), ), - /* Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: CustomTheme.primaryColor, - shape: BoxShape.circle, - ), - child: Icon(icon, size: 24), - ),*/ const SizedBox(width: 16), Text(title, style: const TextStyle(fontSize: 18)), ], From f0c6dd8401bbaa703b029a0650ead5fd00c08e7d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 12 Jan 2026 16:20:36 +0100 Subject: [PATCH 217/222] Adjusted container size and padding --- lib/presentation/widgets/tiles/settings_list_tile.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/presentation/widgets/tiles/settings_list_tile.dart b/lib/presentation/widgets/tiles/settings_list_tile.dart index 0655772..ba05225 100644 --- a/lib/presentation/widgets/tiles/settings_list_tile.dart +++ b/lib/presentation/widgets/tiles/settings_list_tile.dart @@ -38,7 +38,7 @@ class SettingsListTile extends StatelessWidget { onTap: onPressed ?? () {}, child: Container( margin: EdgeInsets.zero, - padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14), + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), decoration: CustomTheme.standardBoxDecoration, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -47,8 +47,8 @@ class SettingsListTile extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Container( - width: 48, - height: 48, + width: 44, + height: 44, decoration: BoxDecoration( color: CustomTheme.primaryColor.withAlpha(40), borderRadius: BorderRadius.circular(10), From 46118c274c0217ceb2996347aec42540e0125677 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 12 Jan 2026 17:46:58 +0100 Subject: [PATCH 218/222] Updated textarea in template --- .gitea/issue_template/bug.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitea/issue_template/bug.yaml b/.gitea/issue_template/bug.yaml index ec3b0eb..0523b96 100644 --- a/.gitea/issue_template/bug.yaml +++ b/.gitea/issue_template/bug.yaml @@ -40,6 +40,15 @@ body: description: Beschreibe, was passiert ist, obwohl es nicht passieren sollte placeholder: Bei Verhalten X tritt folgendes Verhalten auf ... + - type: textarea + id: solution + attributes: + label: Lösungsidee + description: Beschreibe, wie das Problem bzw. gelöst werden kann + placeholder: | + - Button X ändern, sodass ... + - Funktion X so erweitern, dass ... + - Design anpassen, sodass ... - type: textarea attributes: From 6d42d59badaa68aa9d302aba510a18e2c3c62765 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 12 Jan 2026 17:49:35 +0100 Subject: [PATCH 219/222] Removed area --- .gitea/issue_template/bug.yaml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.gitea/issue_template/bug.yaml b/.gitea/issue_template/bug.yaml index 0523b96..2709da1 100644 --- a/.gitea/issue_template/bug.yaml +++ b/.gitea/issue_template/bug.yaml @@ -33,13 +33,6 @@ body: multiple: true options: ['Android', 'iOS'] - - type: textarea - id: bahaviour - attributes: - label: Unerwünschtes Verhalten - description: Beschreibe, was passiert ist, obwohl es nicht passieren sollte - placeholder: Bei Verhalten X tritt folgendes Verhalten auf ... - - type: textarea id: solution attributes: From 7d0da81cf58bf5ba42282cb42520cd0255ab4cf5 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 12 Jan 2026 19:09:31 +0100 Subject: [PATCH 220/222] Updated license tile sizes --- lib/presentation/widgets/tiles/license_tile.dart | 8 ++++---- pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/presentation/widgets/tiles/license_tile.dart b/lib/presentation/widgets/tiles/license_tile.dart index a5d4f0c..5850d9e 100644 --- a/lib/presentation/widgets/tiles/license_tile.dart +++ b/lib/presentation/widgets/tiles/license_tile.dart @@ -20,15 +20,15 @@ class LicenseTile extends StatelessWidget { }, child: Container( margin: const EdgeInsets.only(bottom: 8), - padding: const EdgeInsets.all(16), + padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12), decoration: CustomTheme.standardBoxDecoration.copyWith( borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Container( - width: 48, - height: 48, + width: 50, + height: 50, decoration: BoxDecoration( color: CustomTheme.primaryColor.withAlpha(40), borderRadius: BorderRadius.circular(10), @@ -36,7 +36,7 @@ class LicenseTile extends StatelessWidget { child: Icon( Icons.description, color: CustomTheme.primaryColor, - size: 24, + size: 32, ), ), const SizedBox(width: 16), diff --git a/pubspec.yaml b/pubspec.yaml index 23cce4e..a87ee7d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.5+137 +version: 0.0.5+138 environment: sdk: ^3.8.1 From 2124c523bcc45462ca249e85b123bd627e1547e3 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 12 Jan 2026 19:20:52 +0100 Subject: [PATCH 221/222] Fixed link issue on android --- android/app/src/main/AndroidManifest.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 7e89f4b..3c48c4a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ + + + + + + + + + + From 80e601c10ef5af7710d583bb928f2a490a0b6a5f Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Mon, 12 Jan 2026 19:21:04 +0100 Subject: [PATCH 222/222] Changed formatting of link displayment --- .../licenses/license_detail_view.dart | 25 +++++++++++-------- pubspec.yaml | 2 +- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart index aa597e6..02c3adf 100644 --- a/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart +++ b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart @@ -84,18 +84,21 @@ class LicenseDetailView extends StatelessWidget { GestureDetector( onTap: () async { final uri = Uri.parse(package.homepage!); - if (await canLaunchUrl(uri)) { - launchUrl(uri); - } + await launchUrl(uri, mode: LaunchMode.platformDefault); }, - child: Text( - package.homepage!, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 12, - color: CustomTheme.secondaryColor, - decoration: TextDecoration.underline, - decorationColor: CustomTheme.secondaryColor, + child: SizedBox( + width: 300, + child: Text( + package.homepage!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 12, + color: CustomTheme.secondaryColor, + decoration: TextDecoration.underline, + decorationColor: CustomTheme.secondaryColor, + ), ), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index a87ee7d..2afff43 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: game_tracker description: "Game Tracking App for Card Games" publish_to: 'none' -version: 0.0.5+138 +version: 0.0.5+143 environment: sdk: ^3.8.1