Merge branch 'development' into feature/206-Neuer-Regelsatz-Platzierung
# Conflicts: # lib/l10n/generated/app_localizations.dart # lib/presentation/views/main_menu/match_view/match_detail_view.dart # lib/presentation/views/main_menu/match_view/match_result_view.dart
This commit is contained in:
@@ -56,6 +56,7 @@
|
|||||||
"error_deleting_group": "Fehler beim Löschen der Gruppe, bitte erneut versuchen",
|
"error_deleting_group": "Fehler beim Löschen der Gruppe, bitte erneut versuchen",
|
||||||
"error_editing_group": "Fehler beim Bearbeiten der Gruppe, bitte erneut versuchen",
|
"error_editing_group": "Fehler beim Bearbeiten der Gruppe, bitte erneut versuchen",
|
||||||
"error_reading_file": "Fehler beim Lesen der Datei",
|
"error_reading_file": "Fehler beim Lesen der Datei",
|
||||||
|
"exit_view": "Ansicht verlassen",
|
||||||
"export_canceled": "Export abgebrochen",
|
"export_canceled": "Export abgebrochen",
|
||||||
"export_data": "Daten exportieren",
|
"export_data": "Daten exportieren",
|
||||||
"format_exception": "Formatfehler (siehe Konsole)",
|
"format_exception": "Formatfehler (siehe Konsole)",
|
||||||
@@ -74,6 +75,7 @@
|
|||||||
"legal": "Rechtliches",
|
"legal": "Rechtliches",
|
||||||
"legal_notice": "Impressum",
|
"legal_notice": "Impressum",
|
||||||
"licenses": "Lizenzen",
|
"licenses": "Lizenzen",
|
||||||
|
"live_edit_mode": "Live-Bearbeitungsmodus",
|
||||||
"match_in_progress": "Spiel läuft...",
|
"match_in_progress": "Spiel läuft...",
|
||||||
"match_name": "Spieltitel",
|
"match_name": "Spieltitel",
|
||||||
"match_profile": "Spielprofil",
|
"match_profile": "Spielprofil",
|
||||||
|
|||||||
@@ -1,352 +1,6 @@
|
|||||||
{
|
{
|
||||||
"@@locale": "en",
|
"@@locale": "en",
|
||||||
|
|
||||||
"@all_players": {
|
|
||||||
"description": "Label for all players list"
|
|
||||||
},
|
|
||||||
"@all_players_selected": {
|
|
||||||
"description": "Message when all players are added to selection"
|
|
||||||
},
|
|
||||||
"@amount_of_matches": {
|
|
||||||
"description": "Label for amount of matches statistic"
|
|
||||||
},
|
|
||||||
"@app_name": {
|
|
||||||
"description": "The name of the App"
|
|
||||||
},
|
|
||||||
"@best_player": {
|
|
||||||
"description": "Label for best player statistic"
|
|
||||||
},
|
|
||||||
"@cancel": {
|
|
||||||
"description": "Cancel button text"
|
|
||||||
},
|
|
||||||
"@choose_game": {
|
|
||||||
"description": "Label for choosing a game"
|
|
||||||
},
|
|
||||||
"@choose_group": {
|
|
||||||
"description": "Label for choosing a group"
|
|
||||||
},
|
|
||||||
"@choose_ruleset": {
|
|
||||||
"description": "Label for choosing a ruleset"
|
|
||||||
},
|
|
||||||
"@could_not_add_player": {
|
|
||||||
"description": "Error message when adding a player fails"
|
|
||||||
},
|
|
||||||
"@create_group": {
|
|
||||||
"description": "Button text to create a group"
|
|
||||||
},
|
|
||||||
"@create_match": {
|
|
||||||
"description": "Button text to create a match"
|
|
||||||
},
|
|
||||||
"@create_new_group": {
|
|
||||||
"description": "Appbar text to create a new group"
|
|
||||||
},
|
|
||||||
"@create_new_match": {
|
|
||||||
"description": "Appbar text to create a new match"
|
|
||||||
},
|
|
||||||
"@created_on": {
|
|
||||||
"description": "Label for creation date"
|
|
||||||
},
|
|
||||||
"@data": {
|
|
||||||
"description": "Data label"
|
|
||||||
},
|
|
||||||
"@data_successfully_deleted": {
|
|
||||||
"description": "Success message after deleting data"
|
|
||||||
},
|
|
||||||
"@data_successfully_exported": {
|
|
||||||
"description": "Success message after exporting data"
|
|
||||||
},
|
|
||||||
"@data_successfully_imported": {
|
|
||||||
"description": "Success message after importing data"
|
|
||||||
},
|
|
||||||
"@days_ago": {
|
|
||||||
"description": "Date format for days ago",
|
|
||||||
"placeholders": {
|
|
||||||
"count": {
|
|
||||||
"type": "int"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@delete": {
|
|
||||||
"description": "Delete button text"
|
|
||||||
},
|
|
||||||
"@delete_all_data": {
|
|
||||||
"description": "Confirmation dialog for deleting all data"
|
|
||||||
},
|
|
||||||
"@delete_group": {
|
|
||||||
"description": "Confirmation dialog for deleting a group"
|
|
||||||
},
|
|
||||||
"@delete_match": {
|
|
||||||
"description": "Button text to delete a match"
|
|
||||||
},
|
|
||||||
"@drag_to_set_placement": {
|
|
||||||
"description": "Label for dragging to set placement"
|
|
||||||
},
|
|
||||||
"@edit_group": {
|
|
||||||
"description": "Button & Appbar label for editing a group"
|
|
||||||
},
|
|
||||||
"@edit_match": {
|
|
||||||
"description": "Button & Appbar label for editing a match"
|
|
||||||
},
|
|
||||||
"@enter_points": {
|
|
||||||
"description": "Label to enter players points"
|
|
||||||
},
|
|
||||||
"@enter_results": {
|
|
||||||
"description": "Button text to enter match results"
|
|
||||||
},
|
|
||||||
"@error_creating_group": {
|
|
||||||
"description": "Error message when group creation fails"
|
|
||||||
},
|
|
||||||
"@error_deleting_group": {
|
|
||||||
"description": "Error message when group deletion fails"
|
|
||||||
},
|
|
||||||
"@error_editing_group": {
|
|
||||||
"description": "Error message when group editing fails"
|
|
||||||
},
|
|
||||||
"@error_reading_file": {
|
|
||||||
"description": "Error message when file cannot be read"
|
|
||||||
},
|
|
||||||
"@export_canceled": {
|
|
||||||
"description": "Message when export is canceled"
|
|
||||||
},
|
|
||||||
"@export_data": {
|
|
||||||
"description": "Export data menu item"
|
|
||||||
},
|
|
||||||
"@format_exception": {
|
|
||||||
"description": "Error message for format exceptions"
|
|
||||||
},
|
|
||||||
"@game": {
|
|
||||||
"description": "Game label"
|
|
||||||
},
|
|
||||||
"@game_name": {
|
|
||||||
"description": "Placeholder for game name search"
|
|
||||||
},
|
|
||||||
"@group": {
|
|
||||||
"description": "Group label"
|
|
||||||
},
|
|
||||||
"@group_name": {
|
|
||||||
"description": "Placeholder for group name input"
|
|
||||||
},
|
|
||||||
"@group_profile": {
|
|
||||||
"description": "Title for group profile view"
|
|
||||||
},
|
|
||||||
"@groups": {
|
|
||||||
"description": "Label for groups"
|
|
||||||
},
|
|
||||||
"@home": {
|
|
||||||
"description": "Home tab label"
|
|
||||||
},
|
|
||||||
"@import_canceled": {
|
|
||||||
"description": "Message when import is canceled"
|
|
||||||
},
|
|
||||||
"@import_data": {
|
|
||||||
"description": "Import data menu item"
|
|
||||||
},
|
|
||||||
"@info": {
|
|
||||||
"description": "Info label"
|
|
||||||
},
|
|
||||||
"@invalid_schema": {
|
|
||||||
"description": "Error message for invalid schema"
|
|
||||||
},
|
|
||||||
"@least_points": {
|
|
||||||
"description": "Title for least points ruleset"
|
|
||||||
},
|
|
||||||
"@legal": {
|
|
||||||
"description": "Legal section header"
|
|
||||||
},
|
|
||||||
"@legal_notice": {
|
|
||||||
"description": "Legal notice menu item"
|
|
||||||
},
|
|
||||||
"@licenses": {
|
|
||||||
"description": "Licenses menu item"
|
|
||||||
},
|
|
||||||
"@match_in_progress": {
|
|
||||||
"description": "Message when match is in progress"
|
|
||||||
},
|
|
||||||
"@match_name": {
|
|
||||||
"description": "Placeholder for match name input"
|
|
||||||
},
|
|
||||||
"@match_profile": {
|
|
||||||
"description": "Title for match profile view"
|
|
||||||
},
|
|
||||||
"@matches": {
|
|
||||||
"description": "Label for matches"
|
|
||||||
},
|
|
||||||
"@members": {
|
|
||||||
"description": "Label for group members"
|
|
||||||
},
|
|
||||||
"@most_points": {
|
|
||||||
"description": "Title for most points ruleset"
|
|
||||||
},
|
|
||||||
"@no_data_available": {
|
|
||||||
"description": "Message when no data in the statistic tiles is given"
|
|
||||||
},
|
|
||||||
"@no_groups_created_yet": {
|
|
||||||
"description": "Message when no groups exist"
|
|
||||||
},
|
|
||||||
"@no_licenses_found": {
|
|
||||||
"description": "Message when no licenses are found"
|
|
||||||
},
|
|
||||||
"@no_license_text_available": {
|
|
||||||
"description": "Message when no license text is available"
|
|
||||||
},
|
|
||||||
"@no_matches_created_yet": {
|
|
||||||
"description": "Message when no matches exist"
|
|
||||||
},
|
|
||||||
"@no_players_created_yet": {
|
|
||||||
"description": "Message when no players exist"
|
|
||||||
},
|
|
||||||
"@no_players_found_with_that_name": {
|
|
||||||
"description": "Message when search returns no results"
|
|
||||||
},
|
|
||||||
"@no_players_selected": {
|
|
||||||
"description": "Message when no players are selected"
|
|
||||||
},
|
|
||||||
"@no_recent_matches_available": {
|
|
||||||
"description": "Message when no recent matches exist"
|
|
||||||
},
|
|
||||||
"@no_results_entered_yet": {
|
|
||||||
"description": "Message when no results have been entered yet"
|
|
||||||
},
|
|
||||||
"@no_second_match_available": {
|
|
||||||
"description": "Message when no second match exists"
|
|
||||||
},
|
|
||||||
"@no_statistics_available": {
|
|
||||||
"description": "Message when no statistics are available, because no matches were played yet"
|
|
||||||
},
|
|
||||||
"@none": {
|
|
||||||
"description": "None option label"
|
|
||||||
},
|
|
||||||
"@none_group": {
|
|
||||||
"description": "None group option label"
|
|
||||||
},
|
|
||||||
"@not_available": {
|
|
||||||
"description": "Abbreviation for not available"
|
|
||||||
},
|
|
||||||
"@placement": {
|
|
||||||
"description": "Title for placement ruleset"
|
|
||||||
},
|
|
||||||
"@place": {
|
|
||||||
"description": "Label for placement text in match detail view"
|
|
||||||
},
|
|
||||||
"@played_matches": {
|
|
||||||
"description": "Label for played matches statistic"
|
|
||||||
},
|
|
||||||
"@player_name": {
|
|
||||||
"description": "Placeholder for player name input"
|
|
||||||
},
|
|
||||||
"@players": {
|
|
||||||
"description": "Players label"
|
|
||||||
},
|
|
||||||
"@players_count": {
|
|
||||||
"description": "Shows the number of players",
|
|
||||||
"placeholders": {
|
|
||||||
"count": {
|
|
||||||
"type": "int"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@points": {
|
|
||||||
"description": "Points label"
|
|
||||||
},
|
|
||||||
"@privacy_policy": {
|
|
||||||
"description": "Privacy policy menu item"
|
|
||||||
},
|
|
||||||
"@quick_create": {
|
|
||||||
"description": "Title for quick create section"
|
|
||||||
},
|
|
||||||
"@recent_matches": {
|
|
||||||
"description": "Title for recent matches section"
|
|
||||||
},
|
|
||||||
"@results": {
|
|
||||||
"description": "Label for match results"
|
|
||||||
},
|
|
||||||
"@ruleset": {
|
|
||||||
"description": "Ruleset label"
|
|
||||||
},
|
|
||||||
"@ruleset_least_points": {
|
|
||||||
"description": "Description for least points ruleset"
|
|
||||||
},
|
|
||||||
"@ruleset_most_points": {
|
|
||||||
"description": "Description for most points ruleset"
|
|
||||||
},
|
|
||||||
"@ruleset_placement": {
|
|
||||||
"description": "Description for placement ruleset"
|
|
||||||
},
|
|
||||||
"@ruleset_single_loser": {
|
|
||||||
"description": "Description for single loser ruleset"
|
|
||||||
},
|
|
||||||
"@ruleset_single_winner": {
|
|
||||||
"description": "Description for single winner ruleset"
|
|
||||||
},
|
|
||||||
"@save_changes": {
|
|
||||||
"description": "Save changes button text"
|
|
||||||
},
|
|
||||||
"@search_for_groups": {
|
|
||||||
"description": "Hint text for group search input field"
|
|
||||||
},
|
|
||||||
"@search_for_players": {
|
|
||||||
"description": "Hint text for player search input field"
|
|
||||||
},
|
|
||||||
"@select_winner": {
|
|
||||||
"description": "Label to select the winner"
|
|
||||||
},
|
|
||||||
"@select_loser": {
|
|
||||||
"description": "Label to select the loser"
|
|
||||||
},
|
|
||||||
"@selected_players": {
|
|
||||||
"description": "Shows the number of selected players"
|
|
||||||
},
|
|
||||||
"@settings": {
|
|
||||||
"description": "Label for the App Settings"
|
|
||||||
},
|
|
||||||
"@single_loser": {
|
|
||||||
"description": "Title for single loser ruleset"
|
|
||||||
},
|
|
||||||
"@single_winner": {
|
|
||||||
"description": "Title for single winner ruleset"
|
|
||||||
},
|
|
||||||
"@statistics": {
|
|
||||||
"description": "Statistics tab label"
|
|
||||||
},
|
|
||||||
"@stats": {
|
|
||||||
"description": "Stats tab label (short)"
|
|
||||||
},
|
|
||||||
"@successfully_added_player": {
|
|
||||||
"description": "Success message when adding a player",
|
|
||||||
"placeholders": {
|
|
||||||
"playerName": {
|
|
||||||
"type": "String",
|
|
||||||
"example": "John"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@there_is_no_group_matching_your_search": {
|
|
||||||
"description": "Message when search returns no groups"
|
|
||||||
},
|
|
||||||
"@this_cannot_be_undone": {
|
|
||||||
"description": "Warning message for irreversible actions"
|
|
||||||
},
|
|
||||||
"@today_at": {
|
|
||||||
"description": "Date format for today"
|
|
||||||
},
|
|
||||||
"@undo": {
|
|
||||||
"description": "Undo button text"
|
|
||||||
},
|
|
||||||
"@unknown_exception": {
|
|
||||||
"description": "Error message for unknown exceptions"
|
|
||||||
},
|
|
||||||
"@winner": {
|
|
||||||
"description": "Winner label"
|
|
||||||
},
|
|
||||||
"@winrate": {
|
|
||||||
"description": "Label for winrate statistic"
|
|
||||||
},
|
|
||||||
"@wins": {
|
|
||||||
"description": "Label for wins statistic"
|
|
||||||
},
|
|
||||||
"@yesterday_at": {
|
|
||||||
"description": "Date format for yesterday"
|
|
||||||
},
|
|
||||||
"all_players": "All players",
|
"all_players": "All players",
|
||||||
"all_players_selected": "All players selected",
|
"all_players_selected": "All players selected",
|
||||||
"amount_of_matches": "Amount of Matches",
|
"amount_of_matches": "Amount of Matches",
|
||||||
@@ -403,6 +57,7 @@
|
|||||||
"error_deleting_group": "Error while deleting group, please try again",
|
"error_deleting_group": "Error while deleting group, please try again",
|
||||||
"error_editing_group": "Error while editing group, please try again",
|
"error_editing_group": "Error while editing group, please try again",
|
||||||
"error_reading_file": "Error reading file",
|
"error_reading_file": "Error reading file",
|
||||||
|
"exit_view": "Exit View",
|
||||||
"export_canceled": "Export canceled",
|
"export_canceled": "Export canceled",
|
||||||
"export_data": "Export data",
|
"export_data": "Export data",
|
||||||
"format_exception": "Format Exception (see console)",
|
"format_exception": "Format Exception (see console)",
|
||||||
@@ -421,6 +76,7 @@
|
|||||||
"legal": "Legal",
|
"legal": "Legal",
|
||||||
"legal_notice": "Legal Notice",
|
"legal_notice": "Legal Notice",
|
||||||
"licenses": "Licenses",
|
"licenses": "Licenses",
|
||||||
|
"live_edit_mode": "Live Edit Mode",
|
||||||
"match_in_progress": "Match in progress...",
|
"match_in_progress": "Match in progress...",
|
||||||
"match_name": "Match name",
|
"match_name": "Match name",
|
||||||
"match_profile": "Match Profile",
|
"match_profile": "Match Profile",
|
||||||
|
|||||||
@@ -392,6 +392,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Error reading file'**
|
/// **'Error reading file'**
|
||||||
String get error_reading_file;
|
String get error_reading_file;
|
||||||
|
|
||||||
|
/// No description provided for @exit_view.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Exit View'**
|
||||||
|
String get exit_view;
|
||||||
|
|
||||||
/// Message when export is canceled
|
/// Message when export is canceled
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
@@ -500,6 +506,12 @@ abstract class AppLocalizations {
|
|||||||
/// **'Licenses'**
|
/// **'Licenses'**
|
||||||
String get licenses;
|
String get licenses;
|
||||||
|
|
||||||
|
/// No description provided for @live_edit_mode.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Live Edit Mode'**
|
||||||
|
String get live_edit_mode;
|
||||||
|
|
||||||
/// Message when match is in progress
|
/// Message when match is in progress
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
|
|||||||
@@ -171,6 +171,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get error_reading_file => 'Fehler beim Lesen der Datei';
|
String get error_reading_file => 'Fehler beim Lesen der Datei';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exit_view => 'Ansicht verlassen';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get export_canceled => 'Export abgebrochen';
|
String get export_canceled => 'Export abgebrochen';
|
||||||
|
|
||||||
@@ -225,6 +228,9 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get licenses => 'Lizenzen';
|
String get licenses => 'Lizenzen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get live_edit_mode => 'Live-Bearbeitungsmodus';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get match_in_progress => 'Spiel läuft...';
|
String get match_in_progress => 'Spiel läuft...';
|
||||||
|
|
||||||
|
|||||||
@@ -171,6 +171,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get error_reading_file => 'Error reading file';
|
String get error_reading_file => 'Error reading file';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get exit_view => 'Exit View';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get export_canceled => 'Export canceled';
|
String get export_canceled => 'Export canceled';
|
||||||
|
|
||||||
@@ -225,6 +228,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
@override
|
@override
|
||||||
String get licenses => 'Licenses';
|
String get licenses => 'Licenses';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get live_edit_mode => 'Live Edit Mode';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get match_in_progress => 'Match in progress...';
|
String get match_in_progress => 'Match in progress...';
|
||||||
|
|
||||||
|
|||||||
@@ -240,6 +240,9 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
match: match,
|
match: match,
|
||||||
onWinnerChanged: () {
|
onWinnerChanged: () {
|
||||||
widget.onMatchUpdate.call();
|
widget.onMatchUpdate.call();
|
||||||
|
setState(() {
|
||||||
|
updateScoresForCurrentMatch();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -428,4 +431,10 @@ class _MatchDetailViewState extends State<MatchDetailView> {
|
|||||||
return '${number}th';
|
return '${number}th';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateScoresForCurrentMatch() {
|
||||||
|
db.scoreEntryDao
|
||||||
|
.getAllMatchScores(matchId: match.id)
|
||||||
|
.then((scores) => match.scores = scores);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ import 'package:tallee/data/models/player.dart';
|
|||||||
import 'package:tallee/data/models/score_entry.dart';
|
import 'package:tallee/data/models/score_entry.dart';
|
||||||
import 'package:tallee/l10n/generated/app_localizations.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/custom_width_button.dart';
|
||||||
import 'package:tallee/presentation/widgets/tiles/custom_radio_list_tile.dart';
|
import 'package:tallee/presentation/widgets/tiles/match_result_view/custom_radio_list_tile.dart';
|
||||||
import 'package:tallee/presentation/widgets/tiles/score_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';
|
||||||
import 'package:tallee/presentation/widgets/tiles/text_icon_list_tile.dart';
|
import 'package:tallee/presentation/widgets/tiles/text_icon_list_tile.dart';
|
||||||
|
|
||||||
class MatchResultView extends StatefulWidget {
|
class MatchResultView extends StatefulWidget {
|
||||||
@@ -31,6 +32,8 @@ class MatchResultView extends StatefulWidget {
|
|||||||
class _MatchResultViewState extends State<MatchResultView> {
|
class _MatchResultViewState extends State<MatchResultView> {
|
||||||
late final AppDatabase db;
|
late final AppDatabase db;
|
||||||
|
|
||||||
|
bool isLiveEditMode = false;
|
||||||
|
|
||||||
late final Ruleset ruleset;
|
late final Ruleset ruleset;
|
||||||
|
|
||||||
/// List of all players who participated in the match
|
/// List of all players who participated in the match
|
||||||
@@ -39,6 +42,7 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
/// List of text controllers for score entry, one for each player
|
/// List of text controllers for score entry, one for each player
|
||||||
late final List<TextEditingController> controller;
|
late final List<TextEditingController> controller;
|
||||||
|
|
||||||
|
/// Flag to indicate if the save button should be enabled
|
||||||
late bool canSave;
|
late bool canSave;
|
||||||
|
|
||||||
/// Currently selected winner player
|
/// Currently selected winner player
|
||||||
@@ -58,6 +62,7 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
(index) => TextEditingController()..addListener(() => onTextEnter()),
|
(index) => TextEditingController()..addListener(() => onTextEnter()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Prefill fields
|
||||||
if (widget.match.mvp.isNotEmpty) {
|
if (widget.match.mvp.isNotEmpty) {
|
||||||
if (rulesetSupportsWinnerSelection()) {
|
if (rulesetSupportsWinnerSelection()) {
|
||||||
_selectedPlayer = allPlayers.firstWhere(
|
_selectedPlayer = allPlayers.firstWhere(
|
||||||
@@ -108,186 +113,232 @@ class _MatchResultViewState extends State<MatchResultView> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Container(
|
child: isLiveEditMode
|
||||||
margin: const EdgeInsets.symmetric(
|
// Live Edit Mode
|
||||||
horizontal: 12,
|
? ListView.builder(
|
||||||
vertical: 10,
|
itemCount: allPlayers.length,
|
||||||
),
|
itemBuilder: (context, index) {
|
||||||
padding: const EdgeInsets.symmetric(
|
return LiveEditListTile(
|
||||||
vertical: 10,
|
title: allPlayers[index].name,
|
||||||
horizontal: 10,
|
onChanged: (value) {
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: CustomTheme.boxColor,
|
|
||||||
border: Border.all(color: CustomTheme.boxBorderColor),
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'${getTitleForRuleset(loc)}:',
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 10),
|
|
||||||
if (rulesetSupportsWinnerSelection())
|
|
||||||
Expanded(
|
|
||||||
child: RadioGroup<Player>(
|
|
||||||
groupValue: _selectedPlayer,
|
|
||||||
onChanged: (Player? value) async {
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedPlayer = value;
|
controller[index].text = value.toString();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: ListView.builder(
|
value: int.tryParse(controller[index].text) ?? 0,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
);
|
||||||
itemCount: allPlayers.length,
|
},
|
||||||
itemBuilder: (context, index) {
|
)
|
||||||
return CustomRadioListTile(
|
// Normal Container
|
||||||
text: allPlayers[index].name,
|
: Container(
|
||||||
value: allPlayers[index],
|
margin: const EdgeInsets.symmetric(
|
||||||
onContainerTap: (value) async {
|
horizontal: 12,
|
||||||
|
vertical: 10,
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 10,
|
||||||
|
horizontal: 10,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: CustomTheme.boxColor,
|
||||||
|
border: Border.all(color: CustomTheme.boxBorderColor),
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
getTitleForRuleset(loc),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
|
||||||
|
// Show player selection
|
||||||
|
if (rulesetSupportsWinnerSelection())
|
||||||
|
Expanded(
|
||||||
|
child: RadioGroup<Player>(
|
||||||
|
groupValue: _selectedPlayer,
|
||||||
|
onChanged: (Player? value) async {
|
||||||
setState(() {
|
setState(() {
|
||||||
// Check if the already selected player is the same as the newly tapped player.
|
_selectedPlayer = value;
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
child: ListView.builder(
|
||||||
},
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
),
|
itemCount: allPlayers.length,
|
||||||
),
|
itemBuilder: (context, index) {
|
||||||
),
|
return CustomRadioListTile(
|
||||||
if (rulesetSupportsScoreEntry())
|
text: allPlayers[index].name,
|
||||||
Expanded(
|
value: allPlayers[index],
|
||||||
child: ListView.separated(
|
onContainerTap: (value) async {
|
||||||
itemCount: allPlayers.length,
|
setState(() {
|
||||||
itemBuilder: (context, index) {
|
// Check if the already selected player is the same as the newly tapped player.
|
||||||
return ScoreListTile(
|
if (_selectedPlayer == value) {
|
||||||
text: allPlayers[index].name,
|
// If yes deselected the player by setting it to null.
|
||||||
controller: controller[index],
|
_selectedPlayer = null;
|
||||||
);
|
} else {
|
||||||
},
|
// If no assign the newly tapped player to the selected player.
|
||||||
separatorBuilder: (BuildContext context, int index) {
|
(_selectedPlayer = value);
|
||||||
return const Padding(
|
}
|
||||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
});
|
||||||
child: Divider(indent: 20),
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
|
||||||
if (rulesetSupportsPlacement())
|
|
||||||
Expanded(
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
// Placement indicators
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 8.0),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
for (int i = 0; i < allPlayers.length; i++)
|
|
||||||
Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
height: 60,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: CustomTheme.boxBorderColor,
|
|
||||||
borderRadius: CustomTheme
|
|
||||||
.standardBorderRadiusAll,
|
|
||||||
),
|
|
||||||
alignment: Alignment.center,
|
|
||||||
height: 50,
|
|
||||||
width: 50,
|
|
||||||
child: Text(
|
|
||||||
' #${i + 1} ',
|
|
||||||
style: const TextStyle(
|
|
||||||
color: CustomTheme.textColor,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
fontSize: 16,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// Drag list
|
// Show score entry
|
||||||
|
if (rulesetSupportsScoreEntry())
|
||||||
Expanded(
|
Expanded(
|
||||||
child: ReorderableListView.builder(
|
child: ListView.separated(
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
itemCount: allPlayers.length,
|
||||||
padding: EdgeInsets.zero,
|
itemBuilder: (context, index) {
|
||||||
proxyDecorator: (child, index, animation) {
|
return ScoreListTile(
|
||||||
return AnimatedBuilder(
|
text: allPlayers[index].name,
|
||||||
animation: animation,
|
controller: controller[index],
|
||||||
child: child,
|
);
|
||||||
builder: (context, child) {
|
},
|
||||||
final alpha =
|
separatorBuilder:
|
||||||
(Curves.easeInOut.transform(
|
(BuildContext context, int index) {
|
||||||
animation.value,
|
return const Padding(
|
||||||
) *
|
padding: EdgeInsets.symmetric(
|
||||||
40)
|
vertical: 8.0,
|
||||||
.toInt();
|
),
|
||||||
return Stack(
|
child: Divider(indent: 20),
|
||||||
children: [
|
);
|
||||||
child!,
|
},
|
||||||
Positioned.fill(
|
),
|
||||||
left: 4,
|
),
|
||||||
top: 4,
|
|
||||||
right: 4,
|
// Show draggable placement list
|
||||||
bottom: 4,
|
if (rulesetSupportsPlacement())
|
||||||
child: DecoratedBox(
|
Expanded(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
// Placement indicators
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 8.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
for (
|
||||||
|
int i = 0;
|
||||||
|
i < allPlayers.length;
|
||||||
|
i++
|
||||||
|
)
|
||||||
|
Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
height: 60,
|
||||||
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white.withAlpha(
|
color:
|
||||||
alpha,
|
CustomTheme.boxBorderColor,
|
||||||
),
|
|
||||||
borderRadius: CustomTheme
|
borderRadius: CustomTheme
|
||||||
.standardBorderRadiusAll,
|
.standardBorderRadiusAll,
|
||||||
),
|
),
|
||||||
|
alignment: Alignment.center,
|
||||||
|
height: 50,
|
||||||
|
width: 50,
|
||||||
|
child: Text(
|
||||||
|
' #${i + 1} ',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: CustomTheme.textColor,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 16,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
),
|
||||||
},
|
),
|
||||||
);
|
|
||||||
},
|
// Drag list
|
||||||
onReorder: (int oldIndex, int newIndex) {
|
Expanded(
|
||||||
setState(() {
|
child: ReorderableListView.builder(
|
||||||
if (newIndex > oldIndex) {
|
physics:
|
||||||
newIndex -= 1;
|
const NeverScrollableScrollPhysics(),
|
||||||
}
|
padding: EdgeInsets.zero,
|
||||||
final Player item = allPlayers.removeAt(
|
proxyDecorator: (child, index, animation) {
|
||||||
oldIndex,
|
return AnimatedBuilder(
|
||||||
);
|
animation: animation,
|
||||||
allPlayers.insert(newIndex, item);
|
child: child,
|
||||||
});
|
builder: (context, child) {
|
||||||
},
|
final alpha =
|
||||||
itemCount: allPlayers.length,
|
(Curves.easeInOut.transform(
|
||||||
itemBuilder: (context, index) {
|
animation.value,
|
||||||
return TextIconListTile(
|
) *
|
||||||
key: ValueKey(allPlayers[index].id),
|
40)
|
||||||
text: allPlayers[index].name,
|
.toInt();
|
||||||
icon: Icons.drag_handle,
|
return Stack(
|
||||||
);
|
children: [
|
||||||
},
|
child!,
|
||||||
|
Positioned.fill(
|
||||||
|
left: 4,
|
||||||
|
top: 4,
|
||||||
|
right: 4,
|
||||||
|
bottom: 4,
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white
|
||||||
|
.withAlpha(alpha),
|
||||||
|
borderRadius: CustomTheme
|
||||||
|
.standardBorderRadiusAll,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onReorder: (int oldIndex, int newIndex) {
|
||||||
|
setState(() {
|
||||||
|
if (newIndex > oldIndex) {
|
||||||
|
newIndex -= 1;
|
||||||
|
}
|
||||||
|
final Player item = allPlayers
|
||||||
|
.removeAt(oldIndex);
|
||||||
|
allPlayers.insert(newIndex, item);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
itemCount: allPlayers.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return TextIconListTile(
|
||||||
|
key: ValueKey(allPlayers[index].id),
|
||||||
|
text: allPlayers[index].name,
|
||||||
|
icon: Icons.drag_handle,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
|
||||||
|
if (rulesetSupportsScoreEntry())
|
||||||
|
// Button to switch to live edit mode
|
||||||
|
...[
|
||||||
|
CustomWidthButton(
|
||||||
|
text: isLiveEditMode ? loc.exit_view : loc.live_edit_mode,
|
||||||
|
sizeRelativeToWidth: 0.95,
|
||||||
|
buttonType: ButtonType.secondary,
|
||||||
|
onPressed: () => setState(() {
|
||||||
|
isLiveEditMode = !isLiveEditMode;
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
],
|
||||||
|
|
||||||
|
// Save Changes Button
|
||||||
CustomWidthButton(
|
CustomWidthButton(
|
||||||
text: loc.save_changes,
|
text: loc.save_changes,
|
||||||
sizeRelativeToWidth: 0.95,
|
sizeRelativeToWidth: 0.95,
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ const allDependencies = <Package>[
|
|||||||
_flutter,
|
_flutter,
|
||||||
_flutter_lints,
|
_flutter_lints,
|
||||||
_flutter_localizations,
|
_flutter_localizations,
|
||||||
|
_flutter_numeric_text,
|
||||||
_flutter_plugin_android_lifecycle,
|
_flutter_plugin_android_lifecycle,
|
||||||
_flutter_popup,
|
_flutter_popup,
|
||||||
_flutter_test,
|
_flutter_test,
|
||||||
@@ -169,6 +170,7 @@ const dependencies = <Package>[
|
|||||||
_file_saver,
|
_file_saver,
|
||||||
_flutter,
|
_flutter,
|
||||||
_flutter_localizations,
|
_flutter_localizations,
|
||||||
|
_flutter_numeric_text,
|
||||||
_flutter_popup,
|
_flutter_popup,
|
||||||
_fluttericon,
|
_fluttericon,
|
||||||
_font_awesome_flutter,
|
_font_awesome_flutter,
|
||||||
@@ -2591,6 +2593,42 @@ const _flutter_localizations = Package(
|
|||||||
devDependencies: [PackageRef('flutter_test')],
|
devDependencies: [PackageRef('flutter_test')],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// flutter_numeric_text 1.3.3
|
||||||
|
const _flutter_numeric_text = Package(
|
||||||
|
name: 'flutter_numeric_text',
|
||||||
|
description: 'This widget allows you to animate any text. The widget is easy to use and allows you to seamlessly replace Text(data) with NumericText(data).',
|
||||||
|
homepage: 'https://github.com/strash/flutter_numeric_text',
|
||||||
|
repository: 'https://github.com/strash/flutter_numeric_text',
|
||||||
|
authors: [],
|
||||||
|
version: '1.3.3',
|
||||||
|
spdxIdentifiers: ['MIT'],
|
||||||
|
isMarkdown: false,
|
||||||
|
isSdk: false,
|
||||||
|
dependencies: [PackageRef('flutter')],
|
||||||
|
devDependencies: [PackageRef('flutter_test'), PackageRef('flutter_lints')],
|
||||||
|
license: '''MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Strash One
|
||||||
|
|
||||||
|
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.''',
|
||||||
|
);
|
||||||
|
|
||||||
/// flutter_plugin_android_lifecycle 2.0.34
|
/// flutter_plugin_android_lifecycle 2.0.34
|
||||||
const _flutter_plugin_android_lifecycle = Package(
|
const _flutter_plugin_android_lifecycle = Package(
|
||||||
name: 'flutter_plugin_android_lifecycle',
|
name: 'flutter_plugin_android_lifecycle',
|
||||||
@@ -37713,16 +37751,16 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.''',
|
SOFTWARE.''',
|
||||||
);
|
);
|
||||||
|
|
||||||
/// tallee 0.0.28+262
|
/// tallee 0.0.29+263
|
||||||
const _tallee = Package(
|
const _tallee = Package(
|
||||||
name: 'tallee',
|
name: 'tallee',
|
||||||
description: 'Tracking App for Card Games',
|
description: 'Tracking App for Card Games',
|
||||||
authors: [],
|
authors: [],
|
||||||
version: '0.0.28+262',
|
version: '0.0.29+263',
|
||||||
spdxIdentifiers: ['LGPL-3.0'],
|
spdxIdentifiers: ['LGPL-3.0'],
|
||||||
isMarkdown: false,
|
isMarkdown: false,
|
||||||
isSdk: false,
|
isSdk: false,
|
||||||
dependencies: [PackageRef('clock'), PackageRef('collection'), PackageRef('cupertino_icons'), PackageRef('drift'), PackageRef('drift_flutter'), PackageRef('file_picker'), PackageRef('file_saver'), PackageRef('flutter'), PackageRef('flutter_localizations'), PackageRef('flutter_popup'), PackageRef('fluttericon'), PackageRef('font_awesome_flutter'), PackageRef('intl'), PackageRef('json_schema'), PackageRef('package_info_plus'), PackageRef('path_provider'), PackageRef('provider'), PackageRef('skeletonizer'), PackageRef('url_launcher'), PackageRef('uuid')],
|
dependencies: [PackageRef('clock'), PackageRef('collection'), PackageRef('cupertino_icons'), PackageRef('drift'), PackageRef('drift_flutter'), PackageRef('file_picker'), PackageRef('file_saver'), PackageRef('flutter'), PackageRef('flutter_localizations'), PackageRef('flutter_numeric_text'), PackageRef('flutter_popup'), PackageRef('fluttericon'), PackageRef('font_awesome_flutter'), PackageRef('intl'), PackageRef('json_schema'), PackageRef('package_info_plus'), PackageRef('path_provider'), PackageRef('provider'), PackageRef('skeletonizer'), PackageRef('url_launcher'), PackageRef('uuid')],
|
||||||
devDependencies: [PackageRef('flutter_test'), PackageRef('build_runner'), PackageRef('dart_pubspec_licenses'), PackageRef('drift_dev'), PackageRef('flutter_lints')],
|
devDependencies: [PackageRef('flutter_test'), PackageRef('build_runner'), PackageRef('dart_pubspec_licenses'), PackageRef('drift_dev'), PackageRef('flutter_lints')],
|
||||||
license: '''GNU LESSER GENERAL PUBLIC LICENSE
|
license: '''GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
Version 3, 29 June 2007
|
Version 3, 29 June 2007
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class CustomWidthButton extends StatelessWidget {
|
|||||||
MediaQuery.sizeOf(context).width * sizeRelativeToWidth,
|
MediaQuery.sizeOf(context).width * sizeRelativeToWidth,
|
||||||
60,
|
60,
|
||||||
),
|
),
|
||||||
side: BorderSide(color: borderSideColor, width: 2),
|
side: BorderSide(color: borderSideColor, width: 3),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: CustomTheme.standardBorderRadiusAll,
|
borderRadius: CustomTheme.standardBorderRadiusAll,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class MainMenuButton extends StatefulWidget {
|
class MainMenuButton extends StatefulWidget {
|
||||||
@@ -10,6 +12,7 @@ class MainMenuButton extends StatefulWidget {
|
|||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
this.text,
|
this.text,
|
||||||
|
this.onLongPressed,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The callback to be invoked when the button is pressed.
|
/// The callback to be invoked when the button is pressed.
|
||||||
@@ -21,6 +24,8 @@ class MainMenuButton extends StatefulWidget {
|
|||||||
/// The text of the button.
|
/// The text of the button.
|
||||||
final String? text;
|
final String? text;
|
||||||
|
|
||||||
|
final void Function()? onLongPressed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<MainMenuButton> createState() => _MainMenuButtonState();
|
State<MainMenuButton> createState() => _MainMenuButtonState();
|
||||||
}
|
}
|
||||||
@@ -30,6 +35,14 @@ class _MainMenuButtonState extends State<MainMenuButton>
|
|||||||
late AnimationController _animationController;
|
late AnimationController _animationController;
|
||||||
late Animation<double> _scaleAnimation;
|
late Animation<double> _scaleAnimation;
|
||||||
|
|
||||||
|
/// How long the button needs to be pressed to register it as long press
|
||||||
|
Timer? _longPressTimer;
|
||||||
|
|
||||||
|
/// How much time between two onLongPressed calls
|
||||||
|
Timer? _repeatTimer;
|
||||||
|
|
||||||
|
bool _isLongPressing = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -51,14 +64,29 @@ class _MainMenuButtonState extends State<MainMenuButton>
|
|||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTapDown: (_) {
|
onTapDown: (_) {
|
||||||
_animationController.forward();
|
_animationController.forward();
|
||||||
},
|
if (widget.onLongPressed != null) {
|
||||||
onTapUp: (_) async {
|
_longPressTimer = Timer(const Duration(milliseconds: 400), () {
|
||||||
await _animationController.reverse();
|
_isLongPressing = true;
|
||||||
if (mounted) {
|
widget.onLongPressed?.call();
|
||||||
widget.onPressed();
|
_repeatTimer = Timer.periodic(
|
||||||
|
const Duration(milliseconds: 250),
|
||||||
|
(_) => widget.onLongPressed?.call(),
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onTapUp: (_) async {
|
||||||
|
_cancelTimers();
|
||||||
|
if (mounted && !_isLongPressing) {
|
||||||
|
widget.onPressed();
|
||||||
|
}
|
||||||
|
_isLongPressing = false;
|
||||||
|
await Future.delayed(const Duration(milliseconds: 100));
|
||||||
|
await _animationController.reverse();
|
||||||
|
},
|
||||||
onTapCancel: () {
|
onTapCancel: () {
|
||||||
|
_isLongPressing = false;
|
||||||
|
_cancelTimers();
|
||||||
_animationController.reverse();
|
_animationController.reverse();
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -92,7 +120,15 @@ class _MainMenuButtonState extends State<MainMenuButton>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_cancelTimers();
|
||||||
_animationController.dispose();
|
_animationController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _cancelTimers() {
|
||||||
|
_longPressTimer?.cancel();
|
||||||
|
_longPressTimer = null;
|
||||||
|
_repeatTimer?.cancel();
|
||||||
|
_repeatTimer = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,125 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_numeric_text/flutter_numeric_text.dart';
|
||||||
|
import 'package:tallee/core/custom_theme.dart';
|
||||||
|
import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart';
|
||||||
|
|
||||||
|
class LiveEditListTile extends StatefulWidget {
|
||||||
|
const LiveEditListTile({
|
||||||
|
super.key,
|
||||||
|
required this.title,
|
||||||
|
required this.value,
|
||||||
|
this.onChanged,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
|
||||||
|
final int value;
|
||||||
|
|
||||||
|
final void Function(int newValue)? onChanged;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<LiveEditListTile> createState() => _LiveEditListTileState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LiveEditListTileState extends State<LiveEditListTile> {
|
||||||
|
int _score = 0;
|
||||||
|
final int maxScore = 9999;
|
||||||
|
final int minScore = -9999;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_score = widget.value;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 20),
|
||||||
|
margin: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
||||||
|
decoration: CustomTheme.standardBoxDecoration,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
MainMenuButton(
|
||||||
|
onPressed: () => _score > minScore
|
||||||
|
? {
|
||||||
|
setState(() {
|
||||||
|
_score--;
|
||||||
|
if (widget.onChanged != null) {
|
||||||
|
widget.onChanged!(_score);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
onLongPressed: () => _score > minScore
|
||||||
|
? {
|
||||||
|
setState(() {
|
||||||
|
_score -= 10;
|
||||||
|
if (widget.onChanged != null) {
|
||||||
|
widget.onChanged!(_score);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
icon: Icons.remove_rounded,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 150,
|
||||||
|
child: NumericText(
|
||||||
|
_score.toString(),
|
||||||
|
maxLines: 1,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
textWidthBasis: TextWidthBasis.longestLine,
|
||||||
|
textHeightBehavior: const TextHeightBehavior(
|
||||||
|
applyHeightToFirstAscent: false,
|
||||||
|
applyHeightToLastDescent: false,
|
||||||
|
),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 48,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
MainMenuButton(
|
||||||
|
onPressed: () => _score < maxScore
|
||||||
|
? {
|
||||||
|
setState(() {
|
||||||
|
_score++;
|
||||||
|
if (widget.onChanged != null) {
|
||||||
|
widget.onChanged!(_score);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
onLongPressed: () => _score > minScore
|
||||||
|
? {
|
||||||
|
setState(() {
|
||||||
|
_score += 10;
|
||||||
|
if (widget.onChanged != null) {
|
||||||
|
widget.onChanged!(_score);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
icon: Icons.add_rounded,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,9 +40,13 @@ class ScoreListTile extends StatelessWidget {
|
|||||||
height: 40,
|
height: 40,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: const TextInputType.numberWithOptions(signed: true),
|
||||||
maxLength: 4,
|
maxLength: 5,
|
||||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
inputFormatters: [
|
||||||
|
TextInputFormatter.withFunction((oldValue, newValue) {
|
||||||
|
return isValidScoreInput(newValue.text) ? newValue : oldValue;
|
||||||
|
}),
|
||||||
|
],
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
@@ -62,7 +66,7 @@ class ScoreListTile extends StatelessWidget {
|
|||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: CustomTheme.textColor.withAlpha(100),
|
color: CustomTheme.textColor.withAlpha(250),
|
||||||
width: 2,
|
width: 2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -80,4 +84,21 @@ class ScoreListTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Validates the input for the score text field.
|
||||||
|
bool isValidScoreInput(String text) {
|
||||||
|
if (text.isEmpty || text == '-') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
final isNegative = text.startsWith('-');
|
||||||
|
final digits = isNegative ? text.substring(1) : text;
|
||||||
|
|
||||||
|
if (digits.isEmpty || digits.length > 4) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CHeck if all characters are digits 0 <= x <= 9
|
||||||
|
return digits.codeUnits.every((unit) => unit >= 48 && unit <= 57);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
name: tallee
|
name: tallee
|
||||||
description: "Tracking App for Card Games"
|
description: "Tracking App for Card Games"
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.0.28+262
|
version: 0.0.29+263
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.8.1
|
sdk: ^3.8.1
|
||||||
@@ -18,6 +18,7 @@ dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
flutter_numeric_text: ^1.3.3
|
||||||
flutter_popup: ^3.3.9
|
flutter_popup: ^3.3.9
|
||||||
fluttericon: ^2.0.0
|
fluttericon: ^2.0.0
|
||||||
font_awesome_flutter: ^11.0.0
|
font_awesome_flutter: ^11.0.0
|
||||||
|
|||||||
Reference in New Issue
Block a user