From 03ab2045b2f8924ea8cbe14e6f39679708acee94 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sun, 10 May 2026 14:54:00 +0200 Subject: [PATCH 01/34] Add support for selecting multiple winners and update localization --- lib/l10n/arb/app_de.arb | 1 + lib/l10n/arb/app_en.arb | 1 + lib/l10n/generated/app_localizations.dart | 6 +++ lib/l10n/generated/app_localizations_de.dart | 3 ++ lib/l10n/generated/app_localizations_en.dart | 3 ++ .../match_view/match_detail_view.dart | 1 + .../match_view/match_result_view.dart | 53 ++++++++++++++++++- .../custom_checkbox_list_tile.dart | 52 ++++++++++++++++++ 8 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 lib/presentation/widgets/tiles/match_result_view/custom_checkbox_list_tile.dart diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 5492bab..622d9cb 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -120,6 +120,7 @@ "search_for_groups": "Nach Gruppen suchen", "search_for_players": "Nach Spieler:innen suchen", "select_winner": "Gewinner:in wählen", + "select_winners": "Gewinner:innen wählen", "select_loser": "Verlierer:in wählen", "selected_players": "Ausgewählte Spieler:innen", "settings": "Einstellungen", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 7fb944b..b5d617e 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -120,6 +120,7 @@ "search_for_groups": "Search for groups", "search_for_players": "Search for players", "select_winner": "Select Winner", + "select_winners": "Select Winners", "select_loser": "Select Loser", "selected_players": "Selected players", "settings": "Settings", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index bfdb659..dd7617e 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -770,6 +770,12 @@ abstract class AppLocalizations { /// **'Select Winner'** String get select_winner; + /// No description provided for @select_winners. + /// + /// In en, this message translates to: + /// **'Select Winners'** + String get select_winners; + /// No description provided for @select_loser. /// /// 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 8567ba0..e9e6451 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -366,6 +366,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get select_winner => 'Gewinner:in wählen'; + @override + String get select_winners => 'Gewinner:innen wählen'; + @override String get select_loser => 'Verlierer:in wählen'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 04e68b4..3b90592 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -366,6 +366,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get select_winner => 'Select Winner'; + @override + String get select_winners => 'Select Winners'; + @override String get select_loser => 'Select Loser'; diff --git a/lib/presentation/views/main_menu/match_view/match_detail_view.dart b/lib/presentation/views/main_menu/match_view/match_detail_view.dart index 86c26c6..d8cd627 100644 --- a/lib/presentation/views/main_menu/match_view/match_detail_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_detail_view.dart @@ -269,6 +269,7 @@ class _MatchDetailViewState extends State { /// Returns the widget to be displayed in the result [InfoTile] Widget getResultWidget(AppLocalizations loc) { + ///TODO: add support for multiple winners if (isSingleRowResult()) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, 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 61b2a55..fd2c35a 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 @@ -8,6 +8,7 @@ import 'package:tallee/data/models/player.dart'; import 'package:tallee/data/models/score_entry.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart'; +import 'package:tallee/presentation/widgets/tiles/match_result_view/custom_checkbox_list_tile.dart'; import 'package:tallee/presentation/widgets/tiles/match_result_view/custom_radio_list_tile.dart'; import 'package:tallee/presentation/widgets/tiles/match_result_view/live_edit_list_tile.dart'; import 'package:tallee/presentation/widgets/tiles/match_result_view/score_list_tile.dart'; @@ -45,9 +46,12 @@ class _MatchResultViewState extends State { /// Flag to indicate if the save button should be enabled late bool canSave; - /// Currently selected winner player + /// Currently selected winner player (single winner) Player? _selectedPlayer; + /// Currently selected winners (multiple winners) + Set _selectedWinners = {}; + @override void initState() { db = Provider.of(context, listen: false); @@ -80,7 +84,10 @@ class _MatchResultViewState extends State { final scoreB = widget.match.scores[b.id]?.score ?? 0; return scoreB.compareTo(scoreA); }); + } else if (rulesetSupportsMultipleWinners()) { + //TODO: Implement winners pre filling } + ; super.initState(); } } @@ -319,6 +326,36 @@ class _MatchResultViewState extends State { ], ), ), + + // Show multiple winner selection + if (rulesetSupportsMultipleWinners()) + Expanded( + child: ListView.builder( + physics: const NeverScrollableScrollPhysics(), + itemCount: allPlayers.length, + itemBuilder: (context, index) { + return CustomCheckboxListTile( + text: allPlayers[index].name, + value: _selectedWinners.contains( + allPlayers[index].id, + ), + onChanged: (bool value) { + setState(() { + if (value) { + _selectedWinners.add( + allPlayers[index].id, + ); + } else { + _selectedWinners.remove( + allPlayers[index].id, + ); + } + }); + }, + ); + }, + ), + ), ], ), ), @@ -382,6 +419,8 @@ class _MatchResultViewState extends State { await _handleScores(); } else if (ruleset == Ruleset.placement) { await _handlePlacement(); + } else if (ruleset == Ruleset.multipleWinners) { + await _handleWinners(); } widget.onWinnerChanged?.call(); @@ -399,6 +438,12 @@ class _MatchResultViewState extends State { } } + /// Handles saving the winners to the database. + Future _handleWinners() async { + //TODO: Implement winner handling + return true; + } + /// Handles saving or removing the loser in the database. Future _handleLoser() async { if (_selectedPlayer == null) { @@ -443,6 +488,8 @@ class _MatchResultViewState extends State { return loc.select_loser; case Ruleset.placement: return loc.drag_to_set_placement; + case Ruleset.multipleWinners: + return loc.select_winners; default: return loc.enter_points; } @@ -459,4 +506,8 @@ class _MatchResultViewState extends State { bool rulesetSupportsPlacement() { return ruleset == Ruleset.placement; } + + bool rulesetSupportsMultipleWinners() { + return ruleset == Ruleset.multipleWinners; + } } diff --git a/lib/presentation/widgets/tiles/match_result_view/custom_checkbox_list_tile.dart b/lib/presentation/widgets/tiles/match_result_view/custom_checkbox_list_tile.dart new file mode 100644 index 0000000..77c9242 --- /dev/null +++ b/lib/presentation/widgets/tiles/match_result_view/custom_checkbox_list_tile.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:tallee/core/custom_theme.dart'; + +class CustomCheckboxListTile extends StatelessWidget { + const CustomCheckboxListTile({ + super.key, + required this.text, + required this.value, + required this.onChanged, + }); + + final String text; + final bool value; + final ValueChanged onChanged; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () => onChanged(!value), + child: Container( + margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), + padding: const EdgeInsets.symmetric(horizontal: 2), + decoration: BoxDecoration( + color: CustomTheme.boxColor, + border: Border.all(color: CustomTheme.boxBorderColor), + borderRadius: CustomTheme.standardBorderRadiusAll, + ), + child: Row( + children: [ + Checkbox( + value: value, + onChanged: (bool? v) { + if (v == null) return; + onChanged(v); + }, + ), + Expanded( + child: Text( + text, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + ), + ); + } +} From 3c5c0dbf2068896beb6fc9ffc448cd8e9319fc7f Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sun, 10 May 2026 16:26:51 +0200 Subject: [PATCH 02/34] Add support for multiple winners and update localization --- lib/data/dao/score_entry_dao.dart | 48 ++++++++++++++++++- lib/data/models/match.dart | 2 +- lib/l10n/arb/app_de.arb | 1 + lib/l10n/arb/app_en.arb | 1 + lib/l10n/generated/app_localizations.dart | 6 +++ lib/l10n/generated/app_localizations_de.dart | 3 ++ lib/l10n/generated/app_localizations_en.dart | 3 ++ .../match_view/match_detail_view.dart | 30 ++++++++++-- .../match_view/match_result_view.dart | 29 +++++++---- .../widgets/tiles/match_tile.dart | 3 ++ 10 files changed, 110 insertions(+), 16 deletions(-) diff --git a/lib/data/dao/score_entry_dao.dart b/lib/data/dao/score_entry_dao.dart index cf6a449..d59f40a 100644 --- a/lib/data/dao/score_entry_dao.dart +++ b/lib/data/dao/score_entry_dao.dart @@ -228,7 +228,7 @@ class ScoreEntryDao extends DatabaseAccessor required String playerId, }) async { // Clear previous winner if exists - deleteAllScoresForMatch(matchId: matchId); + await deleteAllScoresForMatch(matchId: matchId); // Set the winner's score to 1 final rowsAffected = await into(scoreEntryTable).insert( @@ -245,7 +245,7 @@ class ScoreEntryDao extends DatabaseAccessor return rowsAffected > 0; } - // Retrieves the winner of a match by looking for a score entry where score + /// Retrieves the winner of a match by looking for a score entry where score /// is 1. Returns `null` if no player found, else the first with the score. Future getWinner({required String matchId}) async { final query = @@ -285,6 +285,48 @@ class ScoreEntryDao extends DatabaseAccessor } } + /* multiple winners handling */ + + /// Sets the winners for a match. + /// + /// Returns `true` if more than 0 rows were affected + Future setWinners({ + required List winners, + required String matchId, + }) async { + // Clear previous winners if exists + await deleteAllScoresForMatch(matchId: matchId); + + if (winners.isEmpty) return false; + + await batch((batch) { + batch.insertAll( + scoreEntryTable, + winners + .map( + (player) => ScoreEntryTableCompanion.insert( + playerId: player.id, + matchId: matchId, + roundNumber: 0, + score: 1, + change: 0, + ), + ) + .toList(), + mode: InsertMode.insertOrReplace, + ); + }); + + return true; + } + + /// Removes the winners of a match. + /// + /// Returns `true` if more than 0 rows were affected, `false` otherwise. + Future removeWinners({required String matchId}) async { + return await deleteAllScoresForMatch(matchId: matchId); + } + /* Loser handling */ Future hasLoser({required String matchId}) async { @@ -354,6 +396,8 @@ class ScoreEntryDao extends DatabaseAccessor } } + /* placement handling */ + /// Sets the placement for each player in a match. /// The highest score is assigned to the first player, the second highest to the second player, and so on. Future setPlacements({ diff --git a/lib/data/models/match.dart b/lib/data/models/match.dart index 679f8a4..2c43fe3 100644 --- a/lib/data/models/match.dart +++ b/lib/data/models/match.dart @@ -155,7 +155,7 @@ class Match { return _getPlayersWithLowestScore().take(1).toList(); case Ruleset.multipleWinners: - return []; + return _getPlayersWithHighestScore().toList(); case Ruleset.placement: return _getPlayersWithHighestScore().take(1).toList(); diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 622d9cb..f9093a2 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -141,6 +141,7 @@ "undo": "Rückgängig", "unknown_exception": "Unbekannter Fehler (siehe Konsole)", "winner": "Gewinner:in", + "winners": "Gewinner:innen", "winrate": "Siegquote", "wins": "Siege", "yesterday_at": "Gestern um" diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index b5d617e..b7da7f2 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -150,6 +150,7 @@ "undo": "Undo", "unknown_exception": "Unknown Exception (see console)", "winner": "Winner", + "winners": "Winners", "winrate": "Winrate", "wins": "Wins", "yesterday_at": "Yesterday at" diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index dd7617e..1bff731 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -896,6 +896,12 @@ abstract class AppLocalizations { /// **'Winner'** String get winner; + /// No description provided for @winners. + /// + /// In en, this message translates to: + /// **'Winners'** + String get winners; + /// No description provided for @winrate. /// /// 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 e9e6451..ea8e1f2 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -434,6 +434,9 @@ class AppLocalizationsDe extends AppLocalizations { @override String get winner => 'Gewinner:in'; + @override + String get winners => 'Gewinner:innen'; + @override String get winrate => 'Siegquote'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 3b90592..48f054b 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -433,6 +433,9 @@ class AppLocalizationsEn extends AppLocalizations { @override String get winner => 'Winner'; + @override + String get winners => 'Winners'; + @override String get winrate => 'Winrate'; diff --git a/lib/presentation/views/main_menu/match_view/match_detail_view.dart b/lib/presentation/views/main_menu/match_view/match_detail_view.dart index d8cd627..952cb60 100644 --- a/lib/presentation/views/main_menu/match_view/match_detail_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_detail_view.dart @@ -269,9 +269,9 @@ class _MatchDetailViewState extends State { /// Returns the widget to be displayed in the result [InfoTile] Widget getResultWidget(AppLocalizations loc) { - ///TODO: add support for multiple winners if (isSingleRowResult()) { return Row( + crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: getSingleResultRow(loc), ); @@ -300,7 +300,8 @@ class _MatchDetailViewState extends State { ), ]; // Single Loser - } else if (match.game.ruleset == Ruleset.singleLoser) { + } else if (match.mvp.isNotEmpty && + match.game.ruleset == Ruleset.singleLoser) { return [ Text( loc.loser, @@ -315,6 +316,28 @@ class _MatchDetailViewState extends State { ), ), ]; + // Multiple Winners + } else if (match.mvp.isNotEmpty && + match.game.ruleset == Ruleset.multipleWinners) { + return [ + Text( + loc.winners, + style: const TextStyle(fontSize: 16, color: CustomTheme.textColor), + ), + Flexible( + child: Container( + padding: EdgeInsets.only(left: 40), + child: Text( + match.mvp.map((player) => player.name).join(', '), + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: CustomTheme.primaryColor, + ), + ), + ), + ), + ]; // No result entered yet } else { return [ @@ -402,7 +425,8 @@ class _MatchDetailViewState extends State { // Returns if the result can be displayed in a single row bool isSingleRowResult() { return match.game.ruleset == Ruleset.singleWinner || - match.game.ruleset == Ruleset.singleLoser; + match.game.ruleset == Ruleset.singleLoser || + match.game.ruleset == Ruleset.multipleWinners; } String getPlacementText(BuildContext context, int rank) { 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 fd2c35a..de31967 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 @@ -50,7 +50,7 @@ class _MatchResultViewState extends State { Player? _selectedPlayer; /// Currently selected winners (multiple winners) - Set _selectedWinners = {}; + Set _selectedWinners = {}; @override void initState() { @@ -85,9 +85,12 @@ class _MatchResultViewState extends State { return scoreB.compareTo(scoreA); }); } else if (rulesetSupportsMultipleWinners()) { - //TODO: Implement winners pre filling + for (int i = 0; i < allPlayers.length; i++) { + if (widget.match.scores[allPlayers[i].id]?.score == 1) { + _selectedWinners.add(allPlayers[i]); + } + } } - ; super.initState(); } } @@ -337,17 +340,17 @@ class _MatchResultViewState extends State { return CustomCheckboxListTile( text: allPlayers[index].name, value: _selectedWinners.contains( - allPlayers[index].id, + allPlayers[index], ), onChanged: (bool value) { setState(() { if (value) { _selectedWinners.add( - allPlayers[index].id, + allPlayers[index], ); } else { _selectedWinners.remove( - allPlayers[index].id, + allPlayers[index], ); } }); @@ -426,7 +429,7 @@ class _MatchResultViewState extends State { widget.onWinnerChanged?.call(); } - /// Handles saving or removing the winner in the database. + /// Handles saving or removing the (single) winner in the database. Future _handleWinner() async { if (_selectedPlayer == null) { return await db.scoreEntryDao.removeWinner(matchId: widget.match.id); @@ -438,10 +441,16 @@ class _MatchResultViewState extends State { } } - /// Handles saving the winners to the database. + /// Handles saving the (multiple) winners to the database. Future _handleWinners() async { - //TODO: Implement winner handling - return true; + if (_selectedWinners.isEmpty) { + return await db.scoreEntryDao.removeWinners(matchId: widget.match.id); + } else { + return await db.scoreEntryDao.setWinners( + matchId: widget.match.id, + winners: allPlayers.where((p) => _selectedWinners.contains(p)).toList(), + ); + } } /// Handles saving or removing the loser in the database. diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index d034763..018c896 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -264,6 +264,9 @@ class _MatchTileState extends State { return '${loc.winner}: $mvpNames (${getPointLabel(loc, mvpScore)})'; } else if (ruleset == Ruleset.placement) { return '${loc.winner}: ${widget.match.mvp.first.name}'; + } else if (ruleset == Ruleset.multipleWinners) { + final mvpNames = widget.match.mvp.map((player) => player.name).join(', '); + return '${loc.winners}: $mvpNames'; } return '${loc.winner}: n.A.'; } From 8076f082bc7a7c09c5eb4dae73872db04739b01c Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sun, 10 May 2026 16:28:08 +0200 Subject: [PATCH 03/34] fix lint --- .../views/main_menu/match_view/match_detail_view.dart | 2 +- .../views/main_menu/match_view/match_result_view.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_detail_view.dart b/lib/presentation/views/main_menu/match_view/match_detail_view.dart index 952cb60..10fc324 100644 --- a/lib/presentation/views/main_menu/match_view/match_detail_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_detail_view.dart @@ -326,7 +326,7 @@ class _MatchDetailViewState extends State { ), Flexible( child: Container( - padding: EdgeInsets.only(left: 40), + padding: const EdgeInsets.only(left: 40), child: Text( match.mvp.map((player) => player.name).join(', '), style: const TextStyle( 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 de31967..d14c259 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 @@ -50,7 +50,7 @@ class _MatchResultViewState extends State { Player? _selectedPlayer; /// Currently selected winners (multiple winners) - Set _selectedWinners = {}; + final Set _selectedWinners = {}; @override void initState() { From 009c53ad89fafe138b2678e1c82f88ca6e77958c Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sun, 10 May 2026 16:36:46 +0200 Subject: [PATCH 04/34] fix title and ruleset/color choose order in game creation --- .../match_view/create_match/create_game_view.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart index e0f9d85..0e96508 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart @@ -98,6 +98,7 @@ class _CreateGameViewState extends State { Ruleset.multipleWinners, translateRulesetToString(Ruleset.multipleWinners, context), ), + (Ruleset.placement, translateRulesetToString(Ruleset.placement, context)), ]; _colors = [ (GameColor.green, translateGameColorToString(GameColor.green, context)), @@ -212,12 +213,12 @@ class _CreateGameViewState extends State { ), ), + // Choose color tile + ChooseTile(title: loc.ruleset, trailing: getRulesetDropdown(loc)), + // Choose ruleset tile if (!isEditMode()) - ChooseTile(title: loc.ruleset, trailing: getColorDropdown(loc)), - - // Choose color tile - ChooseTile(title: loc.color, trailing: getRulesetDropdown(loc)), + ChooseTile(title: loc.color, trailing: getColorDropdown(loc)), // Description input field Container( From 2a3c0fc98c32cef7b64115ff7c92f09ace15011b Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sun, 10 May 2026 17:50:16 +0200 Subject: [PATCH 05/34] fix schema --- assets/schema.json | 34 +++------------------------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index 6bcbe45..7f6aebd 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -102,36 +102,6 @@ ] } }, - "teams": { - "type": "array", - "items": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "name": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "memberIds": { - "type": "array", - "items": { - "type": "string" - } - } - }, - "additionalProperties": false, - "required": [ - "id", - "name", - "createdAt", - "memberIds" - ] - } - }, "matches": { "type": "array", "items": { @@ -195,6 +165,9 @@ }, "notes": { "type": "string" + }, + "teams": { + "type": ["array", "null"] } }, "additionalProperties": false, @@ -214,7 +187,6 @@ "players", "games", "groups", - "teams", "matches" ] } \ No newline at end of file From f98208b5089e08e89b0c1ed9ba229727b56422ed Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 10 May 2026 18:17:01 +0200 Subject: [PATCH 06/34] fix: button enabled condition --- .../match_view/create_match/create_match_view.dart | 6 +++--- 1 file changed, 3 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 14908b6..fd98691 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 @@ -231,11 +231,11 @@ class _CreateMatchViewState extends State { /// Determines whether the "Create Match" button should be enabled. /// /// Returns `true` if: - /// - A ruleset is selected AND + /// - A game is selected AND /// - Either a group is selected OR at least 2 players are selected. bool _enableCreateGameButton() { - return (selectedGroup != null || - (selectedPlayers.length > 1) && selectedGame != null); + return ((selectedGroup != null || selectedPlayers.length > 1) && + selectedGame != null); } /// Handles navigation when the create or save button is pressed. From f5f97f676c34a78c7dec367a2ee4b60b561f042d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 10 May 2026 18:22:56 +0200 Subject: [PATCH 07/34] fix: wrong tile hidden --- .../match_view/create_match/create_game_view.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart index 0e96508..d8dbe68 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart @@ -213,12 +213,15 @@ class _CreateGameViewState extends State { ), ), - // Choose color tile - ChooseTile(title: loc.ruleset, trailing: getRulesetDropdown(loc)), - // Choose ruleset tile if (!isEditMode()) - ChooseTile(title: loc.color, trailing: getColorDropdown(loc)), + ChooseTile( + title: loc.ruleset, + trailing: getRulesetDropdown(loc), + ), + + // Choose color tile + ChooseTile(title: loc.color, trailing: getColorDropdown(loc)), // Description input field Container( From 9ae562d92a8572979d49fc03903a42cc37aac487 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sun, 10 May 2026 18:23:39 +0200 Subject: [PATCH 08/34] update schema test --- test/services/data_transfer_service_test.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/services/data_transfer_service_test.dart b/test/services/data_transfer_service_test.dart index fec70b7..586138a 100644 --- a/test/services/data_transfer_service_test.dart +++ b/test/services/data_transfer_service_test.dart @@ -883,14 +883,6 @@ void main() { 'createdAt': testGroup.createdAt.toIso8601String(), }, ], - 'teams': [ - { - 'id': testTeam.id, - 'name': testTeam.name, - 'memberIds': [testPlayer1.id, testPlayer2.id], - 'createdAt': testTeam.createdAt.toIso8601String(), - }, - ], 'matches': [ { 'id': testMatch.id, From 0c44c54bd7e7df1224b916dbf57197b4f17f6df1 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 10 May 2026 18:24:12 +0200 Subject: [PATCH 09/34] fix: schema test created json --- test/services/data_transfer_service_test.dart | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/services/data_transfer_service_test.dart b/test/services/data_transfer_service_test.dart index fec70b7..586138a 100644 --- a/test/services/data_transfer_service_test.dart +++ b/test/services/data_transfer_service_test.dart @@ -883,14 +883,6 @@ void main() { 'createdAt': testGroup.createdAt.toIso8601String(), }, ], - 'teams': [ - { - 'id': testTeam.id, - 'name': testTeam.name, - 'memberIds': [testPlayer1.id, testPlayer2.id], - 'createdAt': testTeam.createdAt.toIso8601String(), - }, - ], 'matches': [ { 'id': testMatch.id, From 6db265ea993550301356c5d3dfb75060f8cc63cd Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 10 May 2026 18:39:11 +0200 Subject: [PATCH 10/34] feat: replaced variable content with generated lists --- lib/core/enums.dart | 18 ++----- .../create_match/create_game_view.dart | 47 ++++++------------- 2 files changed, 19 insertions(+), 46 deletions(-) diff --git a/lib/core/enums.dart b/lib/core/enums.dart index 605d3aa..99141e4 100644 --- a/lib/core/enums.dart +++ b/lib/core/enums.dart @@ -34,21 +34,13 @@ enum ExportResult { success, canceled, unknownException } /// - [Ruleset.multipleWinners]: Multiple players can be winners. /// - [Ruleset.placement]: The player with the highest placement wins. enum Ruleset { + singleWinner, + multipleWinners, highestScore, lowestScore, - singleWinner, - singleLoser, - multipleWinners, placement, + singleLoser, } -/// Different colors available for games -/// - [GameColor.red]: Red color -/// - [GameColor.blue]: Blue color -/// - [GameColor.green]: Green color -/// - [GameColor.yellow]: Yellow color -/// - [GameColor.purple]: Purple color -/// - [GameColor.orange]: Orange color -/// - [GameColor.pink]: Pink color -/// - [GameColor.teal]: Teal color -enum GameColor { red, blue, green, yellow, purple, orange, pink, teal } +/// Different colors for highlighting games +enum GameColor { red, orange, yellow, green, teal, blue, purple, pink } diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart index d8dbe68..3156476 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart @@ -47,9 +47,9 @@ class _CreateGameViewState extends State { late final AppDatabase db; late List<(Ruleset, String)> _rulesets; - Ruleset? selectedRuleset = Ruleset.singleWinner; - late List<(GameColor, String)> _colors; + + Ruleset? selectedRuleset = Ruleset.singleWinner; GameColor? selectedColor = GameColor.orange; /// Controller for the game name input field. @@ -77,39 +77,20 @@ class _CreateGameViewState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - _rulesets = [ - ( - Ruleset.singleWinner, - translateRulesetToString(Ruleset.singleWinner, context), + _rulesets = List.generate( + Ruleset.values.length, + (index) => ( + Ruleset.values[index], + translateRulesetToString(Ruleset.values[index], context), ), - ( - Ruleset.singleLoser, - translateRulesetToString(Ruleset.singleLoser, context), + ); + _colors = List.generate( + GameColor.values.length, + (index) => ( + GameColor.values[index], + translateGameColorToString(GameColor.values[index], context), ), - ( - Ruleset.highestScore, - translateRulesetToString(Ruleset.highestScore, context), - ), - ( - Ruleset.lowestScore, - translateRulesetToString(Ruleset.lowestScore, context), - ), - ( - Ruleset.multipleWinners, - translateRulesetToString(Ruleset.multipleWinners, context), - ), - (Ruleset.placement, translateRulesetToString(Ruleset.placement, context)), - ]; - _colors = [ - (GameColor.green, translateGameColorToString(GameColor.green, context)), - (GameColor.teal, translateGameColorToString(GameColor.teal, context)), - (GameColor.blue, translateGameColorToString(GameColor.blue, context)), - (GameColor.purple, translateGameColorToString(GameColor.purple, context)), - (GameColor.pink, translateGameColorToString(GameColor.pink, context)), - (GameColor.red, translateGameColorToString(GameColor.red, context)), - (GameColor.orange, translateGameColorToString(GameColor.orange, context)), - (GameColor.yellow, translateGameColorToString(GameColor.yellow, context)), - ]; + ); if (widget.gameToEdit != null) { _gameNameController.text = widget.gameToEdit!.name; From 341b293151082944e58b4ebc8ac11bed21940a8d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 10 May 2026 18:41:34 +0200 Subject: [PATCH 11/34] fix: text alignment --- .../views/main_menu/match_view/match_detail_view.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/presentation/views/main_menu/match_view/match_detail_view.dart b/lib/presentation/views/main_menu/match_view/match_detail_view.dart index 10fc324..9c04590 100644 --- a/lib/presentation/views/main_menu/match_view/match_detail_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_detail_view.dart @@ -326,9 +326,10 @@ class _MatchDetailViewState extends State { ), Flexible( child: Container( - padding: const EdgeInsets.only(left: 40), + padding: const EdgeInsets.only(left: 10), child: Text( match.mvp.map((player) => player.name).join(', '), + textAlign: TextAlign.end, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, From 50bf111f0368788af660cd8011c7f6d9b04c59e9 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 10 May 2026 18:47:48 +0200 Subject: [PATCH 12/34] fix: single result row --- .../match_view/match_detail_view.dart | 102 ++++++++---------- 1 file changed, 43 insertions(+), 59 deletions(-) diff --git a/lib/presentation/views/main_menu/match_view/match_detail_view.dart b/lib/presentation/views/main_menu/match_view/match_detail_view.dart index 9c04590..73d534d 100644 --- a/lib/presentation/views/main_menu/match_view/match_detail_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_detail_view.dart @@ -283,71 +283,55 @@ class _MatchDetailViewState extends State { /// Returns the result row for single winner/loser rulesets or a placeholder /// if no result is entered yet List getSingleResultRow(AppLocalizations loc) { - // Single Winner - if (match.mvp.isNotEmpty && match.game.ruleset == Ruleset.singleWinner) { - return [ - Text( - loc.winner, - style: const TextStyle(fontSize: 16, color: CustomTheme.textColor), - ), - Text( - match.mvp.first.name, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: CustomTheme.primaryColor, + if (match.mvp.isNotEmpty) { + final ruleset = match.game.ruleset; + + if (ruleset == Ruleset.singleWinner || ruleset == Ruleset.singleLoser) { + return [ + Text( + ruleset == Ruleset.singleWinner ? loc.winner : loc.loser, + style: const TextStyle(fontSize: 16, color: CustomTheme.textColor), ), - ), - ]; - // Single Loser - } else if (match.mvp.isNotEmpty && - match.game.ruleset == Ruleset.singleLoser) { - return [ - Text( - loc.loser, - style: const TextStyle(fontSize: 16, color: CustomTheme.textColor), - ), - Text( - match.mvp.first.name, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: CustomTheme.primaryColor, + Text( + match.mvp.first.name, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: CustomTheme.primaryColor, + ), ), - ), - ]; - // Multiple Winners - } else if (match.mvp.isNotEmpty && - match.game.ruleset == Ruleset.multipleWinners) { - return [ - Text( - loc.winners, - style: const TextStyle(fontSize: 16, color: CustomTheme.textColor), - ), - Flexible( - child: Container( - padding: const EdgeInsets.only(left: 10), - child: Text( - match.mvp.map((player) => player.name).join(', '), - textAlign: TextAlign.end, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: CustomTheme.primaryColor, + ]; + } else if (match.game.ruleset == Ruleset.multipleWinners) { + return [ + Text( + loc.winners, + style: const TextStyle(fontSize: 16, color: CustomTheme.textColor), + ), + Flexible( + child: Container( + padding: const EdgeInsets.only(left: 10), + child: Text( + match.mvp.map((player) => player.name).join(', '), + textAlign: TextAlign.end, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: CustomTheme.primaryColor, + ), ), ), ), - ), - ]; - // No result entered yet - } else { - return [ - Text( - loc.no_results_entered_yet, - style: const TextStyle(fontSize: 14, color: CustomTheme.textColor), - ), - ]; + ]; + } } + + // No results yet + return [ + Text( + loc.no_results_entered_yet, + style: const TextStyle(fontSize: 14, color: CustomTheme.textColor), + ), + ]; } /// Returns the result widget for scores or placement From e503db1c1b28b91be8176288c7607d642547fe52 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 10 May 2026 18:51:59 +0200 Subject: [PATCH 13/34] fix: removed unnecessary method --- lib/data/dao/score_entry_dao.dart | 15 +-------------- .../main_menu/match_view/match_result_view.dart | 2 +- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/lib/data/dao/score_entry_dao.dart b/lib/data/dao/score_entry_dao.dart index d59f40a..830135d 100644 --- a/lib/data/dao/score_entry_dao.dart +++ b/lib/data/dao/score_entry_dao.dart @@ -276,13 +276,7 @@ class ScoreEntryDao extends DatabaseAccessor /// Returns `true` if the winner was removed, `false` if there are multiple /// scores or if the winner cannot be removed. Future removeWinner({required String matchId}) async { - final scores = await getAllMatchScores(matchId: matchId); - - if (scores.length > 1) { - return false; - } else { - return await deleteAllScoresForMatch(matchId: matchId); - } + return await deleteAllScoresForMatch(matchId: matchId); } /* multiple winners handling */ @@ -320,13 +314,6 @@ class ScoreEntryDao extends DatabaseAccessor return true; } - /// Removes the winners of a match. - /// - /// Returns `true` if more than 0 rows were affected, `false` otherwise. - Future removeWinners({required String matchId}) async { - return await deleteAllScoresForMatch(matchId: matchId); - } - /* Loser handling */ Future hasLoser({required String matchId}) async { 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 d14c259..ad41a09 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 @@ -444,7 +444,7 @@ class _MatchResultViewState extends State { /// Handles saving the (multiple) winners to the database. Future _handleWinners() async { if (_selectedWinners.isEmpty) { - return await db.scoreEntryDao.removeWinners(matchId: widget.match.id); + return await db.scoreEntryDao.removeWinner(matchId: widget.match.id); } else { return await db.scoreEntryDao.setWinners( matchId: widget.match.id, From 60a92dafe1943ae6a238dd3f8b57f04c0340ce0d Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 10 May 2026 19:07:20 +0200 Subject: [PATCH 14/34] updated logic --- .../match_view/match_result_view.dart | 162 +++++++++--------- 1 file changed, 81 insertions(+), 81 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 ad41a09..a9bb08e 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 @@ -49,8 +49,8 @@ class _MatchResultViewState extends State { /// Currently selected winner player (single winner) Player? _selectedPlayer; - /// Currently selected winners (multiple winners) - final Set _selectedWinners = {}; + /// Currently selected players (multiple winners) + final Set _selectedPlayers = {}; @override void initState() { @@ -68,28 +68,30 @@ class _MatchResultViewState extends State { // Prefill fields if (widget.match.mvp.isNotEmpty) { - if (rulesetSupportsWinnerSelection()) { - _selectedPlayer = allPlayers.firstWhere( - (p) => p.id == widget.match.mvp.first.id, - ); + if (rulesetSupportsPlayerSelection()) { + if (ruleset == Ruleset.multipleWinners) { + for (int i = 0; i < allPlayers.length; i++) { + if (widget.match.scores[allPlayers[i].id]?.score == 1) { + _selectedPlayers.add(allPlayers[i]); + } + } + } else { + _selectedPlayer = allPlayers.firstWhere( + (p) => p.id == widget.match.mvp.first.id, + ); + } } else if (rulesetSupportsScoreEntry()) { for (int i = 0; i < allPlayers.length; i++) { final scoreList = widget.match.scores[allPlayers[i].id]; final score = scoreList?.score ?? 0; controller[i].text = score.toString(); } - } else if (rulesetSupportsPlacement()) { + } else if (rulesetSupportsDragBehaviour()) { allPlayers.sort((a, b) { final scoreA = widget.match.scores[a.id]?.score ?? 0; final scoreB = widget.match.scores[b.id]?.score ?? 0; return scoreB.compareTo(scoreA); }); - } else if (rulesetSupportsMultipleWinners()) { - for (int i = 0; i < allPlayers.length; i++) { - if (widget.match.scores[allPlayers[i].id]?.score == 1) { - _selectedWinners.add(allPlayers[i]); - } - } } super.initState(); } @@ -168,38 +170,68 @@ class _MatchResultViewState extends State { const SizedBox(height: 10), // Show player selection - if (rulesetSupportsWinnerSelection()) + if (rulesetSupportsPlayerSelection()) Expanded( - child: RadioGroup( - groupValue: _selectedPlayer, - onChanged: (Player? value) async { - setState(() { - _selectedPlayer = value; - }); - }, - child: ListView.builder( - physics: const NeverScrollableScrollPhysics(), - itemCount: allPlayers.length, - itemBuilder: (context, index) { - return CustomRadioListTile( - text: allPlayers[index].name, - value: allPlayers[index], - onContainerTap: (value) async { + child: ruleset == Ruleset.multipleWinners + // Multiple winners + ? ListView.builder( + physics: + const NeverScrollableScrollPhysics(), + itemCount: allPlayers.length, + itemBuilder: (context, index) { + return CustomCheckboxListTile( + text: allPlayers[index].name, + value: _selectedPlayers.contains( + allPlayers[index], + ), + onChanged: (bool value) { + setState(() { + if (value) { + _selectedPlayers.add( + allPlayers[index], + ); + } else { + _selectedPlayers.remove( + allPlayers[index], + ); + } + }); + }, + ); + }, + ) + // Single winner / looser + : RadioGroup( + groupValue: _selectedPlayer, + onChanged: (Player? value) async { setState(() { - // Check if the already selected player is the same as the newly tapped player. - if (_selectedPlayer == value) { - // If yes deselected the player by setting it to null. - _selectedPlayer = null; - } else { - // If no assign the newly tapped player to the selected player. - (_selectedPlayer = value); - } + _selectedPlayer = value; }); }, - ); - }, - ), - ), + child: ListView.builder( + physics: + const NeverScrollableScrollPhysics(), + itemCount: allPlayers.length, + itemBuilder: (context, index) { + return CustomRadioListTile( + text: allPlayers[index].name, + value: allPlayers[index], + onContainerTap: (value) async { + setState(() { + // Check if the already selected player is the same as the newly tapped player. + if (_selectedPlayer == value) { + // If yes deselected the player by setting it to null. + _selectedPlayer = null; + } else { + // If no assign the newly tapped player to the selected player. + (_selectedPlayer = value); + } + }); + }, + ); + }, + ), + ), ), // Show score entry @@ -226,7 +258,7 @@ class _MatchResultViewState extends State { ), // Show draggable placement list - if (rulesetSupportsPlacement()) + if (rulesetSupportsDragBehaviour()) Expanded( child: Row( children: [ @@ -329,36 +361,6 @@ class _MatchResultViewState extends State { ], ), ), - - // Show multiple winner selection - if (rulesetSupportsMultipleWinners()) - Expanded( - child: ListView.builder( - physics: const NeverScrollableScrollPhysics(), - itemCount: allPlayers.length, - itemBuilder: (context, index) { - return CustomCheckboxListTile( - text: allPlayers[index].name, - value: _selectedWinners.contains( - allPlayers[index], - ), - onChanged: (bool value) { - setState(() { - if (value) { - _selectedWinners.add( - allPlayers[index], - ); - } else { - _selectedWinners.remove( - allPlayers[index], - ); - } - }); - }, - ); - }, - ), - ), ], ), ), @@ -443,12 +445,12 @@ class _MatchResultViewState extends State { /// Handles saving the (multiple) winners to the database. Future _handleWinners() async { - if (_selectedWinners.isEmpty) { + if (_selectedPlayers.isEmpty) { return await db.scoreEntryDao.removeWinner(matchId: widget.match.id); } else { return await db.scoreEntryDao.setWinners( matchId: widget.match.id, - winners: allPlayers.where((p) => _selectedWinners.contains(p)).toList(), + winners: allPlayers.where((p) => _selectedPlayers.contains(p)).toList(), ); } } @@ -504,19 +506,17 @@ class _MatchResultViewState extends State { } } - bool rulesetSupportsWinnerSelection() { - return ruleset == Ruleset.singleWinner || ruleset == Ruleset.singleLoser; + bool rulesetSupportsPlayerSelection() { + return ruleset == Ruleset.singleWinner || + ruleset == Ruleset.singleLoser || + ruleset == Ruleset.multipleWinners; } bool rulesetSupportsScoreEntry() { return ruleset == Ruleset.lowestScore || ruleset == Ruleset.highestScore; } - bool rulesetSupportsPlacement() { + bool rulesetSupportsDragBehaviour() { return ruleset == Ruleset.placement; } - - bool rulesetSupportsMultipleWinners() { - return ruleset == Ruleset.multipleWinners; - } } From c75b3e4a6d8dea26ef865b424e891b0a5598b37b Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Sun, 10 May 2026 19:12:12 +0200 Subject: [PATCH 15/34] updated comment --- .../views/main_menu/match_view/match_result_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a9bb08e..ba138d6 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 @@ -46,7 +46,7 @@ class _MatchResultViewState extends State { /// Flag to indicate if the save button should be enabled late bool canSave; - /// Currently selected winner player (single winner) + /// Currently selected player (single winner / looser) Player? _selectedPlayer; /// Currently selected players (multiple winners) From 1c5735eb4154bd4142126b0fbfd8f1bddad147b4 Mon Sep 17 00:00:00 2001 From: "Gitea Actions [bot]" Date: Sun, 10 May 2026 17:14:04 +0000 Subject: [PATCH 16/34] Updated version number [skip ci] --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index b4b261e..9b0042b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: tallee description: "Tracking App for Card Games" publish_to: 'none' -version: 0.0.30+264 +version: 0.0.31+265 environment: sdk: ^3.8.1 From 699d4378b2e78563b578a4cb9977afd86852e4f7 Mon Sep 17 00:00:00 2001 From: "Gitea Actions [bot]" Date: Sun, 10 May 2026 17:14:44 +0000 Subject: [PATCH 17/34] Updated licenses [skip ci] --- .../views/main_menu/settings_view/licenses/oss_licenses.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 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 0ea0662..ee5558a 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 @@ -37751,12 +37751,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', ); -/// tallee 0.0.30+264 +/// tallee 0.0.31+265 const _tallee = Package( name: 'tallee', description: 'Tracking App for Card Games', authors: [], - version: '0.0.30+264', + version: '0.0.31+265', spdxIdentifiers: ['LGPL-3.0'], isMarkdown: false, isSdk: false, From 1d20127af4e5fd025a1204bb1131bcf2763131a0 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Sun, 10 May 2026 23:04:43 +0200 Subject: [PATCH 18/34] feat: add haptic feedback for various user interactions --- lib/core/custom_theme.dart | 7 ++++ lib/main.dart | 1 + .../main_menu/custom_navigation_bar.dart | 9 ++-- .../group_view/create_group_view.dart | 3 ++ .../group_view/group_detail_view.dart | 12 +++++- .../main_menu/group_view/group_view.dart | 2 + .../settings_view/settings_view.dart | 41 +++++++++++++++---- .../widgets/buttons/haptic_back_button.dart | 24 +++++++++++ .../widgets/buttons/haptic_close_button.dart | 24 +++++++++++ .../widgets/player_selection.dart | 6 ++- .../widgets/tiles/group_tile.dart | 6 ++- .../widgets/tiles/license_tile.dart | 7 +++- .../widgets/tiles/settings_list_tile.dart | 6 ++- 13 files changed, 131 insertions(+), 17 deletions(-) create mode 100644 lib/presentation/widgets/buttons/haptic_back_button.dart create mode 100644 lib/presentation/widgets/buttons/haptic_close_button.dart diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart index b32ce63..bb6d4b4 100644 --- a/lib/core/custom_theme.dart +++ b/lib/core/custom_theme.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:tallee/presentation/widgets/buttons/haptic_back_button.dart'; +import 'package:tallee/presentation/widgets/buttons/haptic_close_button.dart'; /// Theme class that defines colors, border radius, padding, and decorations class CustomTheme { @@ -83,6 +85,11 @@ class CustomTheme { iconTheme: IconThemeData(color: textColor), ); + static final ActionIconThemeData actionIconTheme = ActionIconThemeData( + backButtonIconBuilder: (context) => const HapticBackButton(), + closeButtonIconBuilder: (context) => const HapticCloseButton(), + ); + static const SearchBarThemeData searchBarTheme = SearchBarThemeData( textStyle: WidgetStatePropertyAll(TextStyle(color: textColor)), hintStyle: WidgetStatePropertyAll(TextStyle(color: hintColor)), diff --git a/lib/main.dart b/lib/main.dart index f159ef7..36eab62 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -42,6 +42,7 @@ class GameTracker extends StatelessWidget { scaffoldBackgroundColor: CustomTheme.backgroundColor, // themes appBarTheme: CustomTheme.appBarTheme, + actionIconTheme: CustomTheme.actionIconTheme, inputDecorationTheme: CustomTheme.inputDecorationTheme, searchBarTheme: CustomTheme.searchBarTheme, radioTheme: CustomTheme.radioTheme, diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index bf6ded3..366ecbd 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:tallee/core/adaptive_page_route.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; @@ -55,8 +56,9 @@ class _CustomNavigationBarState extends State actions: [ IconButton( onPressed: () async { - await Navigator.push( - context, + final navigator = Navigator.of(context); + await HapticFeedback.selectionClick(); + await navigator.push( adaptivePageRoute(builder: (_) => const SettingsView()), ); setState(() { @@ -125,7 +127,8 @@ class _CustomNavigationBarState extends State } /// Handles tab tap events. Updates the current [index] state. - void onTabTapped(int index) { + void onTabTapped(int index) async { + await HapticFeedback.selectionClick(); setState(() { currentIndex = index; }); 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 b4a5b97..55f59aa 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 @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:tallee/core/constants.dart'; import 'package:tallee/core/custom_theme.dart'; @@ -133,8 +134,10 @@ class _CreateGroupViewState extends State { if (!mounted) return; if (success) { + await HapticFeedback.successNotification(); Navigator.pop(context, updatedGroup); } else { + await HapticFeedback.errorNotification(); showSnackbar( message: widget.groupToEdit == null ? loc.error_creating_group diff --git a/lib/presentation/views/main_menu/group_view/group_detail_view.dart b/lib/presentation/views/main_menu/group_view/group_detail_view.dart index 3d5e805..746d0ee 100644 --- a/lib/presentation/views/main_menu/group_view/group_detail_view.dart +++ b/lib/presentation/views/main_menu/group_view/group_detail_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:tallee/core/adaptive_page_route.dart'; @@ -68,6 +69,7 @@ class _GroupDetailViewState extends State { IconButton( icon: const Icon(Icons.delete), onPressed: () async { + await HapticFeedback.selectionClick(); showDialog( context: context, builder: (context) => CustomAlertDialog( @@ -75,11 +77,17 @@ class _GroupDetailViewState extends State { content: Text(loc.this_cannot_be_undone), actions: [ CustomDialogAction( - onPressed: () => Navigator.of(context).pop(true), + onPressed: () async { + await HapticFeedback.warningNotification(); + Navigator.of(context).pop(true); + }, text: loc.delete, ), CustomDialogAction( - onPressed: () => Navigator.of(context).pop(false), + onPressed: () async { + await HapticFeedback.selectionClick(); + Navigator.of(context).pop(false); + }, buttonType: ButtonType.secondary, text: loc.cancel, ), diff --git a/lib/presentation/views/main_menu/group_view/group_view.dart b/lib/presentation/views/main_menu/group_view/group_view.dart index c8a9398..923344e 100644 --- a/lib/presentation/views/main_menu/group_view/group_view.dart +++ b/lib/presentation/views/main_menu/group_view/group_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:tallee/core/adaptive_page_route.dart'; import 'package:tallee/core/constants.dart'; @@ -102,6 +103,7 @@ class _GroupViewState extends State { text: loc.create_group, icon: Icons.group_add, onPressed: () async { + await HapticFeedback.selectionClick(); await Navigator.push( context, adaptivePageRoute( 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 8e1cbdc..c393ae5 100644 --- a/lib/presentation/views/main_menu/settings_view/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view/settings_view.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:intl/intl.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -201,7 +202,8 @@ class _SettingsViewState extends State { children: [ GestureDetector( child: const Icon(Icons.language), - onTap: () => { + onTap: () async => { + await HapticFeedback.lightImpact(), launchUrl( Uri.parse('https://liquid-dev.de'), ), @@ -209,7 +211,8 @@ class _SettingsViewState extends State { ), GestureDetector( child: const FaIcon(FontAwesomeIcons.github), - onTap: () => { + onTap: () async => { + await HapticFeedback.lightImpact(), launchUrl( Uri.parse( 'https://github.com/liquiddevelopmentde', @@ -223,9 +226,12 @@ class _SettingsViewState extends State { ? CupertinoIcons.mail_solid : Icons.email, ), - onTap: () => launchUrl( - Uri.parse('mailto:hi@liquid-dev.de'), - ), + onTap: () async => { + await HapticFeedback.lightImpact(), + launchUrl( + Uri.parse('mailto:hi@liquid-dev.de'), + ), + }, ), ], ), @@ -266,20 +272,38 @@ class _SettingsViewState extends State { void showImportSnackBar({ required BuildContext context, required ImportResult result, - }) { + }) async { final loc = AppLocalizations.of(context); switch (result) { case ImportResult.success: + if (context.mounted) { + await HapticFeedback.successNotification(); + } showSnackbar(context: context, message: loc.data_successfully_imported); case ImportResult.invalidSchema: + if (context.mounted) { + await HapticFeedback.errorNotification(); + } showSnackbar(context: context, message: loc.invalid_schema); case ImportResult.fileReadError: + if (context.mounted) { + await HapticFeedback.errorNotification(); + } showSnackbar(context: context, message: loc.error_reading_file); case ImportResult.canceled: + if (context.mounted) { + await HapticFeedback.errorNotification(); + } showSnackbar(context: context, message: loc.import_canceled); case ImportResult.formatException: + if (context.mounted) { + await HapticFeedback.errorNotification(); + } showSnackbar(context: context, message: loc.format_exception); case ImportResult.unknownException: + if (context.mounted) { + await HapticFeedback.errorNotification(); + } showSnackbar(context: context, message: loc.unknown_exception); } } @@ -291,13 +315,16 @@ class _SettingsViewState extends State { void showExportSnackBar({ required BuildContext context, required ExportResult result, - }) { + }) async { final loc = AppLocalizations.of(context); switch (result) { case ExportResult.success: + await HapticFeedback.successNotification(); showSnackbar(context: context, message: loc.data_successfully_exported); case ExportResult.canceled: + await HapticFeedback.errorNotification(); showSnackbar(context: context, message: loc.export_canceled); + await HapticFeedback.errorNotification(); case ExportResult.unknownException: showSnackbar(context: context, message: loc.unknown_exception); } diff --git a/lib/presentation/widgets/buttons/haptic_back_button.dart b/lib/presentation/widgets/buttons/haptic_back_button.dart new file mode 100644 index 0000000..6f3f76a --- /dev/null +++ b/lib/presentation/widgets/buttons/haptic_back_button.dart @@ -0,0 +1,24 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class HapticBackButton extends StatelessWidget { + const HapticBackButton({super.key}); + + @override + Widget build(BuildContext context) { + final iconData = switch (defaultTargetPlatform) { + TargetPlatform.iOS || + TargetPlatform.macOS => Icons.arrow_back_ios_new_rounded, + _ => Icons.arrow_back_rounded, + }; + + return IconButton( + icon: Icon(iconData), + onPressed: () async { + await HapticFeedback.mediumImpact(); + Navigator.of(context).maybePop(); + }, + ); + } +} diff --git a/lib/presentation/widgets/buttons/haptic_close_button.dart b/lib/presentation/widgets/buttons/haptic_close_button.dart new file mode 100644 index 0000000..bbd8455 --- /dev/null +++ b/lib/presentation/widgets/buttons/haptic_close_button.dart @@ -0,0 +1,24 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class HapticCloseButton extends StatelessWidget { + const HapticCloseButton({super.key}); + + @override + Widget build(BuildContext context) { + final iconData = switch (defaultTargetPlatform) { + TargetPlatform.iOS || TargetPlatform.macOS => CupertinoIcons.xmark, + _ => Icons.close_rounded, + }; + + return IconButton( + icon: Icon(iconData), + onPressed: () async { + await HapticFeedback.mediumImpact(); + Navigator.of(context).maybePop(); + }, + ); + } +} diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index cdcc2ed..b13e098 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:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:tallee/core/common.dart'; import 'package:tallee/core/constants.dart'; @@ -197,7 +198,8 @@ class _PlayerSelectionState extends State { text: suggestedPlayers[index].name, suffixText: getNameCountText(suggestedPlayers[index]), icon: Icons.add, - onPressed: () { + onPressed: () async { + await HapticFeedback.selectionClick(); setState(() { // If the player is not already selected if (!selectedPlayers.contains( @@ -294,8 +296,10 @@ class _PlayerSelectionState extends State { if (success) { _handleSuccessfulPlayerCreation(createdPlayer); + await HapticFeedback.successNotification(); showSnackBarMessage(loc.successfully_added_player(playerName)); } else { + await HapticFeedback.errorNotification(); showSnackBarMessage(loc.could_not_add_player(playerName)); } } diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index f4ace65..9ce5a6b 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:tallee/core/common.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/data/models/group.dart'; @@ -33,7 +34,10 @@ class _GroupTileState extends State { @override Widget build(BuildContext context) { return GestureDetector( - onTap: widget.onTap, + onTap: () async { + await HapticFeedback.selectionClick(); + widget.onTap?.call(); + }, child: AnimatedContainer( margin: CustomTheme.standardMargin, padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), diff --git a/lib/presentation/widgets/tiles/license_tile.dart b/lib/presentation/widgets/tiles/license_tile.dart index 9289ed5..b9663d0 100644 --- a/lib/presentation/widgets/tiles/license_tile.dart +++ b/lib/presentation/widgets/tiles/license_tile.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart'; import 'package:tallee/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart'; @@ -15,8 +16,10 @@ class LicenseTile extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( - onTap: () { - Navigator.of(context).push( + onTap: () async { + final navigator = Navigator.of(context); + await HapticFeedback.selectionClick(); + navigator.push( MaterialPageRoute( builder: (context) => LicenseDetailView(package: package), ), diff --git a/lib/presentation/widgets/tiles/settings_list_tile.dart b/lib/presentation/widgets/tiles/settings_list_tile.dart index de805cd..92a5116 100644 --- a/lib/presentation/widgets/tiles/settings_list_tile.dart +++ b/lib/presentation/widgets/tiles/settings_list_tile.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/presentation/widgets/colored_icon_container.dart'; @@ -36,7 +37,10 @@ class SettingsListTile extends StatelessWidget { child: SizedBox( width: MediaQuery.of(context).size.width * 0.95, child: GestureDetector( - onTap: onPressed ?? () {}, + onTap: () async { + await HapticFeedback.selectionClick(); + onPressed?.call(); + }, child: Container( margin: EdgeInsets.zero, padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), From bc59d1d91ce1fbdd65f9e5d18f6a34ef24f1e401 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Mon, 11 May 2026 10:27:35 +0200 Subject: [PATCH 19/34] feat: add haptic feedback to more user interactions --- .../main_menu/custom_navigation_bar.dart | 3 +- .../group_view/create_group_view.dart | 8 ++- .../group_view/group_detail_view.dart | 15 ++---- .../main_menu/group_view/group_view.dart | 2 - .../create_match/choose_game_view.dart | 5 +- .../create_match/choose_group_view.dart | 3 +- .../create_match/create_game_view.dart | 3 +- .../match_view/match_detail_view.dart | 3 +- .../match_view/match_result_view.dart | 12 ++++- .../settings_view/settings_view.dart | 44 +++++++++------ .../widgets/buttons/custom_width_button.dart | 22 ++++++-- .../widgets/buttons/haptic_back_button.dart | 5 +- .../widgets/buttons/haptic_close_button.dart | 5 +- .../widgets/buttons/haptic_icon_button.dart | 54 +++++++++++++++++++ .../widgets/buttons/main_menu_button.dart | 2 + .../widgets/dialog/custom_dialog_action.dart | 6 ++- .../widgets/player_selection.dart | 3 +- .../widgets/tiles/choose_tile.dart | 8 ++- .../custom_checkbox_list_tile.dart | 9 +++- .../widgets/tiles/match_tile.dart | 6 ++- 20 files changed, 165 insertions(+), 53 deletions(-) create mode 100644 lib/presentation/widgets/buttons/haptic_icon_button.dart diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 366ecbd..6e9e6d0 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -7,6 +7,7 @@ import 'package:tallee/presentation/views/main_menu/group_view/group_view.dart'; import 'package:tallee/presentation/views/main_menu/match_view/match_view.dart'; import 'package:tallee/presentation/views/main_menu/settings_view/settings_view.dart'; import 'package:tallee/presentation/views/main_menu/statistics_view.dart'; +import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart'; import 'package:tallee/presentation/widgets/navbar_item.dart'; class CustomNavigationBar extends StatefulWidget { @@ -54,7 +55,7 @@ class _CustomNavigationBarState extends State backgroundColor: CustomTheme.backgroundColor, scrolledUnderElevation: 0, actions: [ - IconButton( + HapticIconButton( onPressed: () async { final navigator = Navigator.of(context); await HapticFeedback.selectionClick(); 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 55f59aa..84efbe1 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 @@ -135,9 +135,13 @@ class _CreateGroupViewState extends State { if (success) { await HapticFeedback.successNotification(); - Navigator.pop(context, updatedGroup); + if (mounted) { + Navigator.pop(context, updatedGroup); + } } else { - await HapticFeedback.errorNotification(); + if (mounted) { + await HapticFeedback.errorNotification(); + } showSnackbar( message: widget.groupToEdit == null ? loc.error_creating_group diff --git a/lib/presentation/views/main_menu/group_view/group_detail_view.dart b/lib/presentation/views/main_menu/group_view/group_detail_view.dart index 746d0ee..c417ec4 100644 --- a/lib/presentation/views/main_menu/group_view/group_detail_view.dart +++ b/lib/presentation/views/main_menu/group_view/group_detail_view.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; import 'package:tallee/core/adaptive_page_route.dart'; @@ -13,6 +12,7 @@ import 'package:tallee/data/models/player.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/views/main_menu/group_view/create_group_view.dart'; import 'package:tallee/presentation/widgets/app_skeleton.dart'; +import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart'; import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart'; import 'package:tallee/presentation/widgets/colored_icon_container.dart'; import 'package:tallee/presentation/widgets/dialog/custom_alert_dialog.dart'; @@ -66,10 +66,9 @@ class _GroupDetailViewState extends State { appBar: AppBar( title: Text(loc.group_profile), actions: [ - IconButton( + HapticIconButton( icon: const Icon(Icons.delete), onPressed: () async { - await HapticFeedback.selectionClick(); showDialog( context: context, builder: (context) => CustomAlertDialog( @@ -77,17 +76,11 @@ class _GroupDetailViewState extends State { content: Text(loc.this_cannot_be_undone), actions: [ CustomDialogAction( - onPressed: () async { - await HapticFeedback.warningNotification(); - Navigator.of(context).pop(true); - }, + onPressed: () => Navigator.of(context).pop(true), text: loc.delete, ), CustomDialogAction( - onPressed: () async { - await HapticFeedback.selectionClick(); - Navigator.of(context).pop(false); - }, + onPressed: () => Navigator.of(context).pop(false), buttonType: ButtonType.secondary, text: loc.cancel, ), diff --git a/lib/presentation/views/main_menu/group_view/group_view.dart b/lib/presentation/views/main_menu/group_view/group_view.dart index 923344e..c8a9398 100644 --- a/lib/presentation/views/main_menu/group_view/group_view.dart +++ b/lib/presentation/views/main_menu/group_view/group_view.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:tallee/core/adaptive_page_route.dart'; import 'package:tallee/core/constants.dart'; @@ -103,7 +102,6 @@ class _GroupViewState extends State { text: loc.create_group, icon: Icons.group_add, onPressed: () async { - await HapticFeedback.selectionClick(); await Navigator.push( context, adaptivePageRoute( 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 c019213..3c51cab 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 @@ -7,6 +7,7 @@ import 'package:tallee/data/db/database.dart'; import 'package:tallee/data/models/game.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_game_view.dart'; +import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart'; import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:tallee/presentation/widgets/tiles/game_tile.dart'; import 'package:tallee/presentation/widgets/top_centered_message.dart'; @@ -70,7 +71,7 @@ class _ChooseGameViewState extends State { backgroundColor: CustomTheme.backgroundColor, resizeToAvoidBottomInset: false, appBar: AppBar( - leading: IconButton( + leading: HapticIconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () { Navigator.of(context).pop( @@ -83,7 +84,7 @@ class _ChooseGameViewState extends State { }, ), actions: [ - IconButton( + HapticIconButton( icon: const Icon(Icons.add), onPressed: () async { final result = await Navigator.push( 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 c7471d8..1caa101 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 @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/data/models/group.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart'; import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart'; import 'package:tallee/presentation/widgets/tiles/group_tile.dart'; import 'package:tallee/presentation/widgets/top_centered_message.dart'; @@ -45,7 +46,7 @@ class _ChooseGroupViewState extends State { backgroundColor: CustomTheme.backgroundColor, resizeToAvoidBottomInset: false, appBar: AppBar( - leading: IconButton( + leading: HapticIconButton( icon: const Icon(Icons.arrow_back_ios), onPressed: () { Navigator.of(context).pop( diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart index 3156476..4b4a977 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart @@ -12,6 +12,7 @@ import 'package:tallee/data/models/game.dart'; import 'package:tallee/data/models/group.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart'; +import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart'; import 'package:tallee/presentation/widgets/dialog/custom_alert_dialog.dart'; import 'package:tallee/presentation/widgets/dialog/custom_dialog_action.dart'; import 'package:tallee/presentation/widgets/text_input/text_input_field.dart'; @@ -120,7 +121,7 @@ class _CreateGameViewState extends State { title: Text(isEditing ? loc.edit_game : loc.create_game), actions: [ if (isEditMode()) - IconButton( + HapticIconButton( icon: const Icon(Icons.delete), onPressed: () async { if (!context.mounted) return; diff --git a/lib/presentation/views/main_menu/match_view/match_detail_view.dart b/lib/presentation/views/main_menu/match_view/match_detail_view.dart index 73d534d..b34f9df 100644 --- a/lib/presentation/views/main_menu/match_view/match_detail_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_detail_view.dart @@ -11,6 +11,7 @@ import 'package:tallee/data/models/match.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_match_view.dart'; import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart'; +import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart'; import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart'; import 'package:tallee/presentation/widgets/colored_icon_container.dart'; import 'package:tallee/presentation/widgets/dialog/custom_alert_dialog.dart'; @@ -60,7 +61,7 @@ class _MatchDetailViewState extends State { appBar: AppBar( title: Text(loc.match_profile), actions: [ - IconButton( + HapticIconButton( icon: const Icon(Icons.delete), onPressed: () async { showDialog( 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 ba138d6..47f91c5 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 @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/core/enums.dart'; @@ -8,6 +9,7 @@ import 'package:tallee/data/models/player.dart'; import 'package:tallee/data/models/score_entry.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart'; +import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart'; import 'package:tallee/presentation/widgets/tiles/match_result_view/custom_checkbox_list_tile.dart'; import 'package:tallee/presentation/widgets/tiles/match_result_view/custom_radio_list_tile.dart'; import 'package:tallee/presentation/widgets/tiles/match_result_view/live_edit_list_tile.dart'; @@ -112,7 +114,7 @@ class _MatchResultViewState extends State { return Scaffold( backgroundColor: CustomTheme.backgroundColor, appBar: AppBar( - leading: IconButton( + leading: HapticIconButton( icon: const Icon(Icons.close), onPressed: () { widget.onWinnerChanged?.call(); @@ -204,6 +206,7 @@ class _MatchResultViewState extends State { : RadioGroup( groupValue: _selectedPlayer, onChanged: (Player? value) async { + await HapticFeedback.selectionClick(); setState(() { _selectedPlayer = value; }); @@ -217,6 +220,7 @@ class _MatchResultViewState extends State { text: allPlayers[index].name, value: allPlayers[index], onContainerTap: (value) async { + await HapticFeedback.selectionClick(); setState(() { // Check if the already selected player is the same as the newly tapped player. if (_selectedPlayer == value) { @@ -338,6 +342,12 @@ class _MatchResultViewState extends State { }, ); }, + onReorderStart: (int n) async { + await HapticFeedback.selectionClick(); + }, + onReorderEnd: (int n) async { + await HapticFeedback.selectionClick(); + }, onReorder: (int oldIndex, int newIndex) { setState(() { if (newIndex > oldIndex) { 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 c393ae5..d873b7a 100644 --- a/lib/presentation/views/main_menu/settings_view/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view/settings_view.dart @@ -276,35 +276,38 @@ class _SettingsViewState extends State { final loc = AppLocalizations.of(context); switch (result) { case ImportResult.success: + await HapticFeedback.successNotification(); if (context.mounted) { - await HapticFeedback.successNotification(); + showSnackbar( + context: context, + message: loc.data_successfully_imported, + ); } - showSnackbar(context: context, message: loc.data_successfully_imported); case ImportResult.invalidSchema: + await HapticFeedback.errorNotification(); if (context.mounted) { - await HapticFeedback.errorNotification(); + showSnackbar(context: context, message: loc.invalid_schema); } - showSnackbar(context: context, message: loc.invalid_schema); case ImportResult.fileReadError: + await HapticFeedback.errorNotification(); if (context.mounted) { - await HapticFeedback.errorNotification(); + showSnackbar(context: context, message: loc.error_reading_file); } - showSnackbar(context: context, message: loc.error_reading_file); case ImportResult.canceled: + await HapticFeedback.errorNotification(); if (context.mounted) { - await HapticFeedback.errorNotification(); + showSnackbar(context: context, message: loc.import_canceled); } - showSnackbar(context: context, message: loc.import_canceled); case ImportResult.formatException: + await HapticFeedback.errorNotification(); if (context.mounted) { - await HapticFeedback.errorNotification(); + showSnackbar(context: context, message: loc.format_exception); } - showSnackbar(context: context, message: loc.format_exception); case ImportResult.unknownException: + await HapticFeedback.errorNotification(); if (context.mounted) { - await HapticFeedback.errorNotification(); + showSnackbar(context: context, message: loc.unknown_exception); } - showSnackbar(context: context, message: loc.unknown_exception); } } @@ -320,13 +323,22 @@ class _SettingsViewState extends State { switch (result) { case ExportResult.success: await HapticFeedback.successNotification(); - showSnackbar(context: context, message: loc.data_successfully_exported); + if (context.mounted) { + showSnackbar( + context: context, + message: loc.data_successfully_exported, + ); + } case ExportResult.canceled: await HapticFeedback.errorNotification(); - showSnackbar(context: context, message: loc.export_canceled); - await HapticFeedback.errorNotification(); + if (context.mounted) { + showSnackbar(context: context, message: loc.export_canceled); + } case ExportResult.unknownException: - showSnackbar(context: context, message: loc.unknown_exception); + await HapticFeedback.errorNotification(); + if (context.mounted) { + showSnackbar(context: context, message: loc.unknown_exception); + } } } diff --git a/lib/presentation/widgets/buttons/custom_width_button.dart b/lib/presentation/widgets/buttons/custom_width_button.dart index 4fde6f8..556b784 100644 --- a/lib/presentation/widgets/buttons/custom_width_button.dart +++ b/lib/presentation/widgets/buttons/custom_width_button.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/core/enums.dart'; @@ -48,7 +49,12 @@ class CustomWidthButton extends StatelessWidget { )!; return ElevatedButton( - onPressed: onPressed, + onPressed: onPressed == null + ? null + : () async { + await HapticFeedback.selectionClick(); + onPressed!.call(); + }, style: ElevatedButton.styleFrom( foregroundColor: textcolor, disabledForegroundColor: disabledTextColor, @@ -78,7 +84,12 @@ class CustomWidthButton extends StatelessWidget { : Color.lerp(CustomTheme.primaryColor, Colors.black, 0.5)!; return OutlinedButton( - onPressed: onPressed, + onPressed: onPressed == null + ? null + : () async { + await HapticFeedback.selectionClick(); + onPressed!.call(); + }, style: OutlinedButton.styleFrom( foregroundColor: textcolor, disabledForegroundColor: disabledTextColor, @@ -110,7 +121,12 @@ class CustomWidthButton extends StatelessWidget { disabledBackgroundColor = Colors.transparent; return TextButton( - onPressed: onPressed, + onPressed: onPressed == null + ? null + : () async { + await HapticFeedback.selectionClick(); + onPressed!.call(); + }, style: TextButton.styleFrom( foregroundColor: textcolor, disabledForegroundColor: disabledTextColor, diff --git a/lib/presentation/widgets/buttons/haptic_back_button.dart b/lib/presentation/widgets/buttons/haptic_back_button.dart index 6f3f76a..4b672bf 100644 --- a/lib/presentation/widgets/buttons/haptic_back_button.dart +++ b/lib/presentation/widgets/buttons/haptic_back_button.dart @@ -1,6 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; +import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart'; class HapticBackButton extends StatelessWidget { const HapticBackButton({super.key}); @@ -13,10 +13,9 @@ class HapticBackButton extends StatelessWidget { _ => Icons.arrow_back_rounded, }; - return IconButton( + return HapticIconButton( icon: Icon(iconData), onPressed: () async { - await HapticFeedback.mediumImpact(); Navigator.of(context).maybePop(); }, ); diff --git a/lib/presentation/widgets/buttons/haptic_close_button.dart b/lib/presentation/widgets/buttons/haptic_close_button.dart index bbd8455..f9e2f8b 100644 --- a/lib/presentation/widgets/buttons/haptic_close_button.dart +++ b/lib/presentation/widgets/buttons/haptic_close_button.dart @@ -1,7 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; +import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart'; class HapticCloseButton extends StatelessWidget { const HapticCloseButton({super.key}); @@ -13,10 +13,9 @@ class HapticCloseButton extends StatelessWidget { _ => Icons.close_rounded, }; - return IconButton( + return HapticIconButton( icon: Icon(iconData), onPressed: () async { - await HapticFeedback.mediumImpact(); Navigator.of(context).maybePop(); }, ); diff --git a/lib/presentation/widgets/buttons/haptic_icon_button.dart b/lib/presentation/widgets/buttons/haptic_icon_button.dart new file mode 100644 index 0000000..8da8e9f --- /dev/null +++ b/lib/presentation/widgets/buttons/haptic_icon_button.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class HapticIconButton extends StatelessWidget { + const HapticIconButton({ + super.key, + required this.icon, + required this.onPressed, + this.tooltip, + this.iconSize, + this.color, + this.padding, + this.alignment, + this.constraints, + this.style, + this.isSelected, + this.selectedIcon, + }); + + final Widget icon; + final VoidCallback? onPressed; + + final String? tooltip; + final double? iconSize; + final Color? color; + final EdgeInsetsGeometry? padding; + final AlignmentGeometry? alignment; + final BoxConstraints? constraints; + final ButtonStyle? style; + final bool? isSelected; + final Widget? selectedIcon; + + @override + Widget build(BuildContext context) { + return IconButton( + tooltip: tooltip, + iconSize: iconSize, + color: color, + padding: padding, + alignment: alignment ?? Alignment.center, + constraints: constraints, + style: style, + isSelected: isSelected, + selectedIcon: selectedIcon, + icon: icon, + onPressed: onPressed == null + ? null + : () async { + await HapticFeedback.selectionClick(); + onPressed!.call(); + }, + ); + } +} diff --git a/lib/presentation/widgets/buttons/main_menu_button.dart b/lib/presentation/widgets/buttons/main_menu_button.dart index c5c7a34..708c0f4 100644 --- a/lib/presentation/widgets/buttons/main_menu_button.dart +++ b/lib/presentation/widgets/buttons/main_menu_button.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; class MainMenuButton extends StatefulWidget { /// A button for the main menu with an optional icon and a press animation. @@ -78,6 +79,7 @@ class _MainMenuButtonState extends State onTapUp: (_) async { _cancelTimers(); if (mounted && !_isLongPressing) { + await HapticFeedback.selectionClick(); widget.onPressed(); } _isLongPressing = false; diff --git a/lib/presentation/widgets/dialog/custom_dialog_action.dart b/lib/presentation/widgets/dialog/custom_dialog_action.dart index 47024dc..26dc40d 100644 --- a/lib/presentation/widgets/dialog/custom_dialog_action.dart +++ b/lib/presentation/widgets/dialog/custom_dialog_action.dart @@ -1,4 +1,5 @@ import 'package:flutter/cupertino.dart'; +import 'package:flutter/services.dart'; import 'package:tallee/core/enums.dart'; import 'package:tallee/presentation/widgets/buttons/animated_dialog_button.dart'; @@ -26,7 +27,10 @@ class CustomDialogAction extends StatelessWidget { @override Widget build(BuildContext context) { return AnimatedDialogButton( - onPressed: onPressed, + onPressed: () async { + await HapticFeedback.selectionClick(); + onPressed.call(); + }, buttonText: text, buttonType: buttonType, isDescructive: isDestructive, diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index b13e098..00d6c11 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -144,7 +144,8 @@ class _PlayerSelectionState extends State { text: player.name, suffixText: getNameCountText(player), onIconTap: () { - setState(() { + setState(() async { + await HapticFeedback.selectionClick(); // Removes the player from the selection and notifies the parent. selectedPlayers.remove(player); widget.onChanged([...selectedPlayers]); diff --git a/lib/presentation/widgets/tiles/choose_tile.dart b/lib/presentation/widgets/tiles/choose_tile.dart index 41cc7f0..f69aefc 100644 --- a/lib/presentation/widgets/tiles/choose_tile.dart +++ b/lib/presentation/widgets/tiles/choose_tile.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:tallee/core/custom_theme.dart'; class ChooseTile extends StatefulWidget { @@ -30,7 +31,12 @@ class _ChooseTileState extends State { @override Widget build(BuildContext context) { return GestureDetector( - onTap: widget.onPressed, + onTap: () async { + await HapticFeedback.vibrate(); + if (widget.onPressed != null) { + widget.onPressed!.call(); + } + }, child: Container( margin: CustomTheme.tileMargin, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), diff --git a/lib/presentation/widgets/tiles/match_result_view/custom_checkbox_list_tile.dart b/lib/presentation/widgets/tiles/match_result_view/custom_checkbox_list_tile.dart index 77c9242..bb6c933 100644 --- a/lib/presentation/widgets/tiles/match_result_view/custom_checkbox_list_tile.dart +++ b/lib/presentation/widgets/tiles/match_result_view/custom_checkbox_list_tile.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:tallee/core/custom_theme.dart'; class CustomCheckboxListTile extends StatelessWidget { @@ -16,7 +17,10 @@ class CustomCheckboxListTile extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( - onTap: () => onChanged(!value), + onTap: () async { + await HapticFeedback.selectionClick(); + onChanged(!value); + }, child: Container( margin: const EdgeInsets.symmetric(horizontal: 5, vertical: 5), padding: const EdgeInsets.symmetric(horizontal: 2), @@ -29,7 +33,8 @@ class CustomCheckboxListTile extends StatelessWidget { children: [ Checkbox( value: value, - onChanged: (bool? v) { + onChanged: (bool? v) async { + await HapticFeedback.selectionClick(); if (v == null) return; onChanged(v); }, diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index 018c896..6a81dc3 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -1,6 +1,7 @@ import 'dart:core' hide Match; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:intl/intl.dart'; import 'package:tallee/core/common.dart'; import 'package:tallee/core/custom_theme.dart'; @@ -51,7 +52,10 @@ class _MatchTileState extends State { final loc = AppLocalizations.of(context); return GestureDetector( - onTap: widget.onTap, + onTap: () async { + await HapticFeedback.selectionClick(); + widget.onTap.call(); + }, child: Container( margin: EdgeInsets.zero, width: widget.width, From f1899bfe449185187fbc00c01660f13599cf5db5 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Mon, 11 May 2026 10:59:48 +0200 Subject: [PATCH 20/34] feat: add haptic feedback to even more user interactions --- .../create_match/choose_group_view.dart | 10 ++++------ .../create_match/create_game_view.dart | 19 +++++++++++++++++-- .../create_match/create_match_view.dart | 1 - .../widgets/tiles/choose_tile.dart | 14 ++++++++------ lib/presentation/widgets/tiles/game_tile.dart | 15 +++++++++++++-- .../widgets/tiles/group_tile.dart | 4 +++- 6 files changed, 45 insertions(+), 18 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 1caa101..2ef8b68 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 @@ -112,7 +112,10 @@ class _ChooseGroupViewState extends State { padding: const EdgeInsets.only(bottom: 85), itemCount: filteredGroups.length, itemBuilder: (BuildContext context, int index) { - return GestureDetector( + return GroupTile( + group: filteredGroups[index], + isHighlighted: + selectedGroupId == filteredGroups[index].id, onTap: () { setState(() { if (selectedGroupId != filteredGroups[index].id) { @@ -122,11 +125,6 @@ class _ChooseGroupViewState extends State { } }); }, - child: GroupTile( - group: filteredGroups[index], - isHighlighted: - selectedGroupId == filteredGroups[index].id, - ), ); }, ), diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart index 4b4a977..998f4e1 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_game_view.dart @@ -1,6 +1,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_popup/flutter_popup.dart'; import 'package:provider/provider.dart'; import 'package:tallee/core/common.dart'; @@ -330,6 +331,12 @@ class _CreateGameViewState extends State { contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 10), barrierColor: Colors.transparent, contentDecoration: CustomTheme.standardBoxDecoration, + onBeforePopup: () async { + await HapticFeedback.selectionClick(); + }, + onAfterPopup: () async { + await HapticFeedback.selectionClick(); + }, content: StatefulBuilder( builder: (context, setPopupState) => SizedBox( width: 280, @@ -339,7 +346,8 @@ class _CreateGameViewState extends State { children: List.generate( _rulesets.length, (index) => GestureDetector( - onTap: () { + onTap: () async { + await HapticFeedback.selectionClick(); setState(() { selectedRuleset = _rulesets[index].$1; }); @@ -413,6 +421,12 @@ class _CreateGameViewState extends State { contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 10), barrierColor: Colors.transparent, contentDecoration: CustomTheme.standardBoxDecoration, + onBeforePopup: () async { + await HapticFeedback.selectionClick(); + }, + onAfterPopup: () async { + await HapticFeedback.selectionClick(); + }, content: StatefulBuilder( builder: (context, setPopupState) => SizedBox( width: 150, @@ -422,7 +436,8 @@ class _CreateGameViewState extends State { children: List.generate( _colors.length, (index) => GestureDetector( - onTap: () { + onTap: () async { + await HapticFeedback.selectionClick(); setState(() { selectedColor = _colors[index].$1; }); 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 fd98691..85bb936 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 @@ -172,7 +172,6 @@ class _CreateMatchViewState extends State { ) ?? false, ); - selectedGroup = await Navigator.of(context).push( adaptivePageRoute( builder: (context) => ChooseGroupView( diff --git a/lib/presentation/widgets/tiles/choose_tile.dart b/lib/presentation/widgets/tiles/choose_tile.dart index f69aefc..39c3631 100644 --- a/lib/presentation/widgets/tiles/choose_tile.dart +++ b/lib/presentation/widgets/tiles/choose_tile.dart @@ -31,12 +31,14 @@ class _ChooseTileState extends State { @override Widget build(BuildContext context) { return GestureDetector( - onTap: () async { - await HapticFeedback.vibrate(); - if (widget.onPressed != null) { - widget.onPressed!.call(); - } - }, + onTap: widget.onPressed != null + ? () async { + await HapticFeedback.selectionClick(); + if (widget.onPressed != null) { + widget.onPressed!.call(); + } + } + : null, child: Container( margin: CustomTheme.tileMargin, padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), diff --git a/lib/presentation/widgets/tiles/game_tile.dart b/lib/presentation/widgets/tiles/game_tile.dart index 1d494b9..80881d0 100644 --- a/lib/presentation/widgets/tiles/game_tile.dart +++ b/lib/presentation/widgets/tiles/game_tile.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:tallee/core/common.dart'; import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/core/enums.dart'; @@ -53,8 +54,18 @@ class GameTile extends StatelessWidget { final gameColor = badgeColor ?? getColorFromGameColor(GameColor.orange); return GestureDetector( - onTap: onTap, - onLongPress: onLongPress, + onTap: () async { + await HapticFeedback.selectionClick(); + if (onTap != null) { + onTap!.call(); + } + }, + onLongPress: () async { + await HapticFeedback.vibrate(); + if (onLongPress != null) { + onLongPress!.call(); + } + }, child: AnimatedContainer( margin: const EdgeInsets.symmetric(vertical: 10, horizontal: 10), decoration: !isHighlighted diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index 9ce5a6b..f6c406e 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -36,7 +36,9 @@ class _GroupTileState extends State { return GestureDetector( onTap: () async { await HapticFeedback.selectionClick(); - widget.onTap?.call(); + if (widget.onTap != null) { + widget.onTap!.call(); + } }, child: AnimatedContainer( margin: CustomTheme.standardMargin, From 424fa34cabe59482e185f0b2199b1b2578692b86 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Mon, 11 May 2026 18:54:50 +0000 Subject: [PATCH 21/34] bump version to 3.41.0 --- .gitea/workflows/pull_request.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml index 26f4404..825305b 100644 --- a/.gitea/workflows/pull_request.yaml +++ b/.gitea/workflows/pull_request.yaml @@ -20,11 +20,11 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.6 + flutter-version: 3.41.0 - name: Get dependencies run: | - git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.38.6-x64 + git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.41.0-x64 flutter pub get - name: Analyze Formatting @@ -46,11 +46,11 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.6 + flutter-version: 3.41.0 - name: Get dependencies run: | - git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.38.6-x64 + git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.41.0-x64 flutter pub get - name: Run tests From 2aed053c4202618c463e772c73e05de4c1d867e9 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Mon, 11 May 2026 18:55:51 +0000 Subject: [PATCH 22/34] bump flutter version --- .gitea/workflows/push.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.gitea/workflows/push.yaml b/.gitea/workflows/push.yaml index e24f7ad..cfe987a 100644 --- a/.gitea/workflows/push.yaml +++ b/.gitea/workflows/push.yaml @@ -32,11 +32,11 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.6 + flutter-version: 3.41.0 - name: Get dependencies run: | - git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.38.6-x64 + git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.41.0-x64 flutter pub get - name: Build APK @@ -58,11 +58,11 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.6 + flutter-version: 3.41.0 - name: Get dependencies run: | - git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.38.6-x64 + git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.41.0-x64 flutter pub get - name: Run tests @@ -118,11 +118,11 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.6 + flutter-version: 3.41.0 - name: Get dependencies run: | - git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.38.6-x64 + git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.41.0-x64 flutter pub get - name: Generate oss_licenses.dart @@ -161,11 +161,11 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.6 + flutter-version: 3.41.0 - name: Get dependencies run: | - git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.38.6-x64 + git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.41.0-x64 flutter pub get - name: Check code format From 0f3863d446e116c14643245de283444eb5d55094 Mon Sep 17 00:00:00 2001 From: "Gitea Actions [bot]" Date: Mon, 11 May 2026 19:14:13 +0000 Subject: [PATCH 23/34] Updated version number [skip ci] --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 9b0042b..8857c57 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: tallee description: "Tracking App for Card Games" publish_to: 'none' -version: 0.0.31+265 +version: 0.0.32+266 environment: sdk: ^3.8.1 From 7a22deea1b26fba63571b7ad4d29c6cfa28b0bfe Mon Sep 17 00:00:00 2001 From: "Gitea Actions [bot]" Date: Mon, 11 May 2026 19:15:48 +0000 Subject: [PATCH 24/34] Updated licenses [skip ci] --- .../settings_view/licenses/oss_licenses.dart | 478 ++++++++++-------- 1 file changed, 269 insertions(+), 209 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 ee5558a..1cab79e 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 @@ -73,7 +73,6 @@ const allDependencies = [ _io, _jni, _jni_flutter, - _js, _json_annotation, _json_schema, _leak_tracker, @@ -244,13 +243,13 @@ class PackageRef { Package resolve() => allDependencies.firstWhere((d) => d.name == name); } -/// _fe_analyzer_shared 91.0.0 +/// _fe_analyzer_shared 92.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: '91.0.0', + version: '92.0.0', spdxIdentifiers: ['BSD-3-Clause'], isMarkdown: false, isSdk: false, @@ -285,13 +284,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// analyzer 8.4.1 +/// analyzer 9.0.0 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: '8.4.1', + version: '9.0.0', spdxIdentifiers: ['BSD-3-Clause'], isMarkdown: false, isSdk: false, @@ -699,13 +698,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// characters 1.4.0 +/// characters 1.4.1 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', + version: '1.4.1', spdxIdentifiers: ['BSD-3-Clause'], isMarkdown: false, isSdk: false, @@ -2503,13 +2502,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// flutter 3.38.6 +/// flutter 3.41.0 const _flutter = Package( name: 'flutter', description: 'A framework for writing Flutter applications', homepage: 'https://flutter.dev', authors: [], - version: '3.38.6', + version: '3.41.0', spdxIdentifiers: ['BSD-3-Clause'], isMarkdown: false, isSdk: true, @@ -3292,47 +3291,6 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 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.11.0 const _json_annotation = Package( name: 'json_annotation', @@ -3671,18 +3629,18 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// matcher 0.12.17 +/// matcher 0.12.18 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', + version: '0.12.18', 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')], + devDependencies: [PackageRef('fake_async'), PackageRef('test')], license: '''Copyright 2014, the Dart project authors. Redistribution and use in source and binary forms, with or without @@ -3712,18 +3670,18 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// material_color_utilities 0.11.1 +/// material_color_utilities 0.13.0 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', + version: '0.13.0', spdxIdentifiers: ['Apache-2.0'], isMarkdown: false, isSdk: false, dependencies: [PackageRef('collection')], - devDependencies: [PackageRef('matcher'), PackageRef('lints'), PackageRef('test')], + devDependencies: [PackageRef('matcher'), PackageRef('test')], license: '''Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -6288,6 +6246,7 @@ 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. -------------------------------------------------------------------------------- +icu json @@ -6913,6 +6872,7 @@ skia limitations under the License. -------------------------------------------------------------------------------- angle +benchmark boringssl cpu_features flatbuffers @@ -10342,6 +10302,7 @@ prospectively choose to deem waived or otherwise exclude such Section(s) of the License, but only in their entirety and only with respect to the Combined Software. -------------------------------------------------------------------------------- +icu include json @@ -13912,34 +13873,6 @@ License & terms of use: http://www.unicode.org/copyright.html All Rights Reserved. --------------------------------------------------------------------------------- -icu - -Copyright (C) 2016 and later: Unicode, Inc. and others. -License & terms of use: http://www.unicode.org/copyright.html - -Copyright (C) 2002-2010, International Business Machines -Corporation and others. All Rights Reserved. - - --------------------------------------------------------------------------------- -icu - -Copyright (C) 2016 and later: Unicode, Inc. and others. -License & terms of use: http://www.unicode.org/copyright.html - -Copyright (c) 2001-2003 International Business Machines -Corporation and others. All Rights Reserved. - --------------------------------------------------------------------------------- -icu - -Copyright (C) 2016 and later: Unicode, Inc. and others. -License & terms of use: http://www.unicode.org/copyright.html - -Copyright (c) 2001-2010 International Business Machines -Corporation and others. All Rights Reserved. - -------------------------------------------------------------------------------- icu @@ -13954,14 +13887,6 @@ icu Copyright (C) 2016 and later: Unicode, Inc. and others. License & terms of use: http://www.unicode.org/copyright.html -Copyright (c) 2002-2010, International Business Machines Corporation and others. All Rights Reserved. - --------------------------------------------------------------------------------- -icu - -Copyright (C) 2016 and later: Unicode, Inc. and others. -License & terms of use: http://www.unicode.org/copyright.html - Copyright (c) 2002-2015, International Business Machines Corporation and others. All Rights Reserved. @@ -13976,22 +13901,6 @@ Copyright (c) 2002-2016 International Business Machines Corporation and others. All Rights Reserved. --------------------------------------------------------------------------------- -icu - -Copyright (C) 2016 and later: Unicode, Inc. and others. -License & terms of use: http://www.unicode.org/copyright.html - -Copyright (c) 2003-2005, International Business Machines Corporation and others. All Rights Reserved. - --------------------------------------------------------------------------------- -icu - -Copyright (C) 2016 and later: Unicode, Inc. and others. -License & terms of use: http://www.unicode.org/copyright.html - -Copyright (c) 2003-2010, International Business Machines Corporation and others. All Rights Reserved. - -------------------------------------------------------------------------------- icu @@ -14166,17 +14075,6 @@ License & terms of use: http://www.unicode.org/copyright.html -------------------------------------------------------------------------------- icu -Copyright (C) 2016 and later: Unicode, Inc. and others. -License & terms of use: http://www.unicode.org/copyright.html -*************************************************************************** -* -* Copyright (C) 2009 International Business Machines -* Corporation and others. All Rights Reserved. -* -*************************************************************************** --------------------------------------------------------------------------------- -icu - Copyright (C) 2016 and later: Unicode, Inc. and others. License & terms of use: http://www.unicode.org/copyright.html ***************************************************************************** @@ -14187,19 +14085,6 @@ License & terms of use: http://www.unicode.org/copyright.html ***************************************************************************** --------------------------------------------------------------------------------- -icu - -Copyright (C) 2016 and later: Unicode, Inc. and others. -License & terms of use: http://www.unicode.org/copyright.html -******************************************************************************* -* -* Copyright (C) 1995-2001, International Business Machines -* Corporation and others. All Rights Reserved. -* -******************************************************************************* - - -------------------------------------------------------------------------------- icu @@ -14278,6 +14163,15 @@ License & terms of use: http://www.unicode.org/copyright.html ******************************************************************************* +-------------------------------------------------------------------------------- +icu + +Copyright (C) 2016 and later: Unicode, Inc. and others. +License & terms of use: http://www.unicode.org/copyright.html +--------------------------------------------------------- +Copyright (C) 2013, International Business Machines +Corporation and others. All Rights Reserved. + -------------------------------------------------------------------------------- icu @@ -15561,6 +15455,22 @@ angle Copyright (c) 2008-2021 The Khronos Group 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. +-------------------------------------------------------------------------------- +angle + +Copyright (c) 2008-2023 The Khronos Group 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 @@ -18045,6 +17955,28 @@ 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. -------------------------------------------------------------------------------- +volk + +Copyright (c) 2018-2024 Arseny Kapoulkine + +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. +-------------------------------------------------------------------------------- vulkan-validation-layers Copyright (c) 2018-2024 The Khronos Group Inc. @@ -18569,7 +18501,6 @@ Copyright (c) 2020 The ANGLE Project Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. -------------------------------------------------------------------------------- -angle spirv-tools Copyright (c) 2020 The Khronos Group Inc. @@ -19380,6 +19311,22 @@ spirv-tools Copyright (c) 2023 LunarG 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. +-------------------------------------------------------------------------------- +angle + +Copyright (c) 2023 The Khronos Group 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 @@ -19417,6 +19364,7 @@ for details. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. -------------------------------------------------------------------------------- dart +perfetto Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file for details. All rights reserved. Use of this source code is governed by a @@ -19950,6 +19898,12 @@ Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file for details. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. -------------------------------------------------------------------------------- +perfetto + +Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +for details. All rights reserved. Use of this source code is governed by a +BSD-style license that can be found in the LICENSE file. +-------------------------------------------------------------------------------- glfw Copyright (c) Camilla Löwy @@ -20809,11 +20763,6 @@ Copyright 2014-2022 The Khronos Group Inc. SPDX-License-Identifier: Apache-2.0 -------------------------------------------------------------------------------- swiftshader - -Copyright 2014-2023 The Khronos Group Inc. - -SPDX-License-Identifier: Apache-2.0 --------------------------------------------------------------------------------- vulkan vulkan-headers @@ -20851,6 +20800,7 @@ tree. An additional intellectual property rights grant can be found in the file PATENTS. All contributing project authors may be found in the AUTHORS file in the root of the source tree. -------------------------------------------------------------------------------- +benchmark flatbuffers Copyright 2015 Google Inc. All rights reserved. @@ -20936,12 +20886,6 @@ See the License for the specific language governing permissions and limitations under the License. -------------------------------------------------------------------------------- swiftshader - -Copyright 2015-2023 The Khronos Group Inc. - -SPDX-License-Identifier: Apache-2.0 --------------------------------------------------------------------------------- -swiftshader vulkan vulkan-headers @@ -20951,6 +20895,7 @@ Copyright 2015-2023 LunarG, Inc. SPDX-License-Identifier: Apache-2.0 -------------------------------------------------------------------------------- +swiftshader vulkan vulkan-headers @@ -20978,18 +20923,6 @@ skia Copyright 2016 Google Inc. -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. --------------------------------------------------------------------------------- -skia - -Copyright 2016 Google Inc. - -Use of this source code is governed by a BSD-style license that can be -found in the LICENSE file. - -Copyright 2014 Google Inc. - Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. -------------------------------------------------------------------------------- @@ -21007,6 +20940,39 @@ flatbuffers Copyright 2016 Google Inc. All rights reserved. +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. +-------------------------------------------------------------------------------- +benchmark + +Copyright 2016 Ismael Jimenez Martinez. All rights reserved. + +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. +-------------------------------------------------------------------------------- +benchmark + +Copyright 2016 Ismael Jimenez Martinez. All rights reserved. +Copyright 2017 Roman Lebedev. All rights reserved. + 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 @@ -21437,6 +21403,7 @@ tree. An additional intellectual property rights grant can be found in the file PATENTS. All contributing project authors may be found in the AUTHORS file in the root of the source tree. -------------------------------------------------------------------------------- +benchmark flatbuffers Copyright 2018 Google Inc. All rights reserved. @@ -22213,7 +22180,6 @@ 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. -------------------------------------------------------------------------------- -angle swiftshader Copyright 2020 The SwiftShader Authors. All Rights Reserved. @@ -22263,6 +22229,7 @@ tree. An additional intellectual property rights grant can be found in the file PATENTS. All contributing project authors may be found in the AUTHORS file in the root of the source tree. -------------------------------------------------------------------------------- +benchmark flatbuffers Copyright 2021 Google Inc. All rights reserved. @@ -22695,6 +22662,22 @@ Copyright 2023 The ANGLE Project Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. -------------------------------------------------------------------------------- +angle + +Copyright 2023 The Android Open Source Project + +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. +-------------------------------------------------------------------------------- skia Copyright 2023 The Android Open Source Project @@ -22772,6 +22755,7 @@ Copyright 2023-2025 The Khronos Group Inc. SPDX-License-Identifier: Apache-2.0 -------------------------------------------------------------------------------- +swiftshader vulkan-utility-libraries Copyright 2023-2025 The Khronos Group Inc. @@ -22789,6 +22773,12 @@ skia Copyright 2024 Google Inc. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +-------------------------------------------------------------------------------- +angle + +Copyright 2024 Google Inc. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. -------------------------------------------------------------------------------- @@ -22824,6 +22814,12 @@ found in the LICENSE file. -------------------------------------------------------------------------------- skia +Copyright 2024 Google LLC. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +-------------------------------------------------------------------------------- +skia + Copyright 2024 Google LLC. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. -------------------------------------------------------------------------------- @@ -22834,6 +22830,17 @@ Copyright 2024 Google, LLC Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. -------------------------------------------------------------------------------- +angle + +Copyright 2024 The ANGLE Project Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +-------------------------------------------------------------------------------- +angle + +Copyright 2024 The ANGLE Project Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. +-------------------------------------------------------------------------------- skia Copyright 2024 The Android Open Source Project @@ -22915,6 +22922,19 @@ skia Copyright 2025 Google LLC. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +-------------------------------------------------------------------------------- +skia + +Copyright 2025 Google, LLC + +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +-------------------------------------------------------------------------------- +angle + +Copyright 2025 The ANGLE Project Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. -------------------------------------------------------------------------------- @@ -22979,6 +22999,12 @@ Copyright The ANGLE Project Authors. All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. -------------------------------------------------------------------------------- +angle + +Copyright {copyright_year} The ANGLE Project Authors. All rights reserved. +Use of this source code is governed by a BSD-style license that can be +found in the LICENSE file. +-------------------------------------------------------------------------------- harfbuzz Copyright © 1998-2004 David Turner and Werner Lemberg @@ -26723,6 +26749,48 @@ FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. -------------------------------------------------------------------------------- +flutter + +License for the Ahem font embedded below is from: +https://www.w3.org/Style/CSS/Test/Fonts/Ahem/COPYING + +The Ahem font in this directory belongs to the public domain. In +jurisdictions that do not recognize public domain ownership of these +files, the following Creative Commons Zero declaration applies: + + + +which is quoted below: + + The person who has associated a work with this document (the "Work") + affirms that he or she (the "Affirmer") is the/an author or owner of + the Work. The Work may be any work of authorship, including a + database. + + The Affirmer hereby fully, permanently and irrevocably waives and + relinquishes all of her or his copyright and related or neighboring + legal rights in the Work available under any federal or state law, + treaty or contract, including but not limited to moral rights, + publicity and privacy rights, rights protecting against unfair + competition and any rights protecting the extraction, dissemination + and reuse of data, whether such rights are present or future, vested + or contingent (the "Waiver"). The Affirmer makes the Waiver for the + benefit of the public at large and to the detriment of the Affirmer's + heirs or successors. + + The Affirmer understands and intends that the Waiver has the effect + of eliminating and entirely removing from the Affirmer's control all + the copyright and related or neighboring legal rights previously held + by the Affirmer in the Work, to that extent making the Work freely + available to the public for any and all uses and purposes without + restriction of any kind, including commercial use and uses in media + and formats or by methods that have not yet been invented or + conceived. Should the Waiver for any reason be judged legally + ineffective in any jurisdiction, the Affirmer hereby grants a free, + full, permanent, irrevocable, nonexclusive and worldwide license for + all her or his copyright and related or neighboring legal rights in + the Work. +-------------------------------------------------------------------------------- fallback_root_certificates Mozilla Public License Version 2.0 @@ -27105,8 +27173,8 @@ libpng PNG Reference Library License version 2 --------------------------------------- - * Copyright (c) 1995-2024 The PNG Reference Library Authors. - * Copyright (c) 2018-2024 Cosmin Truta. + * Copyright (c) 1995-2025 The PNG Reference Library Authors. + * Copyright (c) 2018-2025 Cosmin Truta. * Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson. * Copyright (c) 1996-1997 Andreas Dilger. * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. @@ -27240,8 +27308,8 @@ libpng PNG Reference Library License version 2 --------------------------------------- - * Copyright (c) 1995-2024 The PNG Reference Library Authors. - * Copyright (c) 2018-2024 Cosmin Truta. + * Copyright (c) 1995-2025 The PNG Reference Library Authors. + * Copyright (c) 2018-2025 Cosmin Truta. * Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson. * Copyright (c) 1996-1997 Andreas Dilger. * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. @@ -28708,7 +28776,7 @@ UNICODE LICENSE V3 COPYRIGHT AND PERMISSION NOTICE -Copyright © 2016-2023 Unicode, Inc. +Copyright © 2016-2025 Unicode, Inc. NOTICE TO USER: Carefully read the following legal agreement. BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR @@ -28744,6 +28812,8 @@ not be used in advertising or otherwise to promote the sale, use or other dealings in these Data Files or Software without prior written authorization of the copyright holder. +SPDX-License-Identifier: Unicode-3.0 + ---------------------------------------------------------------------- Third-Party Software Licenses @@ -29137,6 +29207,34 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---------------------------------------------------------------------- +JSON parsing library (nlohmann/json) + +File: vendor/json/upstream/single_include/nlohmann/json.hpp (only for ICU4C) + +MIT License + +Copyright (c) 2013-2022 Niels Lohmann + +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. + +---------------------------------------------------------------------- + File: install-sh (only for ICU4C) @@ -32422,17 +32520,6 @@ Copyright (C) 2003-2016, International Business Machines --------------------------------------------------------------------------------- -icu - -© 2016 and later: Unicode, Inc. and others. -License & terms of use: http://www.unicode.org/copyright.html - - -Copyright (C) 2008-2013, International Business Machines Corporation and -others. All Rights Reserved. - - -------------------------------------------------------------------------------- icu @@ -32472,16 +32559,6 @@ icu License & terms of use: http://www.unicode.org/copyright.html -Copyright (c) 1999-2007, International Business Machines Corporation and -others. All Rights Reserved. - --------------------------------------------------------------------------------- -icu - -© 2016 and later: Unicode, Inc. and others. -License & terms of use: http://www.unicode.org/copyright.html - - Copyright (c) 1999-2010, International Business Machines Corporation and others. All Rights Reserved. @@ -35972,15 +36049,6 @@ others. All Rights Reserved. --------------------------------------------------------------------------------- -icu - -© 2016 and later: Unicode, Inc. and others. -License & terms of use: http://www.unicode.org/copyright.html -Copyright (C) 2008-2014, International Business Machines Corporation and -others. All Rights Reserved. - - -------------------------------------------------------------------------------- icu @@ -36220,14 +36288,6 @@ others. All Rights Reserved. -------------------------------------------------------------------------------- icu -© 2016 and later: Unicode, Inc. and others. -License & terms of use: http://www.unicode.org/copyright.html -Copyright (c) 2008-2014, International Business Machines Corporation and -others. All Rights Reserved. - --------------------------------------------------------------------------------- -icu - © 2016 and later: Unicode, Inc. and others. License & terms of use: http://www.unicode.org/copyright.html Copyright (c) IBM Corporation, 2000-2010. All rights reserved. @@ -36755,17 +36815,17 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// test 1.26.3 +/// test 1.28.0 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', + version: '1.28.0', 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')], + dependencies: [PackageRef('analyzer'), PackageRef('async'), PackageRef('boolean_selector'), PackageRef('collection'), PackageRef('coverage'), PackageRef('http_multi_server'), PackageRef('io'), 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. @@ -36796,13 +36856,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// test_api 0.7.7 +/// test_api 0.7.8 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', + version: '0.7.8', spdxIdentifiers: ['BSD-3-Clause'], isMarkdown: false, isSdk: false, @@ -36837,13 +36897,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// test_core 0.6.12 +/// test_core 0.6.14 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', + version: '0.6.14', spdxIdentifiers: ['BSD-3-Clause'], isMarkdown: false, isSdk: false, @@ -37751,12 +37811,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', ); -/// tallee 0.0.31+265 +/// tallee 0.0.32+266 const _tallee = Package( name: 'tallee', description: 'Tracking App for Card Games', authors: [], - version: '0.0.31+265', + version: '0.0.32+266', spdxIdentifiers: ['LGPL-3.0'], isMarkdown: false, isSdk: false, From b82cca939fc64a8037e74e65f8abb563c8b0f633 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Wed, 13 May 2026 12:37:11 +0200 Subject: [PATCH 25/34] remove unnecessary click --- lib/presentation/views/main_menu/custom_navigation_bar.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index 6e9e6d0..7e5434b 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -58,7 +58,6 @@ class _CustomNavigationBarState extends State HapticIconButton( onPressed: () async { final navigator = Navigator.of(context); - await HapticFeedback.selectionClick(); await navigator.push( adaptivePageRoute(builder: (_) => const SettingsView()), ); From 7e35ccae6bc4d91903afd4fce897524b3ea68926 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Wed, 13 May 2026 12:37:19 +0200 Subject: [PATCH 26/34] add vibration to long tap --- .../widgets/buttons/main_menu_button.dart | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/presentation/widgets/buttons/main_menu_button.dart b/lib/presentation/widgets/buttons/main_menu_button.dart index 708c0f4..497fea0 100644 --- a/lib/presentation/widgets/buttons/main_menu_button.dart +++ b/lib/presentation/widgets/buttons/main_menu_button.dart @@ -66,14 +66,18 @@ class _MainMenuButtonState extends State onTapDown: (_) { _animationController.forward(); if (widget.onLongPressed != null) { - _longPressTimer = Timer(const Duration(milliseconds: 400), () { - _isLongPressing = true; - widget.onLongPressed?.call(); - _repeatTimer = Timer.periodic( - const Duration(milliseconds: 250), - (_) => widget.onLongPressed?.call(), - ); - }); + _longPressTimer = Timer( + const Duration(milliseconds: 400), + () async { + _isLongPressing = true; + await HapticFeedback.vibrate(); + widget.onLongPressed?.call(); + _repeatTimer = Timer.periodic( + const Duration(milliseconds: 250), + (_) => widget.onLongPressed?.call(), + ); + }, + ); } }, onTapUp: (_) async { From a8369249b41fff05df941d5fbc84354611ee8ab2 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Wed, 13 May 2026 12:44:39 +0200 Subject: [PATCH 27/34] change settings_view buttons to haptic icon button --- .../settings_view/settings_view.dart | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 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 d873b7a..78e1b1b 100644 --- a/lib/presentation/views/main_menu/settings_view/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view/settings_view.dart @@ -10,6 +10,7 @@ import 'package:tallee/core/custom_theme.dart'; import 'package:tallee/core/enums.dart'; import 'package:tallee/l10n/generated/app_localizations.dart'; import 'package:tallee/presentation/views/main_menu/settings_view/licenses_view.dart'; +import 'package:tallee/presentation/widgets/buttons/haptic_icon_button.dart'; import 'package:tallee/presentation/widgets/dialog/custom_alert_dialog.dart'; import 'package:tallee/presentation/widgets/dialog/custom_dialog_action.dart'; import 'package:tallee/presentation/widgets/tiles/settings_list_tile.dart'; @@ -198,20 +199,22 @@ class _SettingsViewState extends State { padding: const EdgeInsets.only(bottom: 12), child: Row( mainAxisAlignment: MainAxisAlignment.center, - spacing: 40, + spacing: 10, children: [ - GestureDetector( - child: const Icon(Icons.language), - onTap: () async => { + HapticIconButton( + color: CustomTheme.textColor, + icon: const Icon(Icons.language), + onPressed: () async => { await HapticFeedback.lightImpact(), launchUrl( Uri.parse('https://liquid-dev.de'), ), }, ), - GestureDetector( - child: const FaIcon(FontAwesomeIcons.github), - onTap: () async => { + HapticIconButton( + color: CustomTheme.textColor, + icon: const FaIcon(FontAwesomeIcons.github), + onPressed: () async => { await HapticFeedback.lightImpact(), launchUrl( Uri.parse( @@ -220,13 +223,14 @@ class _SettingsViewState extends State { ), }, ), - GestureDetector( - child: Icon( + HapticIconButton( + color: CustomTheme.textColor, + icon: Icon( Platform.isIOS ? CupertinoIcons.mail_solid : Icons.email, ), - onTap: () async => { + onPressed: () async => { await HapticFeedback.lightImpact(), launchUrl( Uri.parse('mailto:hi@liquid-dev.de'), From f5fbb3ecc4326fcd7f5085da6114833cdeec6ced Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 13 May 2026 13:48:44 +0200 Subject: [PATCH 28/34] fix: long press feedback on every call --- lib/presentation/widgets/buttons/main_menu_button.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/presentation/widgets/buttons/main_menu_button.dart b/lib/presentation/widgets/buttons/main_menu_button.dart index 497fea0..c300eeb 100644 --- a/lib/presentation/widgets/buttons/main_menu_button.dart +++ b/lib/presentation/widgets/buttons/main_menu_button.dart @@ -70,11 +70,14 @@ class _MainMenuButtonState extends State const Duration(milliseconds: 400), () async { _isLongPressing = true; - await HapticFeedback.vibrate(); widget.onLongPressed?.call(); + await HapticFeedback.heavyImpact(); _repeatTimer = Timer.periodic( const Duration(milliseconds: 250), - (_) => widget.onLongPressed?.call(), + (_) async { + widget.onLongPressed?.call(); + await HapticFeedback.heavyImpact(); + }, ); }, ); From e2882c1c6a9ce65dbcd30be61fa58c041a3fdb51 Mon Sep 17 00:00:00 2001 From: Felix Kirchner Date: Wed, 13 May 2026 13:54:01 +0200 Subject: [PATCH 29/34] changed long press vibration --- lib/presentation/widgets/tiles/game_tile.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/widgets/tiles/game_tile.dart b/lib/presentation/widgets/tiles/game_tile.dart index 80881d0..ee5acf0 100644 --- a/lib/presentation/widgets/tiles/game_tile.dart +++ b/lib/presentation/widgets/tiles/game_tile.dart @@ -61,7 +61,7 @@ class GameTile extends StatelessWidget { } }, onLongPress: () async { - await HapticFeedback.vibrate(); + await HapticFeedback.heavyImpact(); if (onLongPress != null) { onLongPress!.call(); } From f87a873362253af920b5eb0db5fac5d0a1ac0b0c Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Wed, 13 May 2026 15:38:19 +0200 Subject: [PATCH 30/34] remove tooltip attribute --- lib/presentation/widgets/buttons/haptic_icon_button.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/presentation/widgets/buttons/haptic_icon_button.dart b/lib/presentation/widgets/buttons/haptic_icon_button.dart index 8da8e9f..f85800c 100644 --- a/lib/presentation/widgets/buttons/haptic_icon_button.dart +++ b/lib/presentation/widgets/buttons/haptic_icon_button.dart @@ -6,7 +6,6 @@ class HapticIconButton extends StatelessWidget { super.key, required this.icon, required this.onPressed, - this.tooltip, this.iconSize, this.color, this.padding, @@ -20,7 +19,6 @@ class HapticIconButton extends StatelessWidget { final Widget icon; final VoidCallback? onPressed; - final String? tooltip; final double? iconSize; final Color? color; final EdgeInsetsGeometry? padding; @@ -33,7 +31,6 @@ class HapticIconButton extends StatelessWidget { @override Widget build(BuildContext context) { return IconButton( - tooltip: tooltip, iconSize: iconSize, color: color, padding: padding, From 0d497236b0514fd816697c33047340020f90cdf2 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Wed, 13 May 2026 15:55:38 +0200 Subject: [PATCH 31/34] remove space --- lib/presentation/widgets/buttons/haptic_icon_button.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/widgets/buttons/haptic_icon_button.dart b/lib/presentation/widgets/buttons/haptic_icon_button.dart index f85800c..81c2cf0 100644 --- a/lib/presentation/widgets/buttons/haptic_icon_button.dart +++ b/lib/presentation/widgets/buttons/haptic_icon_button.dart @@ -18,7 +18,6 @@ class HapticIconButton extends StatelessWidget { final Widget icon; final VoidCallback? onPressed; - final double? iconSize; final Color? color; final EdgeInsetsGeometry? padding; From f5b24cb923a5b019f26ed7776f9db545574dd058 Mon Sep 17 00:00:00 2001 From: Mathis Kirchner Date: Wed, 13 May 2026 16:31:24 +0200 Subject: [PATCH 32/34] disable button splash --- lib/presentation/widgets/buttons/haptic_icon_button.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/presentation/widgets/buttons/haptic_icon_button.dart b/lib/presentation/widgets/buttons/haptic_icon_button.dart index 81c2cf0..abd8f62 100644 --- a/lib/presentation/widgets/buttons/haptic_icon_button.dart +++ b/lib/presentation/widgets/buttons/haptic_icon_button.dart @@ -31,6 +31,7 @@ class HapticIconButton extends StatelessWidget { Widget build(BuildContext context) { return IconButton( iconSize: iconSize, + highlightColor: Colors.transparent, //disable splash animation color: color, padding: padding, alignment: alignment ?? Alignment.center, From 1d1c02a575bfae999ee882904a4991ce649d5fda Mon Sep 17 00:00:00 2001 From: "Gitea Actions [bot]" Date: Thu, 14 May 2026 13:09:54 +0000 Subject: [PATCH 33/34] Updated version number [skip ci] --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 8857c57..cb0bb83 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: tallee description: "Tracking App for Card Games" publish_to: 'none' -version: 0.0.32+266 +version: 0.0.33+267 environment: sdk: ^3.8.1 From 31dc899741aa6d89aad9758b6d90ed25cfe7a849 Mon Sep 17 00:00:00 2001 From: "Gitea Actions [bot]" Date: Thu, 14 May 2026 13:10:36 +0000 Subject: [PATCH 34/34] Updated licenses [skip ci] --- .../views/main_menu/settings_view/licenses/oss_licenses.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 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 1cab79e..7e699f6 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 @@ -37811,12 +37811,12 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', ); -/// tallee 0.0.32+266 +/// tallee 0.0.33+267 const _tallee = Package( name: 'tallee', description: 'Tracking App for Card Games', authors: [], - version: '0.0.32+266', + version: '0.0.33+267', spdxIdentifiers: ['LGPL-3.0'], isMarkdown: false, isSdk: false,