33 Commits

Author SHA1 Message Date
2b52ec3582 Merge remote-tracking branch 'origin/setup/44-workflows-um-format-stage-erweitern' into setup/44-workflows-um-format-stage-erweitern
All checks were successful
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Has been skipped
Pull Request Pipeline / update-version (pull_request) Successful in 6s
2026-01-17 20:10:53 +01:00
b79e2af958 Tested minor 2026-01-17 20:10:47 +01:00
Gitea Actions [bot]
b8bd737dc3 Updated version number 2026-01-17 19:10:00 +00:00
a0c0936867 Merge remote-tracking branch 'origin/setup/44-workflows-um-format-stage-erweitern' into setup/44-workflows-um-format-stage-erweitern
All checks were successful
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Has been skipped
Pull Request Pipeline / update-version (pull_request) Successful in 4s
2026-01-17 20:09:49 +01:00
b89cfd1f9f Only version number 2026-01-17 20:09:36 +01:00
Gitea Actions [bot]
24bb687317 Updated version number 2026-01-17 19:09:08 +00:00
b315ef3904 Merge remote-tracking branch 'origin/setup/44-workflows-um-format-stage-erweitern' into setup/44-workflows-um-format-stage-erweitern
Some checks failed
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Has been skipped
Pull Request Pipeline / update-version (pull_request) Has been cancelled
2026-01-17 20:09:00 +01:00
40e3673a87 Only version number 2026-01-17 20:08:55 +01:00
Gitea Actions [bot]
19f800ff5f Updated version number 2026-01-17 19:08:28 +00:00
5eee9a238d Tried version update
All checks were successful
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Has been skipped
Pull Request Pipeline / update-version (pull_request) Successful in 6s
2026-01-17 20:08:19 +01:00
bbf031100f Tried version update
Some checks failed
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Has been skipped
Pull Request Pipeline / update-version (pull_request) Failing after 5s
2026-01-17 20:07:33 +01:00
5d9e143027 Tried version update
Some checks failed
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Has been skipped
Pull Request Pipeline / update-version (pull_request) Failing after 5s
2026-01-17 20:06:56 +01:00
10f3aacf99 Tried version update
All checks were successful
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Has been skipped
Pull Request Pipeline / update-version (pull_request) Successful in 4s
2026-01-17 20:05:31 +01:00
8045855f91 Tried version update
Some checks failed
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Has been skipped
Pull Request Pipeline / update-version (pull_request) Failing after 1s
2026-01-17 20:04:19 +01:00
d4bb0c5698 Tried version update
All checks were successful
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Has been skipped
2026-01-17 20:03:46 +01:00
Gitea Actions [bot]
94c510ce67 Auto-format code 2026-01-17 18:59:41 +00:00
674e1111d2 Try sth
All checks were successful
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Successful in 2m15s
2026-01-17 19:57:25 +01:00
ec6105e01d Merge remote-tracking branch 'origin/setup/44-workflows-um-format-stage-erweitern' into setup/44-workflows-um-format-stage-erweitern
Some checks failed
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Has been cancelled
2026-01-17 19:57:05 +01:00
bfb5debbeb Try sth 2026-01-17 19:56:59 +01:00
liquid-actions[bot]
7764b65631 Auto-format code 2026-01-17 18:52:03 +00:00
b5453d9ced Added fetch
Some checks failed
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Failing after 2m16s
2026-01-17 19:49:46 +01:00
6a79b7cad9 Added checkout again
Some checks failed
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Failing after 2m12s
2026-01-17 19:46:38 +01:00
4bf39e8bba added formatting error
Some checks failed
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Failing after 2m17s
2026-01-17 19:43:34 +01:00
fede08b0a8 added formatting error
All checks were successful
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Successful in 2m18s
2026-01-17 19:42:26 +01:00
78aaebcd42 Fixed submodule error
Some checks failed
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Failing after 2m12s
2026-01-17 19:39:11 +01:00
c2bf859f85 Removed branch checkout
Some checks failed
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Failing after 3m3s
2026-01-17 19:35:54 +01:00
8d50cf4b46 Added branch checkout
Some checks failed
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Failing after 2m9s
2026-01-17 19:32:14 +01:00
b763375748 Updated workflow again 2
Some checks failed
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Failing after 3m13s
2026-01-17 19:28:19 +01:00
196b939ca7 Updated workflow again
Some checks failed
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Has been cancelled
2026-01-17 19:26:24 +01:00
c536656dff Added formatting issue
Some checks failed
Pull Request Pipeline / lint (pull_request) Has been skipped
Pull Request Pipeline / test (pull_request) Has been skipped
Pull Request Pipeline / format (pull_request) Has been cancelled
2026-01-17 19:21:21 +01:00
9676ff278d Updated workflow
Some checks are pending
Pull Request Pipeline / lint (pull_request) Has started running
Pull Request Pipeline / test (pull_request) Waiting to run
Pull Request Pipeline / format (pull_request) Successful in 2m6s
2026-01-17 19:18:03 +01:00
b53bb597e1 Updated formatting
Some checks failed
Pull Request Pipeline / lint (pull_request) Waiting to run
Pull Request Pipeline / test (pull_request) Waiting to run
Pull Request Pipeline / format (pull_request) Has been cancelled
2026-01-17 19:12:52 +01:00
929183109c Updated workflow
Some checks failed
Pull Request Pipeline / lint (pull_request) Waiting to run
Pull Request Pipeline / test (pull_request) Waiting to run
Pull Request Pipeline / format (pull_request) Failing after 1m50s
2026-01-17 19:08:40 +01:00
26 changed files with 398 additions and 793 deletions

View File

@@ -6,6 +6,7 @@ on:
jobs: jobs:
lint: lint:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: false
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -17,12 +18,12 @@ jobs:
- name: Install Flutter (wget) - name: Install Flutter (wget)
run: | run: |
wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz wget --quiet https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz
tar xf flutter_linux_3.38.2-stable.tar.xz tar xf flutter_linux_3.38.2-stable.tar.xz
# Set Git safe directory for Flutter path # Set Git safe directory for Flutter path
git config --global --add safe.directory "$(pwd)/flutter" git config --global --add safe.directory "$(pwd)/flutter"
# Set Flutter path # Set Flutter path
echo "$(pwd)/flutter/bin" >> $GITHUB_PATH echo "$(pwd)/flutter/bin" >> $GITEA_PATH
- name: Get dependencies - name: Get dependencies
run: flutter pub get run: flutter pub get
@@ -32,6 +33,7 @@ jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: false
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -43,7 +45,7 @@ jobs:
- name: Install Flutter (wget) - name: Install Flutter (wget)
run: | run: |
wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz wget --quiet https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz
tar xf flutter_linux_3.38.2-stable.tar.xz tar xf flutter_linux_3.38.2-stable.tar.xz
# Set Git safe directory for Flutter path # Set Git safe directory for Flutter path
git config --global --add safe.directory "$(pwd)/flutter" git config --global --add safe.directory "$(pwd)/flutter"
@@ -55,3 +57,84 @@ jobs:
- name: Run tests - name: Run tests
run: flutter test run: flutter test
format:
runs-on: ubuntu-latest
if: false
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install dependencies
run: |
apt-get update
apt-get install -y jq
- name: Install Flutter (wget)
run: |
wget --progress=dot:giga https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz
tar xf flutter_linux_3.38.2-stable.tar.xz
# Set Git safe directory for Flutter path
git config --global --add safe.directory "$(pwd)/flutter"
# Set Flutter path
echo "$(pwd)/flutter/bin" >> $GITHUB_PATH
- name: Get dependencies
run: flutter pub get
- name: Check code format
id: check_format
continue-on-error: true
run: flutter analyze lib test
- name: Format code
if: steps.check_format.outcome == 'failure'
run: |
git fetch origin ${{ gitea.head_ref }}
git checkout ${{ gitea.head_ref }}
dart fix --apply lib
dart fix --apply test
if [ -n "$(git status --porcelain lib test)" ]; then
git config --global user.name "Gitea Actions [bot]"
git config --global user.email "actions@gitea.com"
git add lib test
git commit -m "Auto-format code"
git push
# Alt: git push https://oauth2:${{ secrets.BOT_TOKEN }}@git.yannick-weigert.de/liquid-development/game-tracker.git HEAD:${{ gitea.head_ref }}
else
echo "No changes to commit"
fi
- name: Verify format
run: flutter analyze lib test
update-version:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Update version
uses: https://github.com/stikkyapp/update-pubspec-version@v2
with:
strategy: 'minor'
path: './pubspec.yaml'
- name: Commit version update 2
run: |
git fetch origin ${{ gitea.head_ref }}
git checkout ${{ gitea.head_ref }}
git config --global user.name "Gitea Actions [bot]"
git config --global user.email "actions@gitea.com"
git add pubspec.yaml
git commit -m "Updated version number"
git push
# Alt: git push https://oauth2:${{ secrets.BOT_TOKEN }}@git.yannick-weigert.de/liquid-development/game-tracker.git HEAD:${{ gitea.head_ref }}

View File

@@ -5,32 +5,14 @@ class CustomTheme {
CustomTheme._(); // Private constructor to prevent instantiation CustomTheme._(); // Private constructor to prevent instantiation
// ==================== Colors ==================== // ==================== Colors ====================
/// Primary color of the app theme
static Color primaryColor = const Color(0xFF7505E4); static Color primaryColor = const Color(0xFF7505E4);
/// Secondary color of the app theme
static Color secondaryColor = const Color(0xFFAFA2FF); static Color secondaryColor = const Color(0xFFAFA2FF);
/// Background color of the app theme
static Color backgroundColor = const Color(0xFF0B0B0B); static Color backgroundColor = const Color(0xFF0B0B0B);
/// Default color for boxes and containers
static Color boxColor = const Color(0xFF101010); static Color boxColor = const Color(0xFF101010);
/// Default border color for boxes and containers
static Color boxBorder = const Color(0xFF272727);
/// Color for boxes and containers displayed on boxes
static Color onBoxColor = const Color(0xFF181818); static Color onBoxColor = const Color(0xFF181818);
static Color boxBorder = const Color(0xFF272727);
/// Text color used throughout the app
static const Color textColor = Colors.white; static const Color textColor = Colors.white;
/// Selected color for the [NavbarItem]
static Color navBarItemSelectedColor = primaryColor.withGreen(100); static Color navBarItemSelectedColor = primaryColor.withGreen(100);
/// Unselected color for the [NavbarItem]
static Color navBarItemUnselectedColor = Colors.grey.shade400; static Color navBarItemUnselectedColor = Colors.grey.shade400;
// ==================== Border Radius ==================== // ==================== Border Radius ====================

View File

@@ -4,7 +4,6 @@
"all_players_selected": "Alle Spieler:innen ausgewählt", "all_players_selected": "Alle Spieler:innen ausgewählt",
"amount_of_matches": "Anzahl der Spiele", "amount_of_matches": "Anzahl der Spiele",
"app_name": "Game Tracker", "app_name": "Game Tracker",
"best_player": "Beste:r Spieler:in",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"choose_game": "Spielvorlage wählen", "choose_game": "Spielvorlage wählen",
"choose_group": "Gruppe wählen", "choose_group": "Gruppe wählen",
@@ -14,7 +13,6 @@
"create_match": "Spiel erstellen", "create_match": "Spiel erstellen",
"create_new_group": "Neue Gruppe erstellen", "create_new_group": "Neue Gruppe erstellen",
"create_new_match": "Neues Spiel erstellen", "create_new_match": "Neues Spiel erstellen",
"created_on": "Erstellt am",
"data": "Daten", "data": "Daten",
"data_successfully_deleted": "Daten erfolgreich gelöscht", "data_successfully_deleted": "Daten erfolgreich gelöscht",
"data_successfully_exported": "Daten erfolgreich exportiert", "data_successfully_exported": "Daten erfolgreich exportiert",
@@ -22,13 +20,7 @@
"days_ago": "vor {count} Tagen", "days_ago": "vor {count} Tagen",
"delete": "Löschen", "delete": "Löschen",
"delete_all_data": "Alle Daten löschen", "delete_all_data": "Alle Daten löschen",
"delete_group": "Diese Gruppe löschen",
"edit_group": "Gruppe bearbeiten",
"delete_group": "Gruppe löschen",
"edit_group": "Gruppe bearbeiten",
"error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", "error_creating_group": "Fehler beim Erstellen 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_reading_file": "Fehler beim Lesen der Datei", "error_reading_file": "Fehler beim Lesen der Datei",
"export_canceled": "Export abgebrochen", "export_canceled": "Export abgebrochen",
"export_data": "Daten exportieren", "export_data": "Daten exportieren",
@@ -37,7 +29,6 @@
"game_name": "Spielvorlagenname", "game_name": "Spielvorlagenname",
"group": "Gruppe", "group": "Gruppe",
"group_name": "Gruppenname", "group_name": "Gruppenname",
"group_profile": "Gruppenprofil",
"groups": "Gruppen", "groups": "Gruppen",
"home": "Startseite", "home": "Startseite",
"import_canceled": "Import abgebrochen", "import_canceled": "Import abgebrochen",
@@ -51,7 +42,6 @@
"match_in_progress": "Spiel läuft...", "match_in_progress": "Spiel läuft...",
"match_name": "Spieltitel", "match_name": "Spieltitel",
"matches": "Spiele", "matches": "Spiele",
"members": "Mitglieder",
"most_points": "Höchste Punkte", "most_points": "Höchste Punkte",
"no_data_available": "Keine Daten verfügbar", "no_data_available": "Keine Daten verfügbar",
"no_groups_created_yet": "Noch keine Gruppen erstellt", "no_groups_created_yet": "Noch keine Gruppen erstellt",
@@ -67,7 +57,6 @@
"none": "Kein", "none": "Kein",
"none_group": "Keine", "none_group": "Keine",
"not_available": "Nicht verfügbar", "not_available": "Nicht verfügbar",
"played_matches": "Gespielte Spiele",
"player_name": "Spieler:innenname", "player_name": "Spieler:innenname",
"players": "Spieler:innen", "players": "Spieler:innen",
"players_count": "{count} Spieler", "players_count": "{count} Spieler",

View File

@@ -12,9 +12,6 @@
"@app_name": { "@app_name": {
"description": "The name of the App" "description": "The name of the App"
}, },
"@best_player": {
"description": "Label for best player statistic"
},
"@cancel": { "@cancel": {
"description": "Cancel button text" "description": "Cancel button text"
}, },
@@ -37,13 +34,10 @@
"description": "Button text to create a match" "description": "Button text to create a match"
}, },
"@create_new_group": { "@create_new_group": {
"description": "Appbar text to create a new group" "description": "Button text to create a new group"
}, },
"@create_new_match": { "@create_new_match": {
"description": "Appbar text to create a new match" "description": "Button text to create a new match"
},
"@created_on": {
"description": "Label for creation date"
}, },
"@data": { "@data": {
"description": "Data label" "description": "Data label"
@@ -71,21 +65,9 @@
"@delete_all_data": { "@delete_all_data": {
"description": "Confirmation dialog for deleting all data" "description": "Confirmation dialog for deleting all data"
}, },
"@delete_group": {
"description": "Confirmation dialog for deleting a group"
},
"@edit_group": {
"description": "Button & Appbar label for editing a group"
},
"@error_creating_group": { "@error_creating_group": {
"description": "Error message when group creation fails" "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": { "@error_reading_file": {
"description": "Error message when file cannot be read" "description": "Error message when file cannot be read"
}, },
@@ -110,9 +92,6 @@
"@group_name": { "@group_name": {
"description": "Placeholder for group name input" "description": "Placeholder for group name input"
}, },
"@group_profile": {
"description": "Title for group profile view"
},
"@groups": { "@groups": {
"description": "Label for groups" "description": "Label for groups"
}, },
@@ -152,9 +131,6 @@
"@matches": { "@matches": {
"description": "Label for matches" "description": "Label for matches"
}, },
"@members": {
"description": "Label for group members"
},
"@most_points": { "@most_points": {
"description": "Title for most points ruleset" "description": "Title for most points ruleset"
}, },
@@ -200,9 +176,6 @@
"@not_available": { "@not_available": {
"description": "Abbreviation for not available" "description": "Abbreviation for not available"
}, },
"@played_matches": {
"description": "Label for played matches statistic"
},
"@player_name": { "@player_name": {
"description": "Placeholder for player name input" "description": "Placeholder for player name input"
}, },
@@ -308,7 +281,6 @@
"all_players_selected": "All players selected", "all_players_selected": "All players selected",
"amount_of_matches": "Amount of Matches", "amount_of_matches": "Amount of Matches",
"app_name": "Game Tracker", "app_name": "Game Tracker",
"best_player": "Best Player",
"cancel": "Cancel", "cancel": "Cancel",
"choose_game": "Choose Game", "choose_game": "Choose Game",
"choose_group": "Choose Group", "choose_group": "Choose Group",
@@ -317,7 +289,6 @@
"create_group": "Create Group", "create_group": "Create Group",
"create_match": "Create match", "create_match": "Create match",
"create_new_group": "Create new group", "create_new_group": "Create new group",
"created_on": "Created on",
"create_new_match": "Create new match", "create_new_match": "Create new match",
"data": "Data", "data": "Data",
"data_successfully_deleted": "Data successfully deleted", "data_successfully_deleted": "Data successfully deleted",
@@ -326,11 +297,7 @@
"days_ago": "{count} days ago", "days_ago": "{count} days ago",
"delete": "Delete", "delete": "Delete",
"delete_all_data": "Delete all data", "delete_all_data": "Delete all data",
"delete_group": "Delete Group",
"edit_group": "Edit Group",
"error_creating_group": "Error while creating group, please try again", "error_creating_group": "Error while creating 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_reading_file": "Error reading file", "error_reading_file": "Error reading file",
"export_canceled": "Export canceled", "export_canceled": "Export canceled",
"export_data": "Export data", "export_data": "Export data",
@@ -339,7 +306,6 @@
"game_name": "Game Name", "game_name": "Game Name",
"group": "Group", "group": "Group",
"group_name": "Group name", "group_name": "Group name",
"group_profile": "Group Profile",
"groups": "Groups", "groups": "Groups",
"home": "Home", "home": "Home",
"import_canceled": "Import canceled", "import_canceled": "Import canceled",
@@ -353,7 +319,6 @@
"match_in_progress": "Match in progress...", "match_in_progress": "Match in progress...",
"match_name": "Match name", "match_name": "Match name",
"matches": "Matches", "matches": "Matches",
"members": "Members",
"most_points": "Most Points", "most_points": "Most Points",
"no_data_available": "No data available", "no_data_available": "No data available",
"no_groups_created_yet": "No groups created yet", "no_groups_created_yet": "No groups created yet",
@@ -369,7 +334,6 @@
"none": "None", "none": "None",
"none_group": "None", "none_group": "None",
"not_available": "Not available", "not_available": "Not available",
"played_matches": "Played Matches",
"player_name": "Player name", "player_name": "Player name",
"players": "Players", "players": "Players",
"players_count": "{count} Players", "players_count": "{count} Players",

View File

@@ -122,12 +122,6 @@ abstract class AppLocalizations {
/// **'Game Tracker'** /// **'Game Tracker'**
String get app_name; String get app_name;
/// Label for best player statistic
///
/// In en, this message translates to:
/// **'Best Player'**
String get best_player;
/// Cancel button text /// Cancel button text
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -170,19 +164,13 @@ abstract class AppLocalizations {
/// **'Create match'** /// **'Create match'**
String get create_match; String get create_match;
/// Appbar text to create a new group /// Button text to create a new group
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Create new group'** /// **'Create new group'**
String get create_new_group; String get create_new_group;
/// Label for creation date /// Button text to create a new match
///
/// In en, this message translates to:
/// **'Created on'**
String get created_on;
/// Appbar text to create a new match
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Create new match'** /// **'Create new match'**
@@ -230,36 +218,12 @@ abstract class AppLocalizations {
/// **'Delete all data'** /// **'Delete all data'**
String get delete_all_data; String get delete_all_data;
/// Confirmation dialog for deleting a group
///
/// In en, this message translates to:
/// **'Delete Group'**
String get delete_group;
/// Button & Appbar label for editing a group
///
/// In en, this message translates to:
/// **'Edit Group'**
String get edit_group;
/// Error message when group creation fails /// Error message when group creation fails
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Error while creating group, please try again'** /// **'Error while creating group, please try again'**
String get error_creating_group; String get error_creating_group;
/// Error message when group deletion fails
///
/// In en, this message translates to:
/// **'Error while deleting group, please try again'**
String get error_deleting_group;
/// Error message when group editing fails
///
/// In en, this message translates to:
/// **'Error while editing group, please try again'**
String get error_editing_group;
/// Error message when file cannot be read /// Error message when file cannot be read
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -308,12 +272,6 @@ abstract class AppLocalizations {
/// **'Group name'** /// **'Group name'**
String get group_name; String get group_name;
/// Title for group profile view
///
/// In en, this message translates to:
/// **'Group Profile'**
String get group_profile;
/// Label for groups /// Label for groups
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -392,12 +350,6 @@ abstract class AppLocalizations {
/// **'Matches'** /// **'Matches'**
String get matches; String get matches;
/// Label for group members
///
/// In en, this message translates to:
/// **'Members'**
String get members;
/// Title for most points ruleset /// Title for most points ruleset
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -488,12 +440,6 @@ abstract class AppLocalizations {
/// **'Not available'** /// **'Not available'**
String get not_available; String get not_available;
/// Label for played matches statistic
///
/// In en, this message translates to:
/// **'Played Matches'**
String get played_matches;
/// Placeholder for player name input /// Placeholder for player name input
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View File

@@ -20,9 +20,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get app_name => 'Game Tracker'; String get app_name => 'Game Tracker';
@override
String get best_player => 'Beste:r Spieler:in';
@override @override
String get cancel => 'Abbrechen'; String get cancel => 'Abbrechen';
@@ -49,9 +46,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get create_new_group => 'Neue Gruppe erstellen'; String get create_new_group => 'Neue Gruppe erstellen';
@override
String get created_on => 'Erstellt am';
@override @override
String get create_new_match => 'Neues Spiel erstellen'; String get create_new_match => 'Neues Spiel erstellen';
@@ -78,24 +72,10 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get delete_all_data => 'Alle Daten löschen'; String get delete_all_data => 'Alle Daten löschen';
@override
String get delete_group => 'Gruppe löschen';
@override
String get edit_group => 'Gruppe bearbeiten';
@override @override
String get error_creating_group => String get error_creating_group =>
'Fehler beim Erstellen der Gruppe, bitte erneut versuchen'; 'Fehler beim Erstellen der Gruppe, bitte erneut versuchen';
@override
String get error_deleting_group =>
'Fehler beim Löschen der Gruppe, bitte erneut versuchen';
@override
String get error_editing_group =>
'Fehler beim Bearbeiten der Gruppe, bitte erneut versuchen';
@override @override
String get error_reading_file => 'Fehler beim Lesen der Datei'; String get error_reading_file => 'Fehler beim Lesen der Datei';
@@ -120,9 +100,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get group_name => 'Gruppenname'; String get group_name => 'Gruppenname';
@override
String get group_profile => 'Gruppenprofil';
@override @override
String get groups => 'Gruppen'; String get groups => 'Gruppen';
@@ -162,9 +139,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get matches => 'Spiele'; String get matches => 'Spiele';
@override
String get members => 'Mitglieder';
@override @override
String get most_points => 'Höchste Punkte'; String get most_points => 'Höchste Punkte';
@@ -211,9 +185,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get not_available => 'Nicht verfügbar'; String get not_available => 'Nicht verfügbar';
@override
String get played_matches => 'Gespielte Spiele';
@override @override
String get player_name => 'Spieler:innenname'; String get player_name => 'Spieler:innenname';

View File

@@ -20,9 +20,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get app_name => 'Game Tracker'; String get app_name => 'Game Tracker';
@override
String get best_player => 'Best Player';
@override @override
String get cancel => 'Cancel'; String get cancel => 'Cancel';
@@ -49,9 +46,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get create_new_group => 'Create new group'; String get create_new_group => 'Create new group';
@override
String get created_on => 'Created on';
@override @override
String get create_new_match => 'Create new match'; String get create_new_match => 'Create new match';
@@ -78,24 +72,10 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get delete_all_data => 'Delete all data'; String get delete_all_data => 'Delete all data';
@override
String get delete_group => 'Delete Group';
@override
String get edit_group => 'Edit Group';
@override @override
String get error_creating_group => String get error_creating_group =>
'Error while creating group, please try again'; 'Error while creating group, please try again';
@override
String get error_deleting_group =>
'Error while deleting group, please try again';
@override
String get error_editing_group =>
'Error while editing group, please try again';
@override @override
String get error_reading_file => 'Error reading file'; String get error_reading_file => 'Error reading file';
@@ -120,9 +100,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get group_name => 'Group name'; String get group_name => 'Group name';
@override
String get group_profile => 'Group Profile';
@override @override
String get groups => 'Groups'; String get groups => 'Groups';
@@ -162,9 +139,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get matches => 'Matches'; String get matches => 'Matches';
@override
String get members => 'Members';
@override @override
String get most_points => 'Most Points'; String get most_points => 'Most Points';
@@ -211,9 +185,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get not_available => 'Not available'; String get not_available => 'Not available';
@override
String get played_matches => 'Played Matches';
@override @override
String get player_name => 'Player name'; String get player_name => 'Player name';

View File

@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:game_tracker/core/adaptive_page_route.dart'; import 'package:game_tracker/core/adaptive_page_route.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/views/main_menu/group_view/group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/group_view/groups_view.dart';
import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; import 'package:game_tracker/presentation/views/main_menu/home_view.dart';
import 'package:game_tracker/presentation/views/main_menu/match_view/match_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/match_view.dart';
import 'package:game_tracker/presentation/views/main_menu/settings_view/settings_view.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view/settings_view.dart';

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/constants.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/core/enums.dart';
import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/database.dart';
@@ -11,10 +12,8 @@ import 'package:game_tracker/presentation/widgets/text_input/text_input_field.da
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class CreateGroupView extends StatefulWidget { class CreateGroupView extends StatefulWidget {
const CreateGroupView({super.key, this.groupToEdit}); /// A view that allows the user to create a new group
const CreateGroupView({super.key});
/// The group to edit, if any
final Group? groupToEdit;
@override @override
State<CreateGroupView> createState() => _CreateGroupViewState(); State<CreateGroupView> createState() => _CreateGroupViewState();
@@ -23,29 +22,16 @@ class CreateGroupView extends StatefulWidget {
class _CreateGroupViewState extends State<CreateGroupView> { class _CreateGroupViewState extends State<CreateGroupView> {
late final AppDatabase db; late final AppDatabase db;
/// GlobalKey for ScaffoldMessenger to show snackbars
final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
/// Controller for the group name input field /// Controller for the group name input field
final _groupNameController = TextEditingController(); final _groupNameController = TextEditingController();
/// List of currently selected players /// List of currently selected players
List<Player> selectedPlayers = []; List<Player> selectedPlayers = [];
/// List of initially selected players (when editing a group)
List<Player> initialSelectedPlayers = [];
@override @override
void initState() { void initState() {
super.initState(); super.initState();
db = Provider.of<AppDatabase>(context, listen: false); db = Provider.of<AppDatabase>(context, listen: false);
if(widget.groupToEdit != null) {
_groupNameController.text = widget.groupToEdit!.name;
setState(() {
initialSelectedPlayers = widget.groupToEdit!.members;
selectedPlayers = widget.groupToEdit!.members;
});
}
_groupNameController.addListener(() { _groupNameController.addListener(() {
setState(() {}); setState(() {});
}); });
@@ -61,42 +47,9 @@ class _CreateGroupViewState extends State<CreateGroupView> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
return ScaffoldMessenger( return ScaffoldMessenger(
key: _scaffoldMessengerKey,
child: Scaffold( child: Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: CustomTheme.backgroundColor, backgroundColor: CustomTheme.backgroundColor,
appBar: AppBar(title: Text(widget.groupToEdit == null ? loc.create_new_group : loc.edit_group), actions: widget.groupToEdit == null ? [] : [IconButton(icon: const Icon(Icons.delete), onPressed: () async { appBar: AppBar(title: Text(loc.create_new_group)),
if(widget.groupToEdit != null) {
showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(loc.delete_group),
content: Text(loc.this_cannot_be_undone),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(loc.cancel),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(loc.delete),
),
],
),
).then((confirmed) async {
if (confirmed == true && context.mounted) {
bool success = await db.groupDao.deleteGroup(groupId: widget.groupToEdit!.id);
if (!context.mounted) return;
if (success) {
Navigator.pop(context);
} else {
if (!mounted) return;
showSnackbar(message: loc.error_deleting_group);
}
}
});
}
},)],),
body: SafeArea( body: SafeArea(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@@ -106,11 +59,11 @@ class _CreateGroupViewState extends State<CreateGroupView> {
child: TextInputField( child: TextInputField(
controller: _groupNameController, controller: _groupNameController,
hintText: loc.group_name, hintText: loc.group_name,
maxLength: Constants.MAX_GROUP_NAME_LENGTH,
), ),
), ),
Expanded( Expanded(
child: PlayerSelection( child: PlayerSelection(
initialSelectedPlayers: initialSelectedPlayers,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
selectedPlayers = [...value]; selectedPlayers = [...value];
@@ -119,7 +72,7 @@ class _CreateGroupViewState extends State<CreateGroupView> {
), ),
), ),
CustomWidthButton( CustomWidthButton(
text: widget.groupToEdit == null ? loc.create_group : loc.edit_group, text: loc.create_group,
sizeRelativeToWidth: 0.95, sizeRelativeToWidth: 0.95,
buttonType: ButtonType.primary, buttonType: ButtonType.primary,
onPressed: onPressed:
@@ -127,34 +80,29 @@ class _CreateGroupViewState extends State<CreateGroupView> {
(selectedPlayers.length < 2)) (selectedPlayers.length < 2))
? null ? null
: () async { : () async {
late Group? updatedGroup; bool success = await db.groupDao.addGroup(
late bool success; group: Group(
if (widget.groupToEdit == null) {
success = await db.groupDao.addGroup(
group: Group(
name: _groupNameController.text.trim(),
members: selectedPlayers,
),
);
} else {
updatedGroup = Group(
id: widget.groupToEdit!.id,
name: _groupNameController.text.trim(), name: _groupNameController.text.trim(),
members: selectedPlayers, members: selectedPlayers,
); ),
//TODO: Implement group editing in database );
/*
success = await db.groupDao.updateGroup(
group: updatedGroup,
);
*/
success = true;
}
if (!context.mounted) return; if (!context.mounted) return;
if (success) { if (success) {
Navigator.pop(context, updatedGroup); Navigator.pop(context);
} else { } else {
showSnackbar(message: widget.groupToEdit == null ? loc.error_creating_group : loc.error_editing_group); ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: CustomTheme.boxColor,
content: Center(
child: Text(
AppLocalizations.of(
context,
).error_creating_group,
style: const TextStyle(color: Colors.white),
),
),
),
);
} }
}, },
), ),
@@ -165,21 +113,4 @@ class _CreateGroupViewState extends State<CreateGroupView> {
), ),
); );
} }
/// Displays a snackbar with the given message and optional action.
///
/// [message] The message to display in the snackbar.
void showSnackbar({
required String message,
}) {
final messenger = _scaffoldMessengerKey.currentState;
if (messenger != null) {
messenger.hideCurrentSnackBar();
messenger.showSnackBar(
SnackBar(
content: Text(message, style: const TextStyle(color: Colors.white)),
backgroundColor: CustomTheme.boxColor,
),
);
}
}
} }

View File

@@ -1,279 +0,0 @@
import 'package:flutter/material.dart';
import 'package:game_tracker/core/adaptive_page_route.dart';
import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/data/dto/match.dart';
import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/views/main_menu/group_view/create_group_view.dart';
import 'package:game_tracker/presentation/widgets/app_skeleton.dart';
import 'package:game_tracker/presentation/widgets/buttons/animated_dialog_button.dart';
import 'package:game_tracker/presentation/widgets/buttons/main_menu_button.dart';
import 'package:game_tracker/presentation/widgets/colored_icon_container.dart';
import 'package:game_tracker/presentation/widgets/custom_alert_dialog.dart';
import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart';
import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
class GroupDetailView extends StatefulWidget {
/// A view that displays the profile of a group
/// - [group]: The group to display
const GroupDetailView({
super.key,
required this.group,
required this.callback,
});
/// The group to display
final Group group;
final VoidCallback callback;
@override
State<GroupDetailView> createState() => _GroupDetailViewState();
}
class _GroupDetailViewState extends State<GroupDetailView> {
late final AppDatabase db;
bool isLoading = true;
late Group _group;
/// Total matches played in this group
int totalMatches = 0;
String bestPlayer = '';
@override
void initState() {
super.initState();
_group = widget.group;
db = Provider.of<AppDatabase>(context, listen: false);
_loadStatistics();
}
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
return Scaffold(
backgroundColor: CustomTheme.backgroundColor,
appBar: AppBar(
title: Text(loc.group_profile),
actions: [
IconButton(
icon: const Icon(Icons.delete),
onPressed: () async {
showDialog<bool>(
context: context,
builder: (context) => CustomAlertDialog(
title: '${loc.delete_group}?',
content: loc.this_cannot_be_undone,
actions: [
AnimatedDialogButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(
loc.cancel,
style: const TextStyle(color: CustomTheme.textColor),
),
),
AnimatedDialogButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(
loc.delete,
style: TextStyle(color: CustomTheme.secondaryColor),
),
),
],
),
).then((confirmed) async {
if (confirmed! && context.mounted) {
await db.groupDao.deleteGroup(groupId: _group.id);
if (!context.mounted) return;
Navigator.pop(context);
widget.callback.call();
}
});
},
),
],
),
body: SafeArea(
child: Stack(
alignment: Alignment.center,
children: [
ListView(
padding: const EdgeInsets.only(
left: 12,
right: 12,
top: 20,
bottom: 100,
),
children: [
const Center(
child: ColoredIconContainer(
icon: Icons.group,
containerSize: 55,
iconSize: 38,
),
),
const SizedBox(height: 10),
Text(
_group.name,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: CustomTheme.textColor,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 5),
Text(
'${loc.created_on} ${DateFormat.yMMMd(Localizations.localeOf(context).toString()).format(_group.createdAt)}',
style: const TextStyle(
fontSize: 12,
color: CustomTheme.textColor,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 20),
InfoTile(
title: loc.members,
icon: Icons.people,
horizontalAlignment: CrossAxisAlignment.start,
content: Wrap(
alignment: WrapAlignment.start,
crossAxisAlignment: WrapCrossAlignment.start,
spacing: 12,
runSpacing: 8,
children: _group.members.map((member) {
return TextIconTile(
text: member.name,
iconEnabled: false,
);
}).toList(),
),
),
const SizedBox(height: 15),
InfoTile(
title: loc.statistics,
icon: Icons.bar_chart,
content: AppSkeleton(
enabled: isLoading,
child: Column(
children: [
_buildStatRow(
loc.members,
_group.members.length.toString(),
),
_buildStatRow(
loc.played_matches,
totalMatches.toString(),
),
_buildStatRow(loc.best_player, bestPlayer),
],
),
),
),
],
),
Positioned(
bottom: MediaQuery.paddingOf(context).bottom,
child: MainMenuButton(
text: loc.edit_group,
icon: Icons.edit,
onPressed: () async {
final updatedGroup = await Navigator.push<Group?>(
context,
adaptivePageRoute(
builder: (context) {
return CreateGroupView(
groupToEdit: _group,
);
},
),
);
if (updatedGroup != null && mounted) {
setState(() {
_group = updatedGroup;
});
_loadStatistics();
widget.callback();
}
},
),
),
],
),
),
);
}
/// Builds a single statistic row with a label and value
/// - [label]: The label of the statistic
/// - [value]: The value of the statistic
Widget _buildStatRow(String label, String value) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Text(
label,
style: const TextStyle(
fontSize: 16,
color: CustomTheme.textColor,
),
),
],
),
Text(
value,
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
],
),
);
}
/// Loads statistics for this group
Future<void> _loadStatistics() async {
final matches = await db.matchDao.getAllMatches();
final groupMatches =
matches.where((match) => match.group?.id == _group.id).toList();
setState(() {
totalMatches = groupMatches.length;
bestPlayer = _getBestPlayer(groupMatches);
isLoading = false;
});
}
/// Determines the best player in the group based on match wins
String _getBestPlayer(List<Match> matches) {
final bestPlayerCounts = <Player, int>{};
// Count wins for each player
for (var match in matches) {
if (match.winner != null) {
bestPlayerCounts.update(
match.winner!,
(value) => value + 1,
ifAbsent: () => 1,
);
}
}
// Sort players by win count
final sortedPlayers = bestPlayerCounts.entries.toList()
..sort((a, b) => b.value.compareTo(a.value));
// Get the best player
bestPlayer = sortedPlayers.isNotEmpty ? sortedPlayers.first.key.name : '-';
return bestPlayer;
}
}

View File

@@ -6,14 +6,14 @@ import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/views/main_menu/group_view/group_detail_view.dart';
import 'package:game_tracker/presentation/views/main_menu/group_view/create_group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/group_view/create_group_view.dart';
import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; import 'package:game_tracker/presentation/widgets/app_skeleton.dart';
import 'package:game_tracker/presentation/widgets/buttons/main_menu_button.dart'; import 'package:game_tracker/presentation/widgets/buttons/main_menu_button.dart';
import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart';
import 'package:game_tracker/presentation/widgets/top_centered_message.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:game_tracker/presentation/widgets/top_centered_message.dart';
class GroupsView extends StatefulWidget { class GroupsView extends StatefulWidget {
/// A view that displays a list of groups /// A view that displays a list of groups
const GroupsView({super.key}); const GroupsView({super.key});
@@ -75,22 +75,7 @@ class _GroupsViewState extends State<GroupsView> {
height: MediaQuery.paddingOf(context).bottom - 20, height: MediaQuery.paddingOf(context).bottom - 20,
); );
} }
return GroupTile( return GroupTile(group: groups[index]);
group: groups[index],
onTap: () async {
await Navigator.push(
context,
adaptivePageRoute(
builder: (context) {
return GroupDetailView(
group: groups[index],
callback: loadGroups,
);
},
),
);
},
);
}, },
), ),
), ),
@@ -121,9 +106,6 @@ class _GroupsViewState extends State<GroupsView> {
} }
void loadGroups() { void loadGroups() {
setState(() {
isLoading = true;
});
Future.wait([ Future.wait([
db.groupDao.getAllGroups(), db.groupDao.getAllGroups(),
Future.delayed(Constants.MINIMUM_SKELETON_DURATION), Future.delayed(Constants.MINIMUM_SKELETON_DURATION),

View File

@@ -43,7 +43,6 @@ class _ChooseGameViewState extends State<ChooseGameView> {
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
return Scaffold( return Scaffold(
backgroundColor: CustomTheme.backgroundColor, backgroundColor: CustomTheme.backgroundColor,
resizeToAvoidBottomInset: false,
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back_ios), icon: const Icon(Icons.arrow_back_ios),

View File

@@ -43,7 +43,6 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
return Scaffold( return Scaffold(
backgroundColor: CustomTheme.backgroundColor, backgroundColor: CustomTheme.backgroundColor,
resizeToAvoidBottomInset: false,
appBar: AppBar( appBar: AppBar(
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back_ios), icon: const Icon(Icons.arrow_back_ios),

View File

@@ -0,0 +1,99 @@
import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/core/enums.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart';
class ChooseRulesetView extends StatefulWidget {
/// A view that allows the user to choose a ruleset from a list of available rulesets
/// - [rulesets]: A list of tuples containing the ruleset and its description
/// - [initialRulesetIndex]: The index of the initially selected ruleset
const ChooseRulesetView({
super.key,
required this.rulesets,
required this.initialRulesetIndex,
});
/// A list of tuples containing the ruleset and its description
final List<(Ruleset, String)> rulesets;
/// The index of the initially selected ruleset
final int initialRulesetIndex;
@override
State<ChooseRulesetView> createState() => _ChooseRulesetViewState();
}
class _ChooseRulesetViewState extends State<ChooseRulesetView> {
/// Currently selected ruleset index
late int selectedRulesetIndex;
@override
void initState() {
selectedRulesetIndex = widget.initialRulesetIndex;
super.initState();
}
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
return DefaultTabController(
length: 2,
initialIndex: 0,
child: Scaffold(
backgroundColor: CustomTheme.backgroundColor,
appBar: AppBar(
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: () {
Navigator.of(context).pop(
selectedRulesetIndex == -1
? null
: widget.rulesets[selectedRulesetIndex].$1,
);
},
),
title: Text(loc.choose_ruleset),
),
body: PopScope(
// This fixes that the Android Back Gesture didn't return the
// selectedRulesetIndex and therefore the selected Ruleset wasn't saved
canPop: false,
onPopInvokedWithResult: (bool didPop, Object? result) {
if (didPop) {
return;
}
Navigator.of(context).pop(
selectedRulesetIndex == -1
? null
: widget.rulesets[selectedRulesetIndex].$1,
);
},
child: ListView.builder(
padding: const EdgeInsets.only(bottom: 85),
itemCount: widget.rulesets.length,
itemBuilder: (BuildContext context, int index) {
return TitleDescriptionListTile(
onPressed: () async {
setState(() {
if (selectedRulesetIndex == index) {
selectedRulesetIndex = -1;
} else {
selectedRulesetIndex = index;
}
});
},
title: translateRulesetToString(
widget.rulesets[index].$1,
context,
),
description: widget.rulesets[index].$2,
isHighlighted: selectedRulesetIndex == index,
);
},
),
),
),
);
}
}

View File

@@ -10,6 +10,7 @@ import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_game_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_game_view.dart';
import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_group_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_group_view.dart';
import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_ruleset_view.dart';
import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart'; import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart';
import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart';
import 'package:game_tracker/presentation/widgets/player_selection.dart'; import 'package:game_tracker/presentation/widgets/player_selection.dart';
@@ -57,6 +58,13 @@ class _CreateMatchViewState extends State<CreateMatchView> {
/// the [ChooseGroupView] /// the [ChooseGroupView]
String selectedGroupId = ''; String selectedGroupId = '';
/// The currently selected ruleset
Ruleset? selectedRuleset;
/// The index of the currently selected ruleset in [rulesets] to mark it in
/// the [ChooseRulesetView]
int selectedRulesetIndex = -1;
/// The index of the currently selected game in [games] to mark it in /// The index of the currently selected game in [games] to mark it in
/// the [ChooseGameView] /// the [ChooseGameView]
int selectedGameIndex = -1; int selectedGameIndex = -1;
@@ -64,8 +72,8 @@ class _CreateMatchViewState extends State<CreateMatchView> {
/// The currently selected players /// The currently selected players
List<Player>? selectedPlayers; List<Player>? selectedPlayers;
/// GlobalKey for ScaffoldMessenger to show snackbars /// List of available rulesets with their localized string representations
final _scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>(); late final List<(Ruleset, String)> _rulesets;
@override @override
void initState() { void initState() {
@@ -99,8 +107,15 @@ class _CreateMatchViewState extends State<CreateMatchView> {
super.didChangeDependencies(); super.didChangeDependencies();
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
hintText ??= loc.match_name; hintText ??= loc.match_name;
_rulesets = [
(Ruleset.singleWinner, loc.ruleset_single_winner),
(Ruleset.singleLoser, loc.ruleset_single_loser),
(Ruleset.mostPoints, loc.ruleset_most_points),
(Ruleset.leastPoints, loc.ruleset_least_points),
];
} }
// TODO: Replace when games are implemented
List<(String, String, Ruleset)> games = [ List<(String, String, Ruleset)> games = [
('Example Game 1', 'This is a description', Ruleset.leastPoints), ('Example Game 1', 'This is a description', Ruleset.leastPoints),
('Example Game 2', '', Ruleset.singleWinner), ('Example Game 2', '', Ruleset.singleWinner),
@@ -110,9 +125,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
return ScaffoldMessenger( return ScaffoldMessenger(
key: _scaffoldMessengerKey,
child: Scaffold( child: Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: CustomTheme.backgroundColor, backgroundColor: CustomTheme.backgroundColor,
appBar: AppBar(title: Text(loc.create_new_match)), appBar: AppBar(title: Text(loc.create_new_match)),
body: SafeArea( body: SafeArea(
@@ -144,12 +157,39 @@ class _CreateMatchViewState extends State<CreateMatchView> {
setState(() { setState(() {
if (selectedGameIndex != -1) { if (selectedGameIndex != -1) {
hintText = games[selectedGameIndex].$1; hintText = games[selectedGameIndex].$1;
selectedRuleset = games[selectedGameIndex].$3;
selectedRulesetIndex = _rulesets.indexWhere(
(r) => r.$1 == selectedRuleset,
);
} else { } else {
hintText = loc.match_name; hintText = loc.match_name;
selectedRuleset = null;
} }
}); });
}, },
), ),
ChooseTile(
title: loc.ruleset,
trailingText: selectedRuleset == null
? loc.none
: translateRulesetToString(selectedRuleset!, context),
onPressed: () async {
selectedRuleset = await Navigator.of(context).push(
adaptivePageRoute(
builder: (context) => ChooseRulesetView(
rulesets: _rulesets,
initialRulesetIndex: selectedRulesetIndex,
),
),
);
if (!mounted) return;
selectedRulesetIndex = _rulesets.indexWhere(
(r) => r.$1 == selectedRuleset,
);
selectedGameIndex = -1;
setState(() {});
},
),
ChooseTile( ChooseTile(
title: loc.group, title: loc.group,
trailingText: selectedGroup == null trailingText: selectedGroup == null
@@ -234,6 +274,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
/// - Either a group is selected OR at least 2 players are selected /// - Either a group is selected OR at least 2 players are selected
bool _enableCreateGameButton() { bool _enableCreateGameButton() {
return (selectedGroup != null || return (selectedGroup != null ||
(selectedPlayers != null && selectedPlayers!.length > 1)); (selectedPlayers != null && selectedPlayers!.length > 1)) &&
selectedRuleset != null;
} }
} }

View File

@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart'; import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart';
import 'package:game_tracker/presentation/widgets/colored_icon_container.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class LicenseDetailView extends StatelessWidget { class LicenseDetailView extends StatelessWidget {
@@ -30,11 +29,19 @@ class LicenseDetailView extends StatelessWidget {
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const ColoredIconContainer( Container(
icon: Icons.description, margin: const EdgeInsetsGeometry.only(right: 15),
margin: EdgeInsetsGeometry.only(right: 15), width: 60,
containerSize: 60, height: 60,
iconSize: 30, decoration: BoxDecoration(
color: CustomTheme.primaryColor.withAlpha(40),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
Icons.description,
color: CustomTheme.primaryColor,
size: 30,
),
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,

View File

@@ -31,7 +31,6 @@ class _SettingsViewState extends State<SettingsView> {
version: 'n.A.', version: 'n.A.',
buildNumber: 'n.A.', buildNumber: 'n.A.',
); );
@override @override
void initState() { void initState() {
super.initState(); super.initState();

View File

@@ -1,57 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:game_tracker/core/custom_theme.dart';
class ColoredIconContainer extends StatelessWidget {
/// A customizable container widget that displays an icon with a colored background.
/// - [icon]: The icon to be displayed inside the container.
/// - [containerSize]: The size of the container (width and height).
/// - [iconSize]: The size of the icon inside the container.
/// - [margin]: Optional margin around the container.
/// - [padding]: Optional padding inside the container.
const ColoredIconContainer({
super.key,
required this.icon,
this.containerSize = 44,
this.iconSize = 28,
this.margin,
this.padding,
});
/// The icon to be displayed inside the container.
final IconData icon;
/// The size of the container (width and height).
final double containerSize;
/// The size of the icon inside the container.
final double iconSize;
/// Optional margin around the container.
final EdgeInsetsGeometry? margin;
/// Optional padding inside the container.
final EdgeInsetsGeometry? padding;
@override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
width: containerSize,
height: containerSize,
margin: margin,
padding: padding,
decoration: BoxDecoration(
color: CustomTheme.primaryColor.withAlpha(40),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
icon,
size: iconSize,
color: CustomTheme.primaryColor.withGreen(40),
),
),
],
);
}
}

View File

@@ -70,7 +70,6 @@ class _PlayerSelectionState extends State<PlayerSelection> {
super.initState(); super.initState();
db = Provider.of<AppDatabase>(context, listen: false); db = Provider.of<AppDatabase>(context, listen: false);
suggestedPlayers = skeletonData; suggestedPlayers = skeletonData;
selectedPlayers = widget.initialSelectedPlayers ?? [];
loadPlayerList(); loadPlayerList();
} }
@@ -100,7 +99,7 @@ class _PlayerSelectionState extends State<PlayerSelection> {
if (value.isEmpty) { if (value.isEmpty) {
// If the search is empty, it shows all unselected players. // If the search is empty, it shows all unselected players.
suggestedPlayers = allPlayers.where((player) { suggestedPlayers = allPlayers.where((player) {
return !selectedPlayers.any((p) => p.id == player.id); return !selectedPlayers.contains(player);
}).toList(); }).toList();
} else { } else {
// If there is input, it filters by name match (case-insensitive) and ensures // If there is input, it filters by name match (case-insensitive) and ensures
@@ -109,7 +108,9 @@ class _PlayerSelectionState extends State<PlayerSelection> {
final bool nameMatches = player.name.toLowerCase().contains( final bool nameMatches = player.name.toLowerCase().contains(
value.toLowerCase(), value.toLowerCase(),
); );
final bool isNotSelected = !selectedPlayers.any((p) => p.id == player.id); final bool isNotSelected = !selectedPlayers.contains(
player,
);
return nameMatches && isNotSelected; return nameMatches && isNotSelected;
}).toList(); }).toList();
} }
@@ -124,49 +125,46 @@ class _PlayerSelectionState extends State<PlayerSelection> {
const SizedBox(height: 10), const SizedBox(height: 10),
SizedBox( SizedBox(
height: 50, height: 50,
child: AppSkeleton( child: selectedPlayers.isEmpty
enabled: isLoading, ? Center(child: Text(loc.no_players_selected))
child: selectedPlayers.isEmpty : SingleChildScrollView(
? Center(child: Text(loc.no_players_selected)) scrollDirection: Axis.horizontal,
: SingleChildScrollView( child: Row(
scrollDirection: Axis.horizontal, children: [
child: Row( for (var player in selectedPlayers)
children: [ Padding(
for (var player in selectedPlayers) padding: const EdgeInsets.only(right: 8.0),
Padding( child: TextIconTile(
padding: const EdgeInsets.only(right: 8.0), text: player.name,
child: TextIconTile( onIconTap: () {
text: player.name, setState(() {
onIconTap: () { // Removes the player from the selection and notifies the parent.
setState(() { selectedPlayers.remove(player);
// Removes the player from the selection and notifies the parent. widget.onChanged([...selectedPlayers]);
selectedPlayers.remove(player);
widget.onChanged([...selectedPlayers]);
// Get the current search query // Get the current search query
final currentSearch = _searchBarController final currentSearch = _searchBarController
.text .text
.toLowerCase(); .toLowerCase();
// If the player matches the current search query (or search is empty), // If the player matches the current search query (or search is empty),
// they are added back to the `suggestedPlayers` and the list is re-sorted. // they are added back to the `suggestedPlayers` and the list is re-sorted.
if (currentSearch.isEmpty || if (currentSearch.isEmpty ||
player.name.toLowerCase().contains( player.name.toLowerCase().contains(
currentSearch, currentSearch,
)) { )) {
suggestedPlayers.add(player); suggestedPlayers.add(player);
suggestedPlayers.sort( suggestedPlayers.sort(
(a, b) => a.name.compareTo(b.name), (a, b) => a.name.compareTo(b.name),
); );
} }
}); });
}, },
),
), ),
], ),
), ],
), ),
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Text( Text(
@@ -246,21 +244,7 @@ class _PlayerSelectionState extends State<PlayerSelection> {
// Otherwise, use the loaded players from the database. // Otherwise, use the loaded players from the database.
loadedPlayers.sort((a, b) => a.name.compareTo(b.name)); loadedPlayers.sort((a, b) => a.name.compareTo(b.name));
allPlayers = [...loadedPlayers]; allPlayers = [...loadedPlayers];
if (widget.initialSelectedPlayers != null) { suggestedPlayers = [...loadedPlayers];
// Excludes already selected players from the suggested players list.
suggestedPlayers = loadedPlayers.where((p) => !widget.initialSelectedPlayers!.any((ip) => ip.id == p.id)).toList();
// Ensures that only players available for selection are pre-selected.
selectedPlayers = widget.initialSelectedPlayers!
.where(
(p) => allPlayers.any(
(available) => available.id == p.id,
),
)
.toList();
} else {
// If no initial selection, all loaded players are suggested.
suggestedPlayers = [...loadedPlayers];
}
} }
isLoading = false; isLoading = false;
}); });

View File

@@ -3,17 +3,11 @@ import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart';
class GroupTile extends StatefulWidget { class GroupTile extends StatelessWidget {
/// A tile widget that displays information about a group, including its name and members. /// A tile widget that displays information about a group, including its name and members.
/// - [group]: The group data to be displayed. /// - [group]: The group data to be displayed.
/// - [isHighlighted]: Whether the tile should be highlighted. /// - [isHighlighted]: Whether the tile should be highlighted.
/// - [onTap]: Callback function to be executed when the tile is tapped. const GroupTile({super.key, required this.group, this.isHighlighted = false});
const GroupTile({
super.key,
required this.group,
this.isHighlighted = false,
this.onTap,
});
/// The group data to be displayed. /// The group data to be displayed.
final Group group; final Group group;
@@ -21,72 +15,61 @@ class GroupTile extends StatefulWidget {
/// Whether the tile should be highlighted. /// Whether the tile should be highlighted.
final bool isHighlighted; final bool isHighlighted;
/// Callback function to be executed when the tile is tapped.
final VoidCallback? onTap;
@override
State<GroupTile> createState() => _GroupTileState();
}
class _GroupTileState extends State<GroupTile> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return AnimatedContainer(
onTap: widget.onTap, margin: CustomTheme.standardMargin,
child: AnimatedContainer( padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
margin: CustomTheme.standardMargin, decoration: isHighlighted
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10), ? CustomTheme.highlightedBoxDecoration
decoration: widget.isHighlighted : CustomTheme.standardBoxDecoration,
? CustomTheme.highlightedBoxDecoration duration: const Duration(milliseconds: 150),
: CustomTheme.standardBoxDecoration, child: Column(
duration: const Duration(milliseconds: 150), crossAxisAlignment: CrossAxisAlignment.start,
child: Column( children: [
crossAxisAlignment: CrossAxisAlignment.start, Row(
children: [ mainAxisAlignment: MainAxisAlignment.spaceBetween,
Row( children: [
mainAxisAlignment: MainAxisAlignment.spaceBetween, Flexible(
children: [ child: Text(
Flexible( group.name,
child: Text( overflow: TextOverflow.ellipsis,
widget.group.name, style: const TextStyle(
overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold,
fontSize: 18,
),
),
),
Row(
children: [
Text(
'${group.members.length}',
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.w900,
fontSize: 18, fontSize: 18,
), ),
), ),
), const SizedBox(width: 3),
Row( const Icon(Icons.group, size: 22),
children: [ ],
Text( ),
'${widget.group.members.length}', ],
style: const TextStyle( ),
fontWeight: FontWeight.w900, const SizedBox(height: 5),
fontSize: 18, Wrap(
), alignment: WrapAlignment.start,
), crossAxisAlignment: WrapCrossAlignment.start,
const SizedBox(width: 3), spacing: 12.0,
const Icon(Icons.group, size: 22), runSpacing: 8.0,
], children: <Widget>[
), for (var member in [
], ...group.members,
), ]..sort((a, b) => a.name.compareTo(b.name)))
const SizedBox(height: 5), TextIconTile(text: member.name, iconEnabled: false),
Wrap( ],
alignment: WrapAlignment.start, ),
crossAxisAlignment: WrapCrossAlignment.start, const SizedBox(height: 2.5),
spacing: 12.0, ],
runSpacing: 8.0,
children: <Widget>[
for (var member in [
...widget.group.members,
]..sort((a, b) => a.name.compareTo(b.name)))
TextIconTile(text: member.name, iconEnabled: false),
],
),
const SizedBox(height: 2.5),
],
),
), ),
); );
} }

View File

@@ -17,7 +17,6 @@ class InfoTile extends StatefulWidget {
this.padding, this.padding,
this.height, this.height,
this.width, this.width,
this.horizontalAlignment = CrossAxisAlignment.center,
}); });
/// The title text displayed on the tile. /// The title text displayed on the tile.
@@ -38,9 +37,6 @@ class InfoTile extends StatefulWidget {
/// Optional width for the tile. /// Optional width for the tile.
final double? width; final double? width;
/// The main axis alignment for the content.
final CrossAxisAlignment horizontalAlignment;
@override @override
State<InfoTile> createState() => _InfoTileState(); State<InfoTile> createState() => _InfoTileState();
} }
@@ -55,7 +51,7 @@ class _InfoTileState extends State<InfoTile> {
decoration: CustomTheme.standardBoxDecoration, decoration: CustomTheme.standardBoxDecoration,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: widget.horizontalAlignment, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Row( Row(
children: [ children: [

View File

@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart';
import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart';
import 'package:game_tracker/presentation/widgets/colored_icon_container.dart';
class LicenseTile extends StatelessWidget { class LicenseTile extends StatelessWidget {
/// A tile widget that displays information about a software package license. /// A tile widget that displays information about a software package license.
@@ -30,10 +29,18 @@ class LicenseTile extends StatelessWidget {
), ),
child: Row( child: Row(
children: [ children: [
const ColoredIconContainer( Container(
icon: Icons.description, width: 50,
containerSize: 50, height: 50,
iconSize: 32, decoration: BoxDecoration(
color: CustomTheme.primaryColor.withAlpha(40),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
Icons.description,
color: CustomTheme.primaryColor,
size: 32,
),
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(

View File

@@ -230,7 +230,7 @@ class _MatchTileState extends State<MatchTile> {
} else if (difference.inDays < 7) { } else if (difference.inDays < 7) {
return loc.days_ago(difference.inDays); return loc.days_ago(difference.inDays);
} else { } else {
return '${loc.created_on} ${DateFormat.yMMMd(Localizations.localeOf(context).toString()).format(dateTime)}'; return DateFormat('MMM d, yyyy').format(dateTime);
} }
} }

View File

@@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/presentation/widgets/colored_icon_container.dart';
class SettingsListTile extends StatelessWidget { class SettingsListTile extends StatelessWidget {
/// A customizable settings list tile widget that displays an icon, title, and an optional suffix widget. /// A customizable settings list tile widget that displays an icon, title, and an optional suffix widget.
@@ -47,10 +46,18 @@ class SettingsListTile extends StatelessWidget {
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
ColoredIconContainer( Container(
icon: icon, width: 44,
containerSize: 44, height: 44,
iconSize: 28, decoration: BoxDecoration(
color: CustomTheme.primaryColor.withAlpha(40),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
icon,
size: 28,
color: CustomTheme.primaryColor.withGreen(40),
),
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text(title, style: const TextStyle(fontSize: 18)), Text(title, style: const TextStyle(fontSize: 18)),

View File

@@ -73,6 +73,7 @@ class TitleDescriptionListTile extends StatelessWidget {
const Spacer(), const Spacer(),
Container( Container(
constraints: const BoxConstraints(maxWidth: 115), constraints: const BoxConstraints(maxWidth: 115),
margin: const EdgeInsets.only(top: 4),
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 2, vertical: 2,
horizontal: 6, horizontal: 6,

View File

@@ -1,7 +1,7 @@
name: game_tracker name: game_tracker
description: "Game Tracking App for Card Games" description: "Game Tracking App for Card Games"
publish_to: 'none' publish_to: 'none'
version: 0.0.9+242 version: 0.0.10+216
environment: environment:
sdk: ^3.8.1 sdk: ^3.8.1