Compare commits
1 Commits
feature/88
...
v.0.0.1-al
| Author | SHA1 | Date | |
|---|---|---|---|
| f0379e97c7 |
35
.gitea/ISSUE_TEMPLATE/BUG_REPORT.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
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]
|
||||||
22
.gitea/ISSUE_TEMPLATE/ENHANCEMENT.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
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]
|
||||||
19
.gitea/ISSUE_TEMPLATE/FEATURE.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
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]
|
||||||
16
.gitea/PULL_REQUEST_TEMPLATE.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# [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?*
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
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
|
|
||||||
@@ -10,22 +10,22 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Required for Flutter action
|
|
||||||
- name: Install jq
|
- name: Install jq
|
||||||
run: |
|
run: |
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y jq
|
apt-get install -y jq
|
||||||
|
|
||||||
- name: Set up Flutter
|
- name: Install Flutter (wget)
|
||||||
uses: subosito/flutter-action@v2
|
run: |
|
||||||
with:
|
wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz
|
||||||
channel: stable
|
tar xf flutter_linux_3.38.2-stable.tar.xz
|
||||||
flutter-version: 3.38.6
|
# Set Git safe directory for Flutter path
|
||||||
|
git config --global --add safe.directory "$(pwd)/flutter"
|
||||||
|
# Set Flutter path
|
||||||
|
echo "$(pwd)/flutter/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
- name: Get dependencies
|
- name: Get dependencies
|
||||||
run: |
|
run: flutter pub get
|
||||||
git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.38.6-x64
|
|
||||||
flutter pub get
|
|
||||||
|
|
||||||
- name: Analyze Formatting
|
- name: Analyze Formatting
|
||||||
run: flutter analyze lib test
|
run: flutter analyze lib test
|
||||||
@@ -36,22 +36,22 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Required for Flutter action
|
- name: Install dependencies
|
||||||
- name: Install jq
|
|
||||||
run: |
|
run: |
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y jq
|
apt-get install -y jq
|
||||||
|
|
||||||
- name: Set up Flutter
|
- name: Install Flutter (wget)
|
||||||
uses: subosito/flutter-action@v2
|
run: |
|
||||||
with:
|
wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz
|
||||||
channel: stable
|
tar xf flutter_linux_3.38.2-stable.tar.xz
|
||||||
flutter-version: 3.38.6
|
# Set Git safe directory for Flutter path
|
||||||
|
git config --global --add safe.directory "$(pwd)/flutter"
|
||||||
|
# Set Flutter path
|
||||||
|
echo "$(pwd)/flutter/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
- name: Get dependencies
|
- name: Get dependencies
|
||||||
run: |
|
run: flutter pub get
|
||||||
git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.38.6-x64
|
|
||||||
flutter pub get
|
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: flutter test
|
run: flutter test
|
||||||
@@ -7,194 +7,44 @@ on:
|
|||||||
- "main"
|
- "main"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Java (Temurin 17)
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
distribution: temurin
|
|
||||||
java-version: '17'
|
|
||||||
|
|
||||||
- name: Setup Android SDK
|
|
||||||
uses: android-actions/setup-android@v3
|
|
||||||
|
|
||||||
# Required for Flutter action
|
|
||||||
- name: Install jq
|
|
||||||
run: |
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y jq
|
|
||||||
|
|
||||||
- name: Set up Flutter
|
|
||||||
uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
channel: stable
|
|
||||||
flutter-version: 3.38.6
|
|
||||||
|
|
||||||
- name: Get dependencies
|
|
||||||
run: |
|
|
||||||
git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.38.6-x64
|
|
||||||
flutter pub get
|
|
||||||
|
|
||||||
- name: Build APK
|
|
||||||
run: flutter build apk --release
|
|
||||||
|
|
||||||
test:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
# Required for Flutter action
|
|
||||||
- name: Install jq
|
|
||||||
run: |
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y jq
|
|
||||||
|
|
||||||
- name: Set up Flutter
|
|
||||||
uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
channel: stable
|
|
||||||
flutter-version: 3.38.6
|
|
||||||
|
|
||||||
- name: Get dependencies
|
|
||||||
run: |
|
|
||||||
git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.38.6-x64
|
|
||||||
flutter pub get
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
run: flutter test
|
|
||||||
|
|
||||||
update_version:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: gitea.ref == 'refs/heads/development'
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
token: ${{ secrets.BOT_TOKEN }}
|
|
||||||
ref: ${{ gitea.ref_name }}
|
|
||||||
|
|
||||||
- name: Increment version number
|
|
||||||
uses: stikkyapp/update-pubspec-version@v2
|
|
||||||
with:
|
|
||||||
strategy: 'patch'
|
|
||||||
path: './pubspec.yaml'
|
|
||||||
|
|
||||||
- name: Commit version update
|
|
||||||
env:
|
|
||||||
GITEA_TOKEN: ${{ secrets.BOT_TOKEN }}
|
|
||||||
run: |
|
|
||||||
git config --global user.name "Gitea Actions [bot]"
|
|
||||||
git config --global user.email "actions@yannick-weigert.de"
|
|
||||||
git config pull.rebase false
|
|
||||||
git pull origin ${{ gitea.ref_name }}
|
|
||||||
git add pubspec.yaml
|
|
||||||
git commit -m "Updated version number [skip ci]"
|
|
||||||
git push origin HEAD:${{ gitea.ref_name }}
|
|
||||||
|
|
||||||
generate_licenses:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
needs: update_version
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
token: ${{ secrets.BOT_TOKEN }}
|
|
||||||
ref: ${{ gitea.ref_name }}
|
|
||||||
|
|
||||||
# Required for Flutter action
|
|
||||||
- name: Install jq
|
|
||||||
run: |
|
|
||||||
apt-get update
|
|
||||||
apt-get install -y jq
|
|
||||||
|
|
||||||
- name: Set up Flutter
|
|
||||||
uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
channel: stable
|
|
||||||
flutter-version: 3.38.6
|
|
||||||
|
|
||||||
- name: Get dependencies
|
|
||||||
run: |
|
|
||||||
git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.38.6-x64
|
|
||||||
flutter pub get
|
|
||||||
|
|
||||||
- name: Generate oss_licenses.dart
|
|
||||||
run: flutter pub run dart_pubspec_licenses:generate -o lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart
|
|
||||||
|
|
||||||
- name: Commit license update
|
|
||||||
env:
|
|
||||||
GITEA_TOKEN: ${{ secrets.BOT_TOKEN }}
|
|
||||||
run: |
|
|
||||||
if [ -n "$(git status --porcelain lib test)" ]; then
|
|
||||||
git config --global user.name "Gitea Actions [bot]"
|
|
||||||
git config --global user.email "actions@yannick-weigert.de"
|
|
||||||
git config pull.rebase false
|
|
||||||
git pull origin ${{ gitea.ref_name }}
|
|
||||||
git add lib test
|
|
||||||
git commit -m "Updated licenses [skip ci]"
|
|
||||||
git push origin HEAD:${{ gitea.ref_name }}
|
|
||||||
else
|
|
||||||
echo "No changes to commit"
|
|
||||||
fi
|
|
||||||
|
|
||||||
format:
|
format:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [update_version, generate_licenses]
|
if: false # Needs bot user
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# Required for Flutter action
|
- name: Install dependencies
|
||||||
- name: Install jq
|
|
||||||
run: |
|
run: |
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y jq
|
apt-get install -y jq
|
||||||
|
|
||||||
- name: Set up Flutter
|
- name: Install Flutter (wget)
|
||||||
uses: subosito/flutter-action@v2
|
run: |
|
||||||
with:
|
wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz
|
||||||
channel: stable
|
tar xf flutter_linux_3.38.2-stable.tar.xz
|
||||||
flutter-version: 3.38.6
|
# Set Git safe directory for Flutter path
|
||||||
|
git config --global --add safe.directory "$(pwd)/flutter"
|
||||||
- name: Get dependencies
|
# Set Flutter path
|
||||||
|
echo "$(pwd)/flutter/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
|
- name: Get & upgrade dependencies
|
||||||
run: |
|
run: |
|
||||||
git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.38.6-x64
|
|
||||||
flutter pub get
|
flutter pub get
|
||||||
|
flutter pub upgrade --major-versions
|
||||||
|
|
||||||
- name: Check code format
|
- name: Auto-format
|
||||||
id: check_format
|
|
||||||
continue-on-error: true
|
|
||||||
run: flutter analyze lib test
|
|
||||||
|
|
||||||
- name: Format code
|
|
||||||
if: steps.check_format.outcome == 'failure'
|
|
||||||
env:
|
|
||||||
GITEA_TOKEN: ${{ secrets.BOT_TOKEN }}
|
|
||||||
run: |
|
run: |
|
||||||
git fetch origin ${{ gitea.ref_name }}
|
dart format lib
|
||||||
git checkout ${{ gitea.ref_name }}
|
|
||||||
|
|
||||||
dart fix --apply lib
|
dart fix --apply lib
|
||||||
dart fix --apply test
|
|
||||||
|
|
||||||
if [ -n "$(git status --porcelain lib test)" ]; then
|
# Needs credentials, push access and the right files need to be staged
|
||||||
git config --global user.name "Gitea Actions [bot]"
|
- name: Commit Changes
|
||||||
git config --global user.email "actions@yannick-weigert.de"
|
run: |
|
||||||
git config pull.rebase false
|
git config --global user.name "Gitea Actions"
|
||||||
git pull origin ${{ gitea.ref_name }}
|
git config --global user.email "actions@gitea.com"
|
||||||
git add lib test
|
git status
|
||||||
git commit -m "Auto-format code [skip ci]"
|
git add lib/
|
||||||
git push origin HEAD:${{ gitea.ref_name }}
|
git status
|
||||||
else
|
git commit -m "Actions: Auto-formatting [skip ci]"
|
||||||
echo "No changes to commit"
|
git push
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Verify format
|
|
||||||
run: flutter analyze lib test
|
|
||||||
|
|||||||
27
.gitignore
vendored
@@ -150,9 +150,25 @@ 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
|
||||||
@@ -160,8 +176,17 @@ 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
|
||||||
@@ -171,5 +196,3 @@ app.*.map.json
|
|||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
/devtools_options.yaml
|
/devtools_options.yaml
|
||||||
|
|
||||||
untranslated_messages.json
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# Contributing
|
|
||||||
|
|
||||||
## Code of Conduct
|
|
||||||
|
|
||||||
`<insert link to code of conduct here>`
|
|
||||||
|
|
||||||
## Code Style
|
|
||||||
|
|
||||||
`<insert styling guidelines here>`
|
|
||||||
|
|
||||||
## Repository structure
|
|
||||||
|
|
||||||
`<insert folder structure and explanation here>`
|
|
||||||
165
LICENSE
@@ -1,165 +0,0 @@
|
|||||||
GNU LESSER GENERAL PUBLIC LICENSE
|
|
||||||
Version 3, 29 June 2007
|
|
||||||
|
|
||||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
|
|
||||||
This version of the GNU Lesser General Public License incorporates
|
|
||||||
the terms and conditions of version 3 of the GNU General Public
|
|
||||||
License, supplemented by the additional permissions listed below.
|
|
||||||
|
|
||||||
0. Additional Definitions.
|
|
||||||
|
|
||||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
|
||||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
|
||||||
General Public License.
|
|
||||||
|
|
||||||
"The Library" refers to a covered work governed by this License,
|
|
||||||
other than an Application or a Combined Work as defined below.
|
|
||||||
|
|
||||||
An "Application" is any work that makes use of an interface provided
|
|
||||||
by the Library, but which is not otherwise based on the Library.
|
|
||||||
Defining a subclass of a class defined by the Library is deemed a mode
|
|
||||||
of using an interface provided by the Library.
|
|
||||||
|
|
||||||
A "Combined Work" is a work produced by combining or linking an
|
|
||||||
Application with the Library. The particular version of the Library
|
|
||||||
with which the Combined Work was made is also called the "Linked
|
|
||||||
Version".
|
|
||||||
|
|
||||||
The "Minimal Corresponding Source" for a Combined Work means the
|
|
||||||
Corresponding Source for the Combined Work, excluding any source code
|
|
||||||
for portions of the Combined Work that, considered in isolation, are
|
|
||||||
based on the Application, and not on the Linked Version.
|
|
||||||
|
|
||||||
The "Corresponding Application Code" for a Combined Work means the
|
|
||||||
object code and/or source code for the Application, including any data
|
|
||||||
and utility programs needed for reproducing the Combined Work from the
|
|
||||||
Application, but excluding the System Libraries of the Combined Work.
|
|
||||||
|
|
||||||
1. Exception to Section 3 of the GNU GPL.
|
|
||||||
|
|
||||||
You may convey a covered work under sections 3 and 4 of this License
|
|
||||||
without being bound by section 3 of the GNU GPL.
|
|
||||||
|
|
||||||
2. Conveying Modified Versions.
|
|
||||||
|
|
||||||
If you modify a copy of the Library, and, in your modifications, a
|
|
||||||
facility refers to a function or data to be supplied by an Application
|
|
||||||
that uses the facility (other than as an argument passed when the
|
|
||||||
facility is invoked), then you may convey a copy of the modified
|
|
||||||
version:
|
|
||||||
|
|
||||||
a) under this License, provided that you make a good faith effort to
|
|
||||||
ensure that, in the event an Application does not supply the
|
|
||||||
function or data, the facility still operates, and performs
|
|
||||||
whatever part of its purpose remains meaningful, or
|
|
||||||
|
|
||||||
b) under the GNU GPL, with none of the additional permissions of
|
|
||||||
this License applicable to that copy.
|
|
||||||
|
|
||||||
3. Object Code Incorporating Material from Library Header Files.
|
|
||||||
|
|
||||||
The object code form of an Application may incorporate material from
|
|
||||||
a header file that is part of the Library. You may convey such object
|
|
||||||
code under terms of your choice, provided that, if the incorporated
|
|
||||||
material is not limited to numerical parameters, data structure
|
|
||||||
layouts and accessors, or small macros, inline functions and templates
|
|
||||||
(ten or fewer lines in length), you do both of the following:
|
|
||||||
|
|
||||||
a) Give prominent notice with each copy of the object code that the
|
|
||||||
Library is used in it and that the Library and its use are
|
|
||||||
covered by this License.
|
|
||||||
|
|
||||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
|
||||||
document.
|
|
||||||
|
|
||||||
4. Combined Works.
|
|
||||||
|
|
||||||
You may convey a Combined Work under terms of your choice that,
|
|
||||||
taken together, effectively do not restrict modification of the
|
|
||||||
portions of the Library contained in the Combined Work and reverse
|
|
||||||
engineering for debugging such modifications, if you also do each of
|
|
||||||
the following:
|
|
||||||
|
|
||||||
a) Give prominent notice with each copy of the Combined Work that
|
|
||||||
the Library is used in it and that the Library and its use are
|
|
||||||
covered by this License.
|
|
||||||
|
|
||||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
|
||||||
document.
|
|
||||||
|
|
||||||
c) For a Combined Work that displays copyright notices during
|
|
||||||
execution, include the copyright notice for the Library among
|
|
||||||
these notices, as well as a reference directing the user to the
|
|
||||||
copies of the GNU GPL and this license document.
|
|
||||||
|
|
||||||
d) Do one of the following:
|
|
||||||
|
|
||||||
0) Convey the Minimal Corresponding Source under the terms of this
|
|
||||||
License, and the Corresponding Application Code in a form
|
|
||||||
suitable for, and under terms that permit, the user to
|
|
||||||
recombine or relink the Application with a modified version of
|
|
||||||
the Linked Version to produce a modified Combined Work, in the
|
|
||||||
manner specified by section 6 of the GNU GPL for conveying
|
|
||||||
Corresponding Source.
|
|
||||||
|
|
||||||
1) Use a suitable shared library mechanism for linking with the
|
|
||||||
Library. A suitable mechanism is one that (a) uses at run time
|
|
||||||
a copy of the Library already present on the user's computer
|
|
||||||
system, and (b) will operate properly with a modified version
|
|
||||||
of the Library that is interface-compatible with the Linked
|
|
||||||
Version.
|
|
||||||
|
|
||||||
e) Provide Installation Information, but only if you would otherwise
|
|
||||||
be required to provide such information under section 6 of the
|
|
||||||
GNU GPL, and only to the extent that such information is
|
|
||||||
necessary to install and execute a modified version of the
|
|
||||||
Combined Work produced by recombining or relinking the
|
|
||||||
Application with a modified version of the Linked Version. (If
|
|
||||||
you use option 4d0, the Installation Information must accompany
|
|
||||||
the Minimal Corresponding Source and Corresponding Application
|
|
||||||
Code. If you use option 4d1, you must provide the Installation
|
|
||||||
Information in the manner specified by section 6 of the GNU GPL
|
|
||||||
for conveying Corresponding Source.)
|
|
||||||
|
|
||||||
5. Combined Libraries.
|
|
||||||
|
|
||||||
You may place library facilities that are a work based on the
|
|
||||||
Library side by side in a single library together with other library
|
|
||||||
facilities that are not Applications and are not covered by this
|
|
||||||
License, and convey such a combined library under terms of your
|
|
||||||
choice, if you do both of the following:
|
|
||||||
|
|
||||||
a) Accompany the combined library with a copy of the same work based
|
|
||||||
on the Library, uncombined with any other library facilities,
|
|
||||||
conveyed under the terms of this License.
|
|
||||||
|
|
||||||
b) Give prominent notice with the combined library that part of it
|
|
||||||
is a work based on the Library, and explaining where to find the
|
|
||||||
accompanying uncombined form of the same work.
|
|
||||||
|
|
||||||
6. Revised Versions of the GNU Lesser General Public License.
|
|
||||||
|
|
||||||
The Free Software Foundation may publish revised and/or new versions
|
|
||||||
of the GNU Lesser General Public License from time to time. Such new
|
|
||||||
versions will be similar in spirit to the present version, but may
|
|
||||||
differ in detail to address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the
|
|
||||||
Library as you received it specifies that a certain numbered version
|
|
||||||
of the GNU Lesser General Public License "or any later version"
|
|
||||||
applies to it, you have the option of following the terms and
|
|
||||||
conditions either of that published version or of any later version
|
|
||||||
published by the Free Software Foundation. If the Library as you
|
|
||||||
received it does not specify a version number of the GNU Lesser
|
|
||||||
General Public License, you may choose any version of the GNU Lesser
|
|
||||||
General Public License ever published by the Free Software Foundation.
|
|
||||||
|
|
||||||
If the Library as you received it specifies that a proxy can decide
|
|
||||||
whether future versions of the GNU Lesser General Public License shall
|
|
||||||
apply, that proxy's public statement of acceptance of any version is
|
|
||||||
permanent authorization for you to choose that version for the
|
|
||||||
Library.
|
|
||||||
66
README.md
@@ -1,63 +1,7 @@
|
|||||||
<p align="center">
|
# Game Tracker
|
||||||
<img alt="Tallee Logo" src="/artefacts/app-logo.png" width="200"/>
|
|
||||||
<h2 align="center">Tallee</h2>
|
|
||||||
</p>
|
|
||||||
<p align="center">
|
|
||||||
An open-source app to track card- and board games, manage players & groups and get statistics about your played games.
|
|
||||||
</p>
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://apps.apple.com/">
|
|
||||||
<img src="https://tools.applemediaservices.com/api/badges/download-on-the-app-store/black/en-US"
|
|
||||||
alt="Download on the App Store"
|
|
||||||
height="48"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
<a href="https://play.google.com/">
|
|
||||||
<img alt="Get it on Google Play"
|
|
||||||
title="Google Play"
|
|
||||||
src="https://raw.githubusercontent.com/pd4d10/git-touch/main/assets/google-play-badge.png"
|
|
||||||
height="48"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||

|
|
||||||
|
|
||||||
## Screenshots
|
|
||||||
|
|
||||||
<table align="center" cellspacing="8">
|
|
||||||
<tr>
|
|
||||||
<td><img src="/artefacts/screenshot-1.png" alt="Screenshot 1" width="240" /></td>
|
|
||||||
<td><img src="/artefacts/screenshot-2.png" alt="Screenshot 2" width="240" /></td>
|
|
||||||
<td><img src="/artefacts/screenshot-3.png" alt="Screenshot 3" width="240" /></td>
|
|
||||||
<td><img src="/artefacts/screenshot-4.png" alt="Screenshot 4" width="240" /></td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Contributions are welcome! If you find a bug or have a feature request, please open an issue on GitHub. If you'd like to
|
|
||||||
contribute code, feel free to fork the repository and submit a pull request. For contribution guidelines, please refer
|
|
||||||
to [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This project is licensed under the GNU LGPLv3 License. See the [LICENSE](LICENSE) file for details.
|
|
||||||
|
|
||||||
## Contributors
|
|
||||||
|
|
||||||
<a href="https://github.com/liquiddevelopmentde/game-tracker/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=liquiddevelopmentde/game-tracker" />
|
|
||||||
</a>
|
|
||||||
|
|
||||||
|
|
||||||
## Credits
|
|
||||||
|
|
||||||
Tallee is developed and maintained by [Liquid Development](https://liquid-dev.de). For more information or support regarding Tallee, contact us through our website or [hello@liquid-dev.de](mailto:hello@liquid-dev.de).
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
A all-in-one app to track card- and board games, manage players and groups and get statistics about your played games.
|
||||||
|
|||||||
@@ -11,4 +11,3 @@ linter:
|
|||||||
prefer_const_literals_to_create_immutables: true
|
prefer_const_literals_to_create_immutables: true
|
||||||
unnecessary_const: true
|
unnecessary_const: true
|
||||||
lines_longer_than_80_chars: false
|
lines_longer_than_80_chars: false
|
||||||
constant_identifier_names: false
|
|
||||||
@@ -6,7 +6,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "de.liquid.tallee"
|
namespace = "com.example.game_tracker"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId = "de.liquid.tallee"
|
applicationId = "com.example.game_tracker"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = flutter.minSdkVersion
|
minSdk = flutter.minSdkVersion
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<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="Tallee"
|
android:label="game_tracker"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
@@ -43,14 +42,5 @@
|
|||||||
<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>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 8.0 KiB |
@@ -1,4 +1,4 @@
|
|||||||
package de.liquid.tallee
|
package com.example.game_tracker
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 828 B |
|
Before Width: | Height: | Size: 884 B After Width: | Height: | Size: 448 B |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 938 B After Width: | Height: | Size: 656 B |
|
Before Width: | Height: | Size: 596 B After Width: | Height: | Size: 348 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 508 B |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 704 B |
|
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 824 B |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 6.9 KiB |
@@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="launch_background">#ef681f</color>
|
<color name="app_icon_background">#E6F1E4</color>
|
||||||
|
<color name="launch_background">#0B0B0B</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="ic_launcher_background">#EF681F</color>
|
<!-- Referenz unbedingt als @color/launch_background (nicht @colors/...) -->
|
||||||
|
<color name="ic_launcher_background">@color/app_icon_background</color>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -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.9.1" apply false
|
id("com.android.application") version "8.7.3" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 291 KiB |
|
Before Width: | Height: | Size: 279 KiB |
|
Before Width: | Height: | Size: 354 KiB |
|
Before Width: | Height: | Size: 294 KiB |
@@ -15,91 +15,16 @@
|
|||||||
},
|
},
|
||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id",
|
||||||
"createdAt",
|
"createdAt",
|
||||||
"name",
|
"name"
|
||||||
"description"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"games": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"createdAt": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"ruleset": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"color": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"icon": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"createdAt",
|
|
||||||
"name",
|
|
||||||
"ruleset",
|
|
||||||
"description",
|
|
||||||
"color",
|
|
||||||
"icon"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"groups": {
|
"groups": {
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"id": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"name": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"createdAt": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"memberIds": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"id",
|
|
||||||
"name",
|
|
||||||
"createdAt",
|
|
||||||
"description",
|
|
||||||
"memberIds"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"teams": {
|
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@@ -142,14 +67,11 @@
|
|||||||
"createdAt": {
|
"createdAt": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"endedAt": {
|
|
||||||
"type": ["string", "null"]
|
|
||||||
},
|
|
||||||
"gameId": {
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"groupId": {
|
"groupId": {
|
||||||
"type": ["string", "null"]
|
"anyOf": [
|
||||||
|
{"type": "string"},
|
||||||
|
{"type": "null"}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"playerIds": {
|
"playerIds": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@@ -157,26 +79,26 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"notes": {
|
"winnerId": {
|
||||||
"type": "string"
|
"anyOf": [
|
||||||
|
{"type": "string"},
|
||||||
|
{"type": "null"}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id",
|
||||||
"name",
|
"name",
|
||||||
"createdAt",
|
"createdAt",
|
||||||
"gameId",
|
"groupId",
|
||||||
"playerIds",
|
"playerIds"
|
||||||
"notes"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"players",
|
"players",
|
||||||
"games",
|
|
||||||
"groups",
|
"groups",
|
||||||
"teams",
|
|
||||||
"matches"
|
"matches"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -152,6 +152,7 @@
|
|||||||
B68CF4A64F0B5E45B43D6900 /* Pods-RunnerTests.release.xcconfig */,
|
B68CF4A64F0B5E45B43D6900 /* Pods-RunnerTests.release.xcconfig */,
|
||||||
E754D1191B3E54E52B6DCC49 /* Pods-RunnerTests.profile.xcconfig */,
|
E754D1191B3E54E52B6DCC49 /* Pods-RunnerTests.profile.xcconfig */,
|
||||||
);
|
);
|
||||||
|
name = Pods;
|
||||||
path = Pods;
|
path = Pods;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -477,7 +478,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.liquid.tallee;
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
@@ -660,7 +661,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.liquid.tallee;
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
@@ -683,7 +684,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = de.liquid.tallee;
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
@@ -1 +1,14 @@
|
|||||||
{"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"}]}
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "icon_x1024.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_x1024.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
@@ -5,9 +5,9 @@
|
|||||||
"color-space" : "srgb",
|
"color-space" : "srgb",
|
||||||
"components" : {
|
"components" : {
|
||||||
"alpha" : "1.000",
|
"alpha" : "1.000",
|
||||||
"blue" : "0.122",
|
"blue" : "0.043",
|
||||||
"green" : "0.408",
|
"green" : "0.043",
|
||||||
"red" : "0.937"
|
"red" : "0.043"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"idiom" : "universal"
|
"idiom" : "universal"
|
||||||
@@ -1,8 +1,17 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"filename" : "icon-transparent.png",
|
"filename" : "icon.png",
|
||||||
"idiom" : "universal"
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 21 KiB |
BIN
ios/Runner/Assets.xcassets/LauncherIcon.imageset/icon.png
vendored
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
@@ -1,9 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24412" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<deployment identifier="iOS"/>
|
<deployment identifier="iOS"/>
|
||||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24504"/>
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24405"/>
|
||||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
@@ -20,19 +20,12 @@
|
|||||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Tallee" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="m4u-iU-Cmv">
|
|
||||||
<rect key="frame" x="153" y="747" width="87" height="37"/>
|
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
|
||||||
<fontDescription key="fontDescription" name="Futura-Bold" family="Futura" pointSize="28"/>
|
|
||||||
<color key="textColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
|
||||||
<nil key="highlightedColor"/>
|
|
||||||
</label>
|
|
||||||
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="LauncherIcon" translatesAutoresizingMaskIntoConstraints="NO" id="ygV-Op-Bu5">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="LauncherIcon" translatesAutoresizingMaskIntoConstraints="NO" id="ygV-Op-Bu5">
|
||||||
<rect key="frame" x="46" y="334" width="301" height="184"/>
|
<rect key="frame" x="46" y="334" width="301" height="184"/>
|
||||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
</imageView>
|
</imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" name="LauncherColor"/>
|
<color key="backgroundColor" name="LauncherBackgroundColor"/>
|
||||||
</view>
|
</view>
|
||||||
</viewController>
|
</viewController>
|
||||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
@@ -43,8 +36,8 @@
|
|||||||
<color key="tintColor" red="0.90196078431372551" green="0.94509803921568625" blue="0.89411764705882346" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="tintColor" red="0.90196078431372551" green="0.94509803921568625" blue="0.89411764705882346" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="LauncherIcon" width="1000" height="1000"/>
|
<image name="LauncherIcon" width="1000" height="1000"/>
|
||||||
<namedColor name="LauncherColor">
|
<namedColor name="LauncherBackgroundColor">
|
||||||
<color red="0.93699997663497925" green="0.40799999237060547" blue="0.12200000137090683" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color red="0.90196078431372551" green="0.94509803921568625" blue="0.89411764705882346" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
</namedColor>
|
</namedColor>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -2,12 +2,10 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
|
||||||
<true/>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
<key>CFBundleDisplayName</key>
|
<key>CFBundleDisplayName</key>
|
||||||
<string>Tallee</string>
|
<string>Game Tracker</string>
|
||||||
<key>CFBundleExecutable</key>
|
<key>CFBundleExecutable</key>
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
<key>CFBundleIdentifier</key>
|
<key>CFBundleIdentifier</key>
|
||||||
@@ -15,7 +13,7 @@
|
|||||||
<key>CFBundleInfoDictionaryVersion</key>
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
<string>6.0</string>
|
<string>6.0</string>
|
||||||
<key>CFBundleName</key>
|
<key>CFBundleName</key>
|
||||||
<string>tallee</string>
|
<string>game_tracker</string>
|
||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
@@ -24,15 +22,8 @@
|
|||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
<key>LSApplicationQueriesSchemes</key>
|
|
||||||
<array>
|
|
||||||
<string>https</string>
|
|
||||||
<string>http</string>
|
|
||||||
</array>
|
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
|
||||||
<true/>
|
|
||||||
<key>UILaunchStoryboardName</key>
|
<key>UILaunchStoryboardName</key>
|
||||||
<string>LaunchScreen</string>
|
<string>LaunchScreen</string>
|
||||||
<key>UIMainStoryboardFile</key>
|
<key>UIMainStoryboardFile</key>
|
||||||
@@ -48,5 +39,9 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -3,4 +3,3 @@ 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
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
/// Returns a platform-adaptive page route based on the current platform.
|
|
||||||
/// - On iOS, it returns a [CupertinoPageRoute].
|
|
||||||
/// - On other platforms, it returns a [MaterialPageRoute].
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,22 +1,6 @@
|
|||||||
/// Application-wide constants
|
|
||||||
class Constants {
|
class Constants {
|
||||||
Constants._(); // Private constructor to prevent instantiation
|
Constants._(); // Private constructor to prevent instantiation
|
||||||
|
|
||||||
/// Minimum duration of all app skeletons
|
/// Minimum duration of all app skeletons
|
||||||
static const Duration MINIMUM_SKELETON_DURATION = Duration(milliseconds: 250);
|
static Duration minimumSkeletonDuration = const Duration(milliseconds: 250);
|
||||||
|
|
||||||
/// Maximum length for player names
|
|
||||||
static const int MAX_PLAYER_NAME_LENGTH = 32;
|
|
||||||
|
|
||||||
/// Maximum length for group names
|
|
||||||
static const int MAX_GROUP_NAME_LENGTH = 32;
|
|
||||||
|
|
||||||
/// Maximum length for match names
|
|
||||||
static const int MAX_MATCH_NAME_LENGTH = 32;
|
|
||||||
|
|
||||||
/// Maximum length for game names
|
|
||||||
static const int MAX_GAME_NAME_LENGTH = 32;
|
|
||||||
|
|
||||||
/// Maximum length for team names
|
|
||||||
static const int MAX_TEAM_NAME_LENGTH = 32;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,16 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// Theme class that defines colors, border radius, padding, and decorations
|
|
||||||
class CustomTheme {
|
class CustomTheme {
|
||||||
CustomTheme._(); // Private constructor to prevent instantiation
|
CustomTheme._(); // Private constructor to prevent instantiation
|
||||||
|
|
||||||
// ==================== Colors ====================
|
// ==================== Colors ====================
|
||||||
|
static Color primaryColor = const Color(0xFF7505E4);
|
||||||
/// Primary color of the app theme
|
static Color secondaryColor = const Color(0xFFAFA2FF);
|
||||||
static const Color primaryColor = Color(0xFFef681f);
|
static Color backgroundColor = const Color(0xFF0B0B0B);
|
||||||
|
static Color boxColor = const Color(0xFF101010);
|
||||||
/// Secondary color of the app theme
|
static Color onBoxColor = const Color(0xFF181818);
|
||||||
static const Color secondaryColor = Color(0xFFf2a981);
|
static Color boxBorder = const Color(0xFF272727);
|
||||||
|
static const Color textColor = Colors.white;
|
||||||
/// Background color of the app theme
|
|
||||||
static const backgroundColor = Color(0xFF0B0B0B);
|
|
||||||
|
|
||||||
/// Default color for boxes and containers
|
|
||||||
static const Color boxColor = Color(0xFF101010);
|
|
||||||
|
|
||||||
/// Default border color for boxes and containers
|
|
||||||
static const Color boxBorder = Color(0xFF272727);
|
|
||||||
|
|
||||||
/// Color for boxes and containers displayed on boxes
|
|
||||||
static const Color onBoxColor = Color(0xFF181818);
|
|
||||||
|
|
||||||
/// Text color used throughout the app
|
|
||||||
static const Color textColor = Color(0xFFFFFFFF);
|
|
||||||
|
|
||||||
/// Selected color for the [NavbarItem]
|
|
||||||
static Color navBarItemSelectedColor = primaryColor.withGreen(100);
|
|
||||||
|
|
||||||
/// Unselected color for the [NavbarItem]
|
|
||||||
static Color navBarItemUnselectedColor = Colors.grey.shade400;
|
|
||||||
|
|
||||||
// ==================== Border Radius ====================
|
// ==================== Border Radius ====================
|
||||||
static const double standardBorderRadius = 12.0;
|
static const double standardBorderRadius = 12.0;
|
||||||
@@ -63,18 +42,18 @@ class CustomTheme {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// ==================== App Bar Theme ====================
|
// ==================== App Bar Theme ====================
|
||||||
static const AppBarTheme appBarTheme = AppBarTheme(
|
static AppBarTheme appBarTheme = AppBarTheme(
|
||||||
backgroundColor: backgroundColor,
|
backgroundColor: backgroundColor,
|
||||||
foregroundColor: textColor,
|
foregroundColor: textColor,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
scrolledUnderElevation: 0,
|
scrolledUnderElevation: 0,
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
titleTextStyle: TextStyle(
|
titleTextStyle: const TextStyle(
|
||||||
color: textColor,
|
color: textColor,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
iconTheme: IconThemeData(color: textColor),
|
iconTheme: const IconThemeData(color: textColor),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||||
|
|
||||||
/// Button types used for styling the [CustomWidthButton]
|
/// Button types used for styling the [CustomWidthButton]
|
||||||
/// - [ButtonType.primary]: Primary button style.
|
/// - [ButtonType.primary]: Primary button style.
|
||||||
@@ -29,38 +29,24 @@ enum ImportResult {
|
|||||||
/// - [ExportResult.unknownException]: An exception occurred during export.
|
/// - [ExportResult.unknownException]: An exception occurred during export.
|
||||||
enum ExportResult { success, canceled, unknownException }
|
enum ExportResult { success, canceled, unknownException }
|
||||||
|
|
||||||
/// Different rulesets available for games
|
/// Different rulesets available for matches
|
||||||
/// - [Ruleset.highestScore]: The player with the highest score wins.
|
/// - [Ruleset.singleWinner]: The match is won by a single player
|
||||||
/// - [Ruleset.lowestScore]: The player with the lowest score wins.
|
/// - [Ruleset.singleLoser]: The match is lost by a single player
|
||||||
/// - [Ruleset.singleWinner]: The match is won by a single player.
|
/// - [Ruleset.mostPoints]: The player with the most points wins.
|
||||||
/// - [Ruleset.singleLoser]: The match has a single loser.
|
/// - [Ruleset.leastPoints]: The player with the fewest points wins.
|
||||||
/// - [Ruleset.multipleWinners]: Multiple players can be winners.
|
enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints }
|
||||||
enum Ruleset { highestScore, lowestScore, singleWinner, singleLoser, multipleWinners }
|
|
||||||
|
|
||||||
/// Different colors available for games
|
|
||||||
/// - [GameColor.red]: Red color
|
|
||||||
/// - [GameColor.blue]: Blue color
|
|
||||||
/// - [GameColor.green]: Green color
|
|
||||||
/// - [GameColor.yellow]: Yellow color
|
|
||||||
/// - [GameColor.purple]: Purple color
|
|
||||||
/// - [GameColor.orange]: Orange color
|
|
||||||
/// - [GameColor.pink]: Pink color
|
|
||||||
/// - [GameColor.teal]: Teal color
|
|
||||||
enum GameColor { red, blue, green, yellow, purple, orange, pink, teal }
|
|
||||||
|
|
||||||
/// Translates a [Ruleset] enum value to its corresponding localized string.
|
/// Translates a [Ruleset] enum value to its corresponding localized string.
|
||||||
String translateRulesetToString(Ruleset ruleset, BuildContext context) {
|
String translateRulesetToString(Ruleset ruleset, BuildContext context) {
|
||||||
final loc = AppLocalizations.of(context);
|
final loc = AppLocalizations.of(context);
|
||||||
switch (ruleset) {
|
switch (ruleset) {
|
||||||
case Ruleset.highestScore:
|
|
||||||
return loc.highest_score;
|
|
||||||
case Ruleset.lowestScore:
|
|
||||||
return loc.lowest_score;
|
|
||||||
case Ruleset.singleWinner:
|
case Ruleset.singleWinner:
|
||||||
return loc.single_winner;
|
return loc.single_winner;
|
||||||
case Ruleset.singleLoser:
|
case Ruleset.singleLoser:
|
||||||
return loc.single_loser;
|
return loc.single_loser;
|
||||||
case Ruleset.multipleWinners:
|
case Ruleset.mostPoints:
|
||||||
return loc.multiple_winners;
|
return loc.most_points;
|
||||||
|
case Ruleset.leastPoints:
|
||||||
|
return loc.least_points;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,166 +0,0 @@
|
|||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:tallee/data/db/database.dart';
|
|
||||||
import 'package:tallee/data/db/tables/game_table.dart';
|
|
||||||
import 'package:tallee/data/dto/game.dart';
|
|
||||||
import 'package:tallee/core/enums.dart';
|
|
||||||
|
|
||||||
part 'game_dao.g.dart';
|
|
||||||
|
|
||||||
@DriftAccessor(tables: [GameTable])
|
|
||||||
class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
|
|
||||||
GameDao(super.db);
|
|
||||||
|
|
||||||
/// Retrieves all games from the database.
|
|
||||||
Future<List<Game>> getAllGames() async {
|
|
||||||
final query = select(gameTable);
|
|
||||||
final result = await query.get();
|
|
||||||
return result
|
|
||||||
.map(
|
|
||||||
(row) => Game(
|
|
||||||
id: row.id,
|
|
||||||
name: row.name,
|
|
||||||
ruleset: Ruleset.values.firstWhere((e) => e.name == row.ruleset),
|
|
||||||
description: row.description,
|
|
||||||
color: GameColor.values.firstWhere((e) => e.name == row.color),
|
|
||||||
icon: row.icon,
|
|
||||||
createdAt: row.createdAt,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves a [Game] by its [gameId].
|
|
||||||
Future<Game> getGameById({required String gameId}) async {
|
|
||||||
final query = select(gameTable)..where((g) => g.id.equals(gameId));
|
|
||||||
final result = await query.getSingle();
|
|
||||||
return Game(
|
|
||||||
id: result.id,
|
|
||||||
name: result.name,
|
|
||||||
ruleset: Ruleset.values.firstWhere((e) => e.name == result.ruleset),
|
|
||||||
description: result.description,
|
|
||||||
color: GameColor.values.firstWhere((e) => e.name == result.color),
|
|
||||||
icon: result.icon,
|
|
||||||
createdAt: result.createdAt,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a new [game] to the database.
|
|
||||||
/// If a game with the same ID already exists, no action is taken.
|
|
||||||
/// Returns `true` if the game was added, `false` otherwise.
|
|
||||||
Future<bool> addGame({required Game game}) async {
|
|
||||||
if (!await gameExists(gameId: game.id)) {
|
|
||||||
await into(gameTable).insert(
|
|
||||||
GameTableCompanion.insert(
|
|
||||||
id: game.id,
|
|
||||||
name: game.name,
|
|
||||||
ruleset: game.ruleset.name,
|
|
||||||
description: game.description,
|
|
||||||
color: game.color.name,
|
|
||||||
icon: game.icon,
|
|
||||||
createdAt: game.createdAt,
|
|
||||||
),
|
|
||||||
mode: InsertMode.insertOrReplace,
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds multiple [games] to the database in a batch operation.
|
|
||||||
/// Uses insertOrIgnore to avoid overwriting existing games.
|
|
||||||
Future<bool> addGamesAsList({required List<Game> games}) async {
|
|
||||||
if (games.isEmpty) return false;
|
|
||||||
|
|
||||||
await db.batch(
|
|
||||||
(b) => b.insertAll(
|
|
||||||
gameTable,
|
|
||||||
games
|
|
||||||
.map(
|
|
||||||
(game) => GameTableCompanion.insert(
|
|
||||||
id: game.id,
|
|
||||||
name: game.name,
|
|
||||||
ruleset: game.ruleset.name,
|
|
||||||
description: game.description,
|
|
||||||
color: game.color.name,
|
|
||||||
icon: game.icon,
|
|
||||||
createdAt: game.createdAt,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
mode: InsertMode.insertOrIgnore,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes the game with the given [gameId] from the database.
|
|
||||||
/// Returns `true` if the game was deleted, `false` if the game did not exist.
|
|
||||||
Future<bool> deleteGame({required String gameId}) async {
|
|
||||||
final query = delete(gameTable)..where((g) => g.id.equals(gameId));
|
|
||||||
final rowsAffected = await query.go();
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if a game with the given [gameId] exists in the database.
|
|
||||||
/// Returns `true` if the game exists, `false` otherwise.
|
|
||||||
Future<bool> gameExists({required String gameId}) async {
|
|
||||||
final query = select(gameTable)..where((g) => g.id.equals(gameId));
|
|
||||||
final result = await query.getSingleOrNull();
|
|
||||||
return result != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the name of the game with the given [gameId] to [newName].
|
|
||||||
Future<void> updateGameName({required String gameId, required String newName}) async {
|
|
||||||
await (update(
|
|
||||||
gameTable,
|
|
||||||
)..where((g) => g.id.equals(gameId))).write(GameTableCompanion(name: Value(newName)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the ruleset of the game with the given [gameId].
|
|
||||||
Future<void> updateGameRuleset({required String gameId, required Ruleset newRuleset}) async {
|
|
||||||
await (update(gameTable)..where((g) => g.id.equals(gameId))).write(
|
|
||||||
GameTableCompanion(ruleset: Value(newRuleset.name)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the description of the game with the given [gameId].
|
|
||||||
Future<void> updateGameDescription({
|
|
||||||
required String gameId,
|
|
||||||
required String newDescription,
|
|
||||||
}) async {
|
|
||||||
await (update(gameTable)..where((g) => g.id.equals(gameId))).write(
|
|
||||||
GameTableCompanion(description: Value(newDescription)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the color of the game with the given [gameId].
|
|
||||||
Future<void> updateGameColor({required String gameId, required GameColor newColor}) async {
|
|
||||||
await (update(
|
|
||||||
gameTable,
|
|
||||||
)..where((g) => g.id.equals(gameId))).write(GameTableCompanion(color: Value(newColor.name)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the icon of the game with the given [gameId].
|
|
||||||
Future<void> updateGameIcon({required String gameId, required String newIcon}) async {
|
|
||||||
await (update(
|
|
||||||
gameTable,
|
|
||||||
)..where((g) => g.id.equals(gameId))).write(GameTableCompanion(icon: Value(newIcon)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves the total count of games in the database.
|
|
||||||
Future<int> getGameCount() async {
|
|
||||||
final count = await (selectOnly(
|
|
||||||
gameTable,
|
|
||||||
)..addColumns([gameTable.id.count()])).map((row) => row.read(gameTable.id.count())).getSingle();
|
|
||||||
return count ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes all games from the database.
|
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
|
||||||
Future<bool> deleteAllGames() async {
|
|
||||||
final query = delete(gameTable);
|
|
||||||
final rowsAffected = await query.go();
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'game_dao.dart';
|
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
mixin _$GameDaoMixin on DatabaseAccessor<AppDatabase> {
|
|
||||||
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:tallee/data/db/database.dart';
|
import 'package:game_tracker/data/db/database.dart';
|
||||||
import 'package:tallee/data/db/tables/group_table.dart';
|
import 'package:game_tracker/data/db/tables/group_table.dart';
|
||||||
import 'package:tallee/data/db/tables/player_group_table.dart';
|
import 'package:game_tracker/data/db/tables/player_group_table.dart';
|
||||||
import 'package:tallee/data/dto/group.dart';
|
import 'package:game_tracker/data/dto/group.dart';
|
||||||
import 'package:tallee/data/dto/player.dart';
|
import 'package:game_tracker/data/dto/player.dart';
|
||||||
|
|
||||||
part 'group_dao.g.dart';
|
part 'group_dao.g.dart';
|
||||||
|
|
||||||
@@ -23,7 +23,6 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
|||||||
return Group(
|
return Group(
|
||||||
id: groupData.id,
|
id: groupData.id,
|
||||||
name: groupData.name,
|
name: groupData.name,
|
||||||
description: groupData.description,
|
|
||||||
members: members,
|
members: members,
|
||||||
createdAt: groupData.createdAt,
|
createdAt: groupData.createdAt,
|
||||||
);
|
);
|
||||||
@@ -43,7 +42,6 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
|||||||
return Group(
|
return Group(
|
||||||
id: result.id,
|
id: result.id,
|
||||||
name: result.name,
|
name: result.name,
|
||||||
description: result.description,
|
|
||||||
members: members,
|
members: members,
|
||||||
createdAt: result.createdAt,
|
createdAt: result.createdAt,
|
||||||
);
|
);
|
||||||
@@ -58,7 +56,6 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
|||||||
GroupTableCompanion.insert(
|
GroupTableCompanion.insert(
|
||||||
id: group.id,
|
id: group.id,
|
||||||
name: group.name,
|
name: group.name,
|
||||||
description: group.description,
|
|
||||||
createdAt: group.createdAt,
|
createdAt: group.createdAt,
|
||||||
),
|
),
|
||||||
mode: InsertMode.insertOrReplace,
|
mode: InsertMode.insertOrReplace,
|
||||||
@@ -108,7 +105,6 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
|||||||
(group) => GroupTableCompanion.insert(
|
(group) => GroupTableCompanion.insert(
|
||||||
id: group.id,
|
id: group.id,
|
||||||
name: group.name,
|
name: group.name,
|
||||||
description: group.description,
|
|
||||||
createdAt: group.createdAt,
|
createdAt: group.createdAt,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -136,7 +132,6 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
|||||||
(p) => PlayerTableCompanion.insert(
|
(p) => PlayerTableCompanion.insert(
|
||||||
id: p.id,
|
id: p.id,
|
||||||
name: p.name,
|
name: p.name,
|
||||||
description: p.description,
|
|
||||||
createdAt: p.createdAt,
|
createdAt: p.createdAt,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -181,7 +176,7 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
|||||||
|
|
||||||
/// Updates the name of the group with the given [id] to [newName].
|
/// Updates the name of the group with the given [id] to [newName].
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
Future<bool> updateGroupName({
|
Future<bool> updateGroupname({
|
||||||
required String groupId,
|
required String groupId,
|
||||||
required String newName,
|
required String newName,
|
||||||
}) async {
|
}) async {
|
||||||
@@ -192,21 +187,6 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
|||||||
return rowsAffected > 0;
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the description of the group with the given [groupId] to [newDescription].
|
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
|
||||||
Future<bool> updateGroupDescription({
|
|
||||||
required String groupId,
|
|
||||||
required String newDescription,
|
|
||||||
}) async {
|
|
||||||
final rowsAffected =
|
|
||||||
await (update(groupTable)..where((g) => g.id.equals(groupId))).write(
|
|
||||||
GroupTableCompanion(description: Value(newDescription)),
|
|
||||||
);
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Retrieves the number of groups in the database.
|
/// Retrieves the number of groups in the database.
|
||||||
Future<int> getGroupCount() async {
|
Future<int> getGroupCount() async {
|
||||||
final count =
|
final count =
|
||||||
@@ -231,44 +211,4 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
|||||||
final rowsAffected = await query.go();
|
final rowsAffected = await query.go();
|
||||||
return rowsAffected > 0;
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Replaces all players in a group with the provided list of players.
|
|
||||||
/// Removes all existing players from the group and adds the new players.
|
|
||||||
/// Also adds any new players to the player table if they don't exist.
|
|
||||||
Future<void> replaceGroupPlayers({
|
|
||||||
required String groupId,
|
|
||||||
required List<Player> newPlayers,
|
|
||||||
}) async {
|
|
||||||
await db.transaction(() async {
|
|
||||||
// Remove all existing players from the group
|
|
||||||
final deleteQuery = delete(db.playerGroupTable)
|
|
||||||
..where((p) => p.groupId.equals(groupId));
|
|
||||||
await deleteQuery.go();
|
|
||||||
|
|
||||||
// Add new players to the player table if they don't exist
|
|
||||||
await Future.wait(
|
|
||||||
newPlayers.map((player) async {
|
|
||||||
if (!await db.playerDao.playerExists(playerId: player.id)) {
|
|
||||||
await db.playerDao.addPlayer(player: player);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add the new players to the group
|
|
||||||
await db.batch(
|
|
||||||
(b) => b.insertAll(
|
|
||||||
db.playerGroupTable,
|
|
||||||
newPlayers
|
|
||||||
.map(
|
|
||||||
(player) => PlayerGroupTableCompanion.insert(
|
|
||||||
playerId: player.id,
|
|
||||||
groupId: groupId,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
mode: InsertMode.insertOrReplace,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
98
lib/data/dao/group_match_dao.dart
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:game_tracker/data/db/database.dart';
|
||||||
|
import 'package:game_tracker/data/db/tables/group_match_table.dart';
|
||||||
|
import 'package:game_tracker/data/dto/group.dart';
|
||||||
|
|
||||||
|
part 'group_match_dao.g.dart';
|
||||||
|
|
||||||
|
@DriftAccessor(tables: [GroupMatchTable])
|
||||||
|
class GroupMatchDao extends DatabaseAccessor<AppDatabase>
|
||||||
|
with _$GroupMatchDaoMixin {
|
||||||
|
GroupMatchDao(super.db);
|
||||||
|
|
||||||
|
/// Associates a group with a match by inserting a record into the
|
||||||
|
/// [GroupMatchTable].
|
||||||
|
Future<void> addGroupToMatch({
|
||||||
|
required String matchId,
|
||||||
|
required String groupId,
|
||||||
|
}) async {
|
||||||
|
if (await matchHasGroup(matchId: matchId)) {
|
||||||
|
throw Exception('Match already has a group');
|
||||||
|
}
|
||||||
|
await into(groupMatchTable).insert(
|
||||||
|
GroupMatchTableCompanion.insert(groupId: groupId, matchId: matchId),
|
||||||
|
mode: InsertMode.insertOrIgnore,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the [Group] associated with the given [matchId].
|
||||||
|
/// Returns `null` if no group is found.
|
||||||
|
Future<Group?> getGroupOfMatch({required String matchId}) async {
|
||||||
|
final result = await (select(
|
||||||
|
groupMatchTable,
|
||||||
|
)..where((g) => g.matchId.equals(matchId))).getSingleOrNull();
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final group = await db.groupDao.getGroupById(groupId: result.groupId);
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if there is a group associated with the given [matchId].
|
||||||
|
/// Returns `true` if there is a group, otherwise `false`.
|
||||||
|
Future<bool> matchHasGroup({required String matchId}) async {
|
||||||
|
final count =
|
||||||
|
await (selectOnly(groupMatchTable)
|
||||||
|
..where(groupMatchTable.matchId.equals(matchId))
|
||||||
|
..addColumns([groupMatchTable.groupId.count()]))
|
||||||
|
.map((row) => row.read(groupMatchTable.groupId.count()))
|
||||||
|
.getSingle();
|
||||||
|
return (count ?? 0) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a specific group is associated with a specific match.
|
||||||
|
/// Returns `true` if the group is in the match, otherwise `false`.
|
||||||
|
Future<bool> isGroupInMatch({
|
||||||
|
required String matchId,
|
||||||
|
required String groupId,
|
||||||
|
}) async {
|
||||||
|
final count =
|
||||||
|
await (selectOnly(groupMatchTable)
|
||||||
|
..where(
|
||||||
|
groupMatchTable.matchId.equals(matchId) &
|
||||||
|
groupMatchTable.groupId.equals(groupId),
|
||||||
|
)
|
||||||
|
..addColumns([groupMatchTable.groupId.count()]))
|
||||||
|
.map((row) => row.read(groupMatchTable.groupId.count()))
|
||||||
|
.getSingle();
|
||||||
|
return (count ?? 0) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the association of a group from a match based on [groupId] and
|
||||||
|
/// [matchId].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> removeGroupFromMatch({
|
||||||
|
required String matchId,
|
||||||
|
required String groupId,
|
||||||
|
}) async {
|
||||||
|
final query = delete(groupMatchTable)
|
||||||
|
..where((g) => g.matchId.equals(matchId) & g.groupId.equals(groupId));
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the group associated with a match to [newGroupId] based on
|
||||||
|
/// [matchId].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> updateGroupOfMatch({
|
||||||
|
required String matchId,
|
||||||
|
required String newGroupId,
|
||||||
|
}) async {
|
||||||
|
final updatedRows =
|
||||||
|
await (update(groupMatchTable)..where((g) => g.matchId.equals(matchId)))
|
||||||
|
.write(GroupMatchTableCompanion(groupId: Value(newGroupId)));
|
||||||
|
return updatedRows > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
lib/data/dao/group_match_dao.g.dart
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'group_match_dao.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
mixin _$GroupMatchDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||||
|
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
||||||
|
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
||||||
|
$GroupMatchTableTable get groupMatchTable => attachedDatabase.groupMatchTable;
|
||||||
|
}
|
||||||
@@ -1,17 +1,13 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:tallee/data/db/database.dart';
|
import 'package:game_tracker/data/db/database.dart';
|
||||||
import 'package:tallee/data/db/tables/game_table.dart';
|
import 'package:game_tracker/data/db/tables/match_table.dart';
|
||||||
import 'package:tallee/data/db/tables/group_table.dart';
|
import 'package:game_tracker/data/dto/group.dart';
|
||||||
import 'package:tallee/data/db/tables/match_table.dart';
|
import 'package:game_tracker/data/dto/match.dart';
|
||||||
import 'package:tallee/data/db/tables/player_match_table.dart';
|
import 'package:game_tracker/data/dto/player.dart';
|
||||||
import 'package:tallee/data/dto/game.dart';
|
|
||||||
import 'package:tallee/data/dto/group.dart';
|
|
||||||
import 'package:tallee/data/dto/match.dart';
|
|
||||||
import 'package:tallee/data/dto/player.dart';
|
|
||||||
|
|
||||||
part 'match_dao.g.dart';
|
part 'match_dao.g.dart';
|
||||||
|
|
||||||
@DriftAccessor(tables: [MatchTable, GameTable, GroupTable, PlayerMatchTable])
|
@DriftAccessor(tables: [MatchTable])
|
||||||
class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
||||||
MatchDao(super.db);
|
MatchDao(super.db);
|
||||||
|
|
||||||
@@ -22,23 +18,20 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
|
|
||||||
return Future.wait(
|
return Future.wait(
|
||||||
result.map((row) async {
|
result.map((row) async {
|
||||||
final game = await db.gameDao.getGameById(gameId: row.gameId);
|
final group = await db.groupMatchDao.getGroupOfMatch(matchId: row.id);
|
||||||
Group? group;
|
|
||||||
if (row.groupId != null) {
|
|
||||||
group = await db.groupDao.getGroupById(groupId: row.groupId!);
|
|
||||||
}
|
|
||||||
final players = await db.playerMatchDao.getPlayersOfMatch(
|
final players = await db.playerMatchDao.getPlayersOfMatch(
|
||||||
matchId: row.id,
|
matchId: row.id,
|
||||||
) ?? [];
|
);
|
||||||
|
final winner = row.winnerId != null
|
||||||
|
? await db.playerDao.getPlayerById(playerId: row.winnerId!)
|
||||||
|
: null;
|
||||||
return Match(
|
return Match(
|
||||||
id: row.id,
|
id: row.id,
|
||||||
name: row.name ?? '',
|
name: row.name,
|
||||||
game: game,
|
|
||||||
group: group,
|
group: group,
|
||||||
players: players,
|
players: players,
|
||||||
notes: row.notes ?? '',
|
|
||||||
createdAt: row.createdAt,
|
createdAt: row.createdAt,
|
||||||
endedAt: row.endedAt,
|
winner: winner,
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -49,108 +42,69 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
final query = select(matchTable)..where((g) => g.id.equals(matchId));
|
final query = select(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
final result = await query.getSingle();
|
final result = await query.getSingle();
|
||||||
|
|
||||||
final game = await db.gameDao.getGameById(gameId: result.gameId);
|
List<Player>? players;
|
||||||
|
if (await db.playerMatchDao.matchHasPlayers(matchId: matchId)) {
|
||||||
Group? group;
|
players = await db.playerMatchDao.getPlayersOfMatch(matchId: matchId);
|
||||||
if (result.groupId != null) {
|
}
|
||||||
group = await db.groupDao.getGroupById(groupId: result.groupId!);
|
Group? group;
|
||||||
|
if (await db.groupMatchDao.matchHasGroup(matchId: matchId)) {
|
||||||
|
group = await db.groupMatchDao.getGroupOfMatch(matchId: matchId);
|
||||||
|
}
|
||||||
|
Player? winner;
|
||||||
|
if (result.winnerId != null) {
|
||||||
|
winner = await db.playerDao.getPlayerById(playerId: result.winnerId!);
|
||||||
}
|
}
|
||||||
|
|
||||||
final players = await db.playerMatchDao.getPlayersOfMatch(matchId: matchId) ?? [];
|
|
||||||
|
|
||||||
return Match(
|
return Match(
|
||||||
id: result.id,
|
id: result.id,
|
||||||
name: result.name ?? '',
|
name: result.name,
|
||||||
game: game,
|
|
||||||
group: group,
|
|
||||||
players: players,
|
players: players,
|
||||||
notes: result.notes ?? '',
|
group: group,
|
||||||
|
winner: winner,
|
||||||
createdAt: result.createdAt,
|
createdAt: result.createdAt,
|
||||||
endedAt: result.endedAt,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a new [Match] to the database. Also adds players associations.
|
/// Adds a new [Match] to the database. Also adds players and group
|
||||||
/// This method assumes that the game and group (if any) are already present
|
/// associations. This method assumes that the players and groups added to
|
||||||
/// in the database.
|
/// this match are already present in the database.
|
||||||
Future<void> addMatch({required Match match}) async {
|
Future<void> addMatch({required Match match}) async {
|
||||||
await db.transaction(() async {
|
await db.transaction(() async {
|
||||||
await into(matchTable).insert(
|
await into(matchTable).insert(
|
||||||
MatchTableCompanion.insert(
|
MatchTableCompanion.insert(
|
||||||
id: match.id,
|
id: match.id,
|
||||||
gameId: match.game.id,
|
name: match.name,
|
||||||
groupId: Value(match.group?.id),
|
winnerId: Value(match.winner?.id),
|
||||||
name: Value(match.name),
|
|
||||||
notes: Value(match.notes),
|
|
||||||
createdAt: match.createdAt,
|
createdAt: match.createdAt,
|
||||||
endedAt: Value(match.endedAt),
|
|
||||||
),
|
),
|
||||||
mode: InsertMode.insertOrReplace,
|
mode: InsertMode.insertOrReplace,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (final p in match.players) {
|
if (match.players != null) {
|
||||||
|
for (final p in match.players ?? []) {
|
||||||
await db.playerMatchDao.addPlayerToMatch(
|
await db.playerMatchDao.addPlayerToMatch(
|
||||||
matchId: match.id,
|
matchId: match.id,
|
||||||
playerId: p.id,
|
playerId: p.id,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match.group != null) {
|
||||||
|
await db.groupMatchDao.addGroupToMatch(
|
||||||
|
matchId: match.id,
|
||||||
|
groupId: match.group!.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds multiple [Match]es to the database in a batch operation.
|
/// Adds multiple [Match]s to the database in a batch operation.
|
||||||
/// Also adds associated players and groups if they exist.
|
/// Also adds associated players and groups if they exist.
|
||||||
/// If the [matches] list is empty, the method returns immediately.
|
/// If the [matches] list is empty, the method returns immediately.
|
||||||
/// This method should only be used to import matches from a different device.
|
/// This Method should only be used to import matches from a different device.
|
||||||
Future<void> addMatchAsList({required List<Match> matches}) async {
|
Future<void> addMatchAsList({required List<Match> matches}) async {
|
||||||
if (matches.isEmpty) return;
|
if (matches.isEmpty) return;
|
||||||
await db.transaction(() async {
|
await db.transaction(() async {
|
||||||
// Add all games first (deduplicated)
|
|
||||||
final uniqueGames = <String, Game>{};
|
|
||||||
for (final match in matches) {
|
|
||||||
uniqueGames[match.game.id] = match.game;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (uniqueGames.isNotEmpty) {
|
|
||||||
await db.batch(
|
|
||||||
(b) => b.insertAll(
|
|
||||||
db.gameTable,
|
|
||||||
uniqueGames.values
|
|
||||||
.map(
|
|
||||||
(game) => GameTableCompanion.insert(
|
|
||||||
id: game.id,
|
|
||||||
name: game.name,
|
|
||||||
ruleset: game.ruleset.name,
|
|
||||||
description: game.description,
|
|
||||||
color: game.color.name,
|
|
||||||
icon: game.icon,
|
|
||||||
createdAt: game.createdAt,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
mode: InsertMode.insertOrIgnore,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all groups of the matches in batch
|
|
||||||
await db.batch(
|
|
||||||
(b) => b.insertAll(
|
|
||||||
db.groupTable,
|
|
||||||
matches
|
|
||||||
.where((match) => match.group != null)
|
|
||||||
.map(
|
|
||||||
(match) => GroupTableCompanion.insert(
|
|
||||||
id: match.group!.id,
|
|
||||||
name: match.group!.name,
|
|
||||||
description: match.group!.description,
|
|
||||||
createdAt: match.group!.createdAt,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
mode: InsertMode.insertOrIgnore,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add all matches in batch
|
// Add all matches in batch
|
||||||
await db.batch(
|
await db.batch(
|
||||||
(b) => b.insertAll(
|
(b) => b.insertAll(
|
||||||
@@ -159,12 +113,9 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
.map(
|
.map(
|
||||||
(match) => MatchTableCompanion.insert(
|
(match) => MatchTableCompanion.insert(
|
||||||
id: match.id,
|
id: match.id,
|
||||||
gameId: match.game.id,
|
name: match.name,
|
||||||
groupId: Value(match.group?.id),
|
|
||||||
name: Value(match.name),
|
|
||||||
notes: Value(match.notes),
|
|
||||||
createdAt: match.createdAt,
|
createdAt: match.createdAt,
|
||||||
endedAt: Value(match.endedAt),
|
winnerId: Value(match.winner?.id),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
@@ -172,12 +123,34 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Add all groups of the matches in batch
|
||||||
|
// Using insertOrIgnore to avoid overwriting existing groups (which would
|
||||||
|
// trigger cascade deletes on player_group associations)
|
||||||
|
await db.batch(
|
||||||
|
(b) => b.insertAll(
|
||||||
|
db.groupTable,
|
||||||
|
matches
|
||||||
|
.where((match) => match.group != null)
|
||||||
|
.map(
|
||||||
|
(matches) => GroupTableCompanion.insert(
|
||||||
|
id: matches.group!.id,
|
||||||
|
name: matches.group!.name,
|
||||||
|
createdAt: matches.group!.createdAt,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
mode: InsertMode.insertOrIgnore,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
// Add all players of the matches in batch (unique)
|
// Add all players of the matches in batch (unique)
|
||||||
final uniquePlayers = <String, Player>{};
|
final uniquePlayers = <String, Player>{};
|
||||||
for (final match in matches) {
|
for (final match in matches) {
|
||||||
for (final p in match.players) {
|
if (match.players != null) {
|
||||||
|
for (final p in match.players!) {
|
||||||
uniquePlayers[p.id] = p;
|
uniquePlayers[p.id] = p;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Also include members of groups
|
// Also include members of groups
|
||||||
if (match.group != null) {
|
if (match.group != null) {
|
||||||
for (final m in match.group!.members) {
|
for (final m in match.group!.members) {
|
||||||
@@ -187,6 +160,8 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (uniquePlayers.isNotEmpty) {
|
if (uniquePlayers.isNotEmpty) {
|
||||||
|
// Using insertOrIgnore to avoid triggering cascade deletes on
|
||||||
|
// player_group/player_match associations when players already exist
|
||||||
await db.batch(
|
await db.batch(
|
||||||
(b) => b.insertAll(
|
(b) => b.insertAll(
|
||||||
db.playerTable,
|
db.playerTable,
|
||||||
@@ -195,7 +170,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
(p) => PlayerTableCompanion.insert(
|
(p) => PlayerTableCompanion.insert(
|
||||||
id: p.id,
|
id: p.id,
|
||||||
name: p.name,
|
name: p.name,
|
||||||
description: p.description,
|
|
||||||
createdAt: p.createdAt,
|
createdAt: p.createdAt,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -208,18 +182,19 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
// Add all player-match associations in batch
|
// Add all player-match associations in batch
|
||||||
await db.batch((b) {
|
await db.batch((b) {
|
||||||
for (final match in matches) {
|
for (final match in matches) {
|
||||||
for (final p in match.players) {
|
if (match.players != null) {
|
||||||
|
for (final p in match.players ?? []) {
|
||||||
b.insert(
|
b.insert(
|
||||||
db.playerMatchTable,
|
db.playerMatchTable,
|
||||||
PlayerMatchTableCompanion.insert(
|
PlayerMatchTableCompanion.insert(
|
||||||
matchId: match.id,
|
matchId: match.id,
|
||||||
playerId: p.id,
|
playerId: p.id,
|
||||||
score: 0,
|
|
||||||
),
|
),
|
||||||
mode: InsertMode.insertOrIgnore,
|
mode: InsertMode.insertOrIgnore,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add all player-group associations in batch
|
// Add all player-group associations in batch
|
||||||
@@ -239,6 +214,22 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add all group-match associations in batch
|
||||||
|
await db.batch((b) {
|
||||||
|
for (final match in matches) {
|
||||||
|
if (match.group != null) {
|
||||||
|
b.insert(
|
||||||
|
db.groupMatchTable,
|
||||||
|
GroupMatchTableCompanion.insert(
|
||||||
|
matchId: match.id,
|
||||||
|
groupId: match.group!.id,
|
||||||
|
),
|
||||||
|
mode: InsertMode.insertOrIgnore,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -275,20 +266,52 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
return rowsAffected > 0;
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the notes of the match with the given [matchId].
|
/// Sets the winner of the match with the given [matchId] to the player with
|
||||||
|
/// the given [winnerId].
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
Future<bool> updateMatchNotes({
|
Future<bool> setWinner({
|
||||||
required String matchId,
|
required String matchId,
|
||||||
required String? notes,
|
required String winnerId,
|
||||||
}) async {
|
}) async {
|
||||||
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
final rowsAffected = await query.write(
|
final rowsAffected = await query.write(
|
||||||
MatchTableCompanion(notes: Value(notes)),
|
MatchTableCompanion(winnerId: Value(winnerId)),
|
||||||
);
|
);
|
||||||
return rowsAffected > 0;
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Changes the name of the match with the given [matchId] to [newName].
|
/// Retrieves the winner of the match with the given [matchId].
|
||||||
|
/// Returns the [Player] who won the match, or `null` if no winner is set.
|
||||||
|
Future<Player?> getWinner({required String matchId}) async {
|
||||||
|
final query = select(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
|
final result = await query.getSingleOrNull();
|
||||||
|
if (result == null || result.winnerId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final winner = await db.playerDao.getPlayerById(playerId: result.winnerId!);
|
||||||
|
return winner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the winner of the match with the given [matchId].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> removeWinner({required String matchId}) async {
|
||||||
|
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
|
final rowsAffected = await query.write(
|
||||||
|
const MatchTableCompanion(winnerId: Value(null)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the match with the given [matchId] has a winner set.
|
||||||
|
/// Returns `true` if a winner is set, otherwise `false`.
|
||||||
|
Future<bool> hasWinner({required String matchId}) async {
|
||||||
|
final query = select(matchTable)
|
||||||
|
..where((g) => g.id.equals(matchId) & g.winnerId.isNotNull());
|
||||||
|
final result = await query.getSingleOrNull();
|
||||||
|
return result != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes the title of the match with the given [matchId] to [newName].
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
Future<bool> updateMatchName({
|
Future<bool> updateMatchName({
|
||||||
required String matchId,
|
required String matchId,
|
||||||
@@ -300,174 +323,4 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
|||||||
);
|
);
|
||||||
return rowsAffected > 0;
|
return rowsAffected > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the game of the match with the given [matchId].
|
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
|
||||||
Future<bool> updateMatchGame({
|
|
||||||
required String matchId,
|
|
||||||
required String gameId,
|
|
||||||
}) async {
|
|
||||||
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
|
||||||
final rowsAffected = await query.write(
|
|
||||||
MatchTableCompanion(gameId: Value(gameId)),
|
|
||||||
);
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the group of the match with the given [matchId].
|
|
||||||
/// Pass null to remove the group association.
|
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
|
||||||
Future<bool> updateMatchGroup({
|
|
||||||
required String matchId,
|
|
||||||
required String? groupId,
|
|
||||||
}) async {
|
|
||||||
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
|
||||||
final rowsAffected = await query.write(
|
|
||||||
MatchTableCompanion(groupId: Value(groupId)),
|
|
||||||
);
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the createdAt timestamp of the match with the given [matchId].
|
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
|
||||||
Future<bool> updateMatchCreatedAt({
|
|
||||||
required String matchId,
|
|
||||||
required DateTime createdAt,
|
|
||||||
}) async {
|
|
||||||
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
|
||||||
final rowsAffected = await query.write(
|
|
||||||
MatchTableCompanion(createdAt: Value(createdAt)),
|
|
||||||
);
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the endedAt timestamp of the match with the given [matchId].
|
|
||||||
/// Pass null to remove the ended time (mark match as ongoing).
|
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
|
||||||
Future<bool> updateMatchEndedAt({
|
|
||||||
required String matchId,
|
|
||||||
required DateTime? endedAt,
|
|
||||||
}) async {
|
|
||||||
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
|
||||||
final rowsAffected = await query.write(
|
|
||||||
MatchTableCompanion(endedAt: Value(endedAt)),
|
|
||||||
);
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Replaces all players in a match with the provided list of players.
|
|
||||||
/// Removes all existing players from the match and adds the new players.
|
|
||||||
/// Also adds any new players to the player table if they don't exist.
|
|
||||||
Future<void> replaceMatchPlayers({
|
|
||||||
required String matchId,
|
|
||||||
required List<Player> newPlayers,
|
|
||||||
}) async {
|
|
||||||
await db.transaction(() async {
|
|
||||||
// Remove all existing players from the match
|
|
||||||
final deleteQuery = delete(db.playerMatchTable)
|
|
||||||
..where((p) => p.matchId.equals(matchId));
|
|
||||||
await deleteQuery.go();
|
|
||||||
|
|
||||||
// Add new players to the player table if they don't exist
|
|
||||||
await Future.wait(
|
|
||||||
newPlayers.map((player) async {
|
|
||||||
if (!await db.playerDao.playerExists(playerId: player.id)) {
|
|
||||||
await db.playerDao.addPlayer(player: player);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add the new players to the match
|
|
||||||
await Future.wait(
|
|
||||||
newPlayers.map((player) => db.playerMatchDao.addPlayerToMatch(
|
|
||||||
matchId: matchId,
|
|
||||||
playerId: player.id,
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Winner methods - handle winner logic via player scores
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
/// Checks if a match has a winner.
|
|
||||||
/// Returns true if any player in the match has their score set to 1.
|
|
||||||
Future<bool> hasWinner({required String matchId}) async {
|
|
||||||
final players = await db.playerMatchDao.getPlayersOfMatch(matchId: matchId) ?? [];
|
|
||||||
|
|
||||||
for (final player in players) {
|
|
||||||
final score = await db.playerMatchDao.getPlayerScore(
|
|
||||||
matchId: matchId,
|
|
||||||
playerId: player.id,
|
|
||||||
);
|
|
||||||
if (score == 1) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the winner of a match.
|
|
||||||
/// Returns the player with score 1, or null if no winner is set.
|
|
||||||
Future<Player?> getWinner({required String matchId}) async {
|
|
||||||
final players = await db.playerMatchDao.getPlayersOfMatch(matchId: matchId) ?? [];
|
|
||||||
|
|
||||||
for (final player in players) {
|
|
||||||
final score = await db.playerMatchDao.getPlayerScore(
|
|
||||||
matchId: matchId,
|
|
||||||
playerId: player.id,
|
|
||||||
);
|
|
||||||
if (score == 1) {
|
|
||||||
return player;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the winner of a match.
|
|
||||||
/// Sets all players' scores to 0, then sets the specified player's score to 1.
|
|
||||||
/// Returns `true` if the operation was successful, otherwise `false`.
|
|
||||||
Future<bool> setWinner({
|
|
||||||
required String matchId,
|
|
||||||
required String winnerId,
|
|
||||||
}) async {
|
|
||||||
await db.transaction(() async {
|
|
||||||
final players = await db.playerMatchDao.getPlayersOfMatch(matchId: matchId) ?? [];
|
|
||||||
|
|
||||||
// Set all players' scores to 0
|
|
||||||
for (final player in players) {
|
|
||||||
await db.playerMatchDao.updatePlayerScore(
|
|
||||||
matchId: matchId,
|
|
||||||
playerId: player.id,
|
|
||||||
newScore: 0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the winner's score to 1
|
|
||||||
await db.playerMatchDao.updatePlayerScore(
|
|
||||||
matchId: matchId,
|
|
||||||
playerId: winnerId,
|
|
||||||
newScore: 1,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes the winner of a match.
|
|
||||||
/// Sets the current winner's score to 0 (no winner).
|
|
||||||
/// Returns `true` if a winner was removed, otherwise `false`.
|
|
||||||
Future<bool> removeWinner({required String matchId}) async {
|
|
||||||
final winner = await getWinner(matchId: matchId);
|
|
||||||
if (winner == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
final success = await db.playerMatchDao.updatePlayerScore(
|
|
||||||
matchId: matchId,
|
|
||||||
playerId: winner.id,
|
|
||||||
newScore: 0,
|
|
||||||
);
|
|
||||||
return success;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -4,11 +4,5 @@ part of 'match_dao.dart';
|
|||||||
|
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
mixin _$MatchDaoMixin on DatabaseAccessor<AppDatabase> {
|
mixin _$MatchDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||||
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
|
||||||
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
|
||||||
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
||||||
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
|
||||||
$TeamTableTable get teamTable => attachedDatabase.teamTable;
|
|
||||||
$PlayerMatchTableTable get playerMatchTable =>
|
|
||||||
attachedDatabase.playerMatchTable;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:tallee/data/db/database.dart';
|
import 'package:game_tracker/data/db/database.dart';
|
||||||
import 'package:tallee/data/db/tables/player_table.dart';
|
import 'package:game_tracker/data/db/tables/player_table.dart';
|
||||||
import 'package:tallee/data/dto/player.dart';
|
import 'package:game_tracker/data/dto/player.dart';
|
||||||
|
|
||||||
part 'player_dao.g.dart';
|
part 'player_dao.g.dart';
|
||||||
|
|
||||||
@@ -15,12 +15,7 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
|||||||
final result = await query.get();
|
final result = await query.get();
|
||||||
return result
|
return result
|
||||||
.map(
|
.map(
|
||||||
(row) => Player(
|
(row) => Player(id: row.id, name: row.name, createdAt: row.createdAt),
|
||||||
id: row.id,
|
|
||||||
name: row.name,
|
|
||||||
description: row.description,
|
|
||||||
createdAt: row.createdAt,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
@@ -32,7 +27,6 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
|||||||
return Player(
|
return Player(
|
||||||
id: result.id,
|
id: result.id,
|
||||||
name: result.name,
|
name: result.name,
|
||||||
description: result.description,
|
|
||||||
createdAt: result.createdAt,
|
createdAt: result.createdAt,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -46,7 +40,6 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
|||||||
PlayerTableCompanion.insert(
|
PlayerTableCompanion.insert(
|
||||||
id: player.id,
|
id: player.id,
|
||||||
name: player.name,
|
name: player.name,
|
||||||
description: player.description,
|
|
||||||
createdAt: player.createdAt,
|
createdAt: player.createdAt,
|
||||||
),
|
),
|
||||||
mode: InsertMode.insertOrReplace,
|
mode: InsertMode.insertOrReplace,
|
||||||
@@ -70,7 +63,6 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
|||||||
(player) => PlayerTableCompanion.insert(
|
(player) => PlayerTableCompanion.insert(
|
||||||
id: player.id,
|
id: player.id,
|
||||||
name: player.name,
|
name: player.name,
|
||||||
description: player.description,
|
|
||||||
createdAt: player.createdAt,
|
createdAt: player.createdAt,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -99,7 +91,7 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Updates the name of the player with the given [playerId] to [newName].
|
/// Updates the name of the player with the given [playerId] to [newName].
|
||||||
Future<void> updatePlayerName({
|
Future<void> updatePlayername({
|
||||||
required String playerId,
|
required String playerId,
|
||||||
required String newName,
|
required String newName,
|
||||||
}) async {
|
}) async {
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:tallee/data/db/database.dart';
|
import 'package:game_tracker/data/db/database.dart';
|
||||||
import 'package:tallee/data/db/tables/player_group_table.dart';
|
import 'package:game_tracker/data/db/tables/player_group_table.dart';
|
||||||
import 'package:tallee/data/db/tables/player_table.dart';
|
import 'package:game_tracker/data/db/tables/player_table.dart';
|
||||||
import 'package:tallee/data/dto/player.dart';
|
import 'package:game_tracker/data/dto/player.dart';
|
||||||
|
|
||||||
part 'player_group_dao.g.dart';
|
part 'player_group_dao.g.dart';
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ class PlayerGroupDao extends DatabaseAccessor<AppDatabase>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!await db.playerDao.playerExists(playerId: player.id)) {
|
if (!await db.playerDao.playerExists(playerId: player.id)) {
|
||||||
await db.playerDao.addPlayer(player: player);
|
db.playerDao.addPlayer(player: player);
|
||||||
}
|
}
|
||||||
|
|
||||||
await into(playerGroupTable).insert(
|
await into(playerGroupTable).insert(
|
||||||
|
|||||||
@@ -1,31 +1,23 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:tallee/data/db/database.dart';
|
import 'package:game_tracker/data/db/database.dart';
|
||||||
import 'package:tallee/data/db/tables/player_match_table.dart';
|
import 'package:game_tracker/data/db/tables/player_match_table.dart';
|
||||||
import 'package:tallee/data/db/tables/team_table.dart';
|
import 'package:game_tracker/data/dto/player.dart';
|
||||||
import 'package:tallee/data/dto/player.dart';
|
|
||||||
|
|
||||||
part 'player_match_dao.g.dart';
|
part 'player_match_dao.g.dart';
|
||||||
|
|
||||||
@DriftAccessor(tables: [PlayerMatchTable, TeamTable])
|
@DriftAccessor(tables: [PlayerMatchTable])
|
||||||
class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
|
class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
|
||||||
with _$PlayerMatchDaoMixin {
|
with _$PlayerMatchDaoMixin {
|
||||||
PlayerMatchDao(super.db);
|
PlayerMatchDao(super.db);
|
||||||
|
|
||||||
/// Associates a player with a match by inserting a record into the
|
/// Associates a player with a match by inserting a record into the
|
||||||
/// [PlayerMatchTable]. Optionally associates with a team and sets initial score.
|
/// [PlayerMatchTable].
|
||||||
Future<void> addPlayerToMatch({
|
Future<void> addPlayerToMatch({
|
||||||
required String matchId,
|
required String matchId,
|
||||||
required String playerId,
|
required String playerId,
|
||||||
String? teamId,
|
|
||||||
int score = 0,
|
|
||||||
}) async {
|
}) async {
|
||||||
await into(playerMatchTable).insert(
|
await into(playerMatchTable).insert(
|
||||||
PlayerMatchTableCompanion.insert(
|
PlayerMatchTableCompanion.insert(playerId: playerId, matchId: matchId),
|
||||||
playerId: playerId,
|
|
||||||
matchId: matchId,
|
|
||||||
teamId: Value(teamId),
|
|
||||||
score: score,
|
|
||||||
),
|
|
||||||
mode: InsertMode.insertOrIgnore,
|
mode: InsertMode.insertOrIgnore,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -46,50 +38,6 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
|
|||||||
return players;
|
return players;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves a player's score for a specific match.
|
|
||||||
/// Returns null if the player is not in the match.
|
|
||||||
Future<int?> getPlayerScore({
|
|
||||||
required String matchId,
|
|
||||||
required String playerId,
|
|
||||||
}) async {
|
|
||||||
final result = await (select(playerMatchTable)
|
|
||||||
..where(
|
|
||||||
(p) => p.matchId.equals(matchId) & p.playerId.equals(playerId),
|
|
||||||
))
|
|
||||||
.getSingleOrNull();
|
|
||||||
return result?.score;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the score for a player in a match.
|
|
||||||
/// Returns `true` if the update was successful, otherwise `false`.
|
|
||||||
Future<bool> updatePlayerScore({
|
|
||||||
required String matchId,
|
|
||||||
required String playerId,
|
|
||||||
required int newScore,
|
|
||||||
}) async {
|
|
||||||
final rowsAffected = await (update(playerMatchTable)
|
|
||||||
..where(
|
|
||||||
(p) => p.matchId.equals(matchId) & p.playerId.equals(playerId),
|
|
||||||
))
|
|
||||||
.write(PlayerMatchTableCompanion(score: Value(newScore)));
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the team for a player in a match.
|
|
||||||
/// Returns `true` if the update was successful, otherwise `false`.
|
|
||||||
Future<bool> updatePlayerTeam({
|
|
||||||
required String matchId,
|
|
||||||
required String playerId,
|
|
||||||
required String? teamId,
|
|
||||||
}) async {
|
|
||||||
final rowsAffected = await (update(playerMatchTable)
|
|
||||||
..where(
|
|
||||||
(p) => p.matchId.equals(matchId) & p.playerId.equals(playerId),
|
|
||||||
))
|
|
||||||
.write(PlayerMatchTableCompanion(teamId: Value(teamId)));
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if there are any players associated with the given [matchId].
|
/// Checks if there are any players associated with the given [matchId].
|
||||||
/// Returns `true` if there are players, otherwise `false`.
|
/// Returns `true` if there are players, otherwise `false`.
|
||||||
Future<bool> matchHasPlayers({required String matchId}) async {
|
Future<bool> matchHasPlayers({required String matchId}) async {
|
||||||
@@ -148,7 +96,7 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
|
|||||||
final playersToAdd = newPlayerIdsSet.difference(currentPlayerIds);
|
final playersToAdd = newPlayerIdsSet.difference(currentPlayerIds);
|
||||||
final playersToRemove = currentPlayerIds.difference(newPlayerIdsSet);
|
final playersToRemove = currentPlayerIds.difference(newPlayerIdsSet);
|
||||||
|
|
||||||
await db.transaction(() async {
|
db.transaction(() async {
|
||||||
// Remove old players
|
// Remove old players
|
||||||
if (playersToRemove.isNotEmpty) {
|
if (playersToRemove.isNotEmpty) {
|
||||||
await (delete(playerMatchTable)..where(
|
await (delete(playerMatchTable)..where(
|
||||||
@@ -166,7 +114,6 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
|
|||||||
(id) => PlayerMatchTableCompanion.insert(
|
(id) => PlayerMatchTableCompanion.insert(
|
||||||
playerId: id,
|
playerId: id,
|
||||||
matchId: matchId,
|
matchId: matchId,
|
||||||
score: 0,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
@@ -180,23 +127,4 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves all players in a specific team for a match.
|
|
||||||
Future<List<Player>> getPlayersInTeam({
|
|
||||||
required String matchId,
|
|
||||||
required String teamId,
|
|
||||||
}) async {
|
|
||||||
final result = await (select(playerMatchTable)
|
|
||||||
..where(
|
|
||||||
(p) => p.matchId.equals(matchId) & p.teamId.equals(teamId),
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
|
|
||||||
if (result.isEmpty) return [];
|
|
||||||
|
|
||||||
final futures = result.map(
|
|
||||||
(row) => db.playerDao.getPlayerById(playerId: row.playerId),
|
|
||||||
);
|
|
||||||
return Future.wait(futures);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,10 +5,7 @@ part of 'player_match_dao.dart';
|
|||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
mixin _$PlayerMatchDaoMixin on DatabaseAccessor<AppDatabase> {
|
mixin _$PlayerMatchDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||||
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
||||||
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
|
||||||
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
|
||||||
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
||||||
$TeamTableTable get teamTable => attachedDatabase.teamTable;
|
|
||||||
$PlayerMatchTableTable get playerMatchTable =>
|
$PlayerMatchTableTable get playerMatchTable =>
|
||||||
attachedDatabase.playerMatchTable;
|
attachedDatabase.playerMatchTable;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,191 +0,0 @@
|
|||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:tallee/data/db/database.dart';
|
|
||||||
import 'package:tallee/data/db/tables/score_table.dart';
|
|
||||||
|
|
||||||
part 'score_dao.g.dart';
|
|
||||||
|
|
||||||
/// A data class representing a score entry.
|
|
||||||
class ScoreEntry {
|
|
||||||
final String playerId;
|
|
||||||
final String matchId;
|
|
||||||
final int roundNumber;
|
|
||||||
final int score;
|
|
||||||
final int change;
|
|
||||||
|
|
||||||
ScoreEntry({
|
|
||||||
required this.playerId,
|
|
||||||
required this.matchId,
|
|
||||||
required this.roundNumber,
|
|
||||||
required this.score,
|
|
||||||
required this.change,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@DriftAccessor(tables: [ScoreTable])
|
|
||||||
class ScoreDao extends DatabaseAccessor<AppDatabase> with _$ScoreDaoMixin {
|
|
||||||
ScoreDao(super.db);
|
|
||||||
|
|
||||||
/// Adds a score entry to the database.
|
|
||||||
Future<void> addScore({
|
|
||||||
required String playerId,
|
|
||||||
required String matchId,
|
|
||||||
required int roundNumber,
|
|
||||||
required int score,
|
|
||||||
required int change,
|
|
||||||
}) async {
|
|
||||||
await into(scoreTable).insert(
|
|
||||||
ScoreTableCompanion.insert(
|
|
||||||
playerId: playerId,
|
|
||||||
matchId: matchId,
|
|
||||||
roundNumber: roundNumber,
|
|
||||||
score: score,
|
|
||||||
change: change,
|
|
||||||
),
|
|
||||||
mode: InsertMode.insertOrReplace,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves all scores for a specific match.
|
|
||||||
Future<List<ScoreEntry>> getScoresForMatch({required String matchId}) async {
|
|
||||||
final query = select(scoreTable)..where((s) => s.matchId.equals(matchId));
|
|
||||||
final result = await query.get();
|
|
||||||
return result
|
|
||||||
.map(
|
|
||||||
(row) => ScoreEntry(
|
|
||||||
playerId: row.playerId,
|
|
||||||
matchId: row.matchId,
|
|
||||||
roundNumber: row.roundNumber,
|
|
||||||
score: row.score,
|
|
||||||
change: row.change,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves all scores for a specific player in a match.
|
|
||||||
Future<List<ScoreEntry>> getPlayerScoresInMatch({
|
|
||||||
required String playerId,
|
|
||||||
required String matchId,
|
|
||||||
}) async {
|
|
||||||
final query = select(scoreTable)
|
|
||||||
..where(
|
|
||||||
(s) => s.playerId.equals(playerId) & s.matchId.equals(matchId),
|
|
||||||
)
|
|
||||||
..orderBy([(s) => OrderingTerm.asc(s.roundNumber)]);
|
|
||||||
final result = await query.get();
|
|
||||||
return result
|
|
||||||
.map(
|
|
||||||
(row) => ScoreEntry(
|
|
||||||
playerId: row.playerId,
|
|
||||||
matchId: row.matchId,
|
|
||||||
roundNumber: row.roundNumber,
|
|
||||||
score: row.score,
|
|
||||||
change: row.change,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves the score for a specific round.
|
|
||||||
Future<ScoreEntry?> getScoreForRound({
|
|
||||||
required String playerId,
|
|
||||||
required String matchId,
|
|
||||||
required int roundNumber,
|
|
||||||
}) async {
|
|
||||||
final query = select(scoreTable)
|
|
||||||
..where(
|
|
||||||
(s) =>
|
|
||||||
s.playerId.equals(playerId) &
|
|
||||||
s.matchId.equals(matchId) &
|
|
||||||
s.roundNumber.equals(roundNumber),
|
|
||||||
);
|
|
||||||
final result = await query.getSingleOrNull();
|
|
||||||
if (result == null) return null;
|
|
||||||
return ScoreEntry(
|
|
||||||
playerId: result.playerId,
|
|
||||||
matchId: result.matchId,
|
|
||||||
roundNumber: result.roundNumber,
|
|
||||||
score: result.score,
|
|
||||||
change: result.change,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates a score entry.
|
|
||||||
Future<bool> updateScore({
|
|
||||||
required String playerId,
|
|
||||||
required String matchId,
|
|
||||||
required int roundNumber,
|
|
||||||
required int newScore,
|
|
||||||
required int newChange,
|
|
||||||
}) async {
|
|
||||||
final rowsAffected = await (update(scoreTable)
|
|
||||||
..where(
|
|
||||||
(s) =>
|
|
||||||
s.playerId.equals(playerId) &
|
|
||||||
s.matchId.equals(matchId) &
|
|
||||||
s.roundNumber.equals(roundNumber),
|
|
||||||
))
|
|
||||||
.write(
|
|
||||||
ScoreTableCompanion(
|
|
||||||
score: Value(newScore),
|
|
||||||
change: Value(newChange),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes a score entry.
|
|
||||||
Future<bool> deleteScore({
|
|
||||||
required String playerId,
|
|
||||||
required String matchId,
|
|
||||||
required int roundNumber,
|
|
||||||
}) async {
|
|
||||||
final query = delete(scoreTable)
|
|
||||||
..where(
|
|
||||||
(s) =>
|
|
||||||
s.playerId.equals(playerId) &
|
|
||||||
s.matchId.equals(matchId) &
|
|
||||||
s.roundNumber.equals(roundNumber),
|
|
||||||
);
|
|
||||||
final rowsAffected = await query.go();
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes all scores for a specific match.
|
|
||||||
Future<bool> deleteScoresForMatch({required String matchId}) async {
|
|
||||||
final query = delete(scoreTable)..where((s) => s.matchId.equals(matchId));
|
|
||||||
final rowsAffected = await query.go();
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes all scores for a specific player.
|
|
||||||
Future<bool> deleteScoresForPlayer({required String playerId}) async {
|
|
||||||
final query = delete(scoreTable)..where((s) => s.playerId.equals(playerId));
|
|
||||||
final rowsAffected = await query.go();
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the latest round number for a match.
|
|
||||||
Future<int> getLatestRoundNumber({required String matchId}) async {
|
|
||||||
final query = selectOnly(scoreTable)
|
|
||||||
..where(scoreTable.matchId.equals(matchId))
|
|
||||||
..addColumns([scoreTable.roundNumber.max()]);
|
|
||||||
final result = await query.getSingle();
|
|
||||||
return result.read(scoreTable.roundNumber.max()) ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gets the total score for a player in a match (sum of all changes).
|
|
||||||
Future<int> getTotalScoreForPlayer({
|
|
||||||
required String playerId,
|
|
||||||
required String matchId,
|
|
||||||
}) async {
|
|
||||||
final scores = await getPlayerScoresInMatch(
|
|
||||||
playerId: playerId,
|
|
||||||
matchId: matchId,
|
|
||||||
);
|
|
||||||
if (scores.isEmpty) return 0;
|
|
||||||
// Return the score from the latest round
|
|
||||||
return scores.last.score;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'score_dao.dart';
|
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
mixin _$ScoreDaoMixin on DatabaseAccessor<AppDatabase> {
|
|
||||||
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
|
||||||
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
|
||||||
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
|
||||||
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
|
||||||
$ScoreTableTable get scoreTable => attachedDatabase.scoreTable;
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:tallee/data/db/database.dart';
|
|
||||||
import 'package:tallee/data/db/tables/team_table.dart';
|
|
||||||
import 'package:tallee/data/dto/player.dart';
|
|
||||||
import 'package:tallee/data/dto/team.dart';
|
|
||||||
|
|
||||||
part 'team_dao.g.dart';
|
|
||||||
|
|
||||||
@DriftAccessor(tables: [TeamTable])
|
|
||||||
class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin {
|
|
||||||
TeamDao(super.db);
|
|
||||||
|
|
||||||
/// Retrieves all teams from the database.
|
|
||||||
/// Note: This returns teams without their members. Use getTeamById for full team data.
|
|
||||||
Future<List<Team>> getAllTeams() async {
|
|
||||||
final query = select(teamTable);
|
|
||||||
final result = await query.get();
|
|
||||||
return Future.wait(
|
|
||||||
result.map((row) async {
|
|
||||||
final members = await _getTeamMembers(teamId: row.id);
|
|
||||||
return Team(
|
|
||||||
id: row.id,
|
|
||||||
name: row.name,
|
|
||||||
createdAt: row.createdAt,
|
|
||||||
members: members,
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves a [Team] by its [teamId], including its members.
|
|
||||||
Future<Team> getTeamById({required String teamId}) async {
|
|
||||||
final query = select(teamTable)..where((t) => t.id.equals(teamId));
|
|
||||||
final result = await query.getSingle();
|
|
||||||
final members = await _getTeamMembers(teamId: teamId);
|
|
||||||
return Team(
|
|
||||||
id: result.id,
|
|
||||||
name: result.name,
|
|
||||||
createdAt: result.createdAt,
|
|
||||||
members: members,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Helper method to get team members from player_match_table.
|
|
||||||
/// This assumes team members are tracked via the player_match_table.
|
|
||||||
Future<List<Player>> _getTeamMembers({required String teamId}) async {
|
|
||||||
// Get all player_match entries with this teamId
|
|
||||||
final playerMatchQuery = select(db.playerMatchTable)
|
|
||||||
..where((pm) => pm.teamId.equals(teamId));
|
|
||||||
final playerMatches = await playerMatchQuery.get();
|
|
||||||
|
|
||||||
if (playerMatches.isEmpty) return [];
|
|
||||||
|
|
||||||
// Get unique player IDs
|
|
||||||
final playerIds = playerMatches.map((pm) => pm.playerId).toSet();
|
|
||||||
|
|
||||||
// Fetch all players
|
|
||||||
final players = await Future.wait(
|
|
||||||
playerIds.map((id) => db.playerDao.getPlayerById(playerId: id)),
|
|
||||||
);
|
|
||||||
return players;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a new [team] to the database.
|
|
||||||
/// Returns `true` if the team was added, `false` otherwise.
|
|
||||||
Future<bool> addTeam({required Team team}) async {
|
|
||||||
if (!await teamExists(teamId: team.id)) {
|
|
||||||
await into(teamTable).insert(
|
|
||||||
TeamTableCompanion.insert(
|
|
||||||
id: team.id,
|
|
||||||
name: team.name,
|
|
||||||
createdAt: team.createdAt,
|
|
||||||
),
|
|
||||||
mode: InsertMode.insertOrReplace,
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds multiple [teams] to the database in a batch operation.
|
|
||||||
Future<bool> addTeamsAsList({required List<Team> teams}) async {
|
|
||||||
if (teams.isEmpty) return false;
|
|
||||||
|
|
||||||
await db.batch(
|
|
||||||
(b) => b.insertAll(
|
|
||||||
teamTable,
|
|
||||||
teams
|
|
||||||
.map(
|
|
||||||
(team) => TeamTableCompanion.insert(
|
|
||||||
id: team.id,
|
|
||||||
name: team.name,
|
|
||||||
createdAt: team.createdAt,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
mode: InsertMode.insertOrIgnore,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes the team with the given [teamId] from the database.
|
|
||||||
/// Returns `true` if the team was deleted, `false` otherwise.
|
|
||||||
Future<bool> deleteTeam({required String teamId}) async {
|
|
||||||
final query = delete(teamTable)..where((t) => t.id.equals(teamId));
|
|
||||||
final rowsAffected = await query.go();
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if a team with the given [teamId] exists in the database.
|
|
||||||
/// Returns `true` if the team exists, `false` otherwise.
|
|
||||||
Future<bool> teamExists({required String teamId}) async {
|
|
||||||
final query = select(teamTable)..where((t) => t.id.equals(teamId));
|
|
||||||
final result = await query.getSingleOrNull();
|
|
||||||
return result != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the name of the team with the given [teamId].
|
|
||||||
Future<void> updateTeamName({
|
|
||||||
required String teamId,
|
|
||||||
required String newName,
|
|
||||||
}) async {
|
|
||||||
await (update(teamTable)..where((t) => t.id.equals(teamId))).write(
|
|
||||||
TeamTableCompanion(name: Value(newName)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves the total count of teams in the database.
|
|
||||||
Future<int> getTeamCount() async {
|
|
||||||
final count =
|
|
||||||
await (selectOnly(teamTable)..addColumns([teamTable.id.count()]))
|
|
||||||
.map((row) => row.read(teamTable.id.count()))
|
|
||||||
.getSingle();
|
|
||||||
return count ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Deletes all teams from the database.
|
|
||||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
|
||||||
Future<bool> deleteAllTeams() async {
|
|
||||||
final query = delete(teamTable);
|
|
||||||
final rowsAffected = await query.go();
|
|
||||||
return rowsAffected > 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'team_dao.dart';
|
|
||||||
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
mixin _$TeamDaoMixin on DatabaseAccessor<AppDatabase> {
|
|
||||||
$TeamTableTable get teamTable => attachedDatabase.teamTable;
|
|
||||||
}
|
|
||||||
@@ -1,22 +1,18 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:drift_flutter/drift_flutter.dart';
|
import 'package:drift_flutter/drift_flutter.dart';
|
||||||
|
import 'package:game_tracker/data/dao/group_dao.dart';
|
||||||
|
import 'package:game_tracker/data/dao/group_match_dao.dart';
|
||||||
|
import 'package:game_tracker/data/dao/match_dao.dart';
|
||||||
|
import 'package:game_tracker/data/dao/player_dao.dart';
|
||||||
|
import 'package:game_tracker/data/dao/player_group_dao.dart';
|
||||||
|
import 'package:game_tracker/data/dao/player_match_dao.dart';
|
||||||
|
import 'package:game_tracker/data/db/tables/group_match_table.dart';
|
||||||
|
import 'package:game_tracker/data/db/tables/group_table.dart';
|
||||||
|
import 'package:game_tracker/data/db/tables/match_table.dart';
|
||||||
|
import 'package:game_tracker/data/db/tables/player_group_table.dart';
|
||||||
|
import 'package:game_tracker/data/db/tables/player_match_table.dart';
|
||||||
|
import 'package:game_tracker/data/db/tables/player_table.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:tallee/data/dao/game_dao.dart';
|
|
||||||
import 'package:tallee/data/dao/group_dao.dart';
|
|
||||||
import 'package:tallee/data/dao/match_dao.dart';
|
|
||||||
import 'package:tallee/data/dao/player_dao.dart';
|
|
||||||
import 'package:tallee/data/dao/player_group_dao.dart';
|
|
||||||
import 'package:tallee/data/dao/player_match_dao.dart';
|
|
||||||
import 'package:tallee/data/dao/score_dao.dart';
|
|
||||||
import 'package:tallee/data/dao/team_dao.dart';
|
|
||||||
import 'package:tallee/data/db/tables/game_table.dart';
|
|
||||||
import 'package:tallee/data/db/tables/group_table.dart';
|
|
||||||
import 'package:tallee/data/db/tables/match_table.dart';
|
|
||||||
import 'package:tallee/data/db/tables/player_group_table.dart';
|
|
||||||
import 'package:tallee/data/db/tables/player_match_table.dart';
|
|
||||||
import 'package:tallee/data/db/tables/player_table.dart';
|
|
||||||
import 'package:tallee/data/db/tables/score_table.dart';
|
|
||||||
import 'package:tallee/data/db/tables/team_table.dart';
|
|
||||||
|
|
||||||
part 'database.g.dart';
|
part 'database.g.dart';
|
||||||
|
|
||||||
@@ -27,9 +23,7 @@ part 'database.g.dart';
|
|||||||
MatchTable,
|
MatchTable,
|
||||||
PlayerGroupTable,
|
PlayerGroupTable,
|
||||||
PlayerMatchTable,
|
PlayerMatchTable,
|
||||||
GameTable,
|
GroupMatchTable,
|
||||||
TeamTable,
|
|
||||||
ScoreTable,
|
|
||||||
],
|
],
|
||||||
daos: [
|
daos: [
|
||||||
PlayerDao,
|
PlayerDao,
|
||||||
@@ -37,9 +31,7 @@ part 'database.g.dart';
|
|||||||
MatchDao,
|
MatchDao,
|
||||||
PlayerGroupDao,
|
PlayerGroupDao,
|
||||||
PlayerMatchDao,
|
PlayerMatchDao,
|
||||||
GameDao,
|
GroupMatchDao,
|
||||||
ScoreDao,
|
|
||||||
TeamDao
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
class AppDatabase extends _$AppDatabase {
|
class AppDatabase extends _$AppDatabase {
|
||||||
@@ -60,7 +52,9 @@ class AppDatabase extends _$AppDatabase {
|
|||||||
static QueryExecutor _openConnection() {
|
static QueryExecutor _openConnection() {
|
||||||
return driftDatabase(
|
return driftDatabase(
|
||||||
name: 'gametracker_db',
|
name: 'gametracker_db',
|
||||||
native: const DriftNativeOptions(databaseDirectory: getApplicationSupportDirectory),
|
native: const DriftNativeOptions(
|
||||||
|
databaseDirectory: getApplicationSupportDirectory,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
import 'package:drift/drift.dart';
|
|
||||||
|
|
||||||
class GameTable extends Table {
|
|
||||||
TextColumn get id => text()();
|
|
||||||
TextColumn get name => text()();
|
|
||||||
TextColumn get ruleset => text()();
|
|
||||||
TextColumn get description => text()();
|
|
||||||
TextColumn get color => text()();
|
|
||||||
TextColumn get icon => text()();
|
|
||||||
DateTimeColumn get createdAt => dateTime()();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column<Object>> get primaryKey => {id};
|
|
||||||
}
|
|
||||||
13
lib/data/db/tables/group_match_table.dart
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:game_tracker/data/db/tables/group_table.dart';
|
||||||
|
import 'package:game_tracker/data/db/tables/match_table.dart';
|
||||||
|
|
||||||
|
class GroupMatchTable extends Table {
|
||||||
|
TextColumn get groupId =>
|
||||||
|
text().references(GroupTable, #id, onDelete: KeyAction.cascade)();
|
||||||
|
TextColumn get matchId =>
|
||||||
|
text().references(MatchTable, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column<Object>> get primaryKey => {groupId, matchId};
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ import 'package:drift/drift.dart';
|
|||||||
class GroupTable extends Table {
|
class GroupTable extends Table {
|
||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
TextColumn get name => text()();
|
TextColumn get name => text()();
|
||||||
TextColumn get description => text()();
|
|
||||||
DateTimeColumn get createdAt => dateTime()();
|
DateTimeColumn get createdAt => dateTime()();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,18 +1,10 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:tallee/data/db/tables/game_table.dart';
|
|
||||||
import 'package:tallee/data/db/tables/group_table.dart';
|
|
||||||
|
|
||||||
class MatchTable extends Table {
|
class MatchTable extends Table {
|
||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
TextColumn get gameId =>
|
TextColumn get name => text()();
|
||||||
text().references(GameTable, #id, onDelete: KeyAction.cascade)();
|
late final winnerId = text().nullable()();
|
||||||
// Nullable if not part of a group
|
|
||||||
TextColumn get groupId =>
|
|
||||||
text().references(GroupTable, #id, onDelete: KeyAction.cascade).nullable()();
|
|
||||||
TextColumn get name => text().nullable()();
|
|
||||||
TextColumn get notes => text().nullable()();
|
|
||||||
DateTimeColumn get createdAt => dateTime()();
|
DateTimeColumn get createdAt => dateTime()();
|
||||||
DateTimeColumn get endedAt => dateTime().nullable()();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column<Object>> get primaryKey => {id};
|
Set<Column<Object>> get primaryKey => {id};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:tallee/data/db/tables/group_table.dart';
|
import 'package:game_tracker/data/db/tables/group_table.dart';
|
||||||
import 'package:tallee/data/db/tables/player_table.dart';
|
import 'package:game_tracker/data/db/tables/player_table.dart';
|
||||||
|
|
||||||
class PlayerGroupTable extends Table {
|
class PlayerGroupTable extends Table {
|
||||||
TextColumn get playerId =>
|
TextColumn get playerId =>
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
import 'package:tallee/data/db/tables/match_table.dart';
|
import 'package:game_tracker/data/db/tables/match_table.dart';
|
||||||
import 'package:tallee/data/db/tables/player_table.dart';
|
import 'package:game_tracker/data/db/tables/player_table.dart';
|
||||||
import 'package:tallee/data/db/tables/team_table.dart';
|
|
||||||
|
|
||||||
class PlayerMatchTable extends Table {
|
class PlayerMatchTable extends Table {
|
||||||
TextColumn get playerId =>
|
TextColumn get playerId =>
|
||||||
text().references(PlayerTable, #id, onDelete: KeyAction.cascade)();
|
text().references(PlayerTable, #id, onDelete: KeyAction.cascade)();
|
||||||
TextColumn get matchId =>
|
TextColumn get matchId =>
|
||||||
text().references(MatchTable, #id, onDelete: KeyAction.cascade)();
|
text().references(MatchTable, #id, onDelete: KeyAction.cascade)();
|
||||||
TextColumn get teamId =>
|
|
||||||
text().references(TeamTable, #id).nullable()();
|
|
||||||
IntColumn get score => integer()();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Set<Column<Object>> get primaryKey => {playerId, matchId};
|
Set<Column<Object>> get primaryKey => {playerId, matchId};
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:drift/drift.dart';
|
|||||||
class PlayerTable extends Table {
|
class PlayerTable extends Table {
|
||||||
TextColumn get id => text()();
|
TextColumn get id => text()();
|
||||||
TextColumn get name => text()();
|
TextColumn get name => text()();
|
||||||
TextColumn get description => text()();
|
|
||||||
DateTimeColumn get createdAt => dateTime()();
|
DateTimeColumn get createdAt => dateTime()();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:tallee/data/db/tables/match_table.dart';
|
|
||||||
import 'package:tallee/data/db/tables/player_table.dart';
|
|
||||||
|
|
||||||
class ScoreTable extends Table {
|
|
||||||
TextColumn get playerId =>
|
|
||||||
text().references(PlayerTable, #id, onDelete: KeyAction.cascade)();
|
|
||||||
TextColumn get matchId =>
|
|
||||||
text().references(MatchTable, #id, onDelete: KeyAction.cascade)();
|
|
||||||
IntColumn get roundNumber => integer()();
|
|
||||||
IntColumn get score => integer()();
|
|
||||||
IntColumn get change => integer()();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column<Object>> get primaryKey => {playerId, matchId, roundNumber};
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import 'package:drift/drift.dart';
|
|
||||||
|
|
||||||
class TeamTable extends Table {
|
|
||||||
TextColumn get id => text()();
|
|
||||||
TextColumn get name => text()();
|
|
||||||
DateTimeColumn get createdAt => dateTime()();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Set<Column<Object>> get primaryKey => {id};
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
import 'package:clock/clock.dart';
|
|
||||||
import 'package:uuid/uuid.dart';
|
|
||||||
import 'package:tallee/core/enums.dart';
|
|
||||||
|
|
||||||
class Game {
|
|
||||||
final String id;
|
|
||||||
final DateTime createdAt;
|
|
||||||
final String name;
|
|
||||||
final Ruleset ruleset;
|
|
||||||
final String description;
|
|
||||||
final GameColor color;
|
|
||||||
final String icon;
|
|
||||||
|
|
||||||
Game({
|
|
||||||
String? id,
|
|
||||||
DateTime? createdAt,
|
|
||||||
required this.name,
|
|
||||||
required this.ruleset,
|
|
||||||
required this.description,
|
|
||||||
required this.color,
|
|
||||||
required this.icon,
|
|
||||||
}) : id = id ?? const Uuid().v4(),
|
|
||||||
createdAt = createdAt ?? clock.now();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'Game{id: $id, name: $name, ruleset: $ruleset, description: $description, color: $color, icon: $icon}';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a Game instance from a JSON object.
|
|
||||||
Game.fromJson(Map<String, dynamic> json)
|
|
||||||
: id = json['id'],
|
|
||||||
createdAt = DateTime.parse(json['createdAt']),
|
|
||||||
name = json['name'],
|
|
||||||
ruleset = Ruleset.values.firstWhere((e) => e.name == json['ruleset']),
|
|
||||||
description = json['description'],
|
|
||||||
color = GameColor.values.firstWhere((e) => e.name == json['color']),
|
|
||||||
icon = json['icon'];
|
|
||||||
|
|
||||||
/// Converts the Game instance to a JSON object.
|
|
||||||
Map<String, dynamic> toJson() => {
|
|
||||||
'id': id,
|
|
||||||
'createdAt': createdAt.toIso8601String(),
|
|
||||||
'name': name,
|
|
||||||
'ruleset': ruleset.name,
|
|
||||||
'description': description,
|
|
||||||
'color': color.name,
|
|
||||||
'icon': icon,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,43 +1,40 @@
|
|||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:tallee/data/dto/player.dart';
|
import 'package:game_tracker/data/dto/player.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class Group {
|
class Group {
|
||||||
final String id;
|
final String id;
|
||||||
final String name;
|
|
||||||
final String description;
|
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
|
final String name;
|
||||||
final List<Player> members;
|
final List<Player> members;
|
||||||
|
|
||||||
Group({
|
Group({
|
||||||
String? id,
|
String? id,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.description,
|
|
||||||
required this.members,
|
required this.members,
|
||||||
}) : id = id ?? const Uuid().v4(),
|
}) : id = id ?? const Uuid().v4(),
|
||||||
createdAt = createdAt ?? clock.now();
|
createdAt = createdAt ?? clock.now();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Group{id: $id, name: $name, description: $description, members: $members}';
|
return 'Group{id: $id, name: $name,members: $members}';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a Group instance from a JSON object (memberIds format).
|
/// Creates a Group instance from a JSON object.
|
||||||
/// Player objects are reconstructed from memberIds by the DataTransferService.
|
|
||||||
Group.fromJson(Map<String, dynamic> json)
|
Group.fromJson(Map<String, dynamic> json)
|
||||||
: id = json['id'],
|
: id = json['id'],
|
||||||
createdAt = DateTime.parse(json['createdAt']),
|
createdAt = DateTime.parse(json['createdAt']),
|
||||||
name = json['name'],
|
name = json['name'],
|
||||||
description = json['description'],
|
members = (json['members'] as List)
|
||||||
members = []; // Populated during import via DataTransferService
|
.map((memberJson) => Player.fromJson(memberJson))
|
||||||
|
.toList();
|
||||||
|
|
||||||
/// Converts the Group instance to a JSON object using normalized format (memberIds only).
|
/// Converts the Group instance to a JSON object.
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
'id': id,
|
'id': id,
|
||||||
'createdAt': createdAt.toIso8601String(),
|
'createdAt': createdAt.toIso8601String(),
|
||||||
'name': name,
|
'name': name,
|
||||||
'description': description,
|
'members': members.map((member) => member.toJson()).toList(),
|
||||||
'memberIds': members.map((member) => member.id).toList(),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +1,51 @@
|
|||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:tallee/core/enums.dart';
|
import 'package:game_tracker/data/dto/group.dart';
|
||||||
import 'package:tallee/data/dto/game.dart';
|
import 'package:game_tracker/data/dto/player.dart';
|
||||||
import 'package:tallee/data/dto/group.dart';
|
|
||||||
import 'package:tallee/data/dto/player.dart';
|
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class Match {
|
class Match {
|
||||||
final String id;
|
final String id;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
final DateTime? endedAt;
|
|
||||||
final String name;
|
final String name;
|
||||||
final Game game;
|
final List<Player>? players;
|
||||||
final Group? group;
|
final Group? group;
|
||||||
final List<Player> players;
|
final Player? winner;
|
||||||
final String notes;
|
|
||||||
Player? winner;
|
|
||||||
|
|
||||||
Match({
|
Match({
|
||||||
String? id,
|
String? id,
|
||||||
DateTime? createdAt,
|
DateTime? createdAt,
|
||||||
this.endedAt,
|
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.game,
|
this.players,
|
||||||
this.group,
|
this.group,
|
||||||
this.players = const [],
|
|
||||||
required this.notes,
|
|
||||||
this.winner,
|
this.winner,
|
||||||
}) : id = id ?? const Uuid().v4(),
|
}) : id = id ?? const Uuid().v4(),
|
||||||
createdAt = createdAt ?? clock.now();
|
createdAt = createdAt ?? clock.now();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Match{id: $id, name: $name, game: $game, group: $group, players: $players, notes: $notes, endedAt: $endedAt}';
|
return 'Match{\n\tid: $id,\n\tname: $name,\n\tplayers: $players,\n\tgroup: $group,\n\twinner: $winner\n}';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a Match instance from a JSON object (ID references format).
|
/// Creates a Match instance from a JSON object.
|
||||||
/// Related objects are reconstructed from IDs by the DataTransferService.
|
|
||||||
Match.fromJson(Map<String, dynamic> json)
|
Match.fromJson(Map<String, dynamic> json)
|
||||||
: id = json['id'],
|
: id = json['id'],
|
||||||
createdAt = DateTime.parse(json['createdAt']),
|
|
||||||
endedAt = json['endedAt'] != null ? DateTime.parse(json['endedAt']) : null,
|
|
||||||
name = json['name'],
|
name = json['name'],
|
||||||
game = Game(name: '', ruleset: Ruleset.singleWinner, description: '', color: GameColor.blue, icon: ''), // Populated during import via DataTransferService
|
createdAt = DateTime.parse(json['createdAt']),
|
||||||
group = null, // Populated during import via DataTransferService
|
players = json['players'] != null
|
||||||
players = [], // Populated during import via DataTransferService
|
? (json['players'] as List)
|
||||||
notes = json['notes'] ?? '';
|
.map((playerJson) => Player.fromJson(playerJson))
|
||||||
|
.toList()
|
||||||
|
: null,
|
||||||
|
group = json['group'] != null ? Group.fromJson(json['group']) : null,
|
||||||
|
winner = json['winner'] != null ? Player.fromJson(json['winner']) : null;
|
||||||
|
|
||||||
/// Converts the Match instance to a JSON object using normalized format (ID references only).
|
/// Converts the Match instance to a JSON object.
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
'id': id,
|
'id': id,
|
||||||
'createdAt': createdAt.toIso8601String(),
|
'createdAt': createdAt.toIso8601String(),
|
||||||
'endedAt': endedAt?.toIso8601String(),
|
|
||||||
'name': name,
|
'name': name,
|
||||||
'gameId': game.id,
|
'players': players?.map((player) => player.toJson()).toList(),
|
||||||
'groupId': group?.id,
|
'group': group?.toJson(),
|
||||||
'playerIds': players.map((player) => player.id).toList(),
|
'winner': winner?.toJson(),
|
||||||
'notes': notes,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,33 +5,26 @@ class Player {
|
|||||||
final String id;
|
final String id;
|
||||||
final DateTime createdAt;
|
final DateTime createdAt;
|
||||||
final String name;
|
final String name;
|
||||||
final String description;
|
|
||||||
|
|
||||||
Player({
|
Player({String? id, DateTime? createdAt, required this.name})
|
||||||
String? id,
|
: id = id ?? const Uuid().v4(),
|
||||||
DateTime? createdAt,
|
|
||||||
required this.name,
|
|
||||||
required this.description,
|
|
||||||
}) : id = id ?? const Uuid().v4(),
|
|
||||||
createdAt = createdAt ?? clock.now();
|
createdAt = createdAt ?? clock.now();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'Player{id: $id, name: $name, description: $description}';
|
return 'Player{id: $id,name: $name}';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a Player instance from a JSON object.
|
/// Creates a Player instance from a JSON object.
|
||||||
Player.fromJson(Map<String, dynamic> json)
|
Player.fromJson(Map<String, dynamic> json)
|
||||||
: id = json['id'],
|
: id = json['id'],
|
||||||
createdAt = DateTime.parse(json['createdAt']),
|
createdAt = DateTime.parse(json['createdAt']),
|
||||||
name = json['name'],
|
name = json['name'];
|
||||||
description = json['description'];
|
|
||||||
|
|
||||||
/// Converts the Player instance to a JSON object.
|
/// Converts the Player instance to a JSON object.
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
'id': id,
|
'id': id,
|
||||||
'createdAt': createdAt.toIso8601String(),
|
'createdAt': createdAt.toIso8601String(),
|
||||||
'name': name,
|
'name': name,
|
||||||
'description': description,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
import 'package:clock/clock.dart';
|
|
||||||
import 'package:tallee/data/dto/player.dart';
|
|
||||||
import 'package:uuid/uuid.dart';
|
|
||||||
|
|
||||||
class Team {
|
|
||||||
final String id;
|
|
||||||
final String name;
|
|
||||||
final DateTime createdAt;
|
|
||||||
final List<Player> members;
|
|
||||||
|
|
||||||
Team({
|
|
||||||
String? id,
|
|
||||||
required this.name,
|
|
||||||
DateTime? createdAt,
|
|
||||||
required this.members,
|
|
||||||
}) : id = id ?? const Uuid().v4(),
|
|
||||||
createdAt = createdAt ?? clock.now();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'Team{id: $id, name: $name, members: $members}';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a Team instance from a JSON object (memberIds format).
|
|
||||||
/// Player objects are reconstructed from memberIds by the DataTransferService.
|
|
||||||
Team.fromJson(Map<String, dynamic> json)
|
|
||||||
: id = json['id'],
|
|
||||||
name = json['name'],
|
|
||||||
createdAt = DateTime.parse(json['createdAt']),
|
|
||||||
members = []; // Populated during import via DataTransferService
|
|
||||||
|
|
||||||
/// Converts the Team instance to a JSON object using normalized format (memberIds only).
|
|
||||||
Map<String, dynamic> toJson() => {
|
|
||||||
'id': id,
|
|
||||||
'name': name,
|
|
||||||
'createdAt': createdAt.toIso8601String(),
|
|
||||||
'memberIds': members.map((member) => member.id).toList(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -3,8 +3,6 @@
|
|||||||
"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": "Tallee",
|
|
||||||
"best_player": "Beste:r Spieler:in",
|
|
||||||
"cancel": "Abbrechen",
|
"cancel": "Abbrechen",
|
||||||
"choose_game": "Spielvorlage wählen",
|
"choose_game": "Spielvorlage wählen",
|
||||||
"choose_group": "Gruppe wählen",
|
"choose_group": "Gruppe wählen",
|
||||||
@@ -14,16 +12,12 @@
|
|||||||
"create_match": "Spiel erstellen",
|
"create_match": "Spiel erstellen",
|
||||||
"create_new_group": "Neue Gruppe erstellen",
|
"create_new_group": "Neue Gruppe erstellen",
|
||||||
"create_new_match": "Neues Spiel erstellen",
|
"create_new_match": "Neues Spiel erstellen",
|
||||||
"created_on": "Erstellt am",
|
|
||||||
"data": "Daten",
|
|
||||||
"data_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?",
|
||||||
"delete_group": "Gruppe löschen",
|
|
||||||
"edit_group": "Gruppe bearbeiten",
|
|
||||||
"error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen",
|
"error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen",
|
||||||
"error_reading_file": "Fehler beim Lesen der Datei",
|
"error_reading_file": "Fehler beim Lesen der Datei",
|
||||||
"export_canceled": "Export abgebrochen",
|
"export_canceled": "Export abgebrochen",
|
||||||
@@ -33,7 +27,6 @@
|
|||||||
"game_name": "Spielvorlagenname",
|
"game_name": "Spielvorlagenname",
|
||||||
"group": "Gruppe",
|
"group": "Gruppe",
|
||||||
"group_name": "Gruppenname",
|
"group_name": "Gruppenname",
|
||||||
"group_profile": "Gruppenprofil",
|
|
||||||
"groups": "Gruppen",
|
"groups": "Gruppen",
|
||||||
"home": "Startseite",
|
"home": "Startseite",
|
||||||
"import_canceled": "Import abgebrochen",
|
"import_canceled": "Import abgebrochen",
|
||||||
@@ -41,18 +34,13 @@
|
|||||||
"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",
|
||||||
"members": "Mitglieder",
|
"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",
|
||||||
@@ -63,11 +51,9 @@
|
|||||||
"none": "Kein",
|
"none": "Kein",
|
||||||
"none_group": "Keine",
|
"none_group": "Keine",
|
||||||
"not_available": "Nicht verfügbar",
|
"not_available": "Nicht verfügbar",
|
||||||
"played_matches": "Gespielte Spiele",
|
|
||||||
"player_name": "Spieler:innenname",
|
"player_name": "Spieler:innenname",
|
||||||
"players": "Spieler:innen",
|
"players": "Spieler:innen",
|
||||||
"players_count": "{count} Spieler",
|
"players_count": "{count} Spieler",
|
||||||
"privacy_policy": "Datenschutzerklärung",
|
|
||||||
"quick_create": "Schnellzugriff",
|
"quick_create": "Schnellzugriff",
|
||||||
"recent_matches": "Letzte Spiele",
|
"recent_matches": "Letzte Spiele",
|
||||||
"ruleset": "Regelwerk",
|
"ruleset": "Regelwerk",
|
||||||
@@ -82,14 +68,11 @@
|
|||||||
"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",
|
||||||
"highest_score": "Höchste Punkte",
|
|
||||||
"lowest_score": "Niedrigste Punkte",
|
|
||||||
"multiple_winners": "Mehrere Gewinner:innen",
|
|
||||||
"statistics": "Statistiken",
|
"statistics": "Statistiken",
|
||||||
"stats": "Statistiken",
|
"stats": "Statistiken",
|
||||||
"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",
|
"today_at": "Heute um",
|
||||||
"undo": "Rückgängig",
|
"undo": "Rückgängig",
|
||||||
"unknown_exception": "Unbekannter Fehler (siehe Konsole)",
|
"unknown_exception": "Unbekannter Fehler (siehe Konsole)",
|
||||||
|
|||||||