diff --git a/.gitea/workflows/pull_request.yaml b/.gitea/workflows/pull_request.yaml
index 43d36d2..26f4404 100644
--- a/.gitea/workflows/pull_request.yaml
+++ b/.gitea/workflows/pull_request.yaml
@@ -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
\ No newline at end of file
+ run: flutter test
diff --git a/.gitea/workflows/push.yaml b/.gitea/workflows/push.yaml
index 700e96b..e24f7ad 100644
--- a/.gitea/workflows/push.yaml
+++ b/.gitea/workflows/push.yaml
@@ -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
- run: |
- dart format lib
- dart fix --apply lib
+ - name: Build APK
+ run: flutter build apk --release
+
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
- # Needs credentials, push access and the right files need to be staged
- - name: Commit Changes
+ # Required for Flutter action
+ - name: Install jq
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
+ 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
+
+ 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
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..d6e91f5
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,13 @@
+# Contributing
+
+## Code of Conduct
+
+``
+
+## Code Style
+
+``
+
+## Repository structure
+
+``
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..153d416
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ 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.
\ No newline at end of file
diff --git a/README.md b/README.md
index 4d5b30c..70ab8e6 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,63 @@
-# Game Tracker
+
+
+
Tallee
+
+
+An open-source app to track card- and board games, manage players & groups and get statistics about your played games.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+## Screenshots
+
+
+
+## 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
+
+
+
+
+
+
+## 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.
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index edde6d6..0d5c4b8 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -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
diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 3c48c4a..e722349 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -1,7 +1,7 @@
- #E6F1E4
- #0B0B0B
+ #ef681f
\ No newline at end of file
diff --git a/android/app/src/main/res/values/ic_launcher_background.xml b/android/app/src/main/res/values/ic_launcher_background.xml
index 2971c37..fd4cd85 100644
--- a/android/app/src/main/res/values/ic_launcher_background.xml
+++ b/android/app/src/main/res/values/ic_launcher_background.xml
@@ -1,5 +1,4 @@
-
- @color/app_icon_background
+ #EF681F
\ No newline at end of file
diff --git a/artefacts/app-logo.png b/artefacts/app-logo.png
new file mode 100644
index 0000000..a89cc43
Binary files /dev/null and b/artefacts/app-logo.png differ
diff --git a/artefacts/screenshot-1.png b/artefacts/screenshot-1.png
new file mode 100644
index 0000000..34e75bd
Binary files /dev/null and b/artefacts/screenshot-1.png differ
diff --git a/artefacts/screenshot-2.png b/artefacts/screenshot-2.png
new file mode 100644
index 0000000..541be8b
Binary files /dev/null and b/artefacts/screenshot-2.png differ
diff --git a/artefacts/screenshot-3.png b/artefacts/screenshot-3.png
new file mode 100644
index 0000000..bfa3b7e
Binary files /dev/null and b/artefacts/screenshot-3.png differ
diff --git a/artefacts/screenshot-4.png b/artefacts/screenshot-4.png
new file mode 100644
index 0000000..ef00e29
Binary files /dev/null and b/artefacts/screenshot-4.png differ
diff --git a/assets/schema.json b/assets/schema.json
index b3a8a2c..8021012 100644
--- a/assets/schema.json
+++ b/assets/schema.json
@@ -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"
]
}
\ No newline at end of file
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index 8358b1c..27638af 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -152,7 +152,6 @@
B68CF4A64F0B5E45B43D6900 /* Pods-RunnerTests.release.xcconfig */,
E754D1191B3E54E52B6DCC49 /* Pods-RunnerTests.profile.xcconfig */,
);
- name = Pods;
path = Pods;
sourceTree = "";
};
@@ -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;
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png
new file mode 100644
index 0000000..94edbb6
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/1024.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png
new file mode 100644
index 0000000..11f8618
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/114.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png
new file mode 100644
index 0000000..165a8a8
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/120.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png
new file mode 100644
index 0000000..b346952
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/180.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png
new file mode 100644
index 0000000..8745a0f
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/29.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png
new file mode 100644
index 0000000..9b48058
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/40.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png
new file mode 100644
index 0000000..11c79f1
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/57.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png
new file mode 100644
index 0000000..6a99099
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/58.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png
new file mode 100644
index 0000000..ec9f3f6
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/60.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png
new file mode 100644
index 0000000..ee015b2
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/80.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png
new file mode 100644
index 0000000..ebb1b76
Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/87.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
index 45d1e86..73d3b7f 100644
--- a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
+++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -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"}]}
\ No newline at end of file
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_x1024.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_x1024.png
deleted file mode 100644
index 1abba4a..0000000
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_x1024.png and /dev/null differ
diff --git a/ios/Runner/Assets.xcassets/LauncherBackgroundColor.colorset/Contents.json b/ios/Runner/Assets.xcassets/LauncherColor.colorset/Contents.json
similarity index 74%
rename from ios/Runner/Assets.xcassets/LauncherBackgroundColor.colorset/Contents.json
rename to ios/Runner/Assets.xcassets/LauncherColor.colorset/Contents.json
index 41fe6c8..7356209 100644
--- a/ios/Runner/Assets.xcassets/LauncherBackgroundColor.colorset/Contents.json
+++ b/ios/Runner/Assets.xcassets/LauncherColor.colorset/Contents.json
@@ -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"
diff --git a/ios/Runner/Assets.xcassets/LauncherIcon.imageset/Contents.json b/ios/Runner/Assets.xcassets/LauncherIcon.imageset/Contents.json
index 2945b36..06ddd98 100644
--- a/ios/Runner/Assets.xcassets/LauncherIcon.imageset/Contents.json
+++ b/ios/Runner/Assets.xcassets/LauncherIcon.imageset/Contents.json
@@ -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" : {
diff --git a/ios/Runner/Assets.xcassets/LauncherIcon.imageset/icon-transparent.png b/ios/Runner/Assets.xcassets/LauncherIcon.imageset/icon-transparent.png
new file mode 100644
index 0000000..f926570
Binary files /dev/null and b/ios/Runner/Assets.xcassets/LauncherIcon.imageset/icon-transparent.png differ
diff --git a/ios/Runner/Assets.xcassets/LauncherIcon.imageset/icon.png b/ios/Runner/Assets.xcassets/LauncherIcon.imageset/icon.png
deleted file mode 100644
index ee71baa..0000000
Binary files a/ios/Runner/Assets.xcassets/LauncherIcon.imageset/icon.png and /dev/null differ
diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard
index fb29ee2..28d0ddf 100644
--- a/ios/Runner/Base.lproj/LaunchScreen.storyboard
+++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -1,9 +1,9 @@
-
+
-
+
@@ -20,12 +20,19 @@
+
+
+
+
+
+
+
-
+
@@ -36,8 +43,8 @@
-
-
+
+
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 8db75d8..7e79382 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -2,10 +2,12 @@
+ CADisableMinimumFrameDurationOnPhone
+
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleDisplayName
- Game Tracker
+ Tallee
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
@@ -13,7 +15,7 @@
CFBundleInfoDictionaryVersion
6.0
CFBundleName
- game_tracker
+ tallee
CFBundlePackageType
APPL
CFBundleShortVersionString
@@ -22,13 +24,15 @@
????
CFBundleVersion
$(FLUTTER_BUILD_NUMBER)
+ LSApplicationQueriesSchemes
+
+ https
+ http
+
LSRequiresIPhoneOS
- LSApplicationQueriesSchemes
-
- https
- http
-
+ UIApplicationSupportsIndirectInputEvents
+
UILaunchStoryboardName
LaunchScreen
UIMainStoryboardFile
@@ -44,9 +48,5 @@
UIInterfaceOrientationLandscapeLeft
UIInterfaceOrientationLandscapeRight
- CADisableMinimumFrameDurationOnPhone
-
- UIApplicationSupportsIndirectInputEvents
-
-
\ No newline at end of file
+
diff --git a/lib/core/custom_theme.dart b/lib/core/custom_theme.dart
index 2c18073..0e9fec2 100644
--- a/lib/core/custom_theme.dart
+++ b/lib/core/custom_theme.dart
@@ -7,25 +7,28 @@ 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 Color 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 boxBorderColor = 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);
+
+ /// Background color for the navigation bar
+ static const Color navBarBackgroundColor = Color(0xFF131313);
/// Selected color for the [NavbarItem]
static Color navBarItemSelectedColor = primaryColor.withGreen(100);
@@ -51,7 +54,7 @@ class CustomTheme {
// ==================== Decorations ====================
static BoxDecoration standardBoxDecoration = BoxDecoration(
color: boxColor,
- border: Border.all(color: boxBorder),
+ border: Border.all(color: boxBorderColor),
borderRadius: standardBorderRadiusAll,
);
@@ -63,18 +66,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),
);
}
diff --git a/lib/core/enums.dart b/lib/core/enums.dart
index 17a01f6..d3e0610 100644
--- a/lib/core/enums.dart
+++ b/lib/core/enums.dart
@@ -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;
}
}
diff --git a/lib/data/dao/game_dao.dart b/lib/data/dao/game_dao.dart
new file mode 100644
index 0000000..8e658ed
--- /dev/null
+++ b/lib/data/dao/game_dao.dart
@@ -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 with _$GameDaoMixin {
+ GameDao(super.db);
+
+ /// Retrieves all games from the database.
+ Future> 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 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 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 addGamesAsList({required List 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 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 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 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 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 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 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 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 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 deleteAllGames() async {
+ final query = delete(gameTable);
+ final rowsAffected = await query.go();
+ return rowsAffected > 0;
+ }
+}
diff --git a/lib/data/dao/game_dao.g.dart b/lib/data/dao/game_dao.g.dart
new file mode 100644
index 0000000..b5a29fe
--- /dev/null
+++ b/lib/data/dao/game_dao.g.dart
@@ -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 {
+ $GameTableTable get gameTable => attachedDatabase.gameTable;
+}
diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart
index 98c602a..558a93c 100644
--- a/lib/data/dao/group_dao.dart
+++ b/lib/data/dao/group_dao.dart
@@ -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 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 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 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 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 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 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 updateGroupname({
+ Future updateGroupName({
required String groupId,
required String newName,
}) async {
@@ -187,6 +192,19 @@ class GroupDao extends DatabaseAccessor 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 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 getGroupCount() async {
final count =
@@ -211,4 +229,48 @@ class GroupDao extends DatabaseAccessor 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.
+ /// Returns `true` if the group exists and players were replaced, `false` otherwise.
+ Future replaceGroupPlayers({
+ required String groupId,
+ required List newPlayers,
+ }) async {
+ if (!await groupExists(groupId: groupId)) return false;
+
+ 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,
+ ),
+ );
+ });
+ return true;
+ }
}
diff --git a/lib/data/dao/group_match_dao.dart b/lib/data/dao/group_match_dao.dart
deleted file mode 100644
index d428fb5..0000000
--- a/lib/data/dao/group_match_dao.dart
+++ /dev/null
@@ -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
- with _$GroupMatchDaoMixin {
- GroupMatchDao(super.db);
-
- /// Associates a group with a match by inserting a record into the
- /// [GroupMatchTable].
- Future 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 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 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 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 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 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;
- }
-}
diff --git a/lib/data/dao/group_match_dao.g.dart b/lib/data/dao/group_match_dao.g.dart
deleted file mode 100644
index 5cc0b82..0000000
--- a/lib/data/dao/group_match_dao.g.dart
+++ /dev/null
@@ -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 {
- $GroupTableTable get groupTable => attachedDatabase.groupTable;
- $MatchTableTable get matchTable => attachedDatabase.matchTable;
- $GroupMatchTableTable get groupMatchTable => attachedDatabase.groupMatchTable;
-}
diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart
index 160686a..5726df5 100644
--- a/lib/data/dao/match_dao.dart
+++ b/lib/data/dao/match_dao.dart
@@ -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 with _$MatchDaoMixin {
MatchDao(super.db);
@@ -18,20 +22,23 @@ class MatchDao extends DatabaseAccessor 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,114 +49,134 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin {
final query = select(matchTable)..where((g) => g.id.equals(matchId));
final result = await query.getSingle();
- List? 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 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 ?? []) {
- await db.playerMatchDao.addPlayerToMatch(
- matchId: match.id,
- playerId: p.id,
- );
- }
- }
-
- if (match.group != null) {
- await db.groupMatchDao.addGroupToMatch(
+ for (final p in match.players) {
+ await db.playerMatchDao.addPlayerToMatch(
matchId: match.id,
- groupId: match.group!.id,
+ playerId: p.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 addMatchAsList({required List matches}) async {
if (matches.isEmpty) return;
await db.transaction(() async {
- // Add all matches in batch
- await db.batch(
- (b) => b.insertAll(
- matchTable,
- matches
- .map(
- (match) => MatchTableCompanion.insert(
- id: match.id,
- name: match.name,
- createdAt: match.createdAt,
- winnerId: Value(match.winner?.id),
- ),
- )
- .toList(),
- mode: InsertMode.insertOrReplace,
- ),
- );
+ // Add all games first (deduplicated)
+ final uniqueGames = {};
+ 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
- // Using insertOrIgnore to avoid overwriting existing groups (which would
- // trigger cascade deletes on player_group associations)
await db.batch(
- (b) => b.insertAll(
+ (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,
- ),
- )
+ (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(
+ matchTable,
+ matches
+ .map(
+ (match) => MatchTableCompanion.insert(
+ id: match.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),
+ ),
+ )
+ .toList(),
+ mode: InsertMode.insertOrReplace,
+ ),
+ );
+
// Add all players of the matches in batch (unique)
final uniquePlayers = {};
for (final match in matches) {
- if (match.players != null) {
- for (final p in match.players!) {
- uniquePlayers[p.id] = p;
- }
+ for (final p in match.players) {
+ uniquePlayers[p.id] = p;
}
// Also include members of groups
if (match.group != null) {
@@ -160,19 +187,18 @@ class MatchDao extends DatabaseAccessor 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(
+ (b) => b.insertAll(
db.playerTable,
uniquePlayers.values
.map(
(p) => PlayerTableCompanion.insert(
- id: p.id,
- name: p.name,
- createdAt: p.createdAt,
- ),
- )
+ id: p.id,
+ name: p.name,
+ description: p.description,
+ createdAt: p.createdAt,
+ ),
+ )
.toList(),
mode: InsertMode.insertOrIgnore,
),
@@ -182,17 +208,16 @@ class MatchDao extends DatabaseAccessor 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 ?? []) {
- b.insert(
- db.playerMatchTable,
- PlayerMatchTableCompanion.insert(
- matchId: match.id,
- playerId: p.id,
- ),
- mode: InsertMode.insertOrIgnore,
- );
- }
+ for (final p in match.players) {
+ b.insert(
+ db.playerMatchTable,
+ PlayerMatchTableCompanion.insert(
+ matchId: match.id,
+ playerId: p.id,
+ score: 0,
+ ),
+ mode: InsertMode.insertOrIgnore,
+ );
}
}
});
@@ -214,22 +239,6 @@ class MatchDao extends DatabaseAccessor 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,
- );
- }
- }
- });
});
}
@@ -244,9 +253,9 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin {
/// Retrieves the number of matches in the database.
Future getMatchCount() async {
final count =
- await (selectOnly(matchTable)..addColumns([matchTable.id.count()]))
- .map((row) => row.read(matchTable.id.count()))
- .getSingle();
+ await (selectOnly(matchTable)..addColumns([matchTable.id.count()]))
+ .map((row) => row.read(matchTable.id.count()))
+ .getSingle();
return count ?? 0;
}
@@ -266,52 +275,20 @@ class MatchDao extends DatabaseAccessor 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 setWinner({
+ Future 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 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 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 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 updateMatchName({
required String matchId,
@@ -323,4 +300,174 @@ class MatchDao extends DatabaseAccessor 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 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 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 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 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 replaceMatchPlayers({
+ required String matchId,
+ required List 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 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 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 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 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;
+ }
+}
\ No newline at end of file
diff --git a/lib/data/dao/match_dao.g.dart b/lib/data/dao/match_dao.g.dart
index a9f6f4c..fa75fee 100644
--- a/lib/data/dao/match_dao.g.dart
+++ b/lib/data/dao/match_dao.g.dart
@@ -4,5 +4,11 @@ part of 'match_dao.dart';
// ignore_for_file: type=lint
mixin _$MatchDaoMixin on DatabaseAccessor {
+ $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;
}
diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart
index c8db800..2da9761 100644
--- a/lib/data/dao/player_dao.dart
+++ b/lib/data/dao/player_dao.dart
@@ -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 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 with _$PlayerDaoMixin {
return Player(
id: result.id,
name: result.name,
+ description: result.description,
createdAt: result.createdAt,
);
}
@@ -40,6 +46,7 @@ class PlayerDao extends DatabaseAccessor 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 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 with _$PlayerDaoMixin {
}
/// Updates the name of the player with the given [playerId] to [newName].
- Future updatePlayername({
+ Future updatePlayerName({
required String playerId,
required String newName,
}) async {
diff --git a/lib/data/dao/player_group_dao.dart b/lib/data/dao/player_group_dao.dart
index db45735..c3889c8 100644
--- a/lib/data/dao/player_group_dao.dart
+++ b/lib/data/dao/player_group_dao.dart
@@ -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
}
if (!await db.playerDao.playerExists(playerId: player.id)) {
- db.playerDao.addPlayer(player: player);
+ await db.playerDao.addPlayer(player: player);
}
await into(playerGroupTable).insert(
diff --git a/lib/data/dao/player_match_dao.dart b/lib/data/dao/player_match_dao.dart
index 7ebaee6..48bf282 100644
--- a/lib/data/dao/player_match_dao.dart
+++ b/lib/data/dao/player_match_dao.dart
@@ -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
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 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,
);
}
@@ -32,21 +40,65 @@ class PlayerMatchDao extends DatabaseAccessor
if (result.isEmpty) return null;
final futures = result.map(
- (row) => db.playerDao.getPlayerById(playerId: row.playerId),
+ (row) => db.playerDao.getPlayerById(playerId: row.playerId),
);
final players = await Future.wait(futures);
return players;
}
+ /// Retrieves a player's score for a specific match.
+ /// Returns null if the player is not in the match.
+ Future 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 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 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 matchHasPlayers({required String matchId}) async {
final count =
- await (selectOnly(playerMatchTable)
- ..where(playerMatchTable.matchId.equals(matchId))
- ..addColumns([playerMatchTable.playerId.count()]))
- .map((row) => row.read(playerMatchTable.playerId.count()))
- .getSingle();
+ await (selectOnly(playerMatchTable)
+ ..where(playerMatchTable.matchId.equals(matchId))
+ ..addColumns([playerMatchTable.playerId.count()]))
+ .map((row) => row.read(playerMatchTable.playerId.count()))
+ .getSingle();
return (count ?? 0) > 0;
}
@@ -57,12 +109,12 @@ class PlayerMatchDao extends DatabaseAccessor
required String playerId,
}) async {
final count =
- await (selectOnly(playerMatchTable)
- ..where(playerMatchTable.matchId.equals(matchId))
- ..where(playerMatchTable.playerId.equals(playerId))
- ..addColumns([playerMatchTable.playerId.count()]))
- .map((row) => row.read(playerMatchTable.playerId.count()))
- .getSingle();
+ await (selectOnly(playerMatchTable)
+ ..where(playerMatchTable.matchId.equals(matchId))
+ ..where(playerMatchTable.playerId.equals(playerId))
+ ..addColumns([playerMatchTable.playerId.count()]))
+ .map((row) => row.read(playerMatchTable.playerId.count()))
+ .getSingle();
return (count ?? 0) > 0;
}
@@ -96,14 +148,14 @@ class PlayerMatchDao extends DatabaseAccessor
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(
(pg) =>
- pg.matchId.equals(matchId) &
- pg.playerId.isIn(playersToRemove.toList()),
- ))
+ pg.matchId.equals(matchId) &
+ pg.playerId.isIn(playersToRemove.toList()),
+ ))
.go();
}
@@ -112,14 +164,15 @@ class PlayerMatchDao extends DatabaseAccessor
final inserts = playersToAdd
.map(
(id) => PlayerMatchTableCompanion.insert(
- playerId: id,
- matchId: matchId,
- ),
- )
+ playerId: id,
+ matchId: matchId,
+ score: 0,
+ ),
+ )
.toList();
await Future.wait(
inserts.map(
- (c) => into(
+ (c) => into(
playerMatchTable,
).insert(c, mode: InsertMode.insertOrIgnore),
),
@@ -127,4 +180,23 @@ class PlayerMatchDao extends DatabaseAccessor
}
});
}
+
+ /// Retrieves all players in a specific team for a match.
+ Future> 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);
+ }
}
diff --git a/lib/data/dao/player_match_dao.g.dart b/lib/data/dao/player_match_dao.g.dart
index bcc8ef7..4c8bcbe 100644
--- a/lib/data/dao/player_match_dao.g.dart
+++ b/lib/data/dao/player_match_dao.g.dart
@@ -5,7 +5,10 @@ part of 'player_match_dao.dart';
// ignore_for_file: type=lint
mixin _$PlayerMatchDaoMixin on DatabaseAccessor {
$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;
}
diff --git a/lib/data/dao/score_dao.dart b/lib/data/dao/score_dao.dart
new file mode 100644
index 0000000..04e8fcf
--- /dev/null
+++ b/lib/data/dao/score_dao.dart
@@ -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 with _$ScoreDaoMixin {
+ ScoreDao(super.db);
+
+ /// Adds a score entry to the database.
+ Future 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> 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> 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 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 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 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 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 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 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 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;
+ }
+}
+
diff --git a/lib/data/dao/score_dao.g.dart b/lib/data/dao/score_dao.g.dart
new file mode 100644
index 0000000..1f4e367
--- /dev/null
+++ b/lib/data/dao/score_dao.g.dart
@@ -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 {
+ $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;
+}
diff --git a/lib/data/dao/team_dao.dart b/lib/data/dao/team_dao.dart
new file mode 100644
index 0000000..5c2aadb
--- /dev/null
+++ b/lib/data/dao/team_dao.dart
@@ -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 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> 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 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> _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 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 addTeamsAsList({required List 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 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 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 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 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 deleteAllTeams() async {
+ final query = delete(teamTable);
+ final rowsAffected = await query.go();
+ return rowsAffected > 0;
+ }
+}
+
diff --git a/lib/data/dao/team_dao.g.dart b/lib/data/dao/team_dao.g.dart
new file mode 100644
index 0000000..1bf5b21
--- /dev/null
+++ b/lib/data/dao/team_dao.g.dart
@@ -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 {
+ $TeamTableTable get teamTable => attachedDatabase.teamTable;
+}
diff --git a/lib/data/db/database.dart b/lib/data/db/database.dart
index e6c322f..4ab053e 100644
--- a/lib/data/db/database.dart
+++ b/lib/data/db/database.dart
@@ -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),
);
}
}
diff --git a/lib/data/db/database.g.dart b/lib/data/db/database.g.dart
index 6bc493c..fe14e93 100644
--- a/lib/data/db/database.g.dart
+++ b/lib/data/db/database.g.dart
@@ -27,6 +27,17 @@ class $PlayerTableTable extends PlayerTable
type: DriftSqlType.string,
requiredDuringInsert: true,
);
+ static const VerificationMeta _descriptionMeta = const VerificationMeta(
+ 'description',
+ );
+ @override
+ late final GeneratedColumn description = GeneratedColumn(
+ 'description',
+ aliasedName,
+ false,
+ type: DriftSqlType.string,
+ requiredDuringInsert: true,
+ );
static const VerificationMeta _createdAtMeta = const VerificationMeta(
'createdAt',
);
@@ -39,7 +50,7 @@ class $PlayerTableTable extends PlayerTable
requiredDuringInsert: true,
);
@override
- List get $columns => [id, name, createdAt];
+ List get $columns => [id, name, description, createdAt];
@override
String get aliasedName => _alias ?? actualTableName;
@override
@@ -65,6 +76,17 @@ class $PlayerTableTable extends PlayerTable
} else if (isInserting) {
context.missing(_nameMeta);
}
+ if (data.containsKey('description')) {
+ context.handle(
+ _descriptionMeta,
+ description.isAcceptableOrUnknown(
+ data['description']!,
+ _descriptionMeta,
+ ),
+ );
+ } else if (isInserting) {
+ context.missing(_descriptionMeta);
+ }
if (data.containsKey('created_at')) {
context.handle(
_createdAtMeta,
@@ -90,6 +112,10 @@ class $PlayerTableTable extends PlayerTable
DriftSqlType.string,
data['${effectivePrefix}name'],
)!,
+ description: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}description'],
+ )!,
createdAt: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime,
data['${effectivePrefix}created_at'],
@@ -106,10 +132,12 @@ class $PlayerTableTable extends PlayerTable
class PlayerTableData extends DataClass implements Insertable {
final String id;
final String name;
+ final String description;
final DateTime createdAt;
const PlayerTableData({
required this.id,
required this.name,
+ required this.description,
required this.createdAt,
});
@override
@@ -117,6 +145,7 @@ class PlayerTableData extends DataClass implements Insertable {
final map = {};
map['id'] = Variable(id);
map['name'] = Variable(name);
+ map['description'] = Variable(description);
map['created_at'] = Variable(createdAt);
return map;
}
@@ -125,6 +154,7 @@ class PlayerTableData extends DataClass implements Insertable {
return PlayerTableCompanion(
id: Value(id),
name: Value(name),
+ description: Value(description),
createdAt: Value(createdAt),
);
}
@@ -137,6 +167,7 @@ class PlayerTableData extends DataClass implements Insertable {
return PlayerTableData(
id: serializer.fromJson(json['id']),
name: serializer.fromJson(json['name']),
+ description: serializer.fromJson(json['description']),
createdAt: serializer.fromJson(json['createdAt']),
);
}
@@ -146,20 +177,29 @@ class PlayerTableData extends DataClass implements Insertable {
return {
'id': serializer.toJson(id),
'name': serializer.toJson(name),
+ 'description': serializer.toJson(description),
'createdAt': serializer.toJson(createdAt),
};
}
- PlayerTableData copyWith({String? id, String? name, DateTime? createdAt}) =>
- PlayerTableData(
- id: id ?? this.id,
- name: name ?? this.name,
- createdAt: createdAt ?? this.createdAt,
- );
+ PlayerTableData copyWith({
+ String? id,
+ String? name,
+ String? description,
+ DateTime? createdAt,
+ }) => PlayerTableData(
+ id: id ?? this.id,
+ name: name ?? this.name,
+ description: description ?? this.description,
+ createdAt: createdAt ?? this.createdAt,
+ );
PlayerTableData copyWithCompanion(PlayerTableCompanion data) {
return PlayerTableData(
id: data.id.present ? data.id.value : this.id,
name: data.name.present ? data.name.value : this.name,
+ description: data.description.present
+ ? data.description.value
+ : this.description,
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
);
}
@@ -169,50 +209,58 @@ class PlayerTableData extends DataClass implements Insertable {
return (StringBuffer('PlayerTableData(')
..write('id: $id, ')
..write('name: $name, ')
+ ..write('description: $description, ')
..write('createdAt: $createdAt')
..write(')'))
.toString();
}
@override
- int get hashCode => Object.hash(id, name, createdAt);
+ int get hashCode => Object.hash(id, name, description, createdAt);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is PlayerTableData &&
other.id == this.id &&
other.name == this.name &&
+ other.description == this.description &&
other.createdAt == this.createdAt);
}
class PlayerTableCompanion extends UpdateCompanion {
final Value id;
final Value name;
+ final Value description;
final Value createdAt;
final Value rowid;
const PlayerTableCompanion({
this.id = const Value.absent(),
this.name = const Value.absent(),
+ this.description = const Value.absent(),
this.createdAt = const Value.absent(),
this.rowid = const Value.absent(),
});
PlayerTableCompanion.insert({
required String id,
required String name,
+ required String description,
required DateTime createdAt,
this.rowid = const Value.absent(),
}) : id = Value(id),
name = Value(name),
+ description = Value(description),
createdAt = Value(createdAt);
static Insertable custom({
Expression? id,
Expression? name,
+ Expression? description,
Expression? createdAt,
Expression? rowid,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (name != null) 'name': name,
+ if (description != null) 'description': description,
if (createdAt != null) 'created_at': createdAt,
if (rowid != null) 'rowid': rowid,
});
@@ -221,12 +269,14 @@ class PlayerTableCompanion extends UpdateCompanion {
PlayerTableCompanion copyWith({
Value? id,
Value? name,
+ Value? description,
Value? createdAt,
Value? rowid,
}) {
return PlayerTableCompanion(
id: id ?? this.id,
name: name ?? this.name,
+ description: description ?? this.description,
createdAt: createdAt ?? this.createdAt,
rowid: rowid ?? this.rowid,
);
@@ -241,6 +291,9 @@ class PlayerTableCompanion extends UpdateCompanion {
if (name.present) {
map['name'] = Variable(name.value);
}
+ if (description.present) {
+ map['description'] = Variable(description.value);
+ }
if (createdAt.present) {
map['created_at'] = Variable(createdAt.value);
}
@@ -255,6 +308,7 @@ class PlayerTableCompanion extends UpdateCompanion {
return (StringBuffer('PlayerTableCompanion(')
..write('id: $id, ')
..write('name: $name, ')
+ ..write('description: $description, ')
..write('createdAt: $createdAt, ')
..write('rowid: $rowid')
..write(')'))
@@ -286,6 +340,17 @@ class $GroupTableTable extends GroupTable
type: DriftSqlType.string,
requiredDuringInsert: true,
);
+ static const VerificationMeta _descriptionMeta = const VerificationMeta(
+ 'description',
+ );
+ @override
+ late final GeneratedColumn description = GeneratedColumn(
+ 'description',
+ aliasedName,
+ false,
+ type: DriftSqlType.string,
+ requiredDuringInsert: true,
+ );
static const VerificationMeta _createdAtMeta = const VerificationMeta(
'createdAt',
);
@@ -298,7 +363,7 @@ class $GroupTableTable extends GroupTable
requiredDuringInsert: true,
);
@override
- List get $columns => [id, name, createdAt];
+ List get $columns => [id, name, description, createdAt];
@override
String get aliasedName => _alias ?? actualTableName;
@override
@@ -324,6 +389,17 @@ class $GroupTableTable extends GroupTable
} else if (isInserting) {
context.missing(_nameMeta);
}
+ if (data.containsKey('description')) {
+ context.handle(
+ _descriptionMeta,
+ description.isAcceptableOrUnknown(
+ data['description']!,
+ _descriptionMeta,
+ ),
+ );
+ } else if (isInserting) {
+ context.missing(_descriptionMeta);
+ }
if (data.containsKey('created_at')) {
context.handle(
_createdAtMeta,
@@ -349,6 +425,10 @@ class $GroupTableTable extends GroupTable
DriftSqlType.string,
data['${effectivePrefix}name'],
)!,
+ description: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}description'],
+ )!,
createdAt: attachedDatabase.typeMapping.read(
DriftSqlType.dateTime,
data['${effectivePrefix}created_at'],
@@ -365,10 +445,12 @@ class $GroupTableTable extends GroupTable
class GroupTableData extends DataClass implements Insertable {
final String id;
final String name;
+ final String description;
final DateTime createdAt;
const GroupTableData({
required this.id,
required this.name,
+ required this.description,
required this.createdAt,
});
@override
@@ -376,6 +458,7 @@ class GroupTableData extends DataClass implements Insertable {
final map = {};
map['id'] = Variable(id);
map['name'] = Variable(name);
+ map['description'] = Variable(description);
map['created_at'] = Variable(createdAt);
return map;
}
@@ -384,6 +467,7 @@ class GroupTableData extends DataClass implements Insertable {
return GroupTableCompanion(
id: Value(id),
name: Value(name),
+ description: Value(description),
createdAt: Value(createdAt),
);
}
@@ -396,6 +480,7 @@ class GroupTableData extends DataClass implements Insertable {
return GroupTableData(
id: serializer.fromJson(json['id']),
name: serializer.fromJson(json['name']),
+ description: serializer.fromJson(json['description']),
createdAt: serializer.fromJson(json['createdAt']),
);
}
@@ -405,20 +490,29 @@ class GroupTableData extends DataClass implements Insertable {
return {
'id': serializer.toJson(id),
'name': serializer.toJson(name),
+ 'description': serializer.toJson(description),
'createdAt': serializer.toJson(createdAt),
};
}
- GroupTableData copyWith({String? id, String? name, DateTime? createdAt}) =>
- GroupTableData(
- id: id ?? this.id,
- name: name ?? this.name,
- createdAt: createdAt ?? this.createdAt,
- );
+ GroupTableData copyWith({
+ String? id,
+ String? name,
+ String? description,
+ DateTime? createdAt,
+ }) => GroupTableData(
+ id: id ?? this.id,
+ name: name ?? this.name,
+ description: description ?? this.description,
+ createdAt: createdAt ?? this.createdAt,
+ );
GroupTableData copyWithCompanion(GroupTableCompanion data) {
return GroupTableData(
id: data.id.present ? data.id.value : this.id,
name: data.name.present ? data.name.value : this.name,
+ description: data.description.present
+ ? data.description.value
+ : this.description,
createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt,
);
}
@@ -428,50 +522,58 @@ class GroupTableData extends DataClass implements Insertable {
return (StringBuffer('GroupTableData(')
..write('id: $id, ')
..write('name: $name, ')
+ ..write('description: $description, ')
..write('createdAt: $createdAt')
..write(')'))
.toString();
}
@override
- int get hashCode => Object.hash(id, name, createdAt);
+ int get hashCode => Object.hash(id, name, description, createdAt);
@override
bool operator ==(Object other) =>
identical(this, other) ||
(other is GroupTableData &&
other.id == this.id &&
other.name == this.name &&
+ other.description == this.description &&
other.createdAt == this.createdAt);
}
class GroupTableCompanion extends UpdateCompanion {
final Value id;
final Value name;
+ final Value description;
final Value createdAt;
final Value rowid;
const GroupTableCompanion({
this.id = const Value.absent(),
this.name = const Value.absent(),
+ this.description = const Value.absent(),
this.createdAt = const Value.absent(),
this.rowid = const Value.absent(),
});
GroupTableCompanion.insert({
required String id,
required String name,
+ required String description,
required DateTime createdAt,
this.rowid = const Value.absent(),
}) : id = Value(id),
name = Value(name),
+ description = Value(description),
createdAt = Value(createdAt);
static Insertable custom({
Expression? id,
Expression? name,
+ Expression? description,
Expression? createdAt,
Expression? rowid,
}) {
return RawValuesInsertable({
if (id != null) 'id': id,
if (name != null) 'name': name,
+ if (description != null) 'description': description,
if (createdAt != null) 'created_at': createdAt,
if (rowid != null) 'rowid': rowid,
});
@@ -480,12 +582,14 @@ class GroupTableCompanion extends UpdateCompanion {
GroupTableCompanion copyWith({
Value? id,
Value? name,
+ Value? description,
Value? createdAt,
Value? rowid,
}) {
return GroupTableCompanion(
id: id ?? this.id,
name: name ?? this.name,
+ description: description ?? this.description,
createdAt: createdAt ?? this.createdAt,
rowid: rowid ?? this.rowid,
);
@@ -500,6 +604,9 @@ class GroupTableCompanion extends UpdateCompanion {
if (name.present) {
map['name'] = Variable(name.value);
}
+ if (description.present) {
+ map['description'] = Variable(description.value);
+ }
if (createdAt.present) {
map['created_at'] = Variable(createdAt.value);
}
@@ -514,6 +621,463 @@ class GroupTableCompanion extends UpdateCompanion {
return (StringBuffer('GroupTableCompanion(')
..write('id: $id, ')
..write('name: $name, ')
+ ..write('description: $description, ')
+ ..write('createdAt: $createdAt, ')
+ ..write('rowid: $rowid')
+ ..write(')'))
+ .toString();
+ }
+}
+
+class $GameTableTable extends GameTable
+ with TableInfo<$GameTableTable, GameTableData> {
+ @override
+ final GeneratedDatabase attachedDatabase;
+ final String? _alias;
+ $GameTableTable(this.attachedDatabase, [this._alias]);
+ static const VerificationMeta _idMeta = const VerificationMeta('id');
+ @override
+ late final GeneratedColumn id = GeneratedColumn(
+ 'id',
+ aliasedName,
+ false,
+ type: DriftSqlType.string,
+ requiredDuringInsert: true,
+ );
+ static const VerificationMeta _nameMeta = const VerificationMeta('name');
+ @override
+ late final GeneratedColumn name = GeneratedColumn(
+ 'name',
+ aliasedName,
+ false,
+ type: DriftSqlType.string,
+ requiredDuringInsert: true,
+ );
+ static const VerificationMeta _rulesetMeta = const VerificationMeta(
+ 'ruleset',
+ );
+ @override
+ late final GeneratedColumn ruleset = GeneratedColumn(
+ 'ruleset',
+ aliasedName,
+ false,
+ type: DriftSqlType.string,
+ requiredDuringInsert: true,
+ );
+ static const VerificationMeta _descriptionMeta = const VerificationMeta(
+ 'description',
+ );
+ @override
+ late final GeneratedColumn description = GeneratedColumn(
+ 'description',
+ aliasedName,
+ false,
+ type: DriftSqlType.string,
+ requiredDuringInsert: true,
+ );
+ static const VerificationMeta _colorMeta = const VerificationMeta('color');
+ @override
+ late final GeneratedColumn color = GeneratedColumn(
+ 'color',
+ aliasedName,
+ false,
+ type: DriftSqlType.string,
+ requiredDuringInsert: true,
+ );
+ static const VerificationMeta _iconMeta = const VerificationMeta('icon');
+ @override
+ late final GeneratedColumn icon = GeneratedColumn(
+ 'icon',
+ aliasedName,
+ false,
+ type: DriftSqlType.string,
+ requiredDuringInsert: true,
+ );
+ static const VerificationMeta _createdAtMeta = const VerificationMeta(
+ 'createdAt',
+ );
+ @override
+ late final GeneratedColumn createdAt = GeneratedColumn(
+ 'created_at',
+ aliasedName,
+ false,
+ type: DriftSqlType.dateTime,
+ requiredDuringInsert: true,
+ );
+ @override
+ List get $columns => [
+ id,
+ name,
+ ruleset,
+ description,
+ color,
+ icon,
+ createdAt,
+ ];
+ @override
+ String get aliasedName => _alias ?? actualTableName;
+ @override
+ String get actualTableName => $name;
+ static const String $name = 'game_table';
+ @override
+ VerificationContext validateIntegrity(
+ Insertable instance, {
+ bool isInserting = false,
+ }) {
+ final context = VerificationContext();
+ final data = instance.toColumns(true);
+ if (data.containsKey('id')) {
+ context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
+ } else if (isInserting) {
+ context.missing(_idMeta);
+ }
+ if (data.containsKey('name')) {
+ context.handle(
+ _nameMeta,
+ name.isAcceptableOrUnknown(data['name']!, _nameMeta),
+ );
+ } else if (isInserting) {
+ context.missing(_nameMeta);
+ }
+ if (data.containsKey('ruleset')) {
+ context.handle(
+ _rulesetMeta,
+ ruleset.isAcceptableOrUnknown(data['ruleset']!, _rulesetMeta),
+ );
+ } else if (isInserting) {
+ context.missing(_rulesetMeta);
+ }
+ if (data.containsKey('description')) {
+ context.handle(
+ _descriptionMeta,
+ description.isAcceptableOrUnknown(
+ data['description']!,
+ _descriptionMeta,
+ ),
+ );
+ } else if (isInserting) {
+ context.missing(_descriptionMeta);
+ }
+ if (data.containsKey('color')) {
+ context.handle(
+ _colorMeta,
+ color.isAcceptableOrUnknown(data['color']!, _colorMeta),
+ );
+ } else if (isInserting) {
+ context.missing(_colorMeta);
+ }
+ if (data.containsKey('icon')) {
+ context.handle(
+ _iconMeta,
+ icon.isAcceptableOrUnknown(data['icon']!, _iconMeta),
+ );
+ } else if (isInserting) {
+ context.missing(_iconMeta);
+ }
+ if (data.containsKey('created_at')) {
+ context.handle(
+ _createdAtMeta,
+ createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta),
+ );
+ } else if (isInserting) {
+ context.missing(_createdAtMeta);
+ }
+ return context;
+ }
+
+ @override
+ Set get $primaryKey => {id};
+ @override
+ GameTableData map(Map data, {String? tablePrefix}) {
+ final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : '';
+ return GameTableData(
+ id: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}id'],
+ )!,
+ name: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}name'],
+ )!,
+ ruleset: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}ruleset'],
+ )!,
+ description: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}description'],
+ )!,
+ color: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}color'],
+ )!,
+ icon: attachedDatabase.typeMapping.read(
+ DriftSqlType.string,
+ data['${effectivePrefix}icon'],
+ )!,
+ createdAt: attachedDatabase.typeMapping.read(
+ DriftSqlType.dateTime,
+ data['${effectivePrefix}created_at'],
+ )!,
+ );
+ }
+
+ @override
+ $GameTableTable createAlias(String alias) {
+ return $GameTableTable(attachedDatabase, alias);
+ }
+}
+
+class GameTableData extends DataClass implements Insertable {
+ final String id;
+ final String name;
+ final String ruleset;
+ final String description;
+ final String color;
+ final String icon;
+ final DateTime createdAt;
+ const GameTableData({
+ required this.id,
+ required this.name,
+ required this.ruleset,
+ required this.description,
+ required this.color,
+ required this.icon,
+ required this.createdAt,
+ });
+ @override
+ Map toColumns(bool nullToAbsent) {
+ final map = {};
+ map['id'] = Variable(id);
+ map['name'] = Variable(name);
+ map['ruleset'] = Variable(ruleset);
+ map['description'] = Variable(description);
+ map['color'] = Variable(color);
+ map['icon'] = Variable(icon);
+ map['created_at'] = Variable(createdAt);
+ return map;
+ }
+
+ GameTableCompanion toCompanion(bool nullToAbsent) {
+ return GameTableCompanion(
+ id: Value(id),
+ name: Value(name),
+ ruleset: Value(ruleset),
+ description: Value(description),
+ color: Value(color),
+ icon: Value(icon),
+ createdAt: Value(createdAt),
+ );
+ }
+
+ factory GameTableData.fromJson(
+ Map json, {
+ ValueSerializer? serializer,
+ }) {
+ serializer ??= driftRuntimeOptions.defaultSerializer;
+ return GameTableData(
+ id: serializer.fromJson(json['id']),
+ name: serializer.fromJson(json['name']),
+ ruleset: serializer.fromJson(json['ruleset']),
+ description: serializer.fromJson(json['description']),
+ color: serializer.fromJson(json['color']),
+ icon: serializer.fromJson(json['icon']),
+ createdAt: serializer.fromJson(json['createdAt']),
+ );
+ }
+ @override
+ Map toJson({ValueSerializer? serializer}) {
+ serializer ??= driftRuntimeOptions.defaultSerializer;
+ return