85 Commits

Author SHA1 Message Date
70f570489a Merge pull request 'Einstellungen ausgestalten' (#153) from feature/151-einstellungen-ausgestalten into development
Reviewed-on: #153
Reviewed-by: Mathis Kirchner <mathis.kirchner.mk@gmail.com>
2026-01-12 19:00:26 +00:00
80e601c10e Changed formatting of link displayment
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m4s
Pull Request Pipeline / lint (pull_request) Successful in 2m11s
2026-01-12 19:21:04 +01:00
2124c523bc Fixed link issue on android 2026-01-12 19:20:52 +01:00
7d0da81cf5 Updated license tile sizes 2026-01-12 19:09:31 +01:00
cd3a5c2a49 Merge branch 'development' into feature/151-einstellungen-ausgestalten
All checks were successful
Pull Request Pipeline / lint (pull_request) Successful in 2m18s
Pull Request Pipeline / test (pull_request) Successful in 2m18s
2026-01-12 17:34:56 +00:00
4628e96456 Merge pull request 'Hotfix: Textarea in Bug Template umgestaltet' (#160) from hotifx/issue-template-fix into development
Reviewed-on: #160
Reviewed-by: Mathis Kirchner <mathis.kirchner.mk@gmail.com>
2026-01-12 17:34:48 +00:00
6d42d59bad Removed area
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m8s
Pull Request Pipeline / lint (pull_request) Successful in 2m21s
2026-01-12 17:49:35 +01:00
46118c274c Updated textarea in template
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m14s
Pull Request Pipeline / lint (pull_request) Successful in 2m31s
2026-01-12 17:46:58 +01:00
ec5a686f90 Merge pull request 'Issue-Templates aktualisiert' (#154) from setup/update-issue-and-pr-templates into development
Reviewed-on: #154
Reviewed-by: Mathis Kirchner <mathis.kirchner.mk@gmail.com>
2026-01-12 16:22:04 +00:00
f0c6dd8401 Adjusted container size and padding
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m9s
Pull Request Pipeline / lint (pull_request) Successful in 2m17s
2026-01-12 16:20:36 +01:00
7bdad57cc8 Removed old code 2026-01-12 16:18:13 +01:00
5da1b6eecb Removed expanded widget 2026-01-12 16:17:18 +01:00
cdafd4bb6f Made links clickable 2026-01-12 16:16:59 +01:00
6aee055df2 Removed whitespace 2026-01-12 16:04:37 +01:00
6d9871a5f0 Removed Web
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m8s
Pull Request Pipeline / lint (pull_request) Successful in 2m9s
2026-01-12 16:03:09 +01:00
4bbbcdd93f Updated label
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m8s
Pull Request Pipeline / lint (pull_request) Successful in 2m11s
2026-01-12 00:47:43 +01:00
fed5c55dd4 Updated placeholder
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m7s
Pull Request Pipeline / lint (pull_request) Successful in 2m9s
2026-01-12 00:18:51 +01:00
c157644b44 Folder fix 2/2
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m8s
Pull Request Pipeline / lint (pull_request) Successful in 2m8s
2026-01-11 20:00:51 +01:00
5a5898787f Folder fix 1/2 2026-01-11 20:00:38 +01:00
9d3a45c01d Updated PR Template
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m8s
Pull Request Pipeline / lint (pull_request) Successful in 2m10s
2026-01-11 19:57:02 +01:00
485ac87fdb Added new yaml issue templates
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m6s
Pull Request Pipeline / lint (pull_request) Successful in 2m14s
2026-01-11 19:41:45 +01:00
758f1e6c3a .gitea/PULL_REQUEST_TEMPLATE.md aktualisiert 2026-01-11 16:14:43 +00:00
7fc4bbfb13 Added placeholder setting tiles for legal
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m13s
Pull Request Pipeline / lint (pull_request) Successful in 2m14s
2026-01-11 17:09:05 +01:00
857e05127d Updated arb files 2026-01-11 16:55:28 +01:00
86982ada0f Updated license file
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m6s
Pull Request Pipeline / lint (pull_request) Successful in 2m9s
2026-01-11 16:48:08 +01:00
e51bf3eabb Updated spacing 2026-01-11 16:48:02 +01:00
d7f08c5f50 Updated license tile and adjusted settings tile accordingly
All checks were successful
Pull Request Pipeline / lint (pull_request) Successful in 2m37s
Pull Request Pipeline / test (pull_request) Successful in 2m36s
2026-01-11 16:33:56 +01:00
9248284292 Removed double patterns
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m13s
Pull Request Pipeline / lint (pull_request) Successful in 2m22s
2026-01-11 16:32:19 +01:00
9a00e543d7 Implemented adaptive mail icon 2026-01-11 16:00:19 +01:00
5ea7797b3e Moved files 2026-01-11 15:44:05 +01:00
c8b76ae0ea Updated l10n config 2026-01-11 15:43:46 +01:00
c5fa540c5f Added licenses to settings 2026-01-11 15:43:36 +01:00
e384230a0b Updated gradle version 2026-01-10 23:37:54 +01:00
1e4fd2a164 Added ios permissions for web 2026-01-10 23:37:47 +01:00
a491427a1d Added license tile, social media icons & copyright 2026-01-10 23:37:33 +01:00
906c8d8450 Merge pull request 'Versions- und Build Nr in Einstellungen' (#149) from enhancement/146-versions-und-build-nr-in-einstellungen into development
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 1m58s
Pull Request Pipeline / lint (pull_request) Successful in 2m7s
Reviewed-on: #149
Reviewed-by: Mathis Kirchner <mathis.kirchner.mk@gmail.com>
2026-01-10 20:56:25 +00:00
000bdc8cbc Merge remote-tracking branch 'origin/development' into enhancement/146-versions-und-build-nr-in-einstellungen
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m1s
Pull Request Pipeline / lint (pull_request) Successful in 2m6s
2026-01-10 21:54:09 +01:00
497f30421d Updated string
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m9s
Pull Request Pipeline / lint (pull_request) Successful in 2m9s
2026-01-10 21:29:45 +01:00
adedb85eb2 Merge pull request 'Gruppen nach Spielern durchsuchen' (#147) from enhancement/136-gruppen-nach-spielern-durchsuchen into development
Reviewed-on: #147
Reviewed-by: Felix Kirchner <felix.kirchner.fk@gmail.com>
2026-01-10 20:27:53 +00:00
2a72332bcd Updated error string
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m1s
Pull Request Pipeline / lint (pull_request) Successful in 2m9s
2026-01-10 21:23:28 +01:00
7aa41abe61 Implemented version number 2026-01-10 16:20:21 +01:00
e1263d51ad Merge branch 'development' into enhancement/136-gruppen-nach-spielern-durchsuchen
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m0s
Pull Request Pipeline / lint (pull_request) Successful in 2m3s
2026-01-10 15:06:17 +00:00
8791b5296e Merge pull request 'MatchSummaryTile durch MatchTile ersetzen' (#142) from enhancement/137-matchsummarytile-durch-matchtile-ersetzen into development
Reviewed-on: #142
Reviewed-by: Mathis Kirchner <mathis.kirchner.mk@gmail.com>
2026-01-10 15:05:42 +00:00
f2a4327166 Implemented adaptive page route
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m4s
Pull Request Pipeline / lint (pull_request) Successful in 2m6s
2026-01-10 15:55:19 +01:00
d34990ed50 Removed print 2026-01-10 15:54:41 +01:00
2ef671884d New method for specific winner refreshing 2026-01-10 15:54:11 +01:00
2ef8eb6534 Made winner field non final 2026-01-10 15:53:31 +01:00
6f0e5ba5c2 add ability to search for groups members in choose_group_view.dart
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m3s
Pull Request Pipeline / lint (pull_request) Successful in 2m6s
2026-01-10 15:19:18 +01:00
1c07346aaf Updated version
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m20s
Pull Request Pipeline / lint (pull_request) Successful in 2m28s
2026-01-10 15:16:09 +01:00
830a64b5dd Small changes
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m29s
Pull Request Pipeline / lint (pull_request) Successful in 2m32s
2026-01-10 15:15:52 +01:00
9221f64fa5 Merge branch 'development' into enhancement/137-matchsummarytile-durch-matchtile-ersetzen
# Conflicts:
#	lib/presentation/views/main_menu/match_view/match_view.dart
#	pubspec.yaml
2026-01-10 15:15:14 +01:00
c4f6749882 Fixed state issue in home view
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m2s
Pull Request Pipeline / lint (pull_request) Successful in 2m3s
2026-01-10 15:13:56 +01:00
14a043785e Merge pull request 'PageRoutes adaptable machen' (#145) from enhancement/128-PageRoutes-adaptable-machen into development
Reviewed-on: #145
Reviewed-by: gelbeinhalb <spam@yannick-weigert.de>
Reviewed-by: Felix Kirchner <felix.kirchner.fk@gmail.com>
2026-01-10 14:06:10 +00:00
9eb9c0eb7f remove unneccessary import
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m18s
Pull Request Pipeline / lint (pull_request) Successful in 2m18s
2026-01-10 15:02:01 +01:00
54ec865f04 fix merge issues
Some checks failed
Pull Request Pipeline / test (pull_request) Successful in 2m14s
Pull Request Pipeline / lint (pull_request) Failing after 2m23s
2026-01-10 15:00:33 +01:00
d5e7a17127 Merge remote-tracking branch 'origin/development' into enhancement/128-PageRoutes-adaptable-machen
# Conflicts:
#	lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart
2026-01-10 14:57:33 +01:00
275f64b296 Merge pull request 'SnackBar Sichtbarkeit' (#144) from bug/130-Snackbar-Sichtbarkeit into development
Reviewed-on: #144
Reviewed-by: gelbeinhalb <spam@yannick-weigert.de>
Reviewed-by: Felix Kirchner <felix.kirchner.fk@gmail.com>
2026-01-10 13:54:57 +00:00
97ca62b083 refactor: rename AdaptivePageRoute to adaptivePageRoute for lowerCamelCase
All checks were successful
Pull Request Pipeline / lint (pull_request) Successful in 2m13s
Pull Request Pipeline / test (pull_request) Successful in 2m18s
2026-01-10 14:52:59 +01:00
32fb1550ff Merge remote-tracking branch 'origin/enhancement/128-PageRoutes-adaptable-machen' into enhancement/128-PageRoutes-adaptable-machen
Some checks failed
Pull Request Pipeline / test (pull_request) Successful in 2m4s
Pull Request Pipeline / lint (pull_request) Failing after 2m10s
# Conflicts:
#	lib/main.dart
2026-01-10 14:44:53 +01:00
d67972624e replaced ternary operator solution with custom adaptive_page_route.dart 2026-01-10 14:44:33 +01:00
595cf6ead0 Changed skeleton data from 3 to 2 tiles
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m2s
Pull Request Pipeline / lint (pull_request) Successful in 2m4s
2026-01-10 14:20:09 +01:00
6faafe9fab Changed android page transition to Android U transition
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m5s
Pull Request Pipeline / lint (pull_request) Successful in 2m8s
2026-01-10 14:16:16 +01:00
66b90aac25 Fixed issue with android page transition
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m1s
Pull Request Pipeline / lint (pull_request) Successful in 2m5s
2026-01-10 14:09:08 +01:00
d22855fc1a Fixed match view tile width
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m6s
Pull Request Pipeline / lint (pull_request) Successful in 2m6s
2026-01-10 13:57:54 +01:00
1be86bc3c5 Added theme box design 2026-01-10 13:57:46 +01:00
db3e8215fa Added ios swipe back gesture
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m1s
Pull Request Pipeline / lint (pull_request) Successful in 2m6s
2026-01-10 13:42:09 +01:00
2c4cef76d8 Reimplemented info tile & match tile into it
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m1s
Pull Request Pipeline / lint (pull_request) Successful in 2m3s
2026-01-09 23:55:29 +01:00
a4ef9705f9 made PageRoutes adapt to os, default to MaterialPageRoute
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m4s
Pull Request Pipeline / lint (pull_request) Successful in 2m7s
2026-01-09 22:22:26 +01:00
799c849570 wrap every view that uses a snackbar in ScaffoldMessenger
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m1s
Pull Request Pipeline / lint (pull_request) Successful in 2m6s
2026-01-09 21:55:58 +01:00
644728a9df Removed unneccesary code
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m1s
Pull Request Pipeline / lint (pull_request) Successful in 2m4s
2026-01-09 20:19:23 +01:00
afb7a5f1d4 MatchTiles only show when recent games are available 2026-01-09 20:15:50 +01:00
3d510d5b3d Implemented compact mode for match tiles 2026-01-09 20:04:10 +01:00
a9d2325eee Implemented match tile in home view 2026-01-09 19:50:51 +01:00
349ff948de Modified match tile 2026-01-09 19:50:44 +01:00
c2394c3733 Merge pull request 'MVP-Refactoring' (#139) from refactoring/68-mvp-refactoring into development
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m6s
Pull Request Pipeline / lint (pull_request) Successful in 2m8s
Reviewed-on: #139
Reviewed-by: gelbeinhalb <spam@yannick-weigert.de>
Reviewed-by: Mathis Kirchner <mathis.kirchner.mk@gmail.com>
2026-01-08 20:24:01 +00:00
88766652b9 Small loc corrections
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m6s
Pull Request Pipeline / lint (pull_request) Successful in 2m21s
2026-01-08 21:21:33 +01:00
2a1573ee2d Remove Whitespace
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m12s
Pull Request Pipeline / lint (pull_request) Successful in 2m15s
2026-01-08 21:15:01 +01:00
cfb07bfe28 Updated localizations
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m14s
Pull Request Pipeline / lint (pull_request) Successful in 2m18s
2026-01-08 21:13:24 +01:00
d741990f2f Fixed navbar issue
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m4s
Pull Request Pipeline / lint (pull_request) Successful in 2m9s
2026-01-08 21:07:30 +01:00
76121eb4fb Updated localizations
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m6s
Pull Request Pipeline / lint (pull_request) Successful in 2m18s
2026-01-07 17:30:19 +01:00
d5ee6449b0 Updated localizations
All checks were successful
Pull Request Pipeline / lint (pull_request) Successful in 2m11s
Pull Request Pipeline / test (pull_request) Successful in 2m10s
2026-01-07 17:02:06 +01:00
7d9757abb6 Merge branch 'development' into refactoring/68-mvp-refactoring
# Conflicts:
#	lib/l10n/generated/app_localizations.dart
#	lib/l10n/generated/app_localizations_de.dart
#	lib/l10n/generated/app_localizations_en.dart
2026-01-07 16:58:15 +01:00
d94d51c6ea Merge pull request 'change settings icons to filled and rounded' (#140) from enhancement/135-settings-icons into development
Reviewed-on: #140
Reviewed-by: Felix Kirchner <felix.kirchner.fk@gmail.com>
2026-01-07 14:23:27 +00:00
gelbeinhalb
803cf8a972 update app_localizations
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m0s
Pull Request Pipeline / lint (pull_request) Successful in 2m6s
2026-01-07 15:17:13 +01:00
gelbeinhalb
13be75a65b change settings icons to filled and rounded
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 2m3s
Pull Request Pipeline / lint (pull_request) Successful in 2m5s
2026-01-07 15:12:12 +01:00
38 changed files with 9026 additions and 1138 deletions

View File

@@ -1,35 +0,0 @@
---
name: Bug report
about: Erstelle eine Meldung für etwas, das nicht Funktioniert, wie es soll.
title: ''
labels: 'Task/Bug'
assignees: ''
---
# Bug Report
## Beschreibung
[Eine klare und prägnante Beschreibung des Bugs]
## Schritte zur Reproduktion
1. Schritt 1
2. Schritt 2
3. ...
## Erwartetes Verhalten
[Was hätte passieren sollen]
## Tatsächliches Verhalten
[Was tatsächlich passiert ist]
## Screenshots/Protokolle
[Falls zutreffend, füge Screenshots, Error Logs oder Stack Traces hinzu]
## Umgebung
- Plattform: Android, iOS, Web
- OS: [z. B. iOS 18.5, Android 14]
- Flutter Version: [z.B. 3.35.6]
## Verwandte Issues
[Verweisen Sie auf ähnliche Issues oder PRs]

View File

@@ -1,22 +0,0 @@
---
name: Enhancement
about: Enhancements for current features
title: ''
labels: 'Task\Enhancement'
assignees: ''
---
# Enhancement
## Aktuelles Verhalten
[Beschreibe die bestehende Funktionalität]
## Einschränkungen/Probleme
[Was sind die aktuellen Mängel?]
## Vorgeschlagene Verbesserung
[Wie kann das Problem bzw. die Einschränkung verbessert werden?]
## Zugehörige Issues
[Links zu verwandten oder blockierenden Issues]

View File

@@ -1,19 +0,0 @@
---
name: Feature
about: Neues Feature für die App
title: ''
labels: 'Task\Feature'
assignees: ''
---
# Feature
## Beschreibung
[Ausführliche Erläuterung der vorgeschlagenen Funktion]
## Vorgeschlagene Lösung
[Beschreibe, wie die Feature funktionieren soll]
## Zugehörige Issues
[Links zu verwandten oder blockierenden Issues]

View File

@@ -1,16 +0,0 @@
# [PR Titel]
**Zugehörige Issue(s):**
Closes `<issue-no>`
## Beschreibung
*Eine klare und prägnante Übersicht über die vorgenommenen Änderungen. Erläutere nicht nur das was gemacht wurde, sondern auch warum.*
## Änderungen
- [ ] Neue Funktion X hinzugefügt
- [ ] Bug in Komponente Y behoben
- [ ] Modul Z für bessere Leistung refactored
- [ ] Dependencies aktualisiert
## Zusätzliche Anmerkungen
*Gibt es zusätzlichen Kontext, Einschränkungen oder Informationen, die Reviewer wissen sollten?*

View File

@@ -0,0 +1,53 @@
name: Bug Report
about: Erstelle eine Bug Report
labels: 'Task/Bug'
title: ''
body:
- type: textarea
id: description
attributes:
label: Beschreibung
description: Beschreibe klar und pregnant das Fehlerverhalten
placeholder: |
- Was genau ist das unerwünschte Verhalten?
- Welche Auswirkungen hat der Fehler?
validations:
required: true
- type: textarea
id: reproduce
attributes:
label: Schritte zur Reproduktion
description: Beschreibe, wie der Fehler reproduziert werden kann
placeholder: |
- 1. Schritt 1
- 2. Schritt 2
- 3. ...
- type: dropdown
id: enviroment
attributes:
label: Umgebung
description: Gebe an, auf welchen Platformen dieser Fehler auftritt
list: false
multiple: true
options: ['Android', 'iOS']
- type: textarea
id: solution
attributes:
label: Lösungsidee
description: Beschreibe, wie das Problem bzw. gelöst werden kann
placeholder: |
- Button X ändern, sodass ...
- Funktion X so erweitern, dass ...
- Design anpassen, sodass ...
- type: textarea
attributes:
label: Verwandte Issues
description: Verweise auf ähnliche Issues oder PRs
placeholder: |
- Knüpft an Issue #35 an
- Ersetzt Issue #12
- Brauch Implementierung von #43

View File

@@ -0,0 +1,36 @@
name: Enhancement
about: Erstelle ein Enhancement-Ticket
labels: 'Task/Enhancement'
title: ''
body:
- type: textarea
id: description
attributes:
label: Aktuelles Verhalten
description: Beschreibe, wie die Funktionalität aktuell gestaltet ist
placeholder: |
- Aktuell macht Button X folgendes ...
- Das Problem ist, dass ...
validations:
required: true
- type: textarea
id: solution
attributes:
label: Vorgeschlagene Verbesserung
description: Beschreibe, wie das Problem bzw. die Einschränkung verbessert werden kann
placeholder: |
- Button X ändern, sodass ...
- Funktion X so erweitern, dass ...
- Design anpassen, sodass ...
validations:
required: true
- type: textarea
attributes:
label: Zugehörige Issues
description: Links zu verwandten oder blockierenden Issues
placeholder: |
- Knüpft an Issue #35 an
- Ersetzt Issue #12
- Brauch Implementierung von #43

View File

@@ -0,0 +1,36 @@
name: Feature
about: Erstelle ein Feature-Ticket
labels: 'Task/Feature'
title: ''
body:
- type: textarea
id: description
attributes:
label: Beschreibung
description: Ausführliche Erläuterung der vorgeschlagenen Funktion
placeholder: |
- Welchen Zweck erfüllt das Feature?
- Welches Problem löst das Feature?
- Wer profitiert davon?
- Warum ist es wichtig?
validations:
required: true
- type: textarea
id: solution
attributes:
label: Vorgeschlagene Lösung
description: Beschreibe, wie das Feature funktionieren soll
placeholder: |
- Neues Widget, das folgendermaßen aussieht ...
- Neue Ansicht, die folgende Inhalte hat
- Neue Funktionsweise von Komponente XY
- type: textarea
attributes:
label: Zugehörige Issues
description: Links zu verwandten oder blockierenden Issues
placeholder: |
- Knüpft an Issue #35 an
- Ersetzt Issue #12
- Brauch Implementierung von #43

View File

@@ -0,0 +1,47 @@
name: Pull Request
about: Vorlage für Pull Requests
title: "WIP: [Name des Issues]"
body:
- type: input
id: related_issue
attributes:
label: Zugehörige Issue(s)
description: Issues welche mit diesem Pull Request geschlossen werden sollen
placeholder: "Closes #123"
validations:
required: true
- type: textarea
id: description
attributes:
label: Beschreibung
description: |
Eine klare und prägnante Zusammenfassung aller vorgenommenen Änderungen.
placeholder: |
Was wurde geändert?
validations:
required: true
- type: textarea
id: changes
attributes:
label: Änderungen
description: Liste alle Änderungen detailiert auf, die in diesem Pull Request vorgenommen wurden.
placeholder: |
- Neue Funktion X hinzugefügt
- Bug in Komponente X behoben
- Modul X für bessere Leistung refactored
- Dependencies aktualisiert
- type: textarea
id: additional_notes
attributes:
label: Zusätzliche Anmerkungen
description: |
Gibt es zusätzlichen Kontext, Einschränkungen oder Informationen,
die Reviewer wissen sollten?
placeholder: |
- Bekannte Einschränkungen
- Offene TODOs
- Hinweise für Reviewer

27
.gitignore vendored
View File

@@ -150,25 +150,9 @@ app.*.symbols
.gclient_previous_custom_vars .gclient_previous_custom_vars
.gclient_previous_sync_commits .gclient_previous_sync_commits
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/ .swiftpm/
migrate_working_dir/ migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in # The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line # VS Code which you may wish to be included in version control, so this line
@@ -176,17 +160,8 @@ migrate_working_dir/
#.vscode/ #.vscode/
# Flutter/Dart/Pub related # Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/ /build/
# Symbolication related
app.*.symbols
# Obfuscation related # Obfuscation related
app.*.map.json app.*.map.json
@@ -196,3 +171,5 @@ app.*.map.json
/android/app/profile /android/app/profile
/android/app/release /android/app/release
/devtools_options.yaml /devtools_options.yaml
untranslated_messages.json

View File

@@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application <application
android:label="game_tracker" android:label="game_tracker"
android:name="${applicationName}" android:name="${applicationName}"
@@ -42,5 +43,14 @@
<action android:name="android.intent.action.PROCESS_TEXT"/> <action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/> <data android:mimeType="text/plain"/>
</intent> </intent>
<!-- Required for url_launcher to open URLs in external browser -->
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
</queries> </queries>
</manifest> </manifest>

View File

@@ -18,7 +18,7 @@ pluginManagement {
plugins { plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.3" apply false id("com.android.application") version "8.9.1" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false id("org.jetbrains.kotlin.android") version "2.1.0" apply false
} }

View File

@@ -24,6 +24,11 @@
<string>$(FLUTTER_BUILD_NUMBER)</string> <string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key> <key>LSRequiresIPhoneOS</key>
<true/> <true/>
<key>LSApplicationQueriesSchemes</key>
<array>
<string>https</string>
<string>http</string>
</array>
<key>UILaunchStoryboardName</key> <key>UILaunchStoryboardName</key>
<string>LaunchScreen</string> <string>LaunchScreen</string>
<key>UIMainStoryboardFile</key> <key>UIMainStoryboardFile</key>
@@ -44,4 +49,4 @@
<key>UIApplicationSupportsIndirectInputEvents</key> <key>UIApplicationSupportsIndirectInputEvents</key>
<true/> <true/>
</dict> </dict>
</plist> </plist>

View File

@@ -2,4 +2,5 @@ arb-dir: lib/l10n/arb
template-arb-file: app_en.arb template-arb-file: app_en.arb
output-localization-file: app_localizations.dart output-localization-file: app_localizations.dart
output-dir: lib/l10n/generated output-dir: lib/l10n/generated
nullable-getter: false nullable-getter: false
untranslated-messages-file: lib/l10n/untranslated_messages.json

View File

@@ -0,0 +1,19 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
Route<T> adaptivePageRoute<T>({
required Widget Function(BuildContext) builder,
bool fullscreenDialog = false,
}) {
if (Platform.isIOS) {
return CupertinoPageRoute<T>(
builder: builder,
fullscreenDialog: fullscreenDialog,
);
}
return MaterialPageRoute<T>(
builder: builder,
fullscreenDialog: fullscreenDialog,
);
}

View File

@@ -9,7 +9,7 @@ class Match {
final String name; final String name;
final List<Player>? players; final List<Player>? players;
final Group? group; final Group? group;
final Player? winner; Player? winner;
Match({ Match({
String? id, String? id,

View File

@@ -1,8 +1,9 @@
{ {
"@@locale": "de", "@@locale": "de",
"all_players": "Alle Spieler:innen:", "all_players": "Alle Spieler:innen",
"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",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"choose_game": "Spielvorlage wählen", "choose_game": "Spielvorlage wählen",
"choose_group": "Gruppe wählen", "choose_group": "Gruppe wählen",
@@ -12,12 +13,13 @@
"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",
"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",
"data_successfully_imported": "Daten erfolgreich importiert", "data_successfully_imported": "Daten erfolgreich importiert",
"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",
"error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen", "error_creating_group": "Fehler beim Erstellen 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",
@@ -34,17 +36,21 @@
"info": "Info", "info": "Info",
"invalid_schema": "Ungültiges Schema", "invalid_schema": "Ungültiges Schema",
"least_points": "Niedrigste Punkte", "least_points": "Niedrigste Punkte",
"legal": "Rechtliches",
"legal_notice": "Impressum",
"licenses": "Lizenzen",
"match_in_progress": "Spiel läuft...", "match_in_progress": "Spiel läuft...",
"match_name": "Spieltitel", "match_name": "Spieltitel",
"matches": "Spiele", "matches": "Spiele",
"menu": "Menü",
"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",
"no_licenses_found": "Keine Lizenzen gefunden",
"no_license_text_available": "Kein Lizenztext verfügbar",
"no_matches_created_yet": "Noch keine Spiele erstellt", "no_matches_created_yet": "Noch keine Spiele erstellt",
"no_players_created_yet": "Noch keine Spieler:in erstellt", "no_players_created_yet": "Noch keine Spieler:in erstellt",
"no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden", "no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden",
"no_players_selected": "Keine Spieler:in ausgewählt", "no_players_selected": "Keine Spieler:innen ausgewählt",
"no_recent_matches_available": "Keine letzten Spiele verfügbar", "no_recent_matches_available": "Keine letzten Spiele verfügbar",
"no_second_match_available": "Kein zweites Spiel verfügbar", "no_second_match_available": "Kein zweites Spiel verfügbar",
"no_statistics_available": "Keine Statistiken verfügbar", "no_statistics_available": "Keine Statistiken verfügbar",
@@ -54,6 +60,7 @@
"player_name": "Spieler:innenname", "player_name": "Spieler:innenname",
"players": "Spieler:innen", "players": "Spieler:innen",
"players_count": "{count} Spieler", "players_count": "{count} Spieler",
"privacy_policy": "Datenschutzerklärung",
"quick_create": "Schnellzugriff", "quick_create": "Schnellzugriff",
"recent_matches": "Letzte Spiele", "recent_matches": "Letzte Spiele",
"ruleset": "Regelwerk", "ruleset": "Regelwerk",
@@ -64,7 +71,7 @@
"search_for_groups": "Nach Gruppen suchen", "search_for_groups": "Nach Gruppen suchen",
"search_for_players": "Nach Spieler:innen suchen", "search_for_players": "Nach Spieler:innen suchen",
"select_winner": "Gewinner:in wählen:", "select_winner": "Gewinner:in wählen:",
"selected_players": "Ausgewählte Spieler:innen: {count}", "selected_players": "Ausgewählte Spieler:innen",
"settings": "Einstellungen", "settings": "Einstellungen",
"single_loser": "Ein:e Verlierer:in", "single_loser": "Ein:e Verlierer:in",
"single_winner": "Ein:e Gewinner:in", "single_winner": "Ein:e Gewinner:in",
@@ -73,11 +80,11 @@
"successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt", "successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt",
"there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht", "there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht",
"this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden", "this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden",
"today_at": "Heute um {time}", "today_at": "Heute um",
"undo": "Rückgängig", "undo": "Rückgängig",
"unknown_exception": "Unbekannter Fehler (siehe Konsole)", "unknown_exception": "Unbekannter Fehler (siehe Konsole)",
"winner": "Gewinner*in", "winner": "Gewinner:in",
"winrate": "Siegquote", "winrate": "Siegquote",
"wins": "Siege", "wins": "Siege",
"yesterday_at": "Gestern um {time}" "yesterday_at": "Gestern um"
} }

View File

@@ -1,367 +1,367 @@
{ {
"@@locale": "en", "@@locale": "en",
"@all_players": { "@all_players": {
"description": "Label for all players list" "description": "Label for all players list"
}, },
"@all_players_selected": { "@all_players_selected": {
"description": "Message when all players are added to selection" "description": "Message when all players are added to selection"
}, },
"@amount_of_matches": { "@amount_of_matches": {
"description": "Label for amount of matches statistic" "description": "Label for amount of matches statistic"
}, },
"@cancel": { "@app_name": {
"description": "Cancel button text" "description": "The name of the App"
}, },
"@choose_game": { "@cancel": {
"description": "Label for choosing a game" "description": "Cancel button text"
}, },
"@choose_group": { "@choose_game": {
"description": "Label for choosing a group" "description": "Label for choosing a game"
}, },
"@choose_ruleset": { "@choose_group": {
"description": "Label for choosing a ruleset" "description": "Label for choosing a group"
}, },
"@could_not_add_player": { "@choose_ruleset": {
"description": "Error message when adding a player fails", "description": "Label for choosing a ruleset"
"placeholders": { },
"playerName": { "@could_not_add_player": {
"type": "String", "description": "Error message when adding a player fails"
"example": "John" },
} "@create_group": {
} "description": "Button text to create a group"
}, },
"@create_group": { "@create_match": {
"description": "Button text to create a group" "description": "Button text to create a match"
}, },
"@create_match": { "@create_new_group": {
"description": "Button text to create a match" "description": "Button text to create a new group"
}, },
"@create_new_group": { "@create_new_match": {
"description": "Button text to create a new group" "description": "Button text to create a new match"
}, },
"@create_new_match": { "@data": {
"description": "Button text to create a new match" "description": "Data label"
}, },
"@data_successfully_deleted": { "@data_successfully_deleted": {
"description": "Success message after deleting data" "description": "Success message after deleting data"
}, },
"@data_successfully_exported": { "@data_successfully_exported": {
"description": "Success message after exporting data" "description": "Success message after exporting data"
}, },
"@data_successfully_imported": { "@data_successfully_imported": {
"description": "Success message after importing data" "description": "Success message after importing data"
}, },
"@days_ago": { "@days_ago": {
"description": "Date format for days ago", "description": "Date format for days ago",
"placeholders": { "placeholders": {
"count": { "count": {
"type": "int" "type": "int"
} }
} }
}, },
"@delete": { "@delete": {
"description": "Delete button text" "description": "Delete button text"
}, },
"@delete_all_data": { "@delete_all_data": {
"description": "Confirmation dialog for deleting all data" "description": "Confirmation dialog for deleting all data"
}, },
"@error_creating_group": { "@error_creating_group": {
"description": "Error message when group creation fails" "description": "Error message when group creation fails"
}, },
"@error_reading_file": { "@error_reading_file": {
"description": "Error message when file cannot be read" "description": "Error message when file cannot be read"
}, },
"@export_canceled": { "@export_canceled": {
"description": "Message when export is canceled" "description": "Message when export is canceled"
}, },
"@export_data": { "@export_data": {
"description": "Export data menu item" "description": "Export data menu item"
}, },
"@format_exception": { "@format_exception": {
"description": "Error message for format exceptions" "description": "Error message for format exceptions"
}, },
"@game": { "@game": {
"description": "Game label" "description": "Game label"
}, },
"@game_name": { "@game_name": {
"description": "Placeholder for game name search" "description": "Placeholder for game name search"
}, },
"@game_tracker": { "@group": {
"description": "App Name" "description": "Group label"
}, },
"@group": { "@group_name": {
"description": "Group label" "description": "Placeholder for group name input"
}, },
"@group_name": { "@groups": {
"description": "Placeholder for group name input" "description": "Label for groups"
}, },
"@groups": { "@home": {
"description": "Label for groups" "description": "Home tab label"
}, },
"@home": { "@import_canceled": {
"description": "Home tab label" "description": "Message when import is canceled"
}, },
"@import_canceled": { "@import_data": {
"description": "Message when import is canceled" "description": "Import data menu item"
}, },
"@import_data": { "@info": {
"description": "Import data menu item" "description": "Info label"
}, },
"@info": { "@invalid_schema": {
"description": "Info label" "description": "Error message for invalid schema"
}, },
"@invalid_schema": { "@least_points": {
"description": "Error message for invalid schema" "description": "Title for least points ruleset"
}, },
"@least_points": { "@legal": {
"description": "Title for least points ruleset" "description": "Legal section header"
}, },
"@match_in_progress": { "@legal_notice": {
"description": "Message when match is in progress" "description": "Legal notice menu item"
}, },
"@match_name": { "@licenses": {
"description": "Placeholder for match name input" "description": "Licenses menu item"
}, },
"@matches": { "@match_in_progress": {
"description": "Label for matches" "description": "Message when match is in progress"
}, },
"@menu": { "@match_name": {
"description": "Menu label" "description": "Placeholder for match name input"
}, },
"@most_points": { "@matches": {
"description": "Title for most points ruleset" "description": "Label for matches"
}, },
"@no_data_available": { "@most_points": {
"description": "Message when no data in the statistic tiles is given" "description": "Title for most points ruleset"
}, },
"@no_groups_created_yet": { "@no_data_available": {
"description": "Message when no groups exist" "description": "Message when no data in the statistic tiles is given"
}, },
"@no_matches_created_yet": { "@no_groups_created_yet": {
"description": "Message when no matches exist" "description": "Message when no groups exist"
}, },
"@no_players_created_yet": { "@no_licenses_found": {
"description": "Message when no players exist" "description": "Message when no licenses are found"
}, },
"@no_players_found_with_that_name": { "@no_license_text_available": {
"description": "Message when search returns no results" "description": "Message when no license text is available"
}, },
"@no_players_selected": { "@no_matches_created_yet": {
"description": "Message when no players are selected" "description": "Message when no matches exist"
}, },
"@no_recent_matches_available": { "@no_players_created_yet": {
"description": "Message when no recent matches exist" "description": "Message when no players exist"
}, },
"@no_second_match_available": { "@no_players_found_with_that_name": {
"description": "Message when no second match exists" "description": "Message when search returns no results"
}, },
"@no_statistics_available": { "@no_players_selected": {
"description": "Message when no statistics are available, because no matches were played yet" "description": "Message when no players are selected"
}, },
"@none": { "@no_recent_matches_available": {
"description": "None option label" "description": "Message when no recent matches exist"
}, },
"@none_group": { "@no_second_match_available": {
"description": "None group option label" "description": "Message when no second match exists"
}, },
"@not_available": { "@no_statistics_available": {
"description": "Abbreviation for not available" "description": "Message when no statistics are available, because no matches were played yet"
}, },
"@player_name": { "@none": {
"description": "Placeholder for player name input" "description": "None option label"
}, },
"@players": { "@none_group": {
"description": "Players label" "description": "None group option label"
}, },
"@players_count": { "@not_available": {
"description": "Shows the number of players", "description": "Abbreviation for not available"
"placeholders": { },
"count": { "@player_name": {
"type": "int" "description": "Placeholder for player name input"
} },
} "@players": {
}, "description": "Players label"
"@quick_create": { },
"description": "Title for quick create section" "@players_count": {
}, "description": "Shows the number of players",
"@recent_matches": { "placeholders": {
"description": "Title for recent matches section" "count": {
}, "type": "int"
"@ruleset": { }
"description": "Ruleset label" }
}, },
"@ruleset_least_points": { "@privacy_policy": {
"description": "Description for least points ruleset" "description": "Privacy policy menu item"
}, },
"@ruleset_most_points": { "@quick_create": {
"description": "Description for most points ruleset" "description": "Title for quick create section"
}, },
"@ruleset_single_loser": { "@recent_matches": {
"description": "Description for single loser ruleset" "description": "Title for recent matches section"
}, },
"@ruleset_single_winner": { "@ruleset": {
"description": "Description for single winner ruleset" "description": "Ruleset label"
}, },
"@search_for_groups": { "@ruleset_least_points": {
"description": "Hint text for group search input field" "description": "Description for least points ruleset"
}, },
"@search_for_players": { "@ruleset_most_points": {
"description": "Hint text for player search input field" "description": "Description for most points ruleset"
}, },
"@select_winner": { "@ruleset_single_loser": {
"description": "Label to select the winner" "description": "Description for single loser ruleset"
}, },
"@selected_players": { "@ruleset_single_winner": {
"description": "Shows the number of selected players", "description": "Description for single winner ruleset"
"placeholders": { },
"count": { "@search_for_groups": {
"type": "int", "description": "Hint text for group search input field"
"format": "compact" },
} "@search_for_players": {
} "description": "Hint text for player search input field"
}, },
"@settings": { "@select_winner": {
"description": "Settings label" "description": "Label to select the winner"
}, },
"@single_loser": { "@selected_players": {
"description": "Title for single loser ruleset" "description": "Shows the number of selected players"
}, },
"@single_winner": { "@settings": {
"description": "Title for single winner ruleset" "description": "Label for the App Settings"
}, },
"@statistics": { "@single_loser": {
"description": "Statistics tab label" "description": "Title for single loser ruleset"
}, },
"@stats": { "@single_winner": {
"description": "Stats tab label (short)" "description": "Title for single winner ruleset"
}, },
"@successfully_added_player": { "@statistics": {
"description": "Success message when adding a player", "description": "Statistics tab label"
"placeholders": { },
"playerName": { "@stats": {
"type": "String", "description": "Stats tab label (short)"
"example": "John" },
} "@successfully_added_player": {
} "description": "Success message when adding a player",
}, "placeholders": {
"@there_is_no_group_matching_your_search": { "playerName": {
"description": "Message when search returns no groups" "type": "String",
}, "example": "John"
"@this_cannot_be_undone": { }
"description": "Warning message for irreversible actions" }
}, },
"@today_at": { "@there_is_no_group_matching_your_search": {
"description": "Date format for today", "description": "Message when search returns no groups"
"placeholders": { },
"time": { "@this_cannot_be_undone": {
"type": "String", "description": "Warning message for irreversible actions"
"example": "14:30" },
} "@today_at": {
} "description": "Date format for today"
}, },
"@undo": { "@undo": {
"description": "Undo button text" "description": "Undo button text"
}, },
"@unknown_exception": { "@unknown_exception": {
"description": "Error message for unknown exceptions" "description": "Error message for unknown exceptions"
}, },
"@winner": { "@winner": {
"description": "Winner label" "description": "Winner label"
}, },
"@winrate": { "@winrate": {
"description": "Label for winrate statistic" "description": "Label for winrate statistic"
}, },
"@wins": { "@wins": {
"description": "Label for wins statistic" "description": "Label for wins statistic"
}, },
"@yesterday_at": { "@yesterday_at": {
"description": "Date format for yesterday", "description": "Date format for yesterday"
"placeholders": { },
"time": { "all_players": "All players",
"type": "String", "all_players_selected": "All players selected",
"example": "14:30" "amount_of_matches": "Amount of Matches",
} "app_name": "Game Tracker",
} "cancel": "Cancel",
}, "choose_game": "Choose Game",
"all_players": "All players:", "choose_group": "Choose Group",
"all_players_selected": "All players selected", "choose_ruleset": "Choose Ruleset",
"amount_of_matches": "Amount of Matches", "could_not_add_player": "Could not add player",
"cancel": "Cancel", "create_group": "Create Group",
"choose_game": "Choose Game", "create_match": "Create match",
"choose_group": "Choose Group", "create_new_group": "Create new group",
"choose_ruleset": "Choose Ruleset", "create_new_match": "Create new match",
"could_not_add_player": "Could not add player {playerName}", "data": "Data",
"create_group": "Create Group", "data_successfully_deleted": "Data successfully deleted",
"create_match": "Create match", "data_successfully_exported": "Data successfully exported",
"create_new_group": "Create new group", "data_successfully_imported": "Data successfully imported",
"create_new_match": "Create new match", "days_ago": "{count} days ago",
"data_successfully_deleted": "Data successfully deleted", "delete": "Delete",
"data_successfully_exported": "Data successfully exported", "delete_all_data": "Delete all data",
"data_successfully_imported": "Data successfully imported", "error_creating_group": "Error while creating group, please try again",
"days_ago": "{count} days ago", "error_reading_file": "Error reading file",
"delete": "Delete", "export_canceled": "Export canceled",
"delete_all_data": "Delete all data?", "export_data": "Export data",
"error_creating_group": "Error while creating group, please try again", "format_exception": "Format Exception (see console)",
"error_reading_file": "Error reading file", "game": "Game",
"export_canceled": "Export canceled", "game_name": "Game Name",
"export_data": "Export data", "group": "Group",
"format_exception": "Format Exception (see console)", "group_name": "Group name",
"game": "Game", "groups": "Groups",
"game_name": "Game Name", "home": "Home",
"game_tracker": "Game Tracker", "import_canceled": "Import canceled",
"group": "Group", "import_data": "Import data",
"group_name": "Group name", "info": "Info",
"groups": "Groups", "invalid_schema": "Invalid Schema",
"home": "Home", "least_points": "Least Points",
"import_canceled": "Import canceled", "legal": "Legal",
"import_data": "Import data", "legal_notice": "Legal Notice",
"info": "Info", "licenses": "Licenses",
"invalid_schema": "Invalid Schema", "match_in_progress": "Match in progress...",
"least_points": "Least Points", "match_name": "Match name",
"match_in_progress": "Match in progress...", "matches": "Matches",
"match_name": "Match name", "most_points": "Most Points",
"matches": "Matches", "no_data_available": "No data available",
"menu": "Menu", "no_groups_created_yet": "No groups created yet",
"most_points": "Most Points", "no_licenses_found": "No licenses found",
"no_data_available": "No data available", "no_license_text_available": "No license text available",
"no_groups_created_yet": "No groups created yet", "no_matches_created_yet": "No matches created yet",
"no_matches_created_yet": "No matches created yet", "no_players_created_yet": "No players created yet",
"no_players_created_yet": "No players created yet", "no_players_found_with_that_name": "No players found with that name",
"no_players_found_with_that_name": "No players found with that name", "no_players_selected": "No players selected",
"no_players_selected": "No players selected", "no_recent_matches_available": "No recent matches available",
"no_recent_matches_available": "No recent matches available", "no_second_match_available": "No second match available",
"no_second_match_available": "No second match available", "no_statistics_available": "No statistics available",
"no_statistics_available": "No statistics available", "none": "None",
"none": "None", "none_group": "None",
"none_group": "None", "not_available": "Not available",
"not_available": "Not available", "player_name": "Player name",
"player_name": "Player name", "players": "Players",
"players": "Players", "players_count": "{count} Players",
"players_count": "{count} Players", "privacy_policy": "Privacy Policy",
"quick_create": "Quick Create", "quick_create": "Quick Create",
"recent_matches": "Recent Matches", "recent_matches": "Recent Matches",
"ruleset": "Ruleset", "ruleset": "Ruleset",
"ruleset_least_points": "Inverse scoring: the player with the fewest points wins.", "ruleset_least_points": "Inverse scoring: the player with the fewest points wins.",
"ruleset_most_points": "Traditional ruleset: the player with the most points wins.", "ruleset_most_points": "Traditional ruleset: the player with the most points wins.",
"ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.", "ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.",
"ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.", "ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.",
"search_for_groups": "Search for groups", "search_for_groups": "Search for groups",
"search_for_players": "Search for players", "search_for_players": "Search for players",
"select_winner": "Select Winner:", "select_winner": "Select Winner:",
"selected_players": "Selected players: {count}", "selected_players": "Selected players",
"settings": "Settings", "settings": "Settings",
"single_loser": "Single Loser", "single_loser": "Single Loser",
"single_winner": "Single Winner", "single_winner": "Single Winner",
"statistics": "Statistics", "statistics": "Statistics",
"stats": "Stats", "stats": "Stats",
"successfully_added_player": "Successfully added player {playerName}", "successfully_added_player": "Successfully added player {playerName}",
"there_is_no_group_matching_your_search": "There is no group matching your search", "there_is_no_group_matching_your_search": "There is no group matching your search",
"this_cannot_be_undone": "This can't be undone", "this_cannot_be_undone": "This can't be undone",
"today_at": "Today at {time}", "today_at": "Today at",
"undo": "Undo", "undo": "Undo",
"unknown_exception": "Unknown Exception (see console)", "unknown_exception": "Unknown Exception (see console)",
"winner": "Winner", "winner": "Winner",
"winrate": "Winrate", "winrate": "Winrate",
"wins": "Wins", "wins": "Wins",
"yesterday_at": "Yesterday at {time}" "yesterday_at": "Yesterday at"
} }

View File

@@ -101,7 +101,7 @@ abstract class AppLocalizations {
/// Label for all players list /// Label for all players list
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'All players:'** /// **'All players'**
String get all_players; String get all_players;
/// Message when all players are added to selection /// Message when all players are added to selection
@@ -116,6 +116,12 @@ abstract class AppLocalizations {
/// **'Amount of Matches'** /// **'Amount of Matches'**
String get amount_of_matches; String get amount_of_matches;
/// The name of the App
///
/// In en, this message translates to:
/// **'Game Tracker'**
String get app_name;
/// Cancel button text /// Cancel button text
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -143,8 +149,8 @@ abstract class AppLocalizations {
/// Error message when adding a player fails /// Error message when adding a player fails
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Could not add player {playerName}'** /// **'Could not add player'**
String could_not_add_player(String playerName); String could_not_add_player(Object playerName);
/// Button text to create a group /// Button text to create a group
/// ///
@@ -170,6 +176,12 @@ abstract class AppLocalizations {
/// **'Create new match'** /// **'Create new match'**
String get create_new_match; String get create_new_match;
/// Data label
///
/// In en, this message translates to:
/// **'Data'**
String get data;
/// Success message after deleting data /// Success message after deleting data
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -203,7 +215,7 @@ abstract class AppLocalizations {
/// Confirmation dialog for deleting all data /// Confirmation dialog for deleting all data
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Delete all data?'** /// **'Delete all data'**
String get delete_all_data; String get delete_all_data;
/// Error message when group creation fails /// Error message when group creation fails
@@ -248,12 +260,6 @@ abstract class AppLocalizations {
/// **'Game Name'** /// **'Game Name'**
String get game_name; String get game_name;
/// App Name
///
/// In en, this message translates to:
/// **'Game Tracker'**
String get game_tracker;
/// Group label /// Group label
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -308,6 +314,24 @@ abstract class AppLocalizations {
/// **'Least Points'** /// **'Least Points'**
String get least_points; String get least_points;
/// Legal section header
///
/// In en, this message translates to:
/// **'Legal'**
String get legal;
/// Legal notice menu item
///
/// In en, this message translates to:
/// **'Legal Notice'**
String get legal_notice;
/// Licenses menu item
///
/// In en, this message translates to:
/// **'Licenses'**
String get licenses;
/// Message when match is in progress /// Message when match is in progress
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -326,12 +350,6 @@ abstract class AppLocalizations {
/// **'Matches'** /// **'Matches'**
String get matches; String get matches;
/// Menu label
///
/// In en, this message translates to:
/// **'Menu'**
String get menu;
/// Title for most points ruleset /// Title for most points ruleset
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -350,6 +368,18 @@ abstract class AppLocalizations {
/// **'No groups created yet'** /// **'No groups created yet'**
String get no_groups_created_yet; String get no_groups_created_yet;
/// Message when no licenses are found
///
/// In en, this message translates to:
/// **'No licenses found'**
String get no_licenses_found;
/// Message when no license text is available
///
/// In en, this message translates to:
/// **'No license text available'**
String get no_license_text_available;
/// Message when no matches exist /// Message when no matches exist
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -428,6 +458,12 @@ abstract class AppLocalizations {
/// **'{count} Players'** /// **'{count} Players'**
String players_count(int count); String players_count(int count);
/// Privacy policy menu item
///
/// In en, this message translates to:
/// **'Privacy Policy'**
String get privacy_policy;
/// Title for quick create section /// Title for quick create section
/// ///
/// In en, this message translates to: /// In en, this message translates to:
@@ -491,10 +527,10 @@ abstract class AppLocalizations {
/// Shows the number of selected players /// Shows the number of selected players
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Selected players: {count}'** /// **'Selected players'**
String selected_players(int count); String get selected_players;
/// Settings label /// Label for the App Settings
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Settings'** /// **'Settings'**
@@ -545,8 +581,8 @@ abstract class AppLocalizations {
/// Date format for today /// Date format for today
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Today at {time}'** /// **'Today at'**
String today_at(String time); String get today_at;
/// Undo button text /// Undo button text
/// ///
@@ -581,8 +617,8 @@ abstract class AppLocalizations {
/// Date format for yesterday /// Date format for yesterday
/// ///
/// In en, this message translates to: /// In en, this message translates to:
/// **'Yesterday at {time}'** /// **'Yesterday at'**
String yesterday_at(String time); String get yesterday_at;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate

View File

@@ -9,7 +9,7 @@ class AppLocalizationsDe extends AppLocalizations {
AppLocalizationsDe([String locale = 'de']) : super(locale); AppLocalizationsDe([String locale = 'de']) : super(locale);
@override @override
String get all_players => 'Alle Spieler:innen:'; String get all_players => 'Alle Spieler:innen';
@override @override
String get all_players_selected => 'Alle Spieler:innen ausgewählt'; String get all_players_selected => 'Alle Spieler:innen ausgewählt';
@@ -17,6 +17,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get amount_of_matches => 'Anzahl der Spiele'; String get amount_of_matches => 'Anzahl der Spiele';
@override
String get app_name => 'Game Tracker';
@override @override
String get cancel => 'Abbrechen'; String get cancel => 'Abbrechen';
@@ -30,7 +33,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get choose_ruleset => 'Regelwerk wählen'; String get choose_ruleset => 'Regelwerk wählen';
@override @override
String could_not_add_player(String playerName) { String could_not_add_player(Object playerName) {
return 'Spieler:in $playerName konnte nicht hinzugefügt werden'; return 'Spieler:in $playerName konnte nicht hinzugefügt werden';
} }
@@ -46,6 +49,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get create_new_match => 'Neues Spiel erstellen'; String get create_new_match => 'Neues Spiel erstellen';
@override
String get data => 'Daten';
@override @override
String get data_successfully_deleted => 'Daten erfolgreich gelöscht'; String get data_successfully_deleted => 'Daten erfolgreich gelöscht';
@@ -64,7 +70,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get delete => 'Löschen'; String get delete => 'Löschen';
@override @override
String get delete_all_data => 'Alle Daten löschen?'; String get delete_all_data => 'Alle Daten löschen';
@override @override
String get error_creating_group => String get error_creating_group =>
@@ -88,9 +94,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get game_name => 'Spielvorlagenname'; String get game_name => 'Spielvorlagenname';
@override
String get game_tracker => 'Game Tracker';
@override @override
String get group => 'Gruppe'; String get group => 'Gruppe';
@@ -118,6 +121,15 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get least_points => 'Niedrigste Punkte'; String get least_points => 'Niedrigste Punkte';
@override
String get legal => 'Rechtliches';
@override
String get legal_notice => 'Impressum';
@override
String get licenses => 'Lizenzen';
@override @override
String get match_in_progress => 'Spiel läuft...'; String get match_in_progress => 'Spiel läuft...';
@@ -127,9 +139,6 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get matches => 'Spiele'; String get matches => 'Spiele';
@override
String get menu => 'Menü';
@override @override
String get most_points => 'Höchste Punkte'; String get most_points => 'Höchste Punkte';
@@ -139,6 +148,12 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get no_groups_created_yet => 'Noch keine Gruppen erstellt'; String get no_groups_created_yet => 'Noch keine Gruppen erstellt';
@override
String get no_licenses_found => 'Keine Lizenzen gefunden';
@override
String get no_license_text_available => 'Kein Lizenztext verfügbar';
@override @override
String get no_matches_created_yet => 'Noch keine Spiele erstellt'; String get no_matches_created_yet => 'Noch keine Spiele erstellt';
@@ -150,7 +165,7 @@ class AppLocalizationsDe extends AppLocalizations {
'Keine Spieler:in mit diesem Namen gefunden'; 'Keine Spieler:in mit diesem Namen gefunden';
@override @override
String get no_players_selected => 'Keine Spieler:in ausgewählt'; String get no_players_selected => 'Keine Spieler:innen ausgewählt';
@override @override
String get no_recent_matches_available => 'Keine letzten Spiele verfügbar'; String get no_recent_matches_available => 'Keine letzten Spiele verfügbar';
@@ -181,6 +196,9 @@ class AppLocalizationsDe extends AppLocalizations {
return '$count Spieler'; return '$count Spieler';
} }
@override
String get privacy_policy => 'Datenschutzerklärung';
@override @override
String get quick_create => 'Schnellzugriff'; String get quick_create => 'Schnellzugriff';
@@ -216,14 +234,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get select_winner => 'Gewinner:in wählen:'; String get select_winner => 'Gewinner:in wählen:';
@override @override
String selected_players(int count) { String get selected_players => 'Ausgewählte Spieler:innen';
final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact(
locale: localeName,
);
final String countString = countNumberFormat.format(count);
return 'Ausgewählte Spieler:innen: $countString';
}
@override @override
String get settings => 'Einstellungen'; String get settings => 'Einstellungen';
@@ -254,9 +265,7 @@ class AppLocalizationsDe extends AppLocalizations {
'Dies kann nicht rückgängig gemacht werden'; 'Dies kann nicht rückgängig gemacht werden';
@override @override
String today_at(String time) { String get today_at => 'Heute um';
return 'Heute um $time';
}
@override @override
String get undo => 'Rückgängig'; String get undo => 'Rückgängig';
@@ -265,7 +274,7 @@ class AppLocalizationsDe extends AppLocalizations {
String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)'; String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)';
@override @override
String get winner => 'Gewinner*in'; String get winner => 'Gewinner:in';
@override @override
String get winrate => 'Siegquote'; String get winrate => 'Siegquote';
@@ -274,7 +283,5 @@ class AppLocalizationsDe extends AppLocalizations {
String get wins => 'Siege'; String get wins => 'Siege';
@override @override
String yesterday_at(String time) { String get yesterday_at => 'Gestern um';
return 'Gestern um $time';
}
} }

View File

@@ -9,7 +9,7 @@ class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale); AppLocalizationsEn([String locale = 'en']) : super(locale);
@override @override
String get all_players => 'All players:'; String get all_players => 'All players';
@override @override
String get all_players_selected => 'All players selected'; String get all_players_selected => 'All players selected';
@@ -17,6 +17,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get amount_of_matches => 'Amount of Matches'; String get amount_of_matches => 'Amount of Matches';
@override
String get app_name => 'Game Tracker';
@override @override
String get cancel => 'Cancel'; String get cancel => 'Cancel';
@@ -30,8 +33,8 @@ class AppLocalizationsEn extends AppLocalizations {
String get choose_ruleset => 'Choose Ruleset'; String get choose_ruleset => 'Choose Ruleset';
@override @override
String could_not_add_player(String playerName) { String could_not_add_player(Object playerName) {
return 'Could not add player $playerName'; return 'Could not add player';
} }
@override @override
@@ -46,6 +49,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get create_new_match => 'Create new match'; String get create_new_match => 'Create new match';
@override
String get data => 'Data';
@override @override
String get data_successfully_deleted => 'Data successfully deleted'; String get data_successfully_deleted => 'Data successfully deleted';
@@ -64,7 +70,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get delete => 'Delete'; String get delete => 'Delete';
@override @override
String get delete_all_data => 'Delete all data?'; String get delete_all_data => 'Delete all data';
@override @override
String get error_creating_group => String get error_creating_group =>
@@ -88,9 +94,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get game_name => 'Game Name'; String get game_name => 'Game Name';
@override
String get game_tracker => 'Game Tracker';
@override @override
String get group => 'Group'; String get group => 'Group';
@@ -118,6 +121,15 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get least_points => 'Least Points'; String get least_points => 'Least Points';
@override
String get legal => 'Legal';
@override
String get legal_notice => 'Legal Notice';
@override
String get licenses => 'Licenses';
@override @override
String get match_in_progress => 'Match in progress...'; String get match_in_progress => 'Match in progress...';
@@ -127,9 +139,6 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get matches => 'Matches'; String get matches => 'Matches';
@override
String get menu => 'Menu';
@override @override
String get most_points => 'Most Points'; String get most_points => 'Most Points';
@@ -139,6 +148,12 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get no_groups_created_yet => 'No groups created yet'; String get no_groups_created_yet => 'No groups created yet';
@override
String get no_licenses_found => 'No licenses found';
@override
String get no_license_text_available => 'No license text available';
@override @override
String get no_matches_created_yet => 'No matches created yet'; String get no_matches_created_yet => 'No matches created yet';
@@ -181,6 +196,9 @@ class AppLocalizationsEn extends AppLocalizations {
return '$count Players'; return '$count Players';
} }
@override
String get privacy_policy => 'Privacy Policy';
@override @override
String get quick_create => 'Quick Create'; String get quick_create => 'Quick Create';
@@ -216,14 +234,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get select_winner => 'Select Winner:'; String get select_winner => 'Select Winner:';
@override @override
String selected_players(int count) { String get selected_players => 'Selected players';
final intl.NumberFormat countNumberFormat = intl.NumberFormat.compact(
locale: localeName,
);
final String countString = countNumberFormat.format(count);
return 'Selected players: $countString';
}
@override @override
String get settings => 'Settings'; String get settings => 'Settings';
@@ -253,9 +264,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get this_cannot_be_undone => 'This can\'t be undone'; String get this_cannot_be_undone => 'This can\'t be undone';
@override @override
String today_at(String time) { String get today_at => 'Today at';
return 'Today at $time';
}
@override @override
String get undo => 'Undo'; String get undo => 'Undo';
@@ -273,7 +282,5 @@ class AppLocalizationsEn extends AppLocalizations {
String get wins => 'Wins'; String get wins => 'Wins';
@override @override
String yesterday_at(String time) { String get yesterday_at => 'Yesterday at';
return 'Yesterday at $time';
}
} }

View File

@@ -34,21 +34,23 @@ class GameTracker extends StatelessWidget {
); );
}, },
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
onGenerateTitle: (context) => AppLocalizations.of(context).game_tracker, onGenerateTitle: (context) => AppLocalizations.of(context).app_name,
darkTheme: ThemeData.dark(),
themeMode: ThemeMode.dark, // forces dark mode themeMode: ThemeMode.dark, // forces dark mode
theme: ThemeData( theme: ThemeData(
primaryColor: CustomTheme.primaryColor, primaryColor: CustomTheme.primaryColor,
scaffoldBackgroundColor: CustomTheme.backgroundColor, scaffoldBackgroundColor: CustomTheme.backgroundColor,
appBarTheme: CustomTheme.appBarTheme, appBarTheme: CustomTheme.appBarTheme,
colorScheme: ColorScheme.fromSeed( colorScheme: ColorScheme.fromSeed(
seedColor: CustomTheme.primaryColor, seedColor: CustomTheme.primaryColor,
brightness: Brightness.dark, brightness: Brightness.dark,
).copyWith(surface: CustomTheme.backgroundColor), ).copyWith(surface: CustomTheme.backgroundColor),
pageTransitionsTheme: const PageTransitionsTheme(
builders: {
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.android: PredictiveBackPageTransitionsBuilder(),
},
),
), ),
home: const CustomNavigationBar(), home: const CustomNavigationBar(),
); );
} }

View File

@@ -1,10 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/adaptive_page_route.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/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/groups_view.dart'; import 'package:game_tracker/presentation/views/main_menu/group_view/groups_view.dart';
import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; import 'package:game_tracker/presentation/views/main_menu/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.dart'; import 'package:game_tracker/presentation/views/main_menu/settings_view/settings_view.dart';
import 'package:game_tracker/presentation/views/main_menu/statistics_view.dart'; import 'package:game_tracker/presentation/views/main_menu/statistics_view.dart';
import 'package:game_tracker/presentation/widgets/navbar_item.dart'; import 'package:game_tracker/presentation/widgets/navbar_item.dart';
@@ -56,7 +57,7 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
onPressed: () async { onPressed: () async {
await Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute(builder: (_) => const SettingsView()), adaptivePageRoute(builder: (_) => const SettingsView()),
); );
setState(() { setState(() {
tabKeyCount++; tabKeyCount++;

View File

@@ -44,66 +44,68 @@ class _CreateGroupViewState extends State<CreateGroupView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
return Scaffold( return ScaffoldMessenger(
backgroundColor: CustomTheme.backgroundColor, child: Scaffold(
appBar: AppBar(title: Text(loc.create_new_group)), backgroundColor: CustomTheme.backgroundColor,
body: SafeArea( appBar: AppBar(title: Text(loc.create_new_group)),
child: Column( body: SafeArea(
mainAxisAlignment: MainAxisAlignment.start, child: Column(
children: [ mainAxisAlignment: MainAxisAlignment.start,
Container( children: [
margin: CustomTheme.standardMargin, Container(
child: TextInputField( margin: CustomTheme.standardMargin,
controller: _groupNameController, child: TextInputField(
hintText: loc.group_name, controller: _groupNameController,
hintText: loc.group_name,
),
), ),
), Expanded(
Expanded( child: PlayerSelection(
child: PlayerSelection( onChanged: (value) {
onChanged: (value) { setState(() {
setState(() { selectedPlayers = [...value];
selectedPlayers = [...value]; });
}); },
}, ),
), ),
), CustomWidthButton(
CustomWidthButton( text: loc.create_group,
text: loc.create_group, sizeRelativeToWidth: 0.95,
sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary,
buttonType: ButtonType.primary, onPressed:
onPressed: (_groupNameController.text.isEmpty ||
(_groupNameController.text.isEmpty || (selectedPlayers.length < 2))
(selectedPlayers.length < 2)) ? null
? null : () async {
: () async { bool success = await db.groupDao.addGroup(
bool success = await db.groupDao.addGroup( group: Group(
group: Group( name: _groupNameController.text.trim(),
name: _groupNameController.text.trim(), members: selectedPlayers,
members: selectedPlayers,
),
);
if (!context.mounted) return;
if (success) {
Navigator.pop(context);
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: CustomTheme.boxColor,
content: Center(
child: Text(
AppLocalizations.of(
context,
).error_creating_group,
style: const TextStyle(color: Colors.white),
),
),
), ),
); );
} if (!context.mounted) return;
}, if (success) {
), Navigator.pop(context);
const SizedBox(height: 20), } else {
], ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
backgroundColor: CustomTheme.boxColor,
content: Center(
child: Text(
AppLocalizations.of(
context,
).error_creating_group,
style: const TextStyle(color: Colors.white),
),
),
),
);
}
},
),
const SizedBox(height: 20),
],
),
), ),
), ),
); );

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/adaptive_page_route.dart';
import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/constants.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/database.dart';
@@ -85,7 +86,7 @@ class _GroupsViewState extends State<GroupsView> {
onPressed: () async { onPressed: () async {
await Navigator.push( await Navigator.push(
context, context,
MaterialPageRoute( adaptivePageRoute(
builder: (context) { builder: (context) {
return const CreateGroupView(); return const CreateGroupView();
}, },

View File

@@ -1,14 +1,16 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/adaptive_page_route.dart';
import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/constants.dart';
import 'package:game_tracker/data/db/database.dart'; 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/match.dart'; import 'package:game_tracker/data/dto/match.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/match_view/match_result_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/quick_create_button.dart'; import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart';
import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart';
import 'package:game_tracker/presentation/widgets/tiles/match_summary_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/match_tile.dart';
import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -43,7 +45,6 @@ class _HomeViewState extends State<HomeView> {
Player(name: 'Skeleton Player 2'), Player(name: 'Skeleton Player 2'),
], ],
), ),
winner: Player(name: 'Skeleton Player 1'),
), ),
); );
@@ -90,121 +91,92 @@ class _HomeViewState extends State<HomeView> {
child: InfoTile( child: InfoTile(
width: constraints.maxWidth * 0.95, width: constraints.maxWidth * 0.95,
title: loc.recent_matches, title: loc.recent_matches,
icon: Icons.timer, icon: Icons.history_rounded,
content: Padding( content: Column(
padding: const EdgeInsets.symmetric(horizontal: 40.0), children: [
child: Visibility( if (recentMatches.isNotEmpty)
visible: !isLoading && loadedRecentMatches.isNotEmpty, for (Match match in recentMatches)
replacement: Center( Padding(
heightFactor: 12, padding: const EdgeInsets.symmetric(
child: Text( vertical: 6.0,
AppLocalizations.of( ),
context, child: MatchTile(
).no_recent_matches_available, compact: true,
width: constraints.maxWidth * 0.9,
match: match,
onTap: () async {
await Navigator.of(context).push(
adaptivePageRoute(
fullscreenDialog: true,
builder: (context) =>
MatchResultView(match: match),
),
);
await updatedWinnerinRecentMatches(match.id);
},
),
)
else
Center(
heightFactor: 5,
child: Text(loc.no_recent_matches_available),
), ),
), ],
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MatchSummaryTile(
matchTitle: recentMatches[0].name,
game: 'Winner',
ruleset: 'Ruleset',
players: _getPlayerText(
recentMatches[0],
context,
),
winner: recentMatches[0].winner == null
? AppLocalizations.of(
context,
).match_in_progress
: recentMatches[0].winner!.name,
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 8.0),
child: Divider(),
),
if (loadedRecentMatches.length > 1) ...[
MatchSummaryTile(
matchTitle: recentMatches[1].name,
game: 'Winner',
ruleset: 'Ruleset',
players: _getPlayerText(
recentMatches[1],
context,
),
winner: recentMatches[1].winner == null
? AppLocalizations.of(
context,
).match_in_progress
: recentMatches[1].winner!.name,
),
const SizedBox(height: 8),
] else ...[
Center(
heightFactor: 5.35,
child: Text(
AppLocalizations.of(
context,
).no_second_match_available,
),
),
],
],
),
),
), ),
), ),
), ),
InfoTile( Padding(
width: constraints.maxWidth * 0.95, padding: EdgeInsets.zero,
title: loc.quick_create, child: InfoTile(
icon: Icons.add_box_rounded, width: constraints.maxWidth * 0.95,
content: Column( title: loc.quick_create,
children: [ icon: Icons.add_box_rounded,
Row( content: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
children: [ Row(
QuickCreateButton( mainAxisAlignment: MainAxisAlignment.spaceEvenly,
text: 'Category 1', children: [
onPressed: () {}, QuickCreateButton(
), text: 'Category 1',
QuickCreateButton( onPressed: () {},
text: 'Category 2', ),
onPressed: () {}, QuickCreateButton(
), text: 'Category 2',
], onPressed: () {},
), ),
Row( ],
mainAxisAlignment: MainAxisAlignment.spaceEvenly, ),
children: [ Row(
QuickCreateButton( mainAxisAlignment: MainAxisAlignment.spaceEvenly,
text: 'Category 3', children: [
onPressed: () {}, QuickCreateButton(
), text: 'Category 3',
QuickCreateButton( onPressed: () {},
text: 'Category 4', ),
onPressed: () {}, QuickCreateButton(
), text: 'Category 4',
], onPressed: () {},
), ),
Row( ],
mainAxisAlignment: MainAxisAlignment.spaceEvenly, ),
children: [ Row(
QuickCreateButton( mainAxisAlignment: MainAxisAlignment.spaceEvenly,
text: 'Category 5', children: [
onPressed: () {}, QuickCreateButton(
), text: 'Category 5',
QuickCreateButton( onPressed: () {},
text: 'Category 6', ),
onPressed: () {}, QuickCreateButton(
), text: 'Category 6',
], onPressed: () {},
), ),
], ],
),
],
),
), ),
), ),
SizedBox(height: MediaQuery.paddingOf(context).bottom),
], ],
), ),
), ),
@@ -215,7 +187,7 @@ class _HomeViewState extends State<HomeView> {
/// Loads the data for the HomeView from the database. /// Loads the data for the HomeView from the database.
/// This includes the match count, group count, and recent matches. /// This includes the match count, group count, and recent matches.
void loadHomeViewData() { Future<void> loadHomeViewData() async {
final db = Provider.of<AppDatabase>(context, listen: false); final db = Provider.of<AppDatabase>(context, listen: false);
Future.wait([ Future.wait([
db.matchDao.getMatchCount(), db.matchDao.getMatchCount(),
@@ -231,11 +203,6 @@ class _HomeViewState extends State<HomeView> {
..sort((a, b) => b.createdAt.compareTo(a.createdAt))) ..sort((a, b) => b.createdAt.compareTo(a.createdAt)))
.take(2) .take(2)
.toList(); .toList();
if (loadedRecentMatches.length < 2) {
recentMatches.add(
Match(name: 'Dummy Match', winner: null, group: null, players: null),
);
}
if (mounted) { if (mounted) {
setState(() { setState(() {
isLoading = false; isLoading = false;
@@ -244,18 +211,15 @@ class _HomeViewState extends State<HomeView> {
}); });
} }
/// Generates a text representation of the players in the match. /// Updates the winner information for a specific match in the recent matches list.
/// If the match has a group, it returns the group name and the number of additional players. Future<void> updatedWinnerinRecentMatches(String matchId) async {
/// If there is no group, it returns the count of players. final db = Provider.of<AppDatabase>(context, listen: false);
String _getPlayerText(Match game, context) { final winner = await db.matchDao.getWinner(matchId: matchId);
final loc = AppLocalizations.of(context); final matchIndex = recentMatches.indexWhere((match) => match.id == matchId);
if (game.group == null) { if (matchIndex != -1) {
final playerCount = game.players?.length ?? 0; setState(() {
return loc.players_count(playerCount); recentMatches[matchIndex].winner = winner;
});
} }
if (game.players == null || game.players!.isEmpty) {
return game.group!.name;
}
return '${game.group!.name} + ${game.players!.length}';
} }
} }

View File

@@ -140,7 +140,11 @@ class _ChooseGroupViewState extends State<ChooseGroupView> {
filteredGroups.clear(); filteredGroups.clear();
filteredGroups.addAll( filteredGroups.addAll(
widget.groups.where( widget.groups.where(
(group) => group.name.toLowerCase().contains(query.toLowerCase()), (group) =>
group.name.toLowerCase().contains(query.toLowerCase()) ||
group.members.any(
(player) => player.name.toLowerCase().contains(query.toLowerCase()),
),
), ),
); );
} }

View File

@@ -1,5 +1,5 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/adaptive_page_route.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/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';
@@ -119,140 +119,142 @@ class _CreateMatchViewState extends State<CreateMatchView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
return Scaffold( return ScaffoldMessenger(
backgroundColor: CustomTheme.backgroundColor, child: Scaffold(
appBar: AppBar(title: Text(loc.create_new_match)), backgroundColor: CustomTheme.backgroundColor,
body: SafeArea( appBar: AppBar(title: Text(loc.create_new_match)),
child: Column( body: SafeArea(
mainAxisAlignment: MainAxisAlignment.start, child: Column(
children: [ mainAxisAlignment: MainAxisAlignment.start,
Container( children: [
margin: CustomTheme.tileMargin, Container(
child: TextInputField( margin: CustomTheme.tileMargin,
controller: _matchNameController, child: TextInputField(
hintText: hintText ?? '', controller: _matchNameController,
hintText: hintText ?? '',
),
), ),
), ChooseTile(
ChooseTile( title: loc.game,
title: loc.game, trailingText: selectedGameIndex == -1
trailingText: selectedGameIndex == -1 ? loc.none
? loc.none : games[selectedGameIndex].$1,
: games[selectedGameIndex].$1, onPressed: () async {
onPressed: () async { selectedGameIndex = await Navigator.of(context).push(
selectedGameIndex = await Navigator.of(context).push( adaptivePageRoute(
MaterialPageRoute( builder: (context) => ChooseGameView(
builder: (context) => ChooseGameView( games: games,
games: games, initialGameIndex: selectedGameIndex,
initialGameIndex: selectedGameIndex, ),
), ),
), );
);
setState(() {
if (selectedGameIndex != -1) {
hintText = games[selectedGameIndex].$1;
selectedRuleset = games[selectedGameIndex].$3;
selectedRulesetIndex = _rulesets.indexWhere(
(r) => r.$1 == selectedRuleset,
);
} else {
hintText = AppLocalizations.of(context).match_name;
selectedRuleset = null;
}
});
},
),
ChooseTile(
title: loc.ruleset,
trailingText: selectedRuleset == null
? loc.none
: translateRulesetToString(selectedRuleset!, context),
onPressed: () async {
selectedRuleset = await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ChooseRulesetView(
rulesets: _rulesets,
initialRulesetIndex: selectedRulesetIndex,
),
),
);
if (!mounted) return;
selectedRulesetIndex = _rulesets.indexWhere(
(r) => r.$1 == selectedRuleset,
);
selectedGameIndex = -1;
setState(() {});
},
),
ChooseTile(
title: loc.group,
trailingText: selectedGroup == null
? loc.none_group
: selectedGroup!.name,
onPressed: () async {
selectedGroup = await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => ChooseGroupView(
groups: groupsList,
initialGroupId: selectedGroupId,
),
),
);
selectedGroupId = selectedGroup?.id ?? '';
if (selectedGroup != null) {
filteredPlayerList = playerList
.where(
(p) => !selectedGroup!.members.any((m) => m.id == p.id),
)
.toList();
} else {
filteredPlayerList = List.from(playerList);
}
setState(() {});
},
),
Expanded(
child: PlayerSelection(
key: ValueKey(selectedGroup?.id ?? 'no_group'),
initialSelectedPlayers: selectedPlayers ?? [],
availablePlayers: filteredPlayerList,
onChanged: (value) {
setState(() { setState(() {
selectedPlayers = value; if (selectedGameIndex != -1) {
hintText = games[selectedGameIndex].$1;
selectedRuleset = games[selectedGameIndex].$3;
selectedRulesetIndex = _rulesets.indexWhere(
(r) => r.$1 == selectedRuleset,
);
} else {
hintText = loc.match_name;
selectedRuleset = null;
}
}); });
}, },
), ),
), ChooseTile(
CustomWidthButton( title: loc.ruleset,
text: loc.create_match, trailingText: selectedRuleset == null
sizeRelativeToWidth: 0.95, ? loc.none
buttonType: ButtonType.primary, : translateRulesetToString(selectedRuleset!, context),
onPressed: _enableCreateGameButton() onPressed: () async {
? () async { selectedRuleset = await Navigator.of(context).push(
Match match = Match( adaptivePageRoute(
name: _matchNameController.text.isEmpty builder: (context) => ChooseRulesetView(
? (hintText ?? '') rulesets: _rulesets,
: _matchNameController.text.trim(), initialRulesetIndex: selectedRulesetIndex,
createdAt: DateTime.now(), ),
group: selectedGroup, ),
players: selectedPlayers, );
); if (!mounted) return;
await db.matchDao.addMatch(match: match); selectedRulesetIndex = _rulesets.indexWhere(
if (context.mounted) { (r) => r.$1 == selectedRuleset,
Navigator.pushReplacement( );
context, selectedGameIndex = -1;
CupertinoPageRoute( setState(() {});
fullscreenDialog: true, },
builder: (context) => MatchResultView( ),
match: match, ChooseTile(
onWinnerChanged: widget.onWinnerChanged, title: loc.group,
), trailingText: selectedGroup == null
), ? loc.none_group
: selectedGroup!.name,
onPressed: () async {
selectedGroup = await Navigator.of(context).push(
adaptivePageRoute(
builder: (context) => ChooseGroupView(
groups: groupsList,
initialGroupId: selectedGroupId,
),
),
);
selectedGroupId = selectedGroup?.id ?? '';
if (selectedGroup != null) {
filteredPlayerList = playerList
.where(
(p) => !selectedGroup!.members.any((m) => m.id == p.id),
)
.toList();
} else {
filteredPlayerList = List.from(playerList);
}
setState(() {});
},
),
Expanded(
child: PlayerSelection(
key: ValueKey(selectedGroup?.id ?? 'no_group'),
initialSelectedPlayers: selectedPlayers ?? [],
availablePlayers: filteredPlayerList,
onChanged: (value) {
setState(() {
selectedPlayers = value;
});
},
),
),
CustomWidthButton(
text: loc.create_match,
sizeRelativeToWidth: 0.95,
buttonType: ButtonType.primary,
onPressed: _enableCreateGameButton()
? () async {
Match match = Match(
name: _matchNameController.text.isEmpty
? (hintText ?? '')
: _matchNameController.text.trim(),
createdAt: DateTime.now(),
group: selectedGroup,
players: selectedPlayers,
); );
await db.matchDao.addMatch(match: match);
if (context.mounted) {
Navigator.pushReplacement(
context,
adaptivePageRoute(
fullscreenDialog: true,
builder: (context) => MatchResultView(
match: match,
onWinnerChanged: widget.onWinnerChanged,
),
),
);
}
} }
} : null,
: null, ),
), ],
], ),
), ),
), ),
); );

View File

@@ -1,7 +1,7 @@
import 'dart:core' hide Match; import 'dart:core' hide Match;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:game_tracker/core/adaptive_page_route.dart';
import 'package:game_tracker/core/constants.dart'; import 'package:game_tracker/core/constants.dart';
import 'package:game_tracker/core/custom_theme.dart'; import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/database.dart';
@@ -78,20 +78,26 @@ class _MatchViewState extends State<MatchView> {
height: MediaQuery.paddingOf(context).bottom - 20, height: MediaQuery.paddingOf(context).bottom - 20,
); );
} }
return MatchTile( return Center(
onTap: () async { child: Padding(
Navigator.push( padding: const EdgeInsets.only(bottom: 12.0),
context, child: MatchTile(
CupertinoPageRoute( width: MediaQuery.sizeOf(context).width * 0.95,
fullscreenDialog: true, onTap: () async {
builder: (context) => MatchResultView( Navigator.push(
match: matches[index], context,
onWinnerChanged: loadGames, adaptivePageRoute(
), fullscreenDialog: true,
), builder: (context) => MatchResultView(
); match: matches[index],
}, onWinnerChanged: loadGames,
match: matches[index], ),
),
);
},
match: matches[index],
),
),
); );
}, },
), ),
@@ -105,7 +111,7 @@ class _MatchViewState extends State<MatchView> {
onPressed: () async { onPressed: () async {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( adaptivePageRoute(
builder: (context) => builder: (context) =>
CreateMatchView(onWinnerChanged: loadGames), CreateMatchView(onWinnerChanged: loadGames),
), ),

View File

@@ -1,197 +0,0 @@
import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/core/enums.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart';
import 'package:game_tracker/services/data_transfer_service.dart';
class SettingsView extends StatefulWidget {
const SettingsView({super.key});
@override
State<SettingsView> createState() => _SettingsViewState();
}
class _SettingsViewState extends State<SettingsView> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(backgroundColor: CustomTheme.backgroundColor),
backgroundColor: CustomTheme.backgroundColor,
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) =>
SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 10),
child: Text(
textAlign: TextAlign.start,
loc.menu,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
),
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 10,
),
child: Text(
textAlign: TextAlign.start,
loc.settings,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
),
SettingsListTile(
title: loc.export_data,
icon: Icons.upload_outlined,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () async {
final String json =
await DataTransferService.getAppDataAsJson(context);
final result = await DataTransferService.exportData(
json,
'game_tracker-data',
);
if (!context.mounted) return;
showExportSnackBar(context: context, result: result);
},
),
SettingsListTile(
title: loc.import_data,
icon: Icons.download_outlined,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () async {
final result = await DataTransferService.importData(
context,
);
if (!context.mounted) return;
showImportSnackBar(context: context, result: result);
},
),
SettingsListTile(
title: loc.delete_all_data,
icon: Icons.download_outlined,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () {
showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text(loc.delete_all_data),
content: Text(loc.this_cannot_be_undone),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(loc.cancel),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(loc.delete),
),
],
),
).then((confirmed) {
if (confirmed == true && context.mounted) {
DataTransferService.deleteAllData(context);
showSnackbar(
context: context,
message: AppLocalizations.of(
context,
).data_successfully_deleted,
);
}
});
},
),
],
),
),
),
);
}
/// Displays a snackbar based on the import result.
///
/// [context] The BuildContext to show the snackbar in.
/// [result] The result of the import operation.
void showImportSnackBar({
required BuildContext context,
required ImportResult result,
}) {
final loc = AppLocalizations.of(context);
switch (result) {
case ImportResult.success:
showSnackbar(context: context, message: loc.data_successfully_imported);
case ImportResult.invalidSchema:
showSnackbar(context: context, message: loc.invalid_schema);
case ImportResult.fileReadError:
showSnackbar(context: context, message: loc.error_reading_file);
case ImportResult.canceled:
showSnackbar(context: context, message: loc.import_canceled);
case ImportResult.formatException:
showSnackbar(context: context, message: loc.format_exception);
case ImportResult.unknownException:
showSnackbar(context: context, message: loc.unknown_exception);
}
}
/// Displays a snackbar based on the export result.
///
/// [context] The BuildContext to show the snackbar in.
/// [result] The result of the export operation.
void showExportSnackBar({
required BuildContext context,
required ExportResult result,
}) {
final loc = AppLocalizations.of(context);
switch (result) {
case ExportResult.success:
showSnackbar(context: context, message: loc.data_successfully_exported);
case ExportResult.canceled:
showSnackbar(context: context, message: loc.export_canceled);
case ExportResult.unknownException:
showSnackbar(context: context, message: loc.unknown_exception);
}
}
/// Displays a snackbar with the given message and optional action.
///
/// [context] The BuildContext to show the snackbar in.
/// [message] The message to display in the snackbar.
/// [duration] The duration for which the snackbar is displayed.
/// [action] An optional callback function to execute when the action button is pressed.
void showSnackbar({
required BuildContext context,
required String message,
Duration duration = const Duration(seconds: 3),
VoidCallback? action,
}) {
final loc = AppLocalizations.of(context);
final messenger = ScaffoldMessenger.of(context);
messenger.hideCurrentSnackBar();
messenger.showSnackBar(
SnackBar(
content: Text(message, style: const TextStyle(color: Colors.white)),
backgroundColor: CustomTheme.onBoxColor,
duration: duration,
action: action != null
? SnackBarAction(label: loc.undo, onPressed: action)
: null,
),
);
}
}

View File

@@ -0,0 +1,137 @@
import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart';
import 'package:url_launcher/url_launcher.dart';
class LicenseDetailView extends StatelessWidget {
final Package package;
const LicenseDetailView({super.key, required this.package});
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(title: const Text('Lizenzdetails')),
body: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
margin: const EdgeInsetsGeometry.only(right: 15),
width: 60,
height: 60,
decoration: BoxDecoration(
color: CustomTheme.primaryColor.withAlpha(40),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
Icons.description,
color: CustomTheme.primaryColor,
size: 30,
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
package.name,
textAlign: TextAlign.left,
style: const TextStyle(
height: 0,
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
if (package.version != null) ...[
Text(
'Version ${package.version}',
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 14,
color: Colors.grey.shade300,
),
),
],
],
),
],
),
if (package.authors.isNotEmpty) ...[
const SizedBox(height: 8),
SelectableText(
package.authors.join(', '),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade500,
),
),
],
if (package.homepage != null &&
package.homepage!.isNotEmpty) ...[
const SizedBox(height: 8),
GestureDetector(
onTap: () async {
final uri = Uri.parse(package.homepage!);
await launchUrl(uri, mode: LaunchMode.platformDefault);
},
child: SizedBox(
width: 300,
child: Text(
package.homepage!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: CustomTheme.secondaryColor,
decoration: TextDecoration.underline,
decorationColor: CustomTheme.secondaryColor,
),
),
),
),
],
],
),
),
const SizedBox(height: 20),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 16),
decoration: CustomTheme.standardBoxDecoration,
child: (package.license != null && package.license!.isNotEmpty)
? SelectableText(
package.license!,
style: TextStyle(
fontSize: 12,
color: Colors.grey.shade300,
height: 1.5,
fontFamily: 'monospace',
),
)
: Center(
heightFactor: 25,
child: Text(
loc.no_license_text_available,
style: TextStyle(color: Colors.grey.shade500),
),
),
),
],
),
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,32 @@
import 'package:flutter/material.dart';
import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart';
import 'package:game_tracker/presentation/widgets/tiles/license_tile.dart';
class LicensesView extends StatelessWidget {
const LicensesView({super.key});
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(loc.licenses),
backgroundColor: CustomTheme.backgroundColor,
),
backgroundColor: CustomTheme.backgroundColor,
body: ListView.builder(
itemCount: allDependencies.length,
itemBuilder: (context, index) {
final package = allDependencies[index];
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
child: LicenseTile(package: package),
);
},
),
);
}
}

View File

@@ -0,0 +1,310 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:game_tracker/core/custom_theme.dart';
import 'package:game_tracker/core/enums.dart';
import 'package:game_tracker/l10n/generated/app_localizations.dart';
import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses_view.dart';
import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart';
import 'package:game_tracker/services/data_transfer_service.dart';
import 'package:intl/intl.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
class SettingsView extends StatefulWidget {
const SettingsView({super.key});
@override
State<SettingsView> createState() => _SettingsViewState();
}
class _SettingsViewState extends State<SettingsView> {
PackageInfo _packageInfo = PackageInfo(
appName: 'n.A.',
packageName: 'n.A.',
version: 'n.A.',
buildNumber: 'n.A.',
);
@override
void initState() {
super.initState();
_initPackageInfo();
}
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
return ScaffoldMessenger(
child: Scaffold(
appBar: AppBar(backgroundColor: CustomTheme.backgroundColor),
backgroundColor: CustomTheme.backgroundColor,
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 16, bottom: 10),
child: Text(
textAlign: TextAlign.start,
loc.settings,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
),
Padding(
padding: const EdgeInsets.only(left: 16, top: 10, bottom: 10),
child: Text(
textAlign: TextAlign.start,
loc.data,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
),
SettingsListTile(
title: loc.export_data,
icon: Icons.upload,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () async {
final String json =
await DataTransferService.getAppDataAsJson(context);
final result = await DataTransferService.exportData(
json,
'game_tracker-data',
);
if (!context.mounted) return;
showExportSnackBar(context: context, result: result);
},
),
SettingsListTile(
title: loc.import_data,
icon: Icons.download,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () async {
final result = await DataTransferService.importData(context);
if (!context.mounted) return;
showImportSnackBar(context: context, result: result);
},
),
SettingsListTile(
title: loc.delete_all_data,
icon: Icons.delete,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () {
showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Text('${loc.delete_all_data}?'),
content: Text(loc.this_cannot_be_undone),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: Text(loc.cancel),
),
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text(loc.delete),
),
],
),
).then((confirmed) {
if (confirmed == true && context.mounted) {
DataTransferService.deleteAllData(context);
showSnackbar(
context: context,
message: AppLocalizations.of(
context,
).data_successfully_deleted,
);
}
});
},
),
Padding(
padding: const EdgeInsets.only(left: 16, top: 10, bottom: 10),
child: Text(
textAlign: TextAlign.start,
loc.legal,
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
),
SettingsListTile(
title: loc.licenses,
icon: Icons.insert_drive_file,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => const LicensesView(),
),
);
},
),
SettingsListTile(
title: loc.legal_notice,
icon: Icons.account_balance_sharp,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: null,
),
SettingsListTile(
title: loc.privacy_policy,
icon: Icons.gpp_good_rounded,
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
onPressed: null,
),
Padding(
padding: const EdgeInsets.only(top: 30, bottom: 20),
child: Center(
child: Column(
spacing: 4,
children: [
Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 40,
children: [
GestureDetector(
child: const Icon(Icons.language),
onTap: () => {
launchUrl(Uri.parse('https://liquid-dev.de')),
},
),
GestureDetector(
child: const FaIcon(FontAwesomeIcons.github),
onTap: () => {
launchUrl(
Uri.parse(
'https://github.com/liquiddevelopmentde',
),
),
},
),
GestureDetector(
child: Icon(
Platform.isIOS
? CupertinoIcons.mail_solid
: Icons.email,
),
onTap: () => launchUrl(
Uri.parse('mailto:hi@liquid-dev.de'),
),
),
],
),
),
Text(
'© ${DateFormat('yyyy').format(DateTime.now())} Liquid Development',
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
Text(
'Version ${_packageInfo.version} (${_packageInfo.buildNumber})',
style: TextStyle(
color: Colors.grey.shade600,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
],
),
),
),
],
),
),
),
);
}
/// Displays a snackbar based on the import result.
///
/// [context] The BuildContext to show the snackbar in.
/// [result] The result of the import operation.
void showImportSnackBar({
required BuildContext context,
required ImportResult result,
}) {
final loc = AppLocalizations.of(context);
switch (result) {
case ImportResult.success:
showSnackbar(context: context, message: loc.data_successfully_imported);
case ImportResult.invalidSchema:
showSnackbar(context: context, message: loc.invalid_schema);
case ImportResult.fileReadError:
showSnackbar(context: context, message: loc.error_reading_file);
case ImportResult.canceled:
showSnackbar(context: context, message: loc.import_canceled);
case ImportResult.formatException:
showSnackbar(context: context, message: loc.format_exception);
case ImportResult.unknownException:
showSnackbar(context: context, message: loc.unknown_exception);
}
}
/// Displays a snackbar based on the export result.
///
/// [context] The BuildContext to show the snackbar in.
/// [result] The result of the export operation.
void showExportSnackBar({
required BuildContext context,
required ExportResult result,
}) {
final loc = AppLocalizations.of(context);
switch (result) {
case ExportResult.success:
showSnackbar(context: context, message: loc.data_successfully_exported);
case ExportResult.canceled:
showSnackbar(context: context, message: loc.export_canceled);
case ExportResult.unknownException:
showSnackbar(context: context, message: loc.unknown_exception);
}
}
/// Displays a snackbar with the given message and optional action.
///
/// [context] The BuildContext to show the snackbar in.
/// [message] The message to display in the snackbar.
/// [duration] The duration for which the snackbar is displayed.
/// [action] An optional callback function to execute when the action button is pressed.
void showSnackbar({
required BuildContext context,
required String message,
Duration duration = const Duration(seconds: 3),
VoidCallback? action,
}) {
final loc = AppLocalizations.of(context);
final messenger = ScaffoldMessenger.of(context);
messenger.hideCurrentSnackBar();
messenger.showSnackBar(
SnackBar(
content: Text(message, style: const TextStyle(color: Colors.white)),
backgroundColor: CustomTheme.onBoxColor,
duration: duration,
action: action != null
? SnackBarAction(label: loc.undo, onPressed: action)
: null,
),
);
}
/// Initializes the package information.
Future<void> _initPackageInfo() async {
final info = await PackageInfo.fromPlatform();
setState(() {
_packageInfo = info;
});
}
}

View File

@@ -119,9 +119,7 @@ class _PlayerSelectionState extends State<PlayerSelection> {
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Text( Text(
AppLocalizations.of( loc.selected_players,
context,
).selected_players(selectedPlayers.length),
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),

View File

@@ -0,0 +1,106 @@
import 'package:flutter/material.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/oss_licenses.dart';
class LicenseTile extends StatelessWidget {
final Package package;
const LicenseTile({super.key, required this.package});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => LicenseDetailView(package: package),
),
);
},
child: Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12),
decoration: CustomTheme.standardBoxDecoration.copyWith(
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
color: CustomTheme.primaryColor.withAlpha(40),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
Icons.description,
color: CustomTheme.primaryColor,
size: 32,
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Flexible(
child: Text(
package.name,
overflow: TextOverflow.ellipsis,
maxLines: 1,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
),
if (package.version != null &&
package.version!.isNotEmpty) ...[
const SizedBox(width: 12),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 2,
),
decoration: BoxDecoration(
color: CustomTheme.onBoxColor,
borderRadius: BorderRadius.circular(6),
),
child: Text(
'v${package.version}',
style: TextStyle(
fontSize: 11,
color: Colors.grey.shade500,
fontWeight: FontWeight.w500,
),
),
),
],
],
),
const SizedBox(height: 4),
Text(
package.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 13,
color: Colors.grey.shade400,
height: 1.3,
),
),
],
),
),
const SizedBox(width: 12),
// Arrow Icon
Icon(Icons.chevron_right, color: Colors.grey.shade600, size: 24),
],
),
),
);
}
}

View File

@@ -10,8 +10,16 @@ import 'package:intl/intl.dart';
/// creation date, associated group, winner, and players. /// creation date, associated group, winner, and players.
/// - [match]: The match data to be displayed. /// - [match]: The match data to be displayed.
/// - [onTap]: The callback invoked when the tile is tapped. /// - [onTap]: The callback invoked when the tile is tapped.
/// - [width]: Optional width for the tile.
/// - [compact]: Whether to display the tile in a compact mode
class MatchTile extends StatefulWidget { class MatchTile extends StatefulWidget {
const MatchTile({super.key, required this.match, required this.onTap}); const MatchTile({
super.key,
required this.match,
required this.onTap,
this.width,
this.compact = false,
});
/// The match data to be displayed. /// The match data to be displayed.
final Match match; final Match match;
@@ -19,6 +27,12 @@ class MatchTile extends StatefulWidget {
/// The callback invoked when the tile is tapped. /// The callback invoked when the tile is tapped.
final VoidCallback onTap; final VoidCallback onTap;
/// Optional width for the tile.
final double? width;
/// Whether to display the tile in a compact mode
final bool compact;
@override @override
State<MatchTile> createState() => _MatchTileState(); State<MatchTile> createState() => _MatchTileState();
} }
@@ -41,13 +55,10 @@ class _MatchTileState extends State<MatchTile> {
return GestureDetector( return GestureDetector(
onTap: widget.onTap, onTap: widget.onTap,
child: Container( child: Container(
margin: CustomTheme.tileMargin, margin: EdgeInsets.zero,
width: widget.width,
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
decoration: BoxDecoration( decoration: CustomTheme.standardBoxDecoration,
color: CustomTheme.boxColor,
border: Border.all(color: CustomTheme.boxBorder),
borderRadius: BorderRadius.circular(12),
),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -80,7 +91,22 @@ class _MatchTileState extends State<MatchTile> {
const SizedBox(width: 6), const SizedBox(width: 6),
Expanded( Expanded(
child: Text( child: Text(
group.name, '${group.name}${widget.match.players != null ? ' + ${widget.match.players?.length}' : ''}',
style: const TextStyle(fontSize: 14, color: Colors.grey),
overflow: TextOverflow.ellipsis,
),
),
],
),
const SizedBox(height: 12),
] else if (widget.compact) ...[
Row(
children: [
const Icon(Icons.person, size: 16, color: Colors.grey),
const SizedBox(width: 6),
Expanded(
child: Text(
'${widget.match.players!.length} ${loc.players}',
style: const TextStyle(fontSize: 14, color: Colors.grey), style: const TextStyle(fontSize: 14, color: Colors.grey),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@@ -127,9 +153,46 @@ class _MatchTileState extends State<MatchTile> {
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
] else ...[
Container(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 12,
),
decoration: BoxDecoration(
color: Colors.amber.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Colors.amber.withValues(alpha: 0.3),
width: 1,
),
),
child: Row(
children: [
const Icon(
Icons.watch_later,
size: 20,
color: Colors.amber,
),
const SizedBox(width: 8),
Expanded(
child: Text(
loc.match_in_progress,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: CustomTheme.textColor,
),
overflow: TextOverflow.ellipsis,
),
),
],
),
),
const SizedBox(height: 12),
], ],
if (_allPlayers.isNotEmpty) ...[ if (_allPlayers.isNotEmpty && widget.compact == false) ...[
Text( Text(
loc.players, loc.players,
style: const TextStyle( style: const TextStyle(
@@ -161,13 +224,9 @@ class _MatchTileState extends State<MatchTile> {
final loc = AppLocalizations.of(context); final loc = AppLocalizations.of(context);
if (difference.inDays == 0) { if (difference.inDays == 0) {
return AppLocalizations.of( return "${loc.today_at} ${DateFormat('HH:mm').format(dateTime)}";
context,
).today_at(DateFormat('HH:mm').format(dateTime));
} else if (difference.inDays == 1) { } else if (difference.inDays == 1) {
return AppLocalizations.of( return "${loc.yesterday_at} ${DateFormat('HH:mm').format(dateTime)}";
context,
).yesterday_at(DateFormat('HH:mm').format(dateTime));
} else if (difference.inDays < 7) { } else if (difference.inDays < 7) {
return loc.days_ago(difference.inDays); return loc.days_ago(difference.inDays);
} else { } else {

View File

@@ -38,7 +38,7 @@ class SettingsListTile extends StatelessWidget {
onTap: onPressed ?? () {}, onTap: onPressed ?? () {},
child: Container( child: Container(
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14), padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
decoration: CustomTheme.standardBoxDecoration, decoration: CustomTheme.standardBoxDecoration,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -47,12 +47,17 @@ class SettingsListTile extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Container( Container(
padding: const EdgeInsets.all(8), width: 44,
height: 44,
decoration: BoxDecoration( decoration: BoxDecoration(
color: CustomTheme.primaryColor, color: CustomTheme.primaryColor.withAlpha(40),
shape: BoxShape.circle, borderRadius: BorderRadius.circular(10),
),
child: Icon(
icon,
size: 28,
color: CustomTheme.primaryColor.withGreen(40),
), ),
child: Icon(icon, size: 24),
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text(title, style: const TextStyle(fontSize: 18)), Text(title, style: const TextStyle(fontSize: 18)),

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.1+21 version: 0.0.5+143
environment: environment:
sdk: ^3.8.1 sdk: ^3.8.1
@@ -9,31 +9,32 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
material_symbols_icons: ^4.2815.1 flutter_localizations:
sdk: flutter
# The following adds the Cupertino Icons font to your application. clock: ^1.1.2
# Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.6
cupertino_icons: ^1.0.8
drift: ^2.27.0 drift: ^2.27.0
drift_flutter: ^0.2.4 drift_flutter: ^0.2.4
file_picker: ^10.3.6
file_saver: ^0.3.1
font_awesome_flutter: ^10.12.0
intl: any
json_schema: ^5.2.2
package_info_plus: ^9.0.0
path_provider: ^2.1.5 path_provider: ^2.1.5
provider: ^6.1.5 provider: ^6.1.5
skeletonizer: ^2.1.0+1 skeletonizer: ^2.1.0+1
url_launcher: ^6.3.2
uuid: ^4.5.2 uuid: ^4.5.2
file_picker: ^10.3.6
json_schema: ^5.2.2
file_saver: ^0.3.1
clock: ^1.1.2
intl: any
flutter_localizations:
sdk: flutter
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_lints: ^5.0.0
drift_dev: ^2.27.0
build_runner: ^2.5.4 build_runner: ^2.5.4
dart_pubspec_licenses: ^3.0.14
drift_dev: ^2.27.0
flutter_lints: ^5.0.0
flutter: flutter:
uses-material-design: true uses-material-design: true