Compare commits
145 Commits
feature/11
...
feature/88
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4ea46c6cd | ||
|
|
e881cf0555 | ||
|
|
278544788e | ||
|
|
a12f4eb1c1 | ||
|
|
0eb8e2683c | ||
|
|
07b9b78252 | ||
|
|
fa9ad9dbae | ||
|
|
25699dffc0 | ||
| a4d4703069 | |||
| fabb7bae19 | |||
|
|
70d6178829 | ||
|
|
7aba8554c0 | ||
|
|
dbef735a82 | ||
|
|
ccfea71a35 | ||
|
|
415cae18cd | ||
|
|
2a3ea32193 | ||
|
|
2ea68dcc89 | ||
|
|
acf3a7b003 | ||
|
|
1d352821fc | ||
| d1458443eb | |||
| 9c00b48de5 | |||
| 03ce304a0a | |||
| dde617d429 | |||
| 03a2df4fdf | |||
| 96ef70b209 | |||
| 4ae59ec881 | |||
|
|
3bd6dd4189 | ||
| c162e245bd | |||
| 481afd7533 | |||
| af42ebbf92 | |||
| 34e442dbe9 | |||
| 8783d0ac44 | |||
| 7f356b0c86 | |||
| e4a2ac6b47 | |||
| 6065b53ce9 | |||
| c644ccfb06 | |||
| 2dd7d0285d | |||
| cf5883b430 | |||
| 7af2b43193 | |||
| 90cc4d4c2a | |||
| db2ba2571f | |||
| 3b01c4623c | |||
| c5c9a43459 | |||
| b2e58f2539 | |||
| 58dc5d8c2e | |||
| fd7dbd1155 | |||
| 61b72cf595 | |||
| 81c6c377d4 | |||
| 92f2b4db95 | |||
| 5178adf71c | |||
| 4a6d639f1c | |||
| e9cf707fca | |||
| 2b7941202a | |||
| 92317bcbdf | |||
|
|
b0cb385756 | ||
|
|
118b316a35 | ||
|
|
55f5aac4e2 | ||
| ceade5cafd | |||
| 6060afc543 | |||
| ac6399d707 | |||
|
|
6006c6d3f7 | ||
|
|
0ddb4edbc9 | ||
|
|
7339194ba0 | ||
|
|
bd5e38a3ca | ||
|
|
12b713bb70 | ||
|
|
e55cea0dcc | ||
|
|
b2a3a0cf75 | ||
|
|
f142169371 | ||
|
|
0b778210ef | ||
|
|
748361d04f | ||
|
|
2e6a2b8e55 | ||
|
|
15894096f3 | ||
|
|
d488eac3ae | ||
|
|
19b2685714 | ||
|
|
068ca95afc | ||
|
|
0d28f4b87c | ||
|
|
2e454a530a | ||
|
|
6c39e1e574 | ||
|
|
b33260ec23 | ||
|
|
b108375ad5 | ||
|
|
e09ccf9356 | ||
| 8ee2b6cb06 | |||
| b14b7733ca | |||
|
|
b0b21bcba6 | ||
|
|
dec74e9b62 | ||
|
|
4e73babb71 | ||
| bc51b23563 | |||
| 057f8c1d58 | |||
| 4c1c22123e | |||
| e9929426e0 | |||
| eb404f3ef2 | |||
| c7b4623198 | |||
| ccd0c62e3c | |||
|
|
25e0c75dc6 | ||
|
|
fb59372c97 | ||
| 9f71c22a56 | |||
| 87d7fbebcd | |||
| c625174017 | |||
|
|
8dd2f5f8b8 | ||
|
|
c97fdc2b5f | ||
|
|
764ce13240 | ||
|
|
715b3debbb | ||
|
|
bb11c86816 | ||
| 53a33ca2e1 | |||
| e1dd40a1c3 | |||
|
|
6a6e36ed7c | ||
|
|
d21c37966e | ||
|
|
b6554c104a | ||
|
|
a68bbddc1a | ||
|
|
32fa82e5e7 | ||
|
|
49e990dfea | ||
|
|
40e970a5dc | ||
|
|
b9b6ff85ea | ||
|
|
52c1605ce9 | ||
|
|
c2a9bda8b1 | ||
|
|
a13d9c55ea | ||
|
|
b737cae356 | ||
|
|
3857665444 | ||
|
|
4b900c12bf | ||
|
|
867d0c55da | ||
|
|
02d3220c1a | ||
|
|
3ca612b0a1 | ||
|
|
cb66b76e5a | ||
|
|
b9b72cdd50 | ||
|
|
4a56df7f8f | ||
|
|
f2a12265ad | ||
|
|
c82d72544e | ||
|
|
072021bd4c | ||
|
|
6a9e5dc9eb | ||
| 5da12939cb | |||
| 0d20b5847f | |||
| 973e327232 | |||
|
|
be01b5f72a | ||
|
|
e2fe0c7d4d | ||
|
|
b72ab70e02 | ||
|
|
189daf76dd | ||
|
|
0f987f4c7a | ||
|
|
5dd8f31942 | ||
|
|
0394f5edf9 | ||
|
|
d8abad6fd8 | ||
|
|
7e6c309de0 | ||
|
|
3344575132 | ||
|
|
9b66e58dc0 | ||
|
|
56562b22bb | ||
| 0f79495775 |
@@ -10,22 +10,22 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Required for Flutter action
|
||||
- name: Install jq
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y jq
|
||||
|
||||
- name: Install Flutter (wget)
|
||||
run: |
|
||||
wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz
|
||||
tar xf flutter_linux_3.38.2-stable.tar.xz
|
||||
# Set Git safe directory for Flutter path
|
||||
git config --global --add safe.directory "$(pwd)/flutter"
|
||||
# Set Flutter path
|
||||
echo "$(pwd)/flutter/bin" >> $GITHUB_PATH
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: stable
|
||||
flutter-version: 3.38.6
|
||||
|
||||
- name: Get dependencies
|
||||
run: flutter pub get
|
||||
run: |
|
||||
git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.38.6-x64
|
||||
flutter pub get
|
||||
|
||||
- name: Analyze Formatting
|
||||
run: flutter analyze lib test
|
||||
@@ -36,22 +36,22 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
# Required for Flutter action
|
||||
- name: Install jq
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y jq
|
||||
|
||||
- name: Install Flutter (wget)
|
||||
run: |
|
||||
wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz
|
||||
tar xf flutter_linux_3.38.2-stable.tar.xz
|
||||
# Set Git safe directory for Flutter path
|
||||
git config --global --add safe.directory "$(pwd)/flutter"
|
||||
# Set Flutter path
|
||||
echo "$(pwd)/flutter/bin" >> $GITHUB_PATH
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: stable
|
||||
flutter-version: 3.38.6
|
||||
|
||||
- name: Get dependencies
|
||||
run: flutter pub get
|
||||
run: |
|
||||
git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.38.6-x64
|
||||
flutter pub get
|
||||
|
||||
- name: Run tests
|
||||
run: flutter test
|
||||
@@ -7,44 +7,194 @@ on:
|
||||
- "main"
|
||||
|
||||
jobs:
|
||||
format:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: false # Needs bot user
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
- 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: Install Flutter (wget)
|
||||
run: |
|
||||
wget https://storage.googleapis.com/flutter_infra_release/releases/stable/linux/flutter_linux_3.38.2-stable.tar.xz
|
||||
tar xf flutter_linux_3.38.2-stable.tar.xz
|
||||
# Set Git safe directory for Flutter path
|
||||
git config --global --add safe.directory "$(pwd)/flutter"
|
||||
# Set Flutter path
|
||||
echo "$(pwd)/flutter/bin" >> $GITHUB_PATH
|
||||
- name: Set up Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: stable
|
||||
flutter-version: 3.38.6
|
||||
|
||||
- name: Get & upgrade dependencies
|
||||
- name: Get dependencies
|
||||
run: |
|
||||
git config --global --add safe.directory /opt/hostedtoolcache/flutter/stable-3.38.6-x64
|
||||
flutter pub get
|
||||
flutter pub upgrade --major-versions
|
||||
|
||||
- name: Auto-format
|
||||
- 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: |
|
||||
dart format lib
|
||||
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:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [update_version, generate_licenses]
|
||||
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: Check code 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: |
|
||||
git fetch origin ${{ gitea.ref_name }}
|
||||
git checkout ${{ gitea.ref_name }}
|
||||
|
||||
dart fix --apply lib
|
||||
dart fix --apply test
|
||||
|
||||
# Needs credentials, push access and the right files need to be staged
|
||||
- name: Commit Changes
|
||||
run: |
|
||||
git config --global user.name "Gitea Actions"
|
||||
git config --global user.email "actions@gitea.com"
|
||||
git status
|
||||
git add lib/
|
||||
git status
|
||||
git commit -m "Actions: Auto-formatting [skip ci]"
|
||||
git push
|
||||
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 "Auto-format code [skip ci]"
|
||||
git push origin HEAD:${{ gitea.ref_name }}
|
||||
else
|
||||
echo "No changes to commit"
|
||||
fi
|
||||
|
||||
- name: Verify format
|
||||
run: flutter analyze lib test
|
||||
13
CONTRIBUTING.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# 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
Normal file
@@ -0,0 +1,165 @@
|
||||
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,7 +1,63 @@
|
||||
# Game Tracker
|
||||
<p align="center">
|
||||
<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.
|
||||
|
||||
@@ -6,7 +6,7 @@ plugins {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.example.game_tracker"
|
||||
namespace = "de.liquid.tallee"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
@@ -21,7 +21,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.example.game_tracker"
|
||||
applicationId = "de.liquid.tallee"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<application
|
||||
android:label="game_tracker"
|
||||
android:label="Tallee"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
<activity
|
||||
|
||||
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 17 KiB |
@@ -1,4 +1,4 @@
|
||||
package com.example.game_tracker
|
||||
package de.liquid.tallee
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 828 B After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 448 B After Width: | Height: | Size: 884 B |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 656 B After Width: | Height: | Size: 938 B |
|
Before Width: | Height: | Size: 348 B After Width: | Height: | Size: 596 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 508 B After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 824 B After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 9.0 KiB |
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="app_icon_background">#E6F1E4</color>
|
||||
<color name="launch_background">#0B0B0B</color>
|
||||
<color name="launch_background">#ef681f</color>
|
||||
</resources>
|
||||
@@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Referenz unbedingt als @color/launch_background (nicht @colors/...) -->
|
||||
<color name="ic_launcher_background">@color/app_icon_background</color>
|
||||
<color name="ic_launcher_background">#EF681F</color>
|
||||
</resources>
|
||||
BIN
artefacts/app-logo.png
Normal file
|
After Width: | Height: | Size: 47 KiB |
BIN
artefacts/screenshot-1.png
Normal file
|
After Width: | Height: | Size: 291 KiB |
BIN
artefacts/screenshot-2.png
Normal file
|
After Width: | Height: | Size: 279 KiB |
BIN
artefacts/screenshot-3.png
Normal file
|
After Width: | Height: | Size: 354 KiB |
BIN
artefacts/screenshot-4.png
Normal file
|
After Width: | Height: | Size: 294 KiB |
@@ -15,16 +15,91 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"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": {
|
||||
"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",
|
||||
"items": {
|
||||
"type": "object",
|
||||
@@ -67,11 +142,14 @@
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"endedAt": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"gameId": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupId": {
|
||||
"anyOf": [
|
||||
{"type": "string"},
|
||||
{"type": "null"}
|
||||
]
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"playerIds": {
|
||||
"type": "array",
|
||||
@@ -79,26 +157,26 @@
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"winnerId": {
|
||||
"anyOf": [
|
||||
{"type": "string"},
|
||||
{"type": "null"}
|
||||
]
|
||||
"notes": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name",
|
||||
"createdAt",
|
||||
"groupId",
|
||||
"playerIds"
|
||||
"gameId",
|
||||
"playerIds",
|
||||
"notes"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"players",
|
||||
"games",
|
||||
"groups",
|
||||
"teams",
|
||||
"matches"
|
||||
]
|
||||
}
|
||||
@@ -152,7 +152,6 @@
|
||||
B68CF4A64F0B5E45B43D6900 /* Pods-RunnerTests.release.xcconfig */,
|
||||
E754D1191B3E54E52B6DCC49 /* Pods-RunnerTests.profile.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@@ -478,7 +477,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = de.liquid.tallee;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
@@ -661,7 +660,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = de.liquid.tallee;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
@@ -684,7 +683,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = de.liquid.tallee;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
@@ -1,14 +1 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon_x1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
{"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"}]}
|
||||
|
Before Width: | Height: | Size: 8.8 KiB |
@@ -5,9 +5,9 @@
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.043",
|
||||
"green" : "0.043",
|
||||
"red" : "0.043"
|
||||
"blue" : "0.122",
|
||||
"green" : "0.408",
|
||||
"red" : "0.937"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
@@ -1,17 +1,8 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
"filename" : "icon-transparent.png",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
BIN
ios/Runner/Assets.xcassets/LauncherIcon.imageset/icon-transparent.png
vendored
Normal file
|
After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 9.6 KiB |
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<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">
|
||||
<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">
|
||||
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24405"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24504"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
@@ -20,12 +20,19 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<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">
|
||||
<rect key="frame" x="46" y="334" width="301" height="184"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
</imageView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" name="LauncherBackgroundColor"/>
|
||||
<color key="backgroundColor" name="LauncherColor"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
@@ -36,8 +43,8 @@
|
||||
<color key="tintColor" red="0.90196078431372551" green="0.94509803921568625" blue="0.89411764705882346" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<resources>
|
||||
<image name="LauncherIcon" width="1000" height="1000"/>
|
||||
<namedColor name="LauncherBackgroundColor">
|
||||
<color red="0.90196078431372551" green="0.94509803921568625" blue="0.89411764705882346" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<namedColor name="LauncherColor">
|
||||
<color red="0.93699997663497925" green="0.40799999237060547" blue="0.12200000137090683" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Game Tracker</string>
|
||||
<string>Tallee</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -13,7 +15,7 @@
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>game_tracker</string>
|
||||
<string>tallee</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
@@ -22,13 +24,15 @@
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSApplicationQueriesSchemes</key>
|
||||
<array>
|
||||
<string>https</string>
|
||||
<string>http</string>
|
||||
</array>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
@@ -44,9 +48,5 @@
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -19,7 +19,4 @@ class Constants {
|
||||
|
||||
/// Maximum length for team names
|
||||
static const int MAX_TEAM_NAME_LENGTH = 32;
|
||||
|
||||
/// Maximum length for game descriptions
|
||||
static const int MAX_GAME_DESCRIPTION_LENGTH = 256;
|
||||
}
|
||||
|
||||
@@ -7,25 +7,25 @@ class CustomTheme {
|
||||
// ==================== Colors ====================
|
||||
|
||||
/// Primary color of the app theme
|
||||
static Color primaryColor = const Color(0xFF7505E4);
|
||||
static const Color primaryColor = Color(0xFFef681f);
|
||||
|
||||
/// Secondary color of the app theme
|
||||
static Color secondaryColor = const Color(0xFFAFA2FF);
|
||||
static const Color secondaryColor = Color(0xFFf2a981);
|
||||
|
||||
/// Background color of the app theme
|
||||
static Color backgroundColor = const Color(0xFF0B0B0B);
|
||||
static const backgroundColor = Color(0xFF0B0B0B);
|
||||
|
||||
/// Default color for boxes and containers
|
||||
static Color boxColor = const Color(0xFF101010);
|
||||
static const Color boxColor = Color(0xFF101010);
|
||||
|
||||
/// Default border color for boxes and containers
|
||||
static Color boxBorder = const Color(0xFF272727);
|
||||
static const Color boxBorder = Color(0xFF272727);
|
||||
|
||||
/// Color for boxes and containers displayed on boxes
|
||||
static Color onBoxColor = const Color(0xFF181818);
|
||||
static const Color onBoxColor = Color(0xFF181818);
|
||||
|
||||
/// Text color used throughout the app
|
||||
static const Color textColor = Colors.white;
|
||||
static const Color textColor = Color(0xFFFFFFFF);
|
||||
|
||||
/// Selected color for the [NavbarItem]
|
||||
static Color navBarItemSelectedColor = primaryColor.withGreen(100);
|
||||
@@ -63,18 +63,18 @@ class CustomTheme {
|
||||
);
|
||||
|
||||
// ==================== App Bar Theme ====================
|
||||
static AppBarTheme appBarTheme = AppBarTheme(
|
||||
static const AppBarTheme appBarTheme = AppBarTheme(
|
||||
backgroundColor: backgroundColor,
|
||||
foregroundColor: textColor,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
centerTitle: true,
|
||||
titleTextStyle: const TextStyle(
|
||||
titleTextStyle: TextStyle(
|
||||
color: textColor,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
iconTheme: const IconThemeData(color: textColor),
|
||||
iconTheme: IconThemeData(color: textColor),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
|
||||
/// Button types used for styling the [CustomWidthButton]
|
||||
/// - [ButtonType.primary]: Primary button style.
|
||||
@@ -29,24 +29,38 @@ enum ImportResult {
|
||||
/// - [ExportResult.unknownException]: An exception occurred during export.
|
||||
enum ExportResult { success, canceled, unknownException }
|
||||
|
||||
/// Different rulesets available for matches
|
||||
/// - [Ruleset.singleWinner]: The match is won by a single player
|
||||
/// - [Ruleset.singleLoser]: The match is lost by a single player
|
||||
/// - [Ruleset.mostPoints]: The player with the most points wins.
|
||||
/// - [Ruleset.leastPoints]: The player with the fewest points wins.
|
||||
enum Ruleset { singleWinner, singleLoser, mostPoints, leastPoints }
|
||||
/// Different rulesets available for games
|
||||
/// - [Ruleset.highestScore]: The player with the highest score wins.
|
||||
/// - [Ruleset.lowestScore]: The player with the lowest score wins.
|
||||
/// - [Ruleset.singleWinner]: The match is won by a single player.
|
||||
/// - [Ruleset.singleLoser]: The match has a single loser.
|
||||
/// - [Ruleset.multipleWinners]: Multiple players can be winners.
|
||||
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.
|
||||
String translateRulesetToString(Ruleset ruleset, BuildContext context) {
|
||||
final loc = AppLocalizations.of(context);
|
||||
switch (ruleset) {
|
||||
case Ruleset.highestScore:
|
||||
return loc.highest_score;
|
||||
case Ruleset.lowestScore:
|
||||
return loc.lowest_score;
|
||||
case Ruleset.singleWinner:
|
||||
return loc.single_winner;
|
||||
case Ruleset.singleLoser:
|
||||
return loc.single_loser;
|
||||
case Ruleset.mostPoints:
|
||||
return loc.most_points;
|
||||
case Ruleset.leastPoints:
|
||||
return loc.least_points;
|
||||
case Ruleset.multipleWinners:
|
||||
return loc.multiple_winners;
|
||||
}
|
||||
}
|
||||
|
||||
166
lib/data/dao/game_dao.dart
Normal file
@@ -0,0 +1,166 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
8
lib/data/dao/game_dao.g.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
// 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:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/db/tables/group_table.dart';
|
||||
import 'package:game_tracker/data/db/tables/player_group_table.dart';
|
||||
import 'package:game_tracker/data/dto/group.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:tallee/data/db/database.dart';
|
||||
import 'package:tallee/data/db/tables/group_table.dart';
|
||||
import 'package:tallee/data/db/tables/player_group_table.dart';
|
||||
import 'package:tallee/data/dto/group.dart';
|
||||
import 'package:tallee/data/dto/player.dart';
|
||||
|
||||
part 'group_dao.g.dart';
|
||||
|
||||
@@ -23,6 +23,7 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
return Group(
|
||||
id: groupData.id,
|
||||
name: groupData.name,
|
||||
description: groupData.description,
|
||||
members: members,
|
||||
createdAt: groupData.createdAt,
|
||||
);
|
||||
@@ -42,6 +43,7 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
return Group(
|
||||
id: result.id,
|
||||
name: result.name,
|
||||
description: result.description,
|
||||
members: members,
|
||||
createdAt: result.createdAt,
|
||||
);
|
||||
@@ -56,6 +58,7 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
GroupTableCompanion.insert(
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
description: group.description,
|
||||
createdAt: group.createdAt,
|
||||
),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
@@ -105,6 +108,7 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
(group) => GroupTableCompanion.insert(
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
description: group.description,
|
||||
createdAt: group.createdAt,
|
||||
),
|
||||
)
|
||||
@@ -132,6 +136,7 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
(p) => PlayerTableCompanion.insert(
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
description: p.description,
|
||||
createdAt: p.createdAt,
|
||||
),
|
||||
)
|
||||
@@ -176,7 +181,7 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
|
||||
/// Updates the name of the group with the given [id] to [newName].
|
||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||
Future<bool> updateGroupname({
|
||||
Future<bool> updateGroupName({
|
||||
required String groupId,
|
||||
required String newName,
|
||||
}) async {
|
||||
@@ -187,6 +192,21 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
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.
|
||||
Future<int> getGroupCount() async {
|
||||
final count =
|
||||
@@ -211,4 +231,44 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
final rowsAffected = await query.go();
|
||||
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,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,98 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
// 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,13 +1,17 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/db/tables/match_table.dart';
|
||||
import 'package:game_tracker/data/dto/group.dart';
|
||||
import 'package:game_tracker/data/dto/match.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:tallee/data/db/database.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_match_table.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';
|
||||
|
||||
@DriftAccessor(tables: [MatchTable])
|
||||
@DriftAccessor(tables: [MatchTable, GameTable, GroupTable, PlayerMatchTable])
|
||||
class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
||||
MatchDao(super.db);
|
||||
|
||||
@@ -18,20 +22,23 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
||||
|
||||
return Future.wait(
|
||||
result.map((row) async {
|
||||
final group = await db.groupMatchDao.getGroupOfMatch(matchId: row.id);
|
||||
final game = await db.gameDao.getGameById(gameId: row.gameId);
|
||||
Group? group;
|
||||
if (row.groupId != null) {
|
||||
group = await db.groupDao.getGroupById(groupId: row.groupId!);
|
||||
}
|
||||
final players = await db.playerMatchDao.getPlayersOfMatch(
|
||||
matchId: row.id,
|
||||
);
|
||||
final winner = row.winnerId != null
|
||||
? await db.playerDao.getPlayerById(playerId: row.winnerId!)
|
||||
: null;
|
||||
) ?? [];
|
||||
return Match(
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
name: row.name ?? '',
|
||||
game: game,
|
||||
group: group,
|
||||
players: players,
|
||||
notes: row.notes ?? '',
|
||||
createdAt: row.createdAt,
|
||||
winner: winner,
|
||||
endedAt: row.endedAt,
|
||||
);
|
||||
}),
|
||||
);
|
||||
@@ -42,69 +49,108 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
||||
final query = select(matchTable)..where((g) => g.id.equals(matchId));
|
||||
final result = await query.getSingle();
|
||||
|
||||
List<Player>? players;
|
||||
if (await db.playerMatchDao.matchHasPlayers(matchId: matchId)) {
|
||||
players = await db.playerMatchDao.getPlayersOfMatch(matchId: matchId);
|
||||
}
|
||||
final game = await db.gameDao.getGameById(gameId: result.gameId);
|
||||
|
||||
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!);
|
||||
if (result.groupId != null) {
|
||||
group = await db.groupDao.getGroupById(groupId: result.groupId!);
|
||||
}
|
||||
|
||||
final players = await db.playerMatchDao.getPlayersOfMatch(matchId: matchId) ?? [];
|
||||
|
||||
return Match(
|
||||
id: result.id,
|
||||
name: result.name,
|
||||
players: players,
|
||||
name: result.name ?? '',
|
||||
game: game,
|
||||
group: group,
|
||||
winner: winner,
|
||||
players: players,
|
||||
notes: result.notes ?? '',
|
||||
createdAt: result.createdAt,
|
||||
endedAt: result.endedAt,
|
||||
);
|
||||
}
|
||||
|
||||
/// Adds a new [Match] to the database. Also adds players and group
|
||||
/// associations. This method assumes that the players and groups added to
|
||||
/// this match are already present in the database.
|
||||
/// Adds a new [Match] to the database. Also adds players associations.
|
||||
/// This method assumes that the game and group (if any) are already present
|
||||
/// in the database.
|
||||
Future<void> addMatch({required Match match}) async {
|
||||
await db.transaction(() async {
|
||||
await into(matchTable).insert(
|
||||
MatchTableCompanion.insert(
|
||||
id: match.id,
|
||||
name: match.name,
|
||||
winnerId: Value(match.winner?.id),
|
||||
gameId: match.game.id,
|
||||
groupId: Value(match.group?.id),
|
||||
name: Value(match.name),
|
||||
notes: Value(match.notes),
|
||||
createdAt: match.createdAt,
|
||||
endedAt: Value(match.endedAt),
|
||||
),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
|
||||
if (match.players != null) {
|
||||
for (final p in match.players ?? []) {
|
||||
for (final p in match.players) {
|
||||
await db.playerMatchDao.addPlayerToMatch(
|
||||
matchId: match.id,
|
||||
playerId: p.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (match.group != null) {
|
||||
await db.groupMatchDao.addGroupToMatch(
|
||||
matchId: match.id,
|
||||
groupId: match.group!.id,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Adds multiple [Match]s to the database in a batch operation.
|
||||
/// Adds multiple [Match]es to the database in a batch operation.
|
||||
/// Also adds associated players and groups if they exist.
|
||||
/// 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 {
|
||||
if (matches.isEmpty) return;
|
||||
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
|
||||
await db.batch(
|
||||
(b) => b.insertAll(
|
||||
@@ -113,9 +159,12 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
||||
.map(
|
||||
(match) => MatchTableCompanion.insert(
|
||||
id: match.id,
|
||||
name: match.name,
|
||||
gameId: match.game.id,
|
||||
groupId: Value(match.group?.id),
|
||||
name: Value(match.name),
|
||||
notes: Value(match.notes),
|
||||
createdAt: match.createdAt,
|
||||
winnerId: Value(match.winner?.id),
|
||||
endedAt: Value(match.endedAt),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
@@ -123,34 +172,12 @@ 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)
|
||||
final uniquePlayers = <String, Player>{};
|
||||
for (final match in matches) {
|
||||
if (match.players != null) {
|
||||
for (final p in match.players!) {
|
||||
for (final p in match.players) {
|
||||
uniquePlayers[p.id] = p;
|
||||
}
|
||||
}
|
||||
// Also include members of groups
|
||||
if (match.group != null) {
|
||||
for (final m in match.group!.members) {
|
||||
@@ -160,8 +187,6 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
||||
}
|
||||
|
||||
if (uniquePlayers.isNotEmpty) {
|
||||
// Using insertOrIgnore to avoid triggering cascade deletes on
|
||||
// player_group/player_match associations when players already exist
|
||||
await db.batch(
|
||||
(b) => b.insertAll(
|
||||
db.playerTable,
|
||||
@@ -170,6 +195,7 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
||||
(p) => PlayerTableCompanion.insert(
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
description: p.description,
|
||||
createdAt: p.createdAt,
|
||||
),
|
||||
)
|
||||
@@ -182,19 +208,18 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
||||
// Add all player-match associations in batch
|
||||
await db.batch((b) {
|
||||
for (final match in matches) {
|
||||
if (match.players != null) {
|
||||
for (final p in match.players ?? []) {
|
||||
for (final p in match.players) {
|
||||
b.insert(
|
||||
db.playerMatchTable,
|
||||
PlayerMatchTableCompanion.insert(
|
||||
matchId: match.id,
|
||||
playerId: p.id,
|
||||
score: 0,
|
||||
),
|
||||
mode: InsertMode.insertOrIgnore,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add all player-group associations in batch
|
||||
@@ -214,22 +239,6 @@ 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -266,52 +275,20 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
||||
return rowsAffected > 0;
|
||||
}
|
||||
|
||||
/// Sets the winner of the match with the given [matchId] to the player with
|
||||
/// the given [winnerId].
|
||||
/// Updates the notes of the match with the given [matchId].
|
||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||
Future<bool> setWinner({
|
||||
Future<bool> updateMatchNotes({
|
||||
required String matchId,
|
||||
required String winnerId,
|
||||
required String? notes,
|
||||
}) async {
|
||||
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
||||
final rowsAffected = await query.write(
|
||||
MatchTableCompanion(winnerId: Value(winnerId)),
|
||||
MatchTableCompanion(notes: Value(notes)),
|
||||
);
|
||||
return rowsAffected > 0;
|
||||
}
|
||||
|
||||
/// 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].
|
||||
/// Changes the name of the match with the given [matchId] to [newName].
|
||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||
Future<bool> updateMatchName({
|
||||
required String matchId,
|
||||
@@ -323,4 +300,174 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
||||
);
|
||||
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,5 +4,11 @@ part of 'match_dao.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$MatchDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
||||
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
||||
$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:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/db/tables/player_table.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:tallee/data/db/database.dart';
|
||||
import 'package:tallee/data/db/tables/player_table.dart';
|
||||
import 'package:tallee/data/dto/player.dart';
|
||||
|
||||
part 'player_dao.g.dart';
|
||||
|
||||
@@ -15,7 +15,12 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
||||
final result = await query.get();
|
||||
return result
|
||||
.map(
|
||||
(row) => Player(id: row.id, name: row.name, createdAt: row.createdAt),
|
||||
(row) => Player(
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
createdAt: row.createdAt,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
@@ -27,6 +32,7 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
||||
return Player(
|
||||
id: result.id,
|
||||
name: result.name,
|
||||
description: result.description,
|
||||
createdAt: result.createdAt,
|
||||
);
|
||||
}
|
||||
@@ -40,6 +46,7 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
||||
PlayerTableCompanion.insert(
|
||||
id: player.id,
|
||||
name: player.name,
|
||||
description: player.description,
|
||||
createdAt: player.createdAt,
|
||||
),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
@@ -63,6 +70,7 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
||||
(player) => PlayerTableCompanion.insert(
|
||||
id: player.id,
|
||||
name: player.name,
|
||||
description: player.description,
|
||||
createdAt: player.createdAt,
|
||||
),
|
||||
)
|
||||
@@ -91,7 +99,7 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
||||
}
|
||||
|
||||
/// Updates the name of the player with the given [playerId] to [newName].
|
||||
Future<void> updatePlayername({
|
||||
Future<void> updatePlayerName({
|
||||
required String playerId,
|
||||
required String newName,
|
||||
}) async {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/db/tables/player_group_table.dart';
|
||||
import 'package:game_tracker/data/db/tables/player_table.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:tallee/data/db/database.dart';
|
||||
import 'package:tallee/data/db/tables/player_group_table.dart';
|
||||
import 'package:tallee/data/db/tables/player_table.dart';
|
||||
import 'package:tallee/data/dto/player.dart';
|
||||
|
||||
part 'player_group_dao.g.dart';
|
||||
|
||||
@@ -27,7 +27,7 @@ class PlayerGroupDao extends DatabaseAccessor<AppDatabase>
|
||||
}
|
||||
|
||||
if (!await db.playerDao.playerExists(playerId: player.id)) {
|
||||
db.playerDao.addPlayer(player: player);
|
||||
await db.playerDao.addPlayer(player: player);
|
||||
}
|
||||
|
||||
await into(playerGroupTable).insert(
|
||||
|
||||
@@ -1,23 +1,31 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/db/tables/player_match_table.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:tallee/data/db/database.dart';
|
||||
import 'package:tallee/data/db/tables/player_match_table.dart';
|
||||
import 'package:tallee/data/db/tables/team_table.dart';
|
||||
import 'package:tallee/data/dto/player.dart';
|
||||
|
||||
part 'player_match_dao.g.dart';
|
||||
|
||||
@DriftAccessor(tables: [PlayerMatchTable])
|
||||
@DriftAccessor(tables: [PlayerMatchTable, TeamTable])
|
||||
class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
|
||||
with _$PlayerMatchDaoMixin {
|
||||
PlayerMatchDao(super.db);
|
||||
|
||||
/// Associates a player with a match by inserting a record into the
|
||||
/// [PlayerMatchTable].
|
||||
/// [PlayerMatchTable]. Optionally associates with a team and sets initial score.
|
||||
Future<void> addPlayerToMatch({
|
||||
required String matchId,
|
||||
required String playerId,
|
||||
String? teamId,
|
||||
int score = 0,
|
||||
}) async {
|
||||
await into(playerMatchTable).insert(
|
||||
PlayerMatchTableCompanion.insert(playerId: playerId, matchId: matchId),
|
||||
PlayerMatchTableCompanion.insert(
|
||||
playerId: playerId,
|
||||
matchId: matchId,
|
||||
teamId: Value(teamId),
|
||||
score: score,
|
||||
),
|
||||
mode: InsertMode.insertOrIgnore,
|
||||
);
|
||||
}
|
||||
@@ -38,6 +46,50 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
|
||||
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].
|
||||
/// Returns `true` if there are players, otherwise `false`.
|
||||
Future<bool> matchHasPlayers({required String matchId}) async {
|
||||
@@ -96,7 +148,7 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
|
||||
final playersToAdd = newPlayerIdsSet.difference(currentPlayerIds);
|
||||
final playersToRemove = currentPlayerIds.difference(newPlayerIdsSet);
|
||||
|
||||
db.transaction(() async {
|
||||
await db.transaction(() async {
|
||||
// Remove old players
|
||||
if (playersToRemove.isNotEmpty) {
|
||||
await (delete(playerMatchTable)..where(
|
||||
@@ -114,6 +166,7 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
|
||||
(id) => PlayerMatchTableCompanion.insert(
|
||||
playerId: id,
|
||||
matchId: matchId,
|
||||
score: 0,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
@@ -127,4 +180,23 @@ 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,7 +5,10 @@ part of 'player_match_dao.dart';
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$PlayerMatchDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
||||
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
||||
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
||||
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
||||
$TeamTableTable get teamTable => attachedDatabase.teamTable;
|
||||
$PlayerMatchTableTable get playerMatchTable =>
|
||||
attachedDatabase.playerMatchTable;
|
||||
}
|
||||
|
||||
191
lib/data/dao/score_dao.dart
Normal file
@@ -0,0 +1,191 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
12
lib/data/dao/score_dao.g.dart
Normal file
@@ -0,0 +1,12 @@
|
||||
// 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;
|
||||
}
|
||||
147
lib/data/dao/team_dao.dart
Normal file
@@ -0,0 +1,147 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
8
lib/data/dao/team_dao.g.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
// 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,18 +1,22 @@
|
||||
import 'package:drift/drift.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: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';
|
||||
|
||||
@@ -23,7 +27,9 @@ part 'database.g.dart';
|
||||
MatchTable,
|
||||
PlayerGroupTable,
|
||||
PlayerMatchTable,
|
||||
GroupMatchTable,
|
||||
GameTable,
|
||||
TeamTable,
|
||||
ScoreTable,
|
||||
],
|
||||
daos: [
|
||||
PlayerDao,
|
||||
@@ -31,7 +37,9 @@ part 'database.g.dart';
|
||||
MatchDao,
|
||||
PlayerGroupDao,
|
||||
PlayerMatchDao,
|
||||
GroupMatchDao,
|
||||
GameDao,
|
||||
ScoreDao,
|
||||
TeamDao
|
||||
],
|
||||
)
|
||||
class AppDatabase extends _$AppDatabase {
|
||||
@@ -52,9 +60,7 @@ class AppDatabase extends _$AppDatabase {
|
||||
static QueryExecutor _openConnection() {
|
||||
return driftDatabase(
|
||||
name: 'gametracker_db',
|
||||
native: const DriftNativeOptions(
|
||||
databaseDirectory: getApplicationSupportDirectory,
|
||||
),
|
||||
native: const DriftNativeOptions(databaseDirectory: getApplicationSupportDirectory),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
14
lib/data/db/tables/game_table.dart
Normal file
@@ -0,0 +1,14 @@
|
||||
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};
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
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,6 +3,7 @@ import 'package:drift/drift.dart';
|
||||
class GroupTable extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get name => text()();
|
||||
TextColumn get description => text()();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
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 {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get name => text()();
|
||||
late final winnerId = text().nullable()();
|
||||
TextColumn get gameId =>
|
||||
text().references(GameTable, #id, onDelete: KeyAction.cascade)();
|
||||
// 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 endedAt => dateTime().nullable()();
|
||||
|
||||
@override
|
||||
Set<Column<Object>> get primaryKey => {id};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:game_tracker/data/db/tables/group_table.dart';
|
||||
import 'package:game_tracker/data/db/tables/player_table.dart';
|
||||
import 'package:tallee/data/db/tables/group_table.dart';
|
||||
import 'package:tallee/data/db/tables/player_table.dart';
|
||||
|
||||
class PlayerGroupTable extends Table {
|
||||
TextColumn get playerId =>
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:game_tracker/data/db/tables/match_table.dart';
|
||||
import 'package:game_tracker/data/db/tables/player_table.dart';
|
||||
import 'package:tallee/data/db/tables/match_table.dart';
|
||||
import 'package:tallee/data/db/tables/player_table.dart';
|
||||
import 'package:tallee/data/db/tables/team_table.dart';
|
||||
|
||||
class PlayerMatchTable extends Table {
|
||||
TextColumn get playerId =>
|
||||
text().references(PlayerTable, #id, onDelete: KeyAction.cascade)();
|
||||
TextColumn get matchId =>
|
||||
text().references(MatchTable, #id, onDelete: KeyAction.cascade)();
|
||||
TextColumn get teamId =>
|
||||
text().references(TeamTable, #id).nullable()();
|
||||
IntColumn get score => integer()();
|
||||
|
||||
@override
|
||||
Set<Column<Object>> get primaryKey => {playerId, matchId};
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:drift/drift.dart';
|
||||
class PlayerTable extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get name => text()();
|
||||
TextColumn get description => text()();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
|
||||
@override
|
||||
|
||||
16
lib/data/db/tables/score_table.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
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};
|
||||
}
|
||||
10
lib/data/db/tables/team_table.dart
Normal file
@@ -0,0 +1,10 @@
|
||||
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,23 +1,51 @@
|
||||
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 String? ruleset;
|
||||
final String? description;
|
||||
final int? color;
|
||||
final String? icon;
|
||||
final Ruleset ruleset;
|
||||
final String description;
|
||||
final GameColor color;
|
||||
final String icon;
|
||||
|
||||
Game({
|
||||
String? id,
|
||||
DateTime? createdAt,
|
||||
required this.name,
|
||||
this.ruleset,
|
||||
this.description,
|
||||
this.color,
|
||||
this.icon,
|
||||
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,40 +1,43 @@
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:tallee/data/dto/player.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class Group {
|
||||
final String id;
|
||||
final DateTime createdAt;
|
||||
final String name;
|
||||
final String description;
|
||||
final DateTime createdAt;
|
||||
final List<Player> members;
|
||||
|
||||
Group({
|
||||
String? id,
|
||||
DateTime? createdAt,
|
||||
required this.name,
|
||||
required this.description,
|
||||
required this.members,
|
||||
}) : id = id ?? const Uuid().v4(),
|
||||
createdAt = createdAt ?? clock.now();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Group{id: $id, name: $name,members: $members}';
|
||||
return 'Group{id: $id, name: $name, description: $description, members: $members}';
|
||||
}
|
||||
|
||||
/// Creates a Group instance from a JSON object.
|
||||
/// Creates a Group instance from a JSON object (memberIds format).
|
||||
/// Player objects are reconstructed from memberIds by the DataTransferService.
|
||||
Group.fromJson(Map<String, dynamic> json)
|
||||
: id = json['id'],
|
||||
createdAt = DateTime.parse(json['createdAt']),
|
||||
name = json['name'],
|
||||
members = (json['members'] as List)
|
||||
.map((memberJson) => Player.fromJson(memberJson))
|
||||
.toList();
|
||||
description = json['description'],
|
||||
members = []; // Populated during import via DataTransferService
|
||||
|
||||
/// Converts the Group instance to a JSON object.
|
||||
/// Converts the Group instance to a JSON object using normalized format (memberIds only).
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'name': name,
|
||||
'members': members.map((member) => member.toJson()).toList(),
|
||||
'description': description,
|
||||
'memberIds': members.map((member) => member.id).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,51 +1,60 @@
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:game_tracker/data/dto/group.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/data/dto/game.dart';
|
||||
import 'package:tallee/data/dto/group.dart';
|
||||
import 'package:tallee/data/dto/player.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class Match {
|
||||
final String id;
|
||||
final DateTime createdAt;
|
||||
final DateTime? endedAt;
|
||||
final String name;
|
||||
final List<Player>? players;
|
||||
final Game game;
|
||||
final Group? group;
|
||||
final List<Player> players;
|
||||
final String notes;
|
||||
Player? winner;
|
||||
|
||||
Match({
|
||||
String? id,
|
||||
DateTime? createdAt,
|
||||
this.endedAt,
|
||||
required this.name,
|
||||
this.players,
|
||||
required this.game,
|
||||
this.group,
|
||||
this.players = const [],
|
||||
required this.notes,
|
||||
this.winner,
|
||||
}) : id = id ?? const Uuid().v4(),
|
||||
createdAt = createdAt ?? clock.now();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Match{\n\tid: $id,\n\tname: $name,\n\tplayers: $players,\n\tgroup: $group,\n\twinner: $winner\n}';
|
||||
return 'Match{id: $id, name: $name, game: $game, group: $group, players: $players, notes: $notes, endedAt: $endedAt}';
|
||||
}
|
||||
|
||||
/// Creates a Match instance from a JSON object.
|
||||
/// Creates a Match instance from a JSON object (ID references format).
|
||||
/// Related objects are reconstructed from IDs by the DataTransferService.
|
||||
Match.fromJson(Map<String, dynamic> json)
|
||||
: id = json['id'],
|
||||
name = json['name'],
|
||||
createdAt = DateTime.parse(json['createdAt']),
|
||||
players = json['players'] != null
|
||||
? (json['players'] as List)
|
||||
.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;
|
||||
endedAt = json['endedAt'] != null ? DateTime.parse(json['endedAt']) : null,
|
||||
name = json['name'],
|
||||
game = Game(name: '', ruleset: Ruleset.singleWinner, description: '', color: GameColor.blue, icon: ''), // Populated during import via DataTransferService
|
||||
group = null, // Populated during import via DataTransferService
|
||||
players = [], // Populated during import via DataTransferService
|
||||
notes = json['notes'] ?? '';
|
||||
|
||||
/// Converts the Match instance to a JSON object.
|
||||
/// Converts the Match instance to a JSON object using normalized format (ID references only).
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'endedAt': endedAt?.toIso8601String(),
|
||||
'name': name,
|
||||
'players': players?.map((player) => player.toJson()).toList(),
|
||||
'group': group?.toJson(),
|
||||
'winner': winner?.toJson(),
|
||||
'gameId': game.id,
|
||||
'groupId': group?.id,
|
||||
'playerIds': players.map((player) => player.id).toList(),
|
||||
'notes': notes,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,26 +5,33 @@ class Player {
|
||||
final String id;
|
||||
final DateTime createdAt;
|
||||
final String name;
|
||||
final String description;
|
||||
|
||||
Player({String? id, DateTime? createdAt, required this.name})
|
||||
: id = id ?? const Uuid().v4(),
|
||||
Player({
|
||||
String? id,
|
||||
DateTime? createdAt,
|
||||
required this.name,
|
||||
required this.description,
|
||||
}) : id = id ?? const Uuid().v4(),
|
||||
createdAt = createdAt ?? clock.now();
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'Player{id: $id,name: $name}';
|
||||
return 'Player{id: $id, name: $name, description: $description}';
|
||||
}
|
||||
|
||||
/// Creates a Player instance from a JSON object.
|
||||
Player.fromJson(Map<String, dynamic> json)
|
||||
: id = json['id'],
|
||||
createdAt = DateTime.parse(json['createdAt']),
|
||||
name = json['name'];
|
||||
name = json['name'],
|
||||
description = json['description'];
|
||||
|
||||
/// Converts the Player instance to a JSON object.
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'name': name,
|
||||
'description': description,
|
||||
};
|
||||
}
|
||||
|
||||
40
lib/data/dto/team.dart
Normal file
@@ -0,0 +1,40 @@
|
||||
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,14 +3,13 @@
|
||||
"all_players": "Alle Spieler:innen",
|
||||
"all_players_selected": "Alle Spieler:innen ausgewählt",
|
||||
"amount_of_matches": "Anzahl der Spiele",
|
||||
"app_name": "Game Tracker",
|
||||
"app_name": "Tallee",
|
||||
"best_player": "Beste:r Spieler:in",
|
||||
"cancel": "Abbrechen",
|
||||
"choose_game": "Spielvorlage wählen",
|
||||
"choose_group": "Gruppe wählen",
|
||||
"choose_ruleset": "Regelwerk wählen",
|
||||
"could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden",
|
||||
"create_game": "Spielvorlage erstellen",
|
||||
"create_group": "Gruppe erstellen",
|
||||
"create_match": "Spiel erstellen",
|
||||
"create_new_group": "Neue Gruppe erstellen",
|
||||
@@ -23,10 +22,7 @@
|
||||
"days_ago": "vor {count} Tagen",
|
||||
"delete": "Löschen",
|
||||
"delete_all_data": "Alle Daten löschen",
|
||||
"delete_game": "Spielvorlage löschen",
|
||||
"delete_group": "Gruppe löschen",
|
||||
"description": "Beschreibung",
|
||||
"edit_game": "Spielvorlage bearbeiten",
|
||||
"edit_group": "Gruppe bearbeiten",
|
||||
"error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen",
|
||||
"error_reading_file": "Fehler beim Lesen der Datei",
|
||||
@@ -86,6 +82,9 @@
|
||||
"settings": "Einstellungen",
|
||||
"single_loser": "Ein:e Verlierer:in",
|
||||
"single_winner": "Ein:e Gewinner:in",
|
||||
"highest_score": "Höchste Punkte",
|
||||
"lowest_score": "Niedrigste Punkte",
|
||||
"multiple_winners": "Mehrere Gewinner:innen",
|
||||
"statistics": "Statistiken",
|
||||
"stats": "Statistiken",
|
||||
"successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt",
|
||||
|
||||
@@ -30,9 +30,6 @@
|
||||
"@could_not_add_player": {
|
||||
"description": "Error message when adding a player fails"
|
||||
},
|
||||
"@create_game": {
|
||||
"description": "Button text to create a game"
|
||||
},
|
||||
"@create_group": {
|
||||
"description": "Button text to create a group"
|
||||
},
|
||||
@@ -74,18 +71,9 @@
|
||||
"@delete_all_data": {
|
||||
"description": "Confirmation dialog for deleting all data"
|
||||
},
|
||||
"@delete_game": {
|
||||
"description": "Button text to delete a game"
|
||||
},
|
||||
"@delete_group": {
|
||||
"description": "Button text to delete a group"
|
||||
},
|
||||
"description": {
|
||||
"description": "Description label"
|
||||
},
|
||||
"edit_game": {
|
||||
"description": "Button text to edit a game"
|
||||
},
|
||||
"@edit_group": {
|
||||
"description": "Button text to edit a group"
|
||||
},
|
||||
@@ -313,14 +301,13 @@
|
||||
"all_players": "All players",
|
||||
"all_players_selected": "All players selected",
|
||||
"amount_of_matches": "Amount of Matches",
|
||||
"app_name": "Game Tracker",
|
||||
"app_name": "Tallee",
|
||||
"best_player": "Best Player",
|
||||
"cancel": "Cancel",
|
||||
"choose_game": "Choose Game",
|
||||
"choose_group": "Choose Group",
|
||||
"choose_ruleset": "Choose Ruleset",
|
||||
"could_not_add_player": "Could not add player",
|
||||
"create_game": "Create Game",
|
||||
"create_group": "Create Group",
|
||||
"create_match": "Create match",
|
||||
"create_new_group": "Create new group",
|
||||
@@ -333,10 +320,7 @@
|
||||
"days_ago": "{count} days ago",
|
||||
"delete": "Delete",
|
||||
"delete_all_data": "Delete all data",
|
||||
"delete_game": "Delete Game",
|
||||
"delete_group": "Delete Group",
|
||||
"description": "Description",
|
||||
"edit_game": "Edit Game",
|
||||
"edit_group": "Edit Group",
|
||||
"error_creating_group": "Error while creating group, please try again",
|
||||
"error_reading_file": "Error reading file",
|
||||
@@ -396,6 +380,9 @@
|
||||
"settings": "Settings",
|
||||
"single_loser": "Single Loser",
|
||||
"single_winner": "Single Winner",
|
||||
"highest_score": "Highest Score",
|
||||
"lowest_score": "Lowest Score",
|
||||
"multiple_winners": "Multiple Winners",
|
||||
"statistics": "Statistics",
|
||||
"stats": "Stats",
|
||||
"successfully_added_player": "Successfully added player {playerName}",
|
||||
|
||||
@@ -98,18 +98,6 @@ abstract class AppLocalizations {
|
||||
Locale('en'),
|
||||
];
|
||||
|
||||
/// No description provided for @description.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Description'**
|
||||
String get description;
|
||||
|
||||
/// No description provided for @edit_game.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Edit Game'**
|
||||
String get edit_game;
|
||||
|
||||
/// Label for all players list
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -131,7 +119,7 @@ abstract class AppLocalizations {
|
||||
/// The name of the App
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Game Tracker'**
|
||||
/// **'Tallee'**
|
||||
String get app_name;
|
||||
|
||||
/// Label for best player statistic
|
||||
@@ -170,12 +158,6 @@ abstract class AppLocalizations {
|
||||
/// **'Could not add player'**
|
||||
String could_not_add_player(Object playerName);
|
||||
|
||||
/// Button text to create a game
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Create Game'**
|
||||
String get create_game;
|
||||
|
||||
/// Button text to create a group
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -248,12 +230,6 @@ abstract class AppLocalizations {
|
||||
/// **'Delete all data'**
|
||||
String get delete_all_data;
|
||||
|
||||
/// Button text to delete a game
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete Game'**
|
||||
String get delete_game;
|
||||
|
||||
/// Button text to delete a group
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
@@ -614,6 +590,24 @@ abstract class AppLocalizations {
|
||||
/// **'Single Winner'**
|
||||
String get single_winner;
|
||||
|
||||
/// No description provided for @highest_score.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Highest Score'**
|
||||
String get highest_score;
|
||||
|
||||
/// No description provided for @lowest_score.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Lowest Score'**
|
||||
String get lowest_score;
|
||||
|
||||
/// No description provided for @multiple_winners.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Multiple Winners'**
|
||||
String get multiple_winners;
|
||||
|
||||
/// Statistics tab label
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -8,12 +8,6 @@ import 'app_localizations.dart';
|
||||
class AppLocalizationsDe extends AppLocalizations {
|
||||
AppLocalizationsDe([String locale = 'de']) : super(locale);
|
||||
|
||||
@override
|
||||
String get description => 'Beschreibung';
|
||||
|
||||
@override
|
||||
String get edit_game => 'Spielvorlage bearbeiten';
|
||||
|
||||
@override
|
||||
String get all_players => 'Alle Spieler:innen';
|
||||
|
||||
@@ -24,7 +18,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
String get amount_of_matches => 'Anzahl der Spiele';
|
||||
|
||||
@override
|
||||
String get app_name => 'Game Tracker';
|
||||
String get app_name => 'Tallee';
|
||||
|
||||
@override
|
||||
String get best_player => 'Beste:r Spieler:in';
|
||||
@@ -46,9 +40,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
return 'Spieler:in $playerName konnte nicht hinzugefügt werden';
|
||||
}
|
||||
|
||||
@override
|
||||
String get create_game => 'Spielvorlage erstellen';
|
||||
|
||||
@override
|
||||
String get create_group => 'Gruppe erstellen';
|
||||
|
||||
@@ -87,9 +78,6 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get delete_all_data => 'Alle Daten löschen';
|
||||
|
||||
@override
|
||||
String get delete_game => 'Spielvorlage löschen';
|
||||
|
||||
@override
|
||||
String get delete_group => 'Gruppe löschen';
|
||||
|
||||
@@ -278,6 +266,15 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get single_winner => 'Ein:e Gewinner:in';
|
||||
|
||||
@override
|
||||
String get highest_score => 'Höchste Punkte';
|
||||
|
||||
@override
|
||||
String get lowest_score => 'Niedrigste Punkte';
|
||||
|
||||
@override
|
||||
String get multiple_winners => 'Mehrere Gewinner:innen';
|
||||
|
||||
@override
|
||||
String get statistics => 'Statistiken';
|
||||
|
||||
|
||||
@@ -8,12 +8,6 @@ import 'app_localizations.dart';
|
||||
class AppLocalizationsEn extends AppLocalizations {
|
||||
AppLocalizationsEn([String locale = 'en']) : super(locale);
|
||||
|
||||
@override
|
||||
String get description => 'Description';
|
||||
|
||||
@override
|
||||
String get edit_game => 'Edit Game';
|
||||
|
||||
@override
|
||||
String get all_players => 'All players';
|
||||
|
||||
@@ -24,7 +18,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get amount_of_matches => 'Amount of Matches';
|
||||
|
||||
@override
|
||||
String get app_name => 'Game Tracker';
|
||||
String get app_name => 'Tallee';
|
||||
|
||||
@override
|
||||
String get best_player => 'Best Player';
|
||||
@@ -46,9 +40,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
return 'Could not add player';
|
||||
}
|
||||
|
||||
@override
|
||||
String get create_game => 'Create Game';
|
||||
|
||||
@override
|
||||
String get create_group => 'Create Group';
|
||||
|
||||
@@ -87,9 +78,6 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get delete_all_data => 'Delete all data';
|
||||
|
||||
@override
|
||||
String get delete_game => 'Delete Game';
|
||||
|
||||
@override
|
||||
String get delete_group => 'Delete Group';
|
||||
|
||||
@@ -278,6 +266,15 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get single_winner => 'Single Winner';
|
||||
|
||||
@override
|
||||
String get highest_score => 'Highest Score';
|
||||
|
||||
@override
|
||||
String get lowest_score => 'Lowest Score';
|
||||
|
||||
@override
|
||||
String get multiple_winners => 'Multiple Winners';
|
||||
|
||||
@override
|
||||
String get statistics => 'Statistics';
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||
import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/data/db/database.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/custom_navigation_bar.dart';
|
||||
|
||||
void main() {
|
||||
runApp(
|
||||
@@ -29,9 +29,7 @@ class GameTracker extends StatelessWidget {
|
||||
return supportedLocale;
|
||||
}
|
||||
}
|
||||
return supportedLocales.firstWhere(
|
||||
(locale) => locale.languageCode == 'en',
|
||||
);
|
||||
return supportedLocales.firstWhere((locale) => locale.languageCode == 'en');
|
||||
},
|
||||
debugShowCheckedModeBanner: false,
|
||||
onGenerateTitle: (context) => AppLocalizations.of(context).app_name,
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/adaptive_page_route.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||
import 'package:game_tracker/presentation/views/main_menu/group_view/groups_view.dart';
|
||||
import 'package:game_tracker/presentation/views/main_menu/home_view.dart';
|
||||
import 'package:game_tracker/presentation/views/main_menu/match_view/match_view.dart';
|
||||
import 'package:game_tracker/presentation/views/main_menu/settings_view/settings_view.dart';
|
||||
import 'package:game_tracker/presentation/views/main_menu/statistics_view.dart';
|
||||
import 'package:game_tracker/presentation/widgets/navbar_item.dart';
|
||||
import 'package:tallee/core/adaptive_page_route.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/group_view/groups_view.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/home_view.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/match_view/match_view.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/settings_view/settings_view.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/statistics_view.dart';
|
||||
import 'package:tallee/presentation/widgets/navbar_item.dart';
|
||||
|
||||
class CustomNavigationBar extends StatefulWidget {
|
||||
/// A custom navigation bar widget that provides tabbed navigation
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/constants.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
import 'package:game_tracker/core/enums.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/dto/group.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||
import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart';
|
||||
import 'package:game_tracker/presentation/widgets/player_selection.dart';
|
||||
import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tallee/core/constants.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/data/db/database.dart';
|
||||
import 'package:tallee/data/dto/group.dart';
|
||||
import 'package:tallee/data/dto/player.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart';
|
||||
import 'package:tallee/presentation/widgets/player_selection.dart';
|
||||
import 'package:tallee/presentation/widgets/text_input/text_input_field.dart';
|
||||
|
||||
class CreateGroupView extends StatefulWidget {
|
||||
/// A view that allows the user to create a new group
|
||||
@@ -84,6 +84,7 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
bool success = await db.groupDao.addGroup(
|
||||
group: Group(
|
||||
name: _groupNameController.text.trim(),
|
||||
description: '',
|
||||
members: selectedPlayers,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/dto/group.dart';
|
||||
import 'package:game_tracker/data/dto/match.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||
import 'package:game_tracker/presentation/widgets/app_skeleton.dart';
|
||||
import 'package:game_tracker/presentation/widgets/buttons/animated_dialog_button.dart';
|
||||
import 'package:game_tracker/presentation/widgets/buttons/main_menu_button.dart';
|
||||
import 'package:game_tracker/presentation/widgets/colored_icon_container.dart';
|
||||
import 'package:game_tracker/presentation/widgets/custom_alert_dialog.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/data/db/database.dart';
|
||||
import 'package:tallee/data/dto/group.dart';
|
||||
import 'package:tallee/data/dto/match.dart';
|
||||
import 'package:tallee/data/dto/player.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/widgets/app_skeleton.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/animated_dialog_button.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart';
|
||||
import 'package:tallee/presentation/widgets/colored_icon_container.dart';
|
||||
import 'package:tallee/presentation/widgets/custom_alert_dialog.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/info_tile.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart';
|
||||
|
||||
class GroupProfileView extends StatefulWidget {
|
||||
/// A view that displays the profile of a group
|
||||
@@ -78,7 +78,9 @@ class _GroupProfileViewState extends State<GroupProfileView> {
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: Text(
|
||||
loc.delete,
|
||||
style: TextStyle(color: CustomTheme.secondaryColor),
|
||||
style: const TextStyle(
|
||||
color: CustomTheme.secondaryColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/adaptive_page_route.dart';
|
||||
import 'package:game_tracker/core/constants.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/dto/group.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||
import 'package:game_tracker/presentation/views/main_menu/group_view/create_group_view.dart';
|
||||
import 'package:game_tracker/presentation/views/main_menu/group_view/group_profile_view.dart';
|
||||
import 'package:game_tracker/presentation/widgets/app_skeleton.dart';
|
||||
import 'package:game_tracker/presentation/widgets/buttons/main_menu_button.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart';
|
||||
import 'package:game_tracker/presentation/widgets/top_centered_message.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tallee/core/adaptive_page_route.dart';
|
||||
import 'package:tallee/core/constants.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/data/db/database.dart';
|
||||
import 'package:tallee/data/dto/group.dart';
|
||||
import 'package:tallee/data/dto/player.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/group_view/create_group_view.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/group_view/group_profile_view.dart';
|
||||
import 'package:tallee/presentation/widgets/app_skeleton.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/group_tile.dart';
|
||||
import 'package:tallee/presentation/widgets/top_centered_message.dart';
|
||||
|
||||
class GroupsView extends StatefulWidget {
|
||||
/// A view that displays a list of groups
|
||||
@@ -35,7 +35,8 @@ class _GroupsViewState extends State<GroupsView> {
|
||||
7,
|
||||
Group(
|
||||
name: 'Skeleton Group',
|
||||
members: List.filled(6, Player(name: 'Skeleton Player')),
|
||||
description: '',
|
||||
members: List.filled(6, Player(name: 'Skeleton Player', description: '')),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/adaptive_page_route.dart';
|
||||
import 'package:game_tracker/core/constants.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/dto/group.dart';
|
||||
import 'package:game_tracker/data/dto/match.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||
import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart';
|
||||
import 'package:game_tracker/presentation/widgets/app_skeleton.dart';
|
||||
import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/match_tile.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tallee/core/adaptive_page_route.dart';
|
||||
import 'package:tallee/core/constants.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/data/db/database.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';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart';
|
||||
import 'package:tallee/presentation/widgets/app_skeleton.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/quick_create_button.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/info_tile.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/match_tile.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/quick_info_tile.dart';
|
||||
|
||||
class HomeView extends StatefulWidget {
|
||||
/// The main home view of the application, displaying quick info,
|
||||
@@ -40,13 +42,16 @@ class _HomeViewState extends State<HomeView> {
|
||||
2,
|
||||
Match(
|
||||
name: 'Skeleton Match',
|
||||
game: Game(name: '', ruleset: Ruleset.singleWinner, description: '', color: GameColor.blue, icon: ''),
|
||||
group: Group(
|
||||
name: 'Skeleton Group',
|
||||
description: '',
|
||||
members: [
|
||||
Player(name: 'Skeleton Player 1'),
|
||||
Player(name: 'Skeleton Player 2'),
|
||||
Player(name: 'Skeleton Player 1', description: ''),
|
||||
Player(name: 'Skeleton Player 2', description: ''),
|
||||
],
|
||||
),
|
||||
notes: '',
|
||||
),
|
||||
);
|
||||
|
||||
@@ -99,9 +104,7 @@ class _HomeViewState extends State<HomeView> {
|
||||
if (recentMatches.isNotEmpty)
|
||||
for (Match match in recentMatches)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 6.0,
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(vertical: 6.0),
|
||||
child: MatchTile(
|
||||
compact: true,
|
||||
width: constraints.maxWidth * 0.9,
|
||||
@@ -110,19 +113,15 @@ class _HomeViewState extends State<HomeView> {
|
||||
await Navigator.of(context).push(
|
||||
adaptivePageRoute(
|
||||
fullscreenDialog: true,
|
||||
builder: (context) =>
|
||||
MatchResultView(match: match),
|
||||
builder: (context) => MatchResultView(match: match),
|
||||
),
|
||||
);
|
||||
await updatedWinnerinRecentMatches(match.id);
|
||||
await updatedWinnerInRecentMatches(match.id);
|
||||
},
|
||||
),
|
||||
)
|
||||
else
|
||||
Center(
|
||||
heightFactor: 5,
|
||||
child: Text(loc.no_recent_matches_available),
|
||||
),
|
||||
Center(heightFactor: 5, child: Text(loc.no_recent_matches_available)),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -138,40 +137,22 @@ class _HomeViewState extends State<HomeView> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
QuickCreateButton(
|
||||
text: 'Category 1',
|
||||
onPressed: () {},
|
||||
),
|
||||
QuickCreateButton(
|
||||
text: 'Category 2',
|
||||
onPressed: () {},
|
||||
),
|
||||
QuickCreateButton(text: 'Category 1', onPressed: () {}),
|
||||
QuickCreateButton(text: 'Category 2', onPressed: () {}),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
QuickCreateButton(
|
||||
text: 'Category 3',
|
||||
onPressed: () {},
|
||||
),
|
||||
QuickCreateButton(
|
||||
text: 'Category 4',
|
||||
onPressed: () {},
|
||||
),
|
||||
QuickCreateButton(text: 'Category 3', onPressed: () {}),
|
||||
QuickCreateButton(text: 'Category 4', onPressed: () {}),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
QuickCreateButton(
|
||||
text: 'Category 5',
|
||||
onPressed: () {},
|
||||
),
|
||||
QuickCreateButton(
|
||||
text: 'Category 6',
|
||||
onPressed: () {},
|
||||
),
|
||||
QuickCreateButton(text: 'Category 5', onPressed: () {}),
|
||||
QuickCreateButton(text: 'Category 6', onPressed: () {}),
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -200,9 +181,7 @@ class _HomeViewState extends State<HomeView> {
|
||||
matchCount = results[0] as int;
|
||||
groupCount = results[1] as int;
|
||||
loadedRecentMatches = results[2] as List<Match>;
|
||||
recentMatches =
|
||||
(loadedRecentMatches
|
||||
..sort((a, b) => b.createdAt.compareTo(a.createdAt)))
|
||||
recentMatches = (loadedRecentMatches..sort((a, b) => b.createdAt.compareTo(a.createdAt)))
|
||||
.take(2)
|
||||
.toList();
|
||||
if (mounted) {
|
||||
@@ -214,7 +193,7 @@ class _HomeViewState extends State<HomeView> {
|
||||
}
|
||||
|
||||
/// Updates the winner information for a specific match in the recent matches list.
|
||||
Future<void> updatedWinnerinRecentMatches(String matchId) async {
|
||||
Future<void> updatedWinnerInRecentMatches(String matchId) async {
|
||||
final db = Provider.of<AppDatabase>(context, listen: false);
|
||||
final winner = await db.matchDao.getWinner(matchId: matchId);
|
||||
final matchIndex = recentMatches.indexWhere((match) => match.id == matchId);
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/adaptive_page_route.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
import 'package:game_tracker/core/enums.dart';
|
||||
import 'package:game_tracker/data/dto/game.dart';
|
||||
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||
import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/game_view/create_game_view.dart';
|
||||
import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/title_description_list_tile.dart';
|
||||
|
||||
class ChooseGameView extends StatefulWidget {
|
||||
/// A view that allows the user to choose a game from a list of available games
|
||||
@@ -54,17 +51,6 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
||||
Navigator.of(context).pop(selectedGameIndex);
|
||||
},
|
||||
),
|
||||
actions: [IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () async {
|
||||
await Navigator.push(context, adaptivePageRoute(
|
||||
builder: (context) => CreateGameView(
|
||||
callback: () {}, //TODO: implement callback
|
||||
),
|
||||
)
|
||||
);
|
||||
},
|
||||
)],
|
||||
title: Text(loc.choose_game),
|
||||
),
|
||||
body: PopScope(
|
||||
@@ -99,7 +85,7 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
||||
context,
|
||||
),
|
||||
isHighlighted: selectedGameIndex == index,
|
||||
onTap: () async {
|
||||
onPressed: () async {
|
||||
setState(() {
|
||||
if (selectedGameIndex == index) {
|
||||
selectedGameIndex = -1;
|
||||
@@ -108,16 +94,6 @@ class _ChooseGameViewState extends State<ChooseGameView> {
|
||||
}
|
||||
});
|
||||
},
|
||||
onLongPress: () async {
|
||||
await Navigator.push(context, adaptivePageRoute(
|
||||
builder: (context) => CreateGameView(
|
||||
//TODO: implement callback & giving real game to create game view
|
||||
gameToEdit: Game(name: 'Cabo', description: '', ruleset: 'Highest Points'),
|
||||
callback: () {},
|
||||
),
|
||||
)
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
import 'package:game_tracker/data/dto/group.dart';
|
||||
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||
import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart';
|
||||
import 'package:game_tracker/presentation/widgets/top_centered_message.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/data/dto/group.dart';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/group_tile.dart';
|
||||
import 'package:tallee/presentation/widgets/top_centered_message.dart';
|
||||
|
||||
class ChooseGroupView extends StatefulWidget {
|
||||
/// A view that allows the user to choose a group from a list of groups.
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/adaptive_page_route.dart';
|
||||
import 'package:game_tracker/core/constants.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
import 'package:game_tracker/core/enums.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/dto/group.dart';
|
||||
import 'package:game_tracker/data/dto/match.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||
import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_game_view.dart';
|
||||
import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_group_view.dart';
|
||||
import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart';
|
||||
import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart';
|
||||
import 'package:game_tracker/presentation/widgets/player_selection.dart';
|
||||
import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/choose_tile.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tallee/core/adaptive_page_route.dart';
|
||||
import 'package:tallee/core/constants.dart';
|
||||
import 'package:tallee/core/custom_theme.dart';
|
||||
import 'package:tallee/core/enums.dart';
|
||||
import 'package:tallee/data/db/database.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';
|
||||
import 'package:tallee/l10n/generated/app_localizations.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/match_view/create_match/choose_game_view.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/match_view/create_match/choose_group_view.dart';
|
||||
import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart';
|
||||
import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart';
|
||||
import 'package:tallee/presentation/widgets/player_selection.dart';
|
||||
import 'package:tallee/presentation/widgets/text_input/text_input_field.dart';
|
||||
import 'package:tallee/presentation/widgets/tiles/choose_tile.dart';
|
||||
|
||||
class CreateMatchView extends StatefulWidget {
|
||||
/// A view that allows creating a new match
|
||||
@@ -62,7 +63,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
int selectedGameIndex = -1;
|
||||
|
||||
/// The currently selected players
|
||||
List<Player>? selectedPlayers;
|
||||
List<Player> selectedPlayers = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -99,7 +100,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
}
|
||||
|
||||
List<(String, String, Ruleset)> games = [
|
||||
('Example Game 1', 'This is a description', Ruleset.leastPoints),
|
||||
('Example Game 1', 'This is a description', Ruleset.lowestScore),
|
||||
('Example Game 2', '', Ruleset.singleWinner),
|
||||
];
|
||||
|
||||
@@ -177,7 +178,7 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
Expanded(
|
||||
child: PlayerSelection(
|
||||
key: ValueKey(selectedGroup?.id ?? 'no_group'),
|
||||
initialSelectedPlayers: selectedPlayers ?? [],
|
||||
initialSelectedPlayers: selectedPlayers,
|
||||
availablePlayers: filteredPlayerList,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
@@ -192,13 +193,41 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
buttonType: ButtonType.primary,
|
||||
onPressed: _enableCreateGameButton()
|
||||
? () async {
|
||||
// Use a game from the games list
|
||||
Game? gameToUse;
|
||||
if (selectedGameIndex == -1) {
|
||||
// Use the first game as default if none selected
|
||||
final selectedGame = games[0];
|
||||
gameToUse = Game(
|
||||
name: selectedGame.$1,
|
||||
description: selectedGame.$2,
|
||||
ruleset: selectedGame.$3,
|
||||
color: GameColor.blue,
|
||||
icon: '',
|
||||
);
|
||||
} else {
|
||||
// Use the selected game from the list
|
||||
final selectedGame = games[selectedGameIndex];
|
||||
gameToUse = Game(
|
||||
name: selectedGame.$1,
|
||||
description: selectedGame.$2,
|
||||
ruleset: selectedGame.$3,
|
||||
color: GameColor.blue,
|
||||
icon: '',
|
||||
);
|
||||
}
|
||||
// Add the game to the database if it doesn't exist
|
||||
await db.gameDao.addGame(game: gameToUse);
|
||||
|
||||
Match match = Match(
|
||||
name: _matchNameController.text.isEmpty
|
||||
? (hintText ?? '')
|
||||
: _matchNameController.text.trim(),
|
||||
createdAt: DateTime.now(),
|
||||
game: gameToUse,
|
||||
group: selectedGroup,
|
||||
players: selectedPlayers,
|
||||
notes: '',
|
||||
);
|
||||
await db.matchDao.addMatch(match: match);
|
||||
if (context.mounted) {
|
||||
@@ -230,6 +259,6 @@ class _CreateMatchViewState extends State<CreateMatchView> {
|
||||
/// - Either a group is selected OR at least 2 players are selected
|
||||
bool _enableCreateGameButton() {
|
||||
return (selectedGroup != null ||
|
||||
(selectedPlayers != null && selectedPlayers!.length > 1));
|
||||
(selectedPlayers.length > 1));
|
||||
}
|
||||
}
|
||||