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 Logo +

Tallee

+

+

+An open-source app to track card- and board games, manage players & groups and get statistics about your played games. +

+

+ + Download on the App Store + + + Get it on Google Play + +

+ +![Version](https://img.shields.io/badge/App--Version-0.0.1_Alpha-orange) +![Flutter](https://img.shields.io/badge/Flutter-3.38.6-027DFD?logo=flutter) +![iOS26](https://img.shields.io/badge/iOS-26-white?logo=apple) +![Android16](https://img.shields.io/badge/Android-16-3DDC84?logo=android) + +## Screenshots + + + + + + + + +
Screenshot 1Screenshot 2Screenshot 3Screenshot 4
+ +## 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). + +![Created by Liquid Development](https://img.shields.io/badge/Created_by-Liquid_Development-027DFD?logo=data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyBpZD0iRWJlbmVfMSIgZGF0YS1uYW1lPSJFYmVuZSAxIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA3MjUuNDggODk3LjMiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGZpbGw6ICNmZmY7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIDxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTcwNS4yNiw3MDEuOTJsNi40LDExLjA4Yy0xLjk1LTMuODEtNC4wOS03LjUxLTYuNC0xMS4wOFoiLz4KICA8cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik02MDIuMzksODk3LjI1aC03LjIxYzEuMi4wMywyLjQuMDUsMy42MS4wNXMyLjQxLS4wMiwzLjYxLS4wNVoiLz4KICA8cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik0wLDY5NS4zOGwyLjY4LTQuNjRjLS45MywxLjUyLTEuODIsMy4wNy0yLjY4LDQuNjRaIi8+CiAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNjgyLjU1LDcyMy40NWw2LjA1LDEwLjQ5Yy0xLjc5LTMuNjQtMy44MS03LjE1LTYuMDUtMTAuNDlaIi8+CiAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMzcuNzIsNzMzLjI4bDUuMy05LjE4Yy0xLjk0LDIuOTQtMy43MSw2LjAxLTUuMyw5LjE4WiIvPgogIDxwYXRoIGNsYXNzPSJjbHMtMSIgZD0iTTcxMS42Niw3MTMuMDFsLTYuNC0xMS4wOC0yMjAuNDYtMzgxLjg0aDBWMTAxLjg0YzIwLjY3LTYuOTgsMzUuNTYtMjYuNTIsMzUuNTYtNDkuNTQsMC0yOC44OC0yMy40MS01Mi4zLTUyLjMtNTIuM2gtMjA5LjQ4Yy0yOC44OCwwLTUyLjMsMjMuNDEtNTIuMyw1Mi4zLDAsMjIuNzEsMTQuNDgsNDIuMDMsMzQuNyw0OS4yNXYyMTguNTRsLS4zMy41OEwxOC44OSw3MDQuNzlsLTIuNjgsNC42NGMtOS45OSwxOC4xMi0xNS42OCwzOC45Ni0xNS42OCw2MS4xMiwwLDY5Ljk3LDU2LjY0LDEyNi43LDEyNi41MSwxMjYuN2g0NzUuMzVjNjguMy0xLjkxLDEyMy4wOS01Ny44OCwxMjMuMDktMTI2LjY0LDAtMjAuNzQtNC45OS00MC4zMi0xMy44Mi01Ny42Wk02MDguNTYsODYyLjUzSDExNy40M2MtNDkuMzcsMC04OS4zOS00MC4wMi04OS4zOS04OS4zOSwwLTE0LjM2LDMuMzktMjcuOTMsOS40MS0zOS45Nmw1LjMtOS4xOCwyMzMuMi00MDMuOTJoLS4wOFYxMDQuNTloMTcuODFjOS40NywwLDE3LjE1LTcuNjgsMTcuMTUtMTcuMTVzLTcuNjgtMTcuMTUtMTcuMTUtMTcuMTVoLTM1LjU5di0uMDJjLTkuNzItLjI2LTE3LjUyLTguMi0xNy41Mi0xNy45OHM3LjgtMTcuNzIsMTcuNTItMTcuOTh2LS4wMmgyMDkuMjZjOS45NCwwLDE4LDguMDYsMTgsMThzLTguMDYsMTgtMTgsMThoLTM0LjQ4Yy05LjQ3LDAtMTcuMTUsNy42OC0xNy4xNSwxNy4xNXM3LjY4LDE3LjE1LDE3LjE1LDE3LjE1aDE3LjA0djIxNS40OWguMDdsMjMyLjgyLDQwMy4yNiw2LjA2LDEwLjVjNS44MiwxMS44Niw5LjA5LDI1LjIsOS4wOSwzOS4zLDAsNDkuMzctNDAuMDIsODkuMzktODkuMzksODkuMzlaIi8+CiAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNMzgxLjY4LDU0NS4zOGMtMy4wOCwxLjY4LTYuMTgsMy4zLTkuMzIsNC44NiwzLjA3LTEuNjcsNi4xOC0zLjI5LDkuMzItNC44NloiLz4KICA8cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik01ODMuNDIsNTUxLjE5bC0yMC42Ny0zNS44Yy0xMy42OS0xLjg0LTI3LjY3LTIuNzktNDEuODYtMi43OS0xNy45OSwwLTM1LjYyLDEuNTMtNTIuNzgsNC40Ni0zMC41Niw1LjIxLTU5LjYsMTQuODktODYuNDIsMjguMzMtMy4wOCwxLjY4LTYuMTgsMy4zLTkuMzIsNC44Ni00MS44OCwyMC45OS04OS4xNiwzMi43OS0xMzkuMTksMzIuNzktMzQuODUsMC02OC4zNS01Ljc0LTk5LjYzLTE2LjMxLDAsMCwwLC4wMiwwLC4wMmwtMTYuNTIsMjguNjFjMzcuMDEsMTUuNTMsNzcuNjUsMjQuMTIsMTIwLjMsMjQuMTIsMTcuOTgsMCwzNS42MS0xLjUzLDUyLjc2LTQuNDYsMzIuNzctNS41OSw2My43OC0xNi4zMSw5Mi4yLTMxLjI5Ljg3LS40NiwxLjczLS45MiwyLjYtMS40LDQzLjI5LTIyLjgyLDkyLjYyLTM1Ljc0LDE0NC45Ni0zNS43NCwxOC4yOCwwLDM2LjE4LDEuNTksNTMuNTksNC42MWwtLjAyLS4wMloiLz4KICA8Zz4KICAgIDxjaXJjbGUgY2xhc3M9ImNscy0xIiBjeD0iNTg3LjY0IiBjeT0iODAzLjQiIHI9IjE4Ljk2Ii8+CiAgICA8cGF0aCBjbGFzcz0iY2xzLTEiIGQ9Ik01MTUuNTIsNzg0LjQzSDEwMy41NWMtMTAuOTIsMC0xOS43Niw4LjQ5LTE5Ljc2LDE4Ljk2czguODUsMTguOTYsMTkuNzYsMTguOTZoNDExLjk3YzEwLjkyLDAsMTkuNzYtOC40OSwxOS43Ni0xOC45NnMtOC44NS0xOC45Ni0xOS43Ni0xOC45NloiLz4KICA8L2c+CiAgPGNpcmNsZSBjbGFzcz0iY2xzLTEiIGN4PSIyODMuMzIiIGN5PSI0NjcuNTkiIHI9IjE4Ljk2Ii8+CiAgPGNpcmNsZSBjbGFzcz0iY2xzLTEiIGN4PSIzMjYuMjMiIGN5PSIzNjYuMjUiIHI9IjE4Ljk2Ii8+CiAgPHBhdGggY2xhc3M9ImNscy0xIiBkPSJNNDA2LjU2LDM4NS4yMmMtMjQuMDYsMC00My41NiwxOS41LTQzLjU2LDQzLjU2czE5LjUsNDMuNTYsNDMuNTYsNDMuNTYsNDMuNTYtMTkuNSw0My41Ni00My41Ni0xOS41LTQzLjU2LTQzLjU2LTQzLjU2Wk00MDYuNTYsNDQ3Ljc0Yy0xMC40NywwLTE4Ljk2LTguNDktMTguOTYtMTguOTZzOC40OS0xOC45NiwxOC45Ni0xOC45NiwxOC45Niw4LjQ5LDE4Ljk2LDE4Ljk2LTguNDksMTguOTYtMTguOTYsMTguOTZaIi8+Cjwvc3ZnPg==) -![Version](https://img.shields.io/badge/Version-0.3.0-orange) -![Flutter](https://img.shields.io/badge/Flutter-3.32.1-blue?logo=flutter) -![Dart](https://img.shields.io/badge/Dart-3.8.1-blue?logo=dart) -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 { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'ruleset': serializer.toJson(ruleset), + 'description': serializer.toJson(description), + 'color': serializer.toJson(color), + 'icon': serializer.toJson(icon), + 'createdAt': serializer.toJson(createdAt), + }; + } + + GameTableData copyWith({ + String? id, + String? name, + String? ruleset, + String? description, + String? color, + String? icon, + DateTime? createdAt, + }) => GameTableData( + id: id ?? this.id, + name: name ?? this.name, + ruleset: ruleset ?? this.ruleset, + description: description ?? this.description, + color: color ?? this.color, + icon: icon ?? this.icon, + createdAt: createdAt ?? this.createdAt, + ); + GameTableData copyWithCompanion(GameTableCompanion data) { + return GameTableData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + ruleset: data.ruleset.present ? data.ruleset.value : this.ruleset, + description: data.description.present + ? data.description.value + : this.description, + color: data.color.present ? data.color.value : this.color, + icon: data.icon.present ? data.icon.value : this.icon, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('GameTableData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('ruleset: $ruleset, ') + ..write('description: $description, ') + ..write('color: $color, ') + ..write('icon: $icon, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, name, ruleset, description, color, icon, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is GameTableData && + other.id == this.id && + other.name == this.name && + other.ruleset == this.ruleset && + other.description == this.description && + other.color == this.color && + other.icon == this.icon && + other.createdAt == this.createdAt); +} + +class GameTableCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value ruleset; + final Value description; + final Value color; + final Value icon; + final Value createdAt; + final Value rowid; + const GameTableCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.ruleset = const Value.absent(), + this.description = const Value.absent(), + this.color = const Value.absent(), + this.icon = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + GameTableCompanion.insert({ + required String id, + required String name, + required String ruleset, + required String description, + required String color, + required String icon, + required DateTime createdAt, + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name), + ruleset = Value(ruleset), + description = Value(description), + color = Value(color), + icon = Value(icon), + createdAt = Value(createdAt); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? ruleset, + Expression? description, + Expression? color, + Expression? icon, + Expression? createdAt, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (ruleset != null) 'ruleset': ruleset, + if (description != null) 'description': description, + if (color != null) 'color': color, + if (icon != null) 'icon': icon, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + GameTableCompanion copyWith({ + Value? id, + Value? name, + Value? ruleset, + Value? description, + Value? color, + Value? icon, + Value? createdAt, + Value? rowid, + }) { + return GameTableCompanion( + id: id ?? this.id, + name: name ?? this.name, + ruleset: ruleset ?? this.ruleset, + description: description ?? this.description, + color: color ?? this.color, + icon: icon ?? this.icon, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (ruleset.present) { + map['ruleset'] = Variable(ruleset.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (color.present) { + map['color'] = Variable(color.value); + } + if (icon.present) { + map['icon'] = Variable(icon.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('GameTableCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('ruleset: $ruleset, ') + ..write('description: $description, ') + ..write('color: $color, ') + ..write('icon: $icon, ') ..write('createdAt: $createdAt, ') ..write('rowid: $rowid') ..write(')')) @@ -536,21 +1100,45 @@ class $MatchTableTable extends MatchTable type: DriftSqlType.string, requiredDuringInsert: true, ); + static const VerificationMeta _gameIdMeta = const VerificationMeta('gameId'); + @override + late final GeneratedColumn gameId = GeneratedColumn( + 'game_id', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES game_table (id) ON DELETE CASCADE', + ), + ); + static const VerificationMeta _groupIdMeta = const VerificationMeta( + 'groupId', + ); + @override + late final GeneratedColumn groupId = GeneratedColumn( + 'group_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES group_table (id) ON DELETE CASCADE', + ), + ); static const VerificationMeta _nameMeta = const VerificationMeta('name'); @override late final GeneratedColumn name = GeneratedColumn( 'name', aliasedName, - false, + true, type: DriftSqlType.string, - requiredDuringInsert: true, - ); - static const VerificationMeta _winnerIdMeta = const VerificationMeta( - 'winnerId', + requiredDuringInsert: false, ); + static const VerificationMeta _notesMeta = const VerificationMeta('notes'); @override - late final GeneratedColumn winnerId = GeneratedColumn( - 'winner_id', + late final GeneratedColumn notes = GeneratedColumn( + 'notes', aliasedName, true, type: DriftSqlType.string, @@ -567,8 +1155,27 @@ class $MatchTableTable extends MatchTable type: DriftSqlType.dateTime, requiredDuringInsert: true, ); + static const VerificationMeta _endedAtMeta = const VerificationMeta( + 'endedAt', + ); @override - List get $columns => [id, name, winnerId, createdAt]; + late final GeneratedColumn endedAt = GeneratedColumn( + 'ended_at', + aliasedName, + true, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + gameId, + groupId, + name, + notes, + createdAt, + endedAt, + ]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -586,18 +1193,30 @@ class $MatchTableTable extends MatchTable } else if (isInserting) { context.missing(_idMeta); } + if (data.containsKey('game_id')) { + context.handle( + _gameIdMeta, + gameId.isAcceptableOrUnknown(data['game_id']!, _gameIdMeta), + ); + } else if (isInserting) { + context.missing(_gameIdMeta); + } + if (data.containsKey('group_id')) { + context.handle( + _groupIdMeta, + groupId.isAcceptableOrUnknown(data['group_id']!, _groupIdMeta), + ); + } if (data.containsKey('name')) { context.handle( _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta), ); - } else if (isInserting) { - context.missing(_nameMeta); } - if (data.containsKey('winner_id')) { + if (data.containsKey('notes')) { context.handle( - _winnerIdMeta, - winnerId.isAcceptableOrUnknown(data['winner_id']!, _winnerIdMeta), + _notesMeta, + notes.isAcceptableOrUnknown(data['notes']!, _notesMeta), ); } if (data.containsKey('created_at')) { @@ -608,6 +1227,12 @@ class $MatchTableTable extends MatchTable } else if (isInserting) { context.missing(_createdAtMeta); } + if (data.containsKey('ended_at')) { + context.handle( + _endedAtMeta, + endedAt.isAcceptableOrUnknown(data['ended_at']!, _endedAtMeta), + ); + } return context; } @@ -621,18 +1246,30 @@ class $MatchTableTable extends MatchTable DriftSqlType.string, data['${effectivePrefix}id'], )!, + gameId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}game_id'], + )!, + groupId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}group_id'], + ), name: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}name'], - )!, - winnerId: attachedDatabase.typeMapping.read( + ), + notes: attachedDatabase.typeMapping.read( DriftSqlType.string, - data['${effectivePrefix}winner_id'], + data['${effectivePrefix}notes'], ), createdAt: attachedDatabase.typeMapping.read( DriftSqlType.dateTime, data['${effectivePrefix}created_at'], )!, + endedAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}ended_at'], + ), ); } @@ -644,35 +1281,57 @@ class $MatchTableTable extends MatchTable class MatchTableData extends DataClass implements Insertable { final String id; - final String name; - final String? winnerId; + final String gameId; + final String? groupId; + final String? name; + final String? notes; final DateTime createdAt; + final DateTime? endedAt; const MatchTableData({ required this.id, - required this.name, - this.winnerId, + required this.gameId, + this.groupId, + this.name, + this.notes, required this.createdAt, + this.endedAt, }); @override Map toColumns(bool nullToAbsent) { final map = {}; map['id'] = Variable(id); - map['name'] = Variable(name); - if (!nullToAbsent || winnerId != null) { - map['winner_id'] = Variable(winnerId); + map['game_id'] = Variable(gameId); + if (!nullToAbsent || groupId != null) { + map['group_id'] = Variable(groupId); + } + if (!nullToAbsent || name != null) { + map['name'] = Variable(name); + } + if (!nullToAbsent || notes != null) { + map['notes'] = Variable(notes); } map['created_at'] = Variable(createdAt); + if (!nullToAbsent || endedAt != null) { + map['ended_at'] = Variable(endedAt); + } return map; } MatchTableCompanion toCompanion(bool nullToAbsent) { return MatchTableCompanion( id: Value(id), - name: Value(name), - winnerId: winnerId == null && nullToAbsent + gameId: Value(gameId), + groupId: groupId == null && nullToAbsent ? const Value.absent() - : Value(winnerId), + : Value(groupId), + name: name == null && nullToAbsent ? const Value.absent() : Value(name), + notes: notes == null && nullToAbsent + ? const Value.absent() + : Value(notes), createdAt: Value(createdAt), + endedAt: endedAt == null && nullToAbsent + ? const Value.absent() + : Value(endedAt), ); } @@ -683,9 +1342,12 @@ class MatchTableData extends DataClass implements Insertable { serializer ??= driftRuntimeOptions.defaultSerializer; return MatchTableData( id: serializer.fromJson(json['id']), - name: serializer.fromJson(json['name']), - winnerId: serializer.fromJson(json['winnerId']), + gameId: serializer.fromJson(json['gameId']), + groupId: serializer.fromJson(json['groupId']), + name: serializer.fromJson(json['name']), + notes: serializer.fromJson(json['notes']), createdAt: serializer.fromJson(json['createdAt']), + endedAt: serializer.fromJson(json['endedAt']), ); } @override @@ -693,29 +1355,41 @@ class MatchTableData extends DataClass implements Insertable { serializer ??= driftRuntimeOptions.defaultSerializer; return { 'id': serializer.toJson(id), - 'name': serializer.toJson(name), - 'winnerId': serializer.toJson(winnerId), + 'gameId': serializer.toJson(gameId), + 'groupId': serializer.toJson(groupId), + 'name': serializer.toJson(name), + 'notes': serializer.toJson(notes), 'createdAt': serializer.toJson(createdAt), + 'endedAt': serializer.toJson(endedAt), }; } MatchTableData copyWith({ String? id, - String? name, - Value winnerId = const Value.absent(), + String? gameId, + Value groupId = const Value.absent(), + Value name = const Value.absent(), + Value notes = const Value.absent(), DateTime? createdAt, + Value endedAt = const Value.absent(), }) => MatchTableData( id: id ?? this.id, - name: name ?? this.name, - winnerId: winnerId.present ? winnerId.value : this.winnerId, + gameId: gameId ?? this.gameId, + groupId: groupId.present ? groupId.value : this.groupId, + name: name.present ? name.value : this.name, + notes: notes.present ? notes.value : this.notes, createdAt: createdAt ?? this.createdAt, + endedAt: endedAt.present ? endedAt.value : this.endedAt, ); MatchTableData copyWithCompanion(MatchTableCompanion data) { return MatchTableData( id: data.id.present ? data.id.value : this.id, + gameId: data.gameId.present ? data.gameId.value : this.gameId, + groupId: data.groupId.present ? data.groupId.value : this.groupId, name: data.name.present ? data.name.value : this.name, - winnerId: data.winnerId.present ? data.winnerId.value : this.winnerId, + notes: data.notes.present ? data.notes.value : this.notes, createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + endedAt: data.endedAt.present ? data.endedAt.value : this.endedAt, ); } @@ -723,75 +1397,103 @@ class MatchTableData extends DataClass implements Insertable { String toString() { return (StringBuffer('MatchTableData(') ..write('id: $id, ') + ..write('gameId: $gameId, ') + ..write('groupId: $groupId, ') ..write('name: $name, ') - ..write('winnerId: $winnerId, ') - ..write('createdAt: $createdAt') + ..write('notes: $notes, ') + ..write('createdAt: $createdAt, ') + ..write('endedAt: $endedAt') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(id, name, winnerId, createdAt); + int get hashCode => + Object.hash(id, gameId, groupId, name, notes, createdAt, endedAt); @override bool operator ==(Object other) => identical(this, other) || (other is MatchTableData && other.id == this.id && + other.gameId == this.gameId && + other.groupId == this.groupId && other.name == this.name && - other.winnerId == this.winnerId && - other.createdAt == this.createdAt); + other.notes == this.notes && + other.createdAt == this.createdAt && + other.endedAt == this.endedAt); } class MatchTableCompanion extends UpdateCompanion { final Value id; - final Value name; - final Value winnerId; + final Value gameId; + final Value groupId; + final Value name; + final Value notes; final Value createdAt; + final Value endedAt; final Value rowid; const MatchTableCompanion({ this.id = const Value.absent(), + this.gameId = const Value.absent(), + this.groupId = const Value.absent(), this.name = const Value.absent(), - this.winnerId = const Value.absent(), + this.notes = const Value.absent(), this.createdAt = const Value.absent(), + this.endedAt = const Value.absent(), this.rowid = const Value.absent(), }); MatchTableCompanion.insert({ required String id, - required String name, - this.winnerId = const Value.absent(), + required String gameId, + this.groupId = const Value.absent(), + this.name = const Value.absent(), + this.notes = const Value.absent(), required DateTime createdAt, + this.endedAt = const Value.absent(), this.rowid = const Value.absent(), }) : id = Value(id), - name = Value(name), + gameId = Value(gameId), createdAt = Value(createdAt); static Insertable custom({ Expression? id, + Expression? gameId, + Expression? groupId, Expression? name, - Expression? winnerId, + Expression? notes, Expression? createdAt, + Expression? endedAt, Expression? rowid, }) { return RawValuesInsertable({ if (id != null) 'id': id, + if (gameId != null) 'game_id': gameId, + if (groupId != null) 'group_id': groupId, if (name != null) 'name': name, - if (winnerId != null) 'winner_id': winnerId, + if (notes != null) 'notes': notes, if (createdAt != null) 'created_at': createdAt, + if (endedAt != null) 'ended_at': endedAt, if (rowid != null) 'rowid': rowid, }); } MatchTableCompanion copyWith({ Value? id, - Value? name, - Value? winnerId, + Value? gameId, + Value? groupId, + Value? name, + Value? notes, Value? createdAt, + Value? endedAt, Value? rowid, }) { return MatchTableCompanion( id: id ?? this.id, + gameId: gameId ?? this.gameId, + groupId: groupId ?? this.groupId, name: name ?? this.name, - winnerId: winnerId ?? this.winnerId, + notes: notes ?? this.notes, createdAt: createdAt ?? this.createdAt, + endedAt: endedAt ?? this.endedAt, rowid: rowid ?? this.rowid, ); } @@ -802,15 +1504,24 @@ class MatchTableCompanion extends UpdateCompanion { if (id.present) { map['id'] = Variable(id.value); } + if (gameId.present) { + map['game_id'] = Variable(gameId.value); + } + if (groupId.present) { + map['group_id'] = Variable(groupId.value); + } if (name.present) { map['name'] = Variable(name.value); } - if (winnerId.present) { - map['winner_id'] = Variable(winnerId.value); + if (notes.present) { + map['notes'] = Variable(notes.value); } if (createdAt.present) { map['created_at'] = Variable(createdAt.value); } + if (endedAt.present) { + map['ended_at'] = Variable(endedAt.value); + } if (rowid.present) { map['rowid'] = Variable(rowid.value); } @@ -821,9 +1532,12 @@ class MatchTableCompanion extends UpdateCompanion { String toString() { return (StringBuffer('MatchTableCompanion(') ..write('id: $id, ') + ..write('gameId: $gameId, ') + ..write('groupId: $groupId, ') ..write('name: $name, ') - ..write('winnerId: $winnerId, ') + ..write('notes: $notes, ') ..write('createdAt: $createdAt, ') + ..write('endedAt: $endedAt, ') ..write('rowid: $rowid') ..write(')')) .toString(); @@ -1055,6 +1769,265 @@ class PlayerGroupTableCompanion extends UpdateCompanion { } } +class $TeamTableTable extends TeamTable + with TableInfo<$TeamTableTable, TeamTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $TeamTableTable(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 _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, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'team_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('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 + TeamTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return TeamTableData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + createdAt: attachedDatabase.typeMapping.read( + DriftSqlType.dateTime, + data['${effectivePrefix}created_at'], + )!, + ); + } + + @override + $TeamTableTable createAlias(String alias) { + return $TeamTableTable(attachedDatabase, alias); + } +} + +class TeamTableData extends DataClass implements Insertable { + final String id; + final String name; + final DateTime createdAt; + const TeamTableData({ + required this.id, + required this.name, + required this.createdAt, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['created_at'] = Variable(createdAt); + return map; + } + + TeamTableCompanion toCompanion(bool nullToAbsent) { + return TeamTableCompanion( + id: Value(id), + name: Value(name), + createdAt: Value(createdAt), + ); + } + + factory TeamTableData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return TeamTableData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'createdAt': serializer.toJson(createdAt), + }; + } + + TeamTableData copyWith({String? id, String? name, DateTime? createdAt}) => + TeamTableData( + id: id ?? this.id, + name: name ?? this.name, + createdAt: createdAt ?? this.createdAt, + ); + TeamTableData copyWithCompanion(TeamTableCompanion data) { + return TeamTableData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('TeamTableData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is TeamTableData && + other.id == this.id && + other.name == this.name && + other.createdAt == this.createdAt); +} + +class TeamTableCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value createdAt; + final Value rowid; + const TeamTableCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.createdAt = const Value.absent(), + this.rowid = const Value.absent(), + }); + TeamTableCompanion.insert({ + required String id, + required String name, + required DateTime createdAt, + this.rowid = const Value.absent(), + }) : id = Value(id), + name = Value(name), + createdAt = Value(createdAt); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? createdAt, + Expression? rowid, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (createdAt != null) 'created_at': createdAt, + if (rowid != null) 'rowid': rowid, + }); + } + + TeamTableCompanion copyWith({ + Value? id, + Value? name, + Value? createdAt, + Value? rowid, + }) { + return TeamTableCompanion( + id: id ?? this.id, + name: name ?? this.name, + createdAt: createdAt ?? this.createdAt, + rowid: rowid ?? this.rowid, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (rowid.present) { + map['rowid'] = Variable(rowid.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('TeamTableCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('createdAt: $createdAt, ') + ..write('rowid: $rowid') + ..write(')')) + .toString(); + } +} + class $PlayerMatchTableTable extends PlayerMatchTable with TableInfo<$PlayerMatchTableTable, PlayerMatchTableData> { @override @@ -1089,8 +2062,29 @@ class $PlayerMatchTableTable extends PlayerMatchTable 'REFERENCES match_table (id) ON DELETE CASCADE', ), ); + static const VerificationMeta _teamIdMeta = const VerificationMeta('teamId'); @override - List get $columns => [playerId, matchId]; + late final GeneratedColumn teamId = GeneratedColumn( + 'team_id', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES team_table (id)', + ), + ); + static const VerificationMeta _scoreMeta = const VerificationMeta('score'); + @override + late final GeneratedColumn score = GeneratedColumn( + 'score', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [playerId, matchId, teamId, score]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -1119,6 +2113,20 @@ class $PlayerMatchTableTable extends PlayerMatchTable } else if (isInserting) { context.missing(_matchIdMeta); } + if (data.containsKey('team_id')) { + context.handle( + _teamIdMeta, + teamId.isAcceptableOrUnknown(data['team_id']!, _teamIdMeta), + ); + } + if (data.containsKey('score')) { + context.handle( + _scoreMeta, + score.isAcceptableOrUnknown(data['score']!, _scoreMeta), + ); + } else if (isInserting) { + context.missing(_scoreMeta); + } return context; } @@ -1136,6 +2144,14 @@ class $PlayerMatchTableTable extends PlayerMatchTable DriftSqlType.string, data['${effectivePrefix}match_id'], )!, + teamId: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}team_id'], + ), + score: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}score'], + )!, ); } @@ -1149,12 +2165,23 @@ class PlayerMatchTableData extends DataClass implements Insertable { final String playerId; final String matchId; - const PlayerMatchTableData({required this.playerId, required this.matchId}); + final String? teamId; + final int score; + const PlayerMatchTableData({ + required this.playerId, + required this.matchId, + this.teamId, + required this.score, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; map['player_id'] = Variable(playerId); map['match_id'] = Variable(matchId); + if (!nullToAbsent || teamId != null) { + map['team_id'] = Variable(teamId); + } + map['score'] = Variable(score); return map; } @@ -1162,6 +2189,10 @@ class PlayerMatchTableData extends DataClass return PlayerMatchTableCompanion( playerId: Value(playerId), matchId: Value(matchId), + teamId: teamId == null && nullToAbsent + ? const Value.absent() + : Value(teamId), + score: Value(score), ); } @@ -1173,6 +2204,8 @@ class PlayerMatchTableData extends DataClass return PlayerMatchTableData( playerId: serializer.fromJson(json['playerId']), matchId: serializer.fromJson(json['matchId']), + teamId: serializer.fromJson(json['teamId']), + score: serializer.fromJson(json['score']), ); } @override @@ -1181,18 +2214,28 @@ class PlayerMatchTableData extends DataClass return { 'playerId': serializer.toJson(playerId), 'matchId': serializer.toJson(matchId), + 'teamId': serializer.toJson(teamId), + 'score': serializer.toJson(score), }; } - PlayerMatchTableData copyWith({String? playerId, String? matchId}) => - PlayerMatchTableData( - playerId: playerId ?? this.playerId, - matchId: matchId ?? this.matchId, - ); + PlayerMatchTableData copyWith({ + String? playerId, + String? matchId, + Value teamId = const Value.absent(), + int? score, + }) => PlayerMatchTableData( + playerId: playerId ?? this.playerId, + matchId: matchId ?? this.matchId, + teamId: teamId.present ? teamId.value : this.teamId, + score: score ?? this.score, + ); PlayerMatchTableData copyWithCompanion(PlayerMatchTableCompanion data) { return PlayerMatchTableData( playerId: data.playerId.present ? data.playerId.value : this.playerId, matchId: data.matchId.present ? data.matchId.value : this.matchId, + teamId: data.teamId.present ? data.teamId.value : this.teamId, + score: data.score.present ? data.score.value : this.score, ); } @@ -1200,44 +2243,59 @@ class PlayerMatchTableData extends DataClass String toString() { return (StringBuffer('PlayerMatchTableData(') ..write('playerId: $playerId, ') - ..write('matchId: $matchId') + ..write('matchId: $matchId, ') + ..write('teamId: $teamId, ') + ..write('score: $score') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(playerId, matchId); + int get hashCode => Object.hash(playerId, matchId, teamId, score); @override bool operator ==(Object other) => identical(this, other) || (other is PlayerMatchTableData && other.playerId == this.playerId && - other.matchId == this.matchId); + other.matchId == this.matchId && + other.teamId == this.teamId && + other.score == this.score); } class PlayerMatchTableCompanion extends UpdateCompanion { final Value playerId; final Value matchId; + final Value teamId; + final Value score; final Value rowid; const PlayerMatchTableCompanion({ this.playerId = const Value.absent(), this.matchId = const Value.absent(), + this.teamId = const Value.absent(), + this.score = const Value.absent(), this.rowid = const Value.absent(), }); PlayerMatchTableCompanion.insert({ required String playerId, required String matchId, + this.teamId = const Value.absent(), + required int score, this.rowid = const Value.absent(), }) : playerId = Value(playerId), - matchId = Value(matchId); + matchId = Value(matchId), + score = Value(score); static Insertable custom({ Expression? playerId, Expression? matchId, + Expression? teamId, + Expression? score, Expression? rowid, }) { return RawValuesInsertable({ if (playerId != null) 'player_id': playerId, if (matchId != null) 'match_id': matchId, + if (teamId != null) 'team_id': teamId, + if (score != null) 'score': score, if (rowid != null) 'rowid': rowid, }); } @@ -1245,11 +2303,15 @@ class PlayerMatchTableCompanion extends UpdateCompanion { PlayerMatchTableCompanion copyWith({ Value? playerId, Value? matchId, + Value? teamId, + Value? score, Value? rowid, }) { return PlayerMatchTableCompanion( playerId: playerId ?? this.playerId, matchId: matchId ?? this.matchId, + teamId: teamId ?? this.teamId, + score: score ?? this.score, rowid: rowid ?? this.rowid, ); } @@ -1263,6 +2325,12 @@ class PlayerMatchTableCompanion extends UpdateCompanion { if (matchId.present) { map['match_id'] = Variable(matchId.value); } + if (teamId.present) { + map['team_id'] = Variable(teamId.value); + } + if (score.present) { + map['score'] = Variable(score.value); + } if (rowid.present) { map['rowid'] = Variable(rowid.value); } @@ -1274,30 +2342,32 @@ class PlayerMatchTableCompanion extends UpdateCompanion { return (StringBuffer('PlayerMatchTableCompanion(') ..write('playerId: $playerId, ') ..write('matchId: $matchId, ') + ..write('teamId: $teamId, ') + ..write('score: $score, ') ..write('rowid: $rowid') ..write(')')) .toString(); } } -class $GroupMatchTableTable extends GroupMatchTable - with TableInfo<$GroupMatchTableTable, GroupMatchTableData> { +class $ScoreTableTable extends ScoreTable + with TableInfo<$ScoreTableTable, ScoreTableData> { @override final GeneratedDatabase attachedDatabase; final String? _alias; - $GroupMatchTableTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _groupIdMeta = const VerificationMeta( - 'groupId', + $ScoreTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _playerIdMeta = const VerificationMeta( + 'playerId', ); @override - late final GeneratedColumn groupId = GeneratedColumn( - 'group_id', + late final GeneratedColumn playerId = GeneratedColumn( + 'player_id', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true, defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES group_table (id) ON DELETE CASCADE', + 'REFERENCES player_table (id) ON DELETE CASCADE', ), ); static const VerificationMeta _matchIdMeta = const VerificationMeta( @@ -1314,27 +2384,62 @@ class $GroupMatchTableTable extends GroupMatchTable 'REFERENCES match_table (id) ON DELETE CASCADE', ), ); + static const VerificationMeta _roundNumberMeta = const VerificationMeta( + 'roundNumber', + ); @override - List get $columns => [groupId, matchId]; + late final GeneratedColumn roundNumber = GeneratedColumn( + 'round_number', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + static const VerificationMeta _scoreMeta = const VerificationMeta('score'); + @override + late final GeneratedColumn score = GeneratedColumn( + 'score', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + static const VerificationMeta _changeMeta = const VerificationMeta('change'); + @override + late final GeneratedColumn change = GeneratedColumn( + 'change', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + @override + List get $columns => [ + playerId, + matchId, + roundNumber, + score, + change, + ]; @override String get aliasedName => _alias ?? actualTableName; @override String get actualTableName => $name; - static const String $name = 'group_match_table'; + static const String $name = 'score_table'; @override VerificationContext validateIntegrity( - Insertable instance, { + Insertable instance, { bool isInserting = false, }) { final context = VerificationContext(); final data = instance.toColumns(true); - if (data.containsKey('group_id')) { + if (data.containsKey('player_id')) { context.handle( - _groupIdMeta, - groupId.isAcceptableOrUnknown(data['group_id']!, _groupIdMeta), + _playerIdMeta, + playerId.isAcceptableOrUnknown(data['player_id']!, _playerIdMeta), ); } else if (isInserting) { - context.missing(_groupIdMeta); + context.missing(_playerIdMeta); } if (data.containsKey('match_id')) { context.handle( @@ -1344,137 +2449,240 @@ class $GroupMatchTableTable extends GroupMatchTable } else if (isInserting) { context.missing(_matchIdMeta); } + if (data.containsKey('round_number')) { + context.handle( + _roundNumberMeta, + roundNumber.isAcceptableOrUnknown( + data['round_number']!, + _roundNumberMeta, + ), + ); + } else if (isInserting) { + context.missing(_roundNumberMeta); + } + if (data.containsKey('score')) { + context.handle( + _scoreMeta, + score.isAcceptableOrUnknown(data['score']!, _scoreMeta), + ); + } else if (isInserting) { + context.missing(_scoreMeta); + } + if (data.containsKey('change')) { + context.handle( + _changeMeta, + change.isAcceptableOrUnknown(data['change']!, _changeMeta), + ); + } else if (isInserting) { + context.missing(_changeMeta); + } return context; } @override - Set get $primaryKey => {groupId, matchId}; + Set get $primaryKey => {playerId, matchId, roundNumber}; @override - GroupMatchTableData map(Map data, {String? tablePrefix}) { + ScoreTableData map(Map data, {String? tablePrefix}) { final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return GroupMatchTableData( - groupId: attachedDatabase.typeMapping.read( + return ScoreTableData( + playerId: attachedDatabase.typeMapping.read( DriftSqlType.string, - data['${effectivePrefix}group_id'], + data['${effectivePrefix}player_id'], )!, matchId: attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}match_id'], )!, + roundNumber: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}round_number'], + )!, + score: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}score'], + )!, + change: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}change'], + )!, ); } @override - $GroupMatchTableTable createAlias(String alias) { - return $GroupMatchTableTable(attachedDatabase, alias); + $ScoreTableTable createAlias(String alias) { + return $ScoreTableTable(attachedDatabase, alias); } } -class GroupMatchTableData extends DataClass - implements Insertable { - final String groupId; +class ScoreTableData extends DataClass implements Insertable { + final String playerId; final String matchId; - const GroupMatchTableData({required this.groupId, required this.matchId}); + final int roundNumber; + final int score; + final int change; + const ScoreTableData({ + required this.playerId, + required this.matchId, + required this.roundNumber, + required this.score, + required this.change, + }); @override Map toColumns(bool nullToAbsent) { final map = {}; - map['group_id'] = Variable(groupId); + map['player_id'] = Variable(playerId); map['match_id'] = Variable(matchId); + map['round_number'] = Variable(roundNumber); + map['score'] = Variable(score); + map['change'] = Variable(change); return map; } - GroupMatchTableCompanion toCompanion(bool nullToAbsent) { - return GroupMatchTableCompanion( - groupId: Value(groupId), + ScoreTableCompanion toCompanion(bool nullToAbsent) { + return ScoreTableCompanion( + playerId: Value(playerId), matchId: Value(matchId), + roundNumber: Value(roundNumber), + score: Value(score), + change: Value(change), ); } - factory GroupMatchTableData.fromJson( + factory ScoreTableData.fromJson( Map json, { ValueSerializer? serializer, }) { serializer ??= driftRuntimeOptions.defaultSerializer; - return GroupMatchTableData( - groupId: serializer.fromJson(json['groupId']), + return ScoreTableData( + playerId: serializer.fromJson(json['playerId']), matchId: serializer.fromJson(json['matchId']), + roundNumber: serializer.fromJson(json['roundNumber']), + score: serializer.fromJson(json['score']), + change: serializer.fromJson(json['change']), ); } @override Map toJson({ValueSerializer? serializer}) { serializer ??= driftRuntimeOptions.defaultSerializer; return { - 'groupId': serializer.toJson(groupId), + 'playerId': serializer.toJson(playerId), 'matchId': serializer.toJson(matchId), + 'roundNumber': serializer.toJson(roundNumber), + 'score': serializer.toJson(score), + 'change': serializer.toJson(change), }; } - GroupMatchTableData copyWith({String? groupId, String? matchId}) => - GroupMatchTableData( - groupId: groupId ?? this.groupId, - matchId: matchId ?? this.matchId, - ); - GroupMatchTableData copyWithCompanion(GroupMatchTableCompanion data) { - return GroupMatchTableData( - groupId: data.groupId.present ? data.groupId.value : this.groupId, + ScoreTableData copyWith({ + String? playerId, + String? matchId, + int? roundNumber, + int? score, + int? change, + }) => ScoreTableData( + playerId: playerId ?? this.playerId, + matchId: matchId ?? this.matchId, + roundNumber: roundNumber ?? this.roundNumber, + score: score ?? this.score, + change: change ?? this.change, + ); + ScoreTableData copyWithCompanion(ScoreTableCompanion data) { + return ScoreTableData( + playerId: data.playerId.present ? data.playerId.value : this.playerId, matchId: data.matchId.present ? data.matchId.value : this.matchId, + roundNumber: data.roundNumber.present + ? data.roundNumber.value + : this.roundNumber, + score: data.score.present ? data.score.value : this.score, + change: data.change.present ? data.change.value : this.change, ); } @override String toString() { - return (StringBuffer('GroupMatchTableData(') - ..write('groupId: $groupId, ') - ..write('matchId: $matchId') + return (StringBuffer('ScoreTableData(') + ..write('playerId: $playerId, ') + ..write('matchId: $matchId, ') + ..write('roundNumber: $roundNumber, ') + ..write('score: $score, ') + ..write('change: $change') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(groupId, matchId); + int get hashCode => + Object.hash(playerId, matchId, roundNumber, score, change); @override bool operator ==(Object other) => identical(this, other) || - (other is GroupMatchTableData && - other.groupId == this.groupId && - other.matchId == this.matchId); + (other is ScoreTableData && + other.playerId == this.playerId && + other.matchId == this.matchId && + other.roundNumber == this.roundNumber && + other.score == this.score && + other.change == this.change); } -class GroupMatchTableCompanion extends UpdateCompanion { - final Value groupId; +class ScoreTableCompanion extends UpdateCompanion { + final Value playerId; final Value matchId; + final Value roundNumber; + final Value score; + final Value change; final Value rowid; - const GroupMatchTableCompanion({ - this.groupId = const Value.absent(), + const ScoreTableCompanion({ + this.playerId = const Value.absent(), this.matchId = const Value.absent(), + this.roundNumber = const Value.absent(), + this.score = const Value.absent(), + this.change = const Value.absent(), this.rowid = const Value.absent(), }); - GroupMatchTableCompanion.insert({ - required String groupId, + ScoreTableCompanion.insert({ + required String playerId, required String matchId, + required int roundNumber, + required int score, + required int change, this.rowid = const Value.absent(), - }) : groupId = Value(groupId), - matchId = Value(matchId); - static Insertable custom({ - Expression? groupId, + }) : playerId = Value(playerId), + matchId = Value(matchId), + roundNumber = Value(roundNumber), + score = Value(score), + change = Value(change); + static Insertable custom({ + Expression? playerId, Expression? matchId, + Expression? roundNumber, + Expression? score, + Expression? change, Expression? rowid, }) { return RawValuesInsertable({ - if (groupId != null) 'group_id': groupId, + if (playerId != null) 'player_id': playerId, if (matchId != null) 'match_id': matchId, + if (roundNumber != null) 'round_number': roundNumber, + if (score != null) 'score': score, + if (change != null) 'change': change, if (rowid != null) 'rowid': rowid, }); } - GroupMatchTableCompanion copyWith({ - Value? groupId, + ScoreTableCompanion copyWith({ + Value? playerId, Value? matchId, + Value? roundNumber, + Value? score, + Value? change, Value? rowid, }) { - return GroupMatchTableCompanion( - groupId: groupId ?? this.groupId, + return ScoreTableCompanion( + playerId: playerId ?? this.playerId, matchId: matchId ?? this.matchId, + roundNumber: roundNumber ?? this.roundNumber, + score: score ?? this.score, + change: change ?? this.change, rowid: rowid ?? this.rowid, ); } @@ -1482,12 +2690,21 @@ class GroupMatchTableCompanion extends UpdateCompanion { @override Map toColumns(bool nullToAbsent) { final map = {}; - if (groupId.present) { - map['group_id'] = Variable(groupId.value); + if (playerId.present) { + map['player_id'] = Variable(playerId.value); } if (matchId.present) { map['match_id'] = Variable(matchId.value); } + if (roundNumber.present) { + map['round_number'] = Variable(roundNumber.value); + } + if (score.present) { + map['score'] = Variable(score.value); + } + if (change.present) { + map['change'] = Variable(change.value); + } if (rowid.present) { map['rowid'] = Variable(rowid.value); } @@ -1496,9 +2713,12 @@ class GroupMatchTableCompanion extends UpdateCompanion { @override String toString() { - return (StringBuffer('GroupMatchTableCompanion(') - ..write('groupId: $groupId, ') + return (StringBuffer('ScoreTableCompanion(') + ..write('playerId: $playerId, ') ..write('matchId: $matchId, ') + ..write('roundNumber: $roundNumber, ') + ..write('score: $score, ') + ..write('change: $change, ') ..write('rowid: $rowid') ..write(')')) .toString(); @@ -1510,16 +2730,16 @@ abstract class _$AppDatabase extends GeneratedDatabase { $AppDatabaseManager get managers => $AppDatabaseManager(this); late final $PlayerTableTable playerTable = $PlayerTableTable(this); late final $GroupTableTable groupTable = $GroupTableTable(this); + late final $GameTableTable gameTable = $GameTableTable(this); late final $MatchTableTable matchTable = $MatchTableTable(this); late final $PlayerGroupTableTable playerGroupTable = $PlayerGroupTableTable( this, ); + late final $TeamTableTable teamTable = $TeamTableTable(this); late final $PlayerMatchTableTable playerMatchTable = $PlayerMatchTableTable( this, ); - late final $GroupMatchTableTable groupMatchTable = $GroupMatchTableTable( - this, - ); + late final $ScoreTableTable scoreTable = $ScoreTableTable(this); late final PlayerDao playerDao = PlayerDao(this as AppDatabase); late final GroupDao groupDao = GroupDao(this as AppDatabase); late final MatchDao matchDao = MatchDao(this as AppDatabase); @@ -1529,7 +2749,9 @@ abstract class _$AppDatabase extends GeneratedDatabase { late final PlayerMatchDao playerMatchDao = PlayerMatchDao( this as AppDatabase, ); - late final GroupMatchDao groupMatchDao = GroupMatchDao(this as AppDatabase); + late final GameDao gameDao = GameDao(this as AppDatabase); + late final ScoreDao scoreDao = ScoreDao(this as AppDatabase); + late final TeamDao teamDao = TeamDao(this as AppDatabase); @override Iterable> get allTables => allSchemaEntities.whereType>(); @@ -1537,13 +2759,29 @@ abstract class _$AppDatabase extends GeneratedDatabase { List get allSchemaEntities => [ playerTable, groupTable, + gameTable, matchTable, playerGroupTable, + teamTable, playerMatchTable, - groupMatchTable, + scoreTable, ]; @override StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([ + WritePropagation( + on: TableUpdateQuery.onTableName( + 'game_table', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('match_table', kind: UpdateKind.delete)], + ), + WritePropagation( + on: TableUpdateQuery.onTableName( + 'group_table', + limitUpdateKind: UpdateKind.delete, + ), + result: [TableUpdate('match_table', kind: UpdateKind.delete)], + ), WritePropagation( on: TableUpdateQuery.onTableName( 'player_table', @@ -1574,17 +2812,17 @@ abstract class _$AppDatabase extends GeneratedDatabase { ), WritePropagation( on: TableUpdateQuery.onTableName( - 'group_table', + 'player_table', limitUpdateKind: UpdateKind.delete, ), - result: [TableUpdate('group_match_table', kind: UpdateKind.delete)], + result: [TableUpdate('score_table', kind: UpdateKind.delete)], ), WritePropagation( on: TableUpdateQuery.onTableName( 'match_table', limitUpdateKind: UpdateKind.delete, ), - result: [TableUpdate('group_match_table', kind: UpdateKind.delete)], + result: [TableUpdate('score_table', kind: UpdateKind.delete)], ), ]); } @@ -1593,6 +2831,7 @@ typedef $$PlayerTableTableCreateCompanionBuilder = PlayerTableCompanion Function({ required String id, required String name, + required String description, required DateTime createdAt, Value rowid, }); @@ -1600,6 +2839,7 @@ typedef $$PlayerTableTableUpdateCompanionBuilder = PlayerTableCompanion Function({ Value id, Value name, + Value description, Value createdAt, Value rowid, }); @@ -1653,6 +2893,24 @@ final class $$PlayerTableTableReferences manager.$state.copyWith(prefetchedData: cache), ); } + + static MultiTypedResultKey<$ScoreTableTable, List> + _scoreTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.scoreTable, + aliasName: $_aliasNameGenerator(db.playerTable.id, db.scoreTable.playerId), + ); + + $$ScoreTableTableProcessedTableManager get scoreTableRefs { + final manager = $$ScoreTableTableTableManager( + $_db, + $_db.scoreTable, + ).filter((f) => f.playerId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_scoreTableRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } } class $$PlayerTableTableFilterComposer @@ -1674,6 +2932,11 @@ class $$PlayerTableTableFilterComposer builder: (column) => ColumnFilters(column), ); + ColumnFilters get description => $composableBuilder( + column: $table.description, + builder: (column) => ColumnFilters(column), + ); + ColumnFilters get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnFilters(column), @@ -1728,6 +2991,31 @@ class $$PlayerTableTableFilterComposer ); return f(composer); } + + Expression scoreTableRefs( + Expression Function($$ScoreTableTableFilterComposer f) f, + ) { + final $$ScoreTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.scoreTable, + getReferencedColumn: (t) => t.playerId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ScoreTableTableFilterComposer( + $db: $db, + $table: $db.scoreTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } } class $$PlayerTableTableOrderingComposer @@ -1749,6 +3037,11 @@ class $$PlayerTableTableOrderingComposer builder: (column) => ColumnOrderings(column), ); + ColumnOrderings get description => $composableBuilder( + column: $table.description, + builder: (column) => ColumnOrderings(column), + ); + ColumnOrderings get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnOrderings(column), @@ -1770,6 +3063,11 @@ class $$PlayerTableTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); + GeneratedColumn get description => $composableBuilder( + column: $table.description, + builder: (column) => column, + ); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); @@ -1822,6 +3120,31 @@ class $$PlayerTableTableAnnotationComposer ); return f(composer); } + + Expression scoreTableRefs( + Expression Function($$ScoreTableTableAnnotationComposer a) f, + ) { + final $$ScoreTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.scoreTable, + getReferencedColumn: (t) => t.playerId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$ScoreTableTableAnnotationComposer( + $db: $db, + $table: $db.scoreTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } } class $$PlayerTableTableTableManager @@ -1840,6 +3163,7 @@ class $$PlayerTableTableTableManager PrefetchHooks Function({ bool playerGroupTableRefs, bool playerMatchTableRefs, + bool scoreTableRefs, }) > { $$PlayerTableTableTableManager(_$AppDatabase db, $PlayerTableTable table) @@ -1857,11 +3181,13 @@ class $$PlayerTableTableTableManager ({ Value id = const Value.absent(), Value name = const Value.absent(), + Value description = const Value.absent(), Value createdAt = const Value.absent(), Value rowid = const Value.absent(), }) => PlayerTableCompanion( id: id, name: name, + description: description, createdAt: createdAt, rowid: rowid, ), @@ -1869,11 +3195,13 @@ class $$PlayerTableTableTableManager ({ required String id, required String name, + required String description, required DateTime createdAt, Value rowid = const Value.absent(), }) => PlayerTableCompanion.insert( id: id, name: name, + description: description, createdAt: createdAt, rowid: rowid, ), @@ -1886,12 +3214,17 @@ class $$PlayerTableTableTableManager ) .toList(), prefetchHooksCallback: - ({playerGroupTableRefs = false, playerMatchTableRefs = false}) { + ({ + playerGroupTableRefs = false, + playerMatchTableRefs = false, + scoreTableRefs = false, + }) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ if (playerGroupTableRefs) db.playerGroupTable, if (playerMatchTableRefs) db.playerMatchTable, + if (scoreTableRefs) db.scoreTable, ], addJoins: null, getPrefetchedDataCallback: (items) async { @@ -1938,6 +3271,27 @@ class $$PlayerTableTableTableManager ), typedResults: items, ), + if (scoreTableRefs) + await $_getPrefetchedData< + PlayerTableData, + $PlayerTableTable, + ScoreTableData + >( + currentTable: table, + referencedTable: $$PlayerTableTableReferences + ._scoreTableRefsTable(db), + managerFromTypedResult: (p0) => + $$PlayerTableTableReferences( + db, + table, + p0, + ).scoreTableRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.playerId == item.id, + ), + typedResults: items, + ), ]; }, ); @@ -1961,12 +3315,14 @@ typedef $$PlayerTableTableProcessedTableManager = PrefetchHooks Function({ bool playerGroupTableRefs, bool playerMatchTableRefs, + bool scoreTableRefs, }) >; typedef $$GroupTableTableCreateCompanionBuilder = GroupTableCompanion Function({ required String id, required String name, + required String description, required DateTime createdAt, Value rowid, }); @@ -1974,6 +3330,7 @@ typedef $$GroupTableTableUpdateCompanionBuilder = GroupTableCompanion Function({ Value id, Value name, + Value description, Value createdAt, Value rowid, }); @@ -1982,6 +3339,24 @@ final class $$GroupTableTableReferences extends BaseReferences<_$AppDatabase, $GroupTableTable, GroupTableData> { $$GroupTableTableReferences(super.$_db, super.$_table, super.$_typedResult); + static MultiTypedResultKey<$MatchTableTable, List> + _matchTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.matchTable, + aliasName: $_aliasNameGenerator(db.groupTable.id, db.matchTable.groupId), + ); + + $$MatchTableTableProcessedTableManager get matchTableRefs { + final manager = $$MatchTableTableTableManager( + $_db, + $_db.matchTable, + ).filter((f) => f.groupId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_matchTableRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } + static MultiTypedResultKey<$PlayerGroupTableTable, List> _playerGroupTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( db.playerGroupTable, @@ -2004,29 +3379,6 @@ final class $$GroupTableTableReferences manager.$state.copyWith(prefetchedData: cache), ); } - - static MultiTypedResultKey<$GroupMatchTableTable, List> - _groupMatchTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.groupMatchTable, - aliasName: $_aliasNameGenerator( - db.groupTable.id, - db.groupMatchTable.groupId, - ), - ); - - $$GroupMatchTableTableProcessedTableManager get groupMatchTableRefs { - final manager = $$GroupMatchTableTableTableManager( - $_db, - $_db.groupMatchTable, - ).filter((f) => f.groupId.id.sqlEquals($_itemColumn('id')!)); - - final cache = $_typedResult.readTableOrNull( - _groupMatchTableRefsTable($_db), - ); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache), - ); - } } class $$GroupTableTableFilterComposer @@ -2048,11 +3400,41 @@ class $$GroupTableTableFilterComposer builder: (column) => ColumnFilters(column), ); + ColumnFilters get description => $composableBuilder( + column: $table.description, + builder: (column) => ColumnFilters(column), + ); + ColumnFilters get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnFilters(column), ); + Expression matchTableRefs( + Expression Function($$MatchTableTableFilterComposer f) f, + ) { + final $$MatchTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.matchTable, + getReferencedColumn: (t) => t.groupId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$MatchTableTableFilterComposer( + $db: $db, + $table: $db.matchTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } + Expression playerGroupTableRefs( Expression Function($$PlayerGroupTableTableFilterComposer f) f, ) { @@ -2077,31 +3459,6 @@ class $$GroupTableTableFilterComposer ); return f(composer); } - - Expression groupMatchTableRefs( - Expression Function($$GroupMatchTableTableFilterComposer f) f, - ) { - final $$GroupMatchTableTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.groupMatchTable, - getReferencedColumn: (t) => t.groupId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GroupMatchTableTableFilterComposer( - $db: $db, - $table: $db.groupMatchTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } } class $$GroupTableTableOrderingComposer @@ -2123,6 +3480,11 @@ class $$GroupTableTableOrderingComposer builder: (column) => ColumnOrderings(column), ); + ColumnOrderings get description => $composableBuilder( + column: $table.description, + builder: (column) => ColumnOrderings(column), + ); + ColumnOrderings get createdAt => $composableBuilder( column: $table.createdAt, builder: (column) => ColumnOrderings(column), @@ -2144,9 +3506,39 @@ class $$GroupTableTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); + GeneratedColumn get description => $composableBuilder( + column: $table.description, + builder: (column) => column, + ); + GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); + Expression matchTableRefs( + Expression Function($$MatchTableTableAnnotationComposer a) f, + ) { + final $$MatchTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.matchTable, + getReferencedColumn: (t) => t.groupId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$MatchTableTableAnnotationComposer( + $db: $db, + $table: $db.matchTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } + Expression playerGroupTableRefs( Expression Function($$PlayerGroupTableTableAnnotationComposer a) f, ) { @@ -2171,31 +3563,6 @@ class $$GroupTableTableAnnotationComposer ); return f(composer); } - - Expression groupMatchTableRefs( - Expression Function($$GroupMatchTableTableAnnotationComposer a) f, - ) { - final $$GroupMatchTableTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.groupMatchTable, - getReferencedColumn: (t) => t.groupId, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GroupMatchTableTableAnnotationComposer( - $db: $db, - $table: $db.groupMatchTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return f(composer); - } } class $$GroupTableTableTableManager @@ -2212,8 +3579,8 @@ class $$GroupTableTableTableManager (GroupTableData, $$GroupTableTableReferences), GroupTableData, PrefetchHooks Function({ + bool matchTableRefs, bool playerGroupTableRefs, - bool groupMatchTableRefs, }) > { $$GroupTableTableTableManager(_$AppDatabase db, $GroupTableTable table) @@ -2231,11 +3598,13 @@ class $$GroupTableTableTableManager ({ Value id = const Value.absent(), Value name = const Value.absent(), + Value description = const Value.absent(), Value createdAt = const Value.absent(), Value rowid = const Value.absent(), }) => GroupTableCompanion( id: id, name: name, + description: description, createdAt: createdAt, rowid: rowid, ), @@ -2243,11 +3612,13 @@ class $$GroupTableTableTableManager ({ required String id, required String name, + required String description, required DateTime createdAt, Value rowid = const Value.absent(), }) => GroupTableCompanion.insert( id: id, name: name, + description: description, createdAt: createdAt, rowid: rowid, ), @@ -2260,16 +3631,37 @@ class $$GroupTableTableTableManager ) .toList(), prefetchHooksCallback: - ({playerGroupTableRefs = false, groupMatchTableRefs = false}) { + ({matchTableRefs = false, playerGroupTableRefs = false}) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ + if (matchTableRefs) db.matchTable, if (playerGroupTableRefs) db.playerGroupTable, - if (groupMatchTableRefs) db.groupMatchTable, ], addJoins: null, getPrefetchedDataCallback: (items) async { return [ + if (matchTableRefs) + await $_getPrefetchedData< + GroupTableData, + $GroupTableTable, + MatchTableData + >( + currentTable: table, + referencedTable: $$GroupTableTableReferences + ._matchTableRefsTable(db), + managerFromTypedResult: (p0) => + $$GroupTableTableReferences( + db, + table, + p0, + ).matchTableRefs, + referencedItemsForCurrentItem: + (item, referencedItems) => referencedItems.where( + (e) => e.groupId == item.id, + ), + typedResults: items, + ), if (playerGroupTableRefs) await $_getPrefetchedData< GroupTableData, @@ -2291,27 +3683,6 @@ class $$GroupTableTableTableManager ), typedResults: items, ), - if (groupMatchTableRefs) - await $_getPrefetchedData< - GroupTableData, - $GroupTableTable, - GroupMatchTableData - >( - currentTable: table, - referencedTable: $$GroupTableTableReferences - ._groupMatchTableRefsTable(db), - managerFromTypedResult: (p0) => - $$GroupTableTableReferences( - db, - table, - p0, - ).groupMatchTableRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems.where( - (e) => e.groupId == item.id, - ), - typedResults: items, - ), ]; }, ); @@ -2332,25 +3703,369 @@ typedef $$GroupTableTableProcessedTableManager = $$GroupTableTableUpdateCompanionBuilder, (GroupTableData, $$GroupTableTableReferences), GroupTableData, - PrefetchHooks Function({ - bool playerGroupTableRefs, - bool groupMatchTableRefs, - }) + PrefetchHooks Function({bool matchTableRefs, bool playerGroupTableRefs}) + >; +typedef $$GameTableTableCreateCompanionBuilder = + GameTableCompanion Function({ + required String id, + required String name, + required String ruleset, + required String description, + required String color, + required String icon, + required DateTime createdAt, + Value rowid, + }); +typedef $$GameTableTableUpdateCompanionBuilder = + GameTableCompanion Function({ + Value id, + Value name, + Value ruleset, + Value description, + Value color, + Value icon, + Value createdAt, + Value rowid, + }); + +final class $$GameTableTableReferences + extends BaseReferences<_$AppDatabase, $GameTableTable, GameTableData> { + $$GameTableTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$MatchTableTable, List> + _matchTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.matchTable, + aliasName: $_aliasNameGenerator(db.gameTable.id, db.matchTable.gameId), + ); + + $$MatchTableTableProcessedTableManager get matchTableRefs { + final manager = $$MatchTableTableTableManager( + $_db, + $_db.matchTable, + ).filter((f) => f.gameId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull(_matchTableRefsTable($_db)); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$GameTableTableFilterComposer + extends Composer<_$AppDatabase, $GameTableTable> { + $$GameTableTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get ruleset => $composableBuilder( + column: $table.ruleset, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get description => $composableBuilder( + column: $table.description, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get color => $composableBuilder( + column: $table.color, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get icon => $composableBuilder( + column: $table.icon, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); + + Expression matchTableRefs( + Expression Function($$MatchTableTableFilterComposer f) f, + ) { + final $$MatchTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.matchTable, + getReferencedColumn: (t) => t.gameId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$MatchTableTableFilterComposer( + $db: $db, + $table: $db.matchTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$GameTableTableOrderingComposer + extends Composer<_$AppDatabase, $GameTableTable> { + $$GameTableTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get ruleset => $composableBuilder( + column: $table.ruleset, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get description => $composableBuilder( + column: $table.description, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get color => $composableBuilder( + column: $table.color, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get icon => $composableBuilder( + column: $table.icon, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$GameTableTableAnnotationComposer + extends Composer<_$AppDatabase, $GameTableTable> { + $$GameTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get ruleset => + $composableBuilder(column: $table.ruleset, builder: (column) => column); + + GeneratedColumn get description => $composableBuilder( + column: $table.description, + builder: (column) => column, + ); + + GeneratedColumn get color => + $composableBuilder(column: $table.color, builder: (column) => column); + + GeneratedColumn get icon => + $composableBuilder(column: $table.icon, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + Expression matchTableRefs( + Expression Function($$MatchTableTableAnnotationComposer a) f, + ) { + final $$MatchTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.matchTable, + getReferencedColumn: (t) => t.gameId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$MatchTableTableAnnotationComposer( + $db: $db, + $table: $db.matchTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$GameTableTableTableManager + extends + RootTableManager< + _$AppDatabase, + $GameTableTable, + GameTableData, + $$GameTableTableFilterComposer, + $$GameTableTableOrderingComposer, + $$GameTableTableAnnotationComposer, + $$GameTableTableCreateCompanionBuilder, + $$GameTableTableUpdateCompanionBuilder, + (GameTableData, $$GameTableTableReferences), + GameTableData, + PrefetchHooks Function({bool matchTableRefs}) + > { + $$GameTableTableTableManager(_$AppDatabase db, $GameTableTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$GameTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$GameTableTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$GameTableTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value ruleset = const Value.absent(), + Value description = const Value.absent(), + Value color = const Value.absent(), + Value icon = const Value.absent(), + Value createdAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => GameTableCompanion( + id: id, + name: name, + ruleset: ruleset, + description: description, + color: color, + icon: icon, + createdAt: createdAt, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String name, + required String ruleset, + required String description, + required String color, + required String icon, + required DateTime createdAt, + Value rowid = const Value.absent(), + }) => GameTableCompanion.insert( + id: id, + name: name, + ruleset: ruleset, + description: description, + color: color, + icon: icon, + createdAt: createdAt, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$GameTableTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({matchTableRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [if (matchTableRefs) db.matchTable], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (matchTableRefs) + await $_getPrefetchedData< + GameTableData, + $GameTableTable, + MatchTableData + >( + currentTable: table, + referencedTable: $$GameTableTableReferences + ._matchTableRefsTable(db), + managerFromTypedResult: (p0) => + $$GameTableTableReferences( + db, + table, + p0, + ).matchTableRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.gameId == item.id), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$GameTableTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $GameTableTable, + GameTableData, + $$GameTableTableFilterComposer, + $$GameTableTableOrderingComposer, + $$GameTableTableAnnotationComposer, + $$GameTableTableCreateCompanionBuilder, + $$GameTableTableUpdateCompanionBuilder, + (GameTableData, $$GameTableTableReferences), + GameTableData, + PrefetchHooks Function({bool matchTableRefs}) >; typedef $$MatchTableTableCreateCompanionBuilder = MatchTableCompanion Function({ required String id, - required String name, - Value winnerId, + required String gameId, + Value groupId, + Value name, + Value notes, required DateTime createdAt, + Value endedAt, Value rowid, }); typedef $$MatchTableTableUpdateCompanionBuilder = MatchTableCompanion Function({ Value id, - Value name, - Value winnerId, + Value gameId, + Value groupId, + Value name, + Value notes, Value createdAt, + Value endedAt, Value rowid, }); @@ -2358,6 +4073,42 @@ final class $$MatchTableTableReferences extends BaseReferences<_$AppDatabase, $MatchTableTable, MatchTableData> { $$MatchTableTableReferences(super.$_db, super.$_table, super.$_typedResult); + static $GameTableTable _gameIdTable(_$AppDatabase db) => db.gameTable + .createAlias($_aliasNameGenerator(db.matchTable.gameId, db.gameTable.id)); + + $$GameTableTableProcessedTableManager get gameId { + final $_column = $_itemColumn('game_id')!; + + final manager = $$GameTableTableTableManager( + $_db, + $_db.gameTable, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_gameIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static $GroupTableTable _groupIdTable(_$AppDatabase db) => + db.groupTable.createAlias( + $_aliasNameGenerator(db.matchTable.groupId, db.groupTable.id), + ); + + $$GroupTableTableProcessedTableManager? get groupId { + final $_column = $_itemColumn('group_id'); + if ($_column == null) return null; + final manager = $$GroupTableTableTableManager( + $_db, + $_db.groupTable, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_groupIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + static MultiTypedResultKey<$PlayerMatchTableTable, List> _playerMatchTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( db.playerMatchTable, @@ -2381,24 +4132,19 @@ final class $$MatchTableTableReferences ); } - static MultiTypedResultKey<$GroupMatchTableTable, List> - _groupMatchTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( - db.groupMatchTable, - aliasName: $_aliasNameGenerator( - db.matchTable.id, - db.groupMatchTable.matchId, - ), + static MultiTypedResultKey<$ScoreTableTable, List> + _scoreTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.scoreTable, + aliasName: $_aliasNameGenerator(db.matchTable.id, db.scoreTable.matchId), ); - $$GroupMatchTableTableProcessedTableManager get groupMatchTableRefs { - final manager = $$GroupMatchTableTableTableManager( + $$ScoreTableTableProcessedTableManager get scoreTableRefs { + final manager = $$ScoreTableTableTableManager( $_db, - $_db.groupMatchTable, + $_db.scoreTable, ).filter((f) => f.matchId.id.sqlEquals($_itemColumn('id')!)); - final cache = $_typedResult.readTableOrNull( - _groupMatchTableRefsTable($_db), - ); + final cache = $_typedResult.readTableOrNull(_scoreTableRefsTable($_db)); return ProcessedTableManager( manager.$state.copyWith(prefetchedData: cache), ); @@ -2424,8 +4170,8 @@ class $$MatchTableTableFilterComposer builder: (column) => ColumnFilters(column), ); - ColumnFilters get winnerId => $composableBuilder( - column: $table.winnerId, + ColumnFilters get notes => $composableBuilder( + column: $table.notes, builder: (column) => ColumnFilters(column), ); @@ -2434,6 +4180,57 @@ class $$MatchTableTableFilterComposer builder: (column) => ColumnFilters(column), ); + ColumnFilters get endedAt => $composableBuilder( + column: $table.endedAt, + builder: (column) => ColumnFilters(column), + ); + + $$GameTableTableFilterComposer get gameId { + final $$GameTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.gameId, + referencedTable: $db.gameTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GameTableTableFilterComposer( + $db: $db, + $table: $db.gameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$GroupTableTableFilterComposer get groupId { + final $$GroupTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.groupId, + referencedTable: $db.groupTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableTableFilterComposer( + $db: $db, + $table: $db.groupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + Expression playerMatchTableRefs( Expression Function($$PlayerMatchTableTableFilterComposer f) f, ) { @@ -2459,22 +4256,22 @@ class $$MatchTableTableFilterComposer return f(composer); } - Expression groupMatchTableRefs( - Expression Function($$GroupMatchTableTableFilterComposer f) f, + Expression scoreTableRefs( + Expression Function($$ScoreTableTableFilterComposer f) f, ) { - final $$GroupMatchTableTableFilterComposer composer = $composerBuilder( + final $$ScoreTableTableFilterComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.groupMatchTable, + referencedTable: $db.scoreTable, getReferencedColumn: (t) => t.matchId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GroupMatchTableTableFilterComposer( + }) => $$ScoreTableTableFilterComposer( $db: $db, - $table: $db.groupMatchTable, + $table: $db.scoreTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -2504,8 +4301,8 @@ class $$MatchTableTableOrderingComposer builder: (column) => ColumnOrderings(column), ); - ColumnOrderings get winnerId => $composableBuilder( - column: $table.winnerId, + ColumnOrderings get notes => $composableBuilder( + column: $table.notes, builder: (column) => ColumnOrderings(column), ); @@ -2513,6 +4310,57 @@ class $$MatchTableTableOrderingComposer column: $table.createdAt, builder: (column) => ColumnOrderings(column), ); + + ColumnOrderings get endedAt => $composableBuilder( + column: $table.endedAt, + builder: (column) => ColumnOrderings(column), + ); + + $$GameTableTableOrderingComposer get gameId { + final $$GameTableTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.gameId, + referencedTable: $db.gameTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GameTableTableOrderingComposer( + $db: $db, + $table: $db.gameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$GroupTableTableOrderingComposer get groupId { + final $$GroupTableTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.groupId, + referencedTable: $db.groupTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableTableOrderingComposer( + $db: $db, + $table: $db.groupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } } class $$MatchTableTableAnnotationComposer @@ -2530,12 +4378,61 @@ class $$MatchTableTableAnnotationComposer GeneratedColumn get name => $composableBuilder(column: $table.name, builder: (column) => column); - GeneratedColumn get winnerId => - $composableBuilder(column: $table.winnerId, builder: (column) => column); + GeneratedColumn get notes => + $composableBuilder(column: $table.notes, builder: (column) => column); GeneratedColumn get createdAt => $composableBuilder(column: $table.createdAt, builder: (column) => column); + GeneratedColumn get endedAt => + $composableBuilder(column: $table.endedAt, builder: (column) => column); + + $$GameTableTableAnnotationComposer get gameId { + final $$GameTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.gameId, + referencedTable: $db.gameTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GameTableTableAnnotationComposer( + $db: $db, + $table: $db.gameTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$GroupTableTableAnnotationComposer get groupId { + final $$GroupTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.groupId, + referencedTable: $db.groupTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$GroupTableTableAnnotationComposer( + $db: $db, + $table: $db.groupTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + Expression playerMatchTableRefs( Expression Function($$PlayerMatchTableTableAnnotationComposer a) f, ) { @@ -2561,22 +4458,22 @@ class $$MatchTableTableAnnotationComposer return f(composer); } - Expression groupMatchTableRefs( - Expression Function($$GroupMatchTableTableAnnotationComposer a) f, + Expression scoreTableRefs( + Expression Function($$ScoreTableTableAnnotationComposer a) f, ) { - final $$GroupMatchTableTableAnnotationComposer composer = $composerBuilder( + final $$ScoreTableTableAnnotationComposer composer = $composerBuilder( composer: this, getCurrentColumn: (t) => t.id, - referencedTable: $db.groupMatchTable, + referencedTable: $db.scoreTable, getReferencedColumn: (t) => t.matchId, builder: ( joinBuilder, { $addJoinBuilderToRootComposer, $removeJoinBuilderFromRootComposer, - }) => $$GroupMatchTableTableAnnotationComposer( + }) => $$ScoreTableTableAnnotationComposer( $db: $db, - $table: $db.groupMatchTable, + $table: $db.scoreTable, $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, joinBuilder: joinBuilder, $removeJoinBuilderFromRootComposer: @@ -2601,8 +4498,10 @@ class $$MatchTableTableTableManager (MatchTableData, $$MatchTableTableReferences), MatchTableData, PrefetchHooks Function({ + bool gameId, + bool groupId, bool playerMatchTableRefs, - bool groupMatchTableRefs, + bool scoreTableRefs, }) > { $$MatchTableTableTableManager(_$AppDatabase db, $MatchTableTable table) @@ -2619,29 +4518,41 @@ class $$MatchTableTableTableManager updateCompanionCallback: ({ Value id = const Value.absent(), - Value name = const Value.absent(), - Value winnerId = const Value.absent(), + Value gameId = const Value.absent(), + Value groupId = const Value.absent(), + Value name = const Value.absent(), + Value notes = const Value.absent(), Value createdAt = const Value.absent(), + Value endedAt = const Value.absent(), Value rowid = const Value.absent(), }) => MatchTableCompanion( id: id, + gameId: gameId, + groupId: groupId, name: name, - winnerId: winnerId, + notes: notes, createdAt: createdAt, + endedAt: endedAt, rowid: rowid, ), createCompanionCallback: ({ required String id, - required String name, - Value winnerId = const Value.absent(), + required String gameId, + Value groupId = const Value.absent(), + Value name = const Value.absent(), + Value notes = const Value.absent(), required DateTime createdAt, + Value endedAt = const Value.absent(), Value rowid = const Value.absent(), }) => MatchTableCompanion.insert( id: id, + gameId: gameId, + groupId: groupId, name: name, - winnerId: winnerId, + notes: notes, createdAt: createdAt, + endedAt: endedAt, rowid: rowid, ), withReferenceMapper: (p0) => p0 @@ -2653,14 +4564,65 @@ class $$MatchTableTableTableManager ) .toList(), prefetchHooksCallback: - ({playerMatchTableRefs = false, groupMatchTableRefs = false}) { + ({ + gameId = false, + groupId = false, + playerMatchTableRefs = false, + scoreTableRefs = false, + }) { return PrefetchHooks( db: db, explicitlyWatchedTables: [ if (playerMatchTableRefs) db.playerMatchTable, - if (groupMatchTableRefs) db.groupMatchTable, + if (scoreTableRefs) db.scoreTable, ], - addJoins: null, + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (gameId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.gameId, + referencedTable: $$MatchTableTableReferences + ._gameIdTable(db), + referencedColumn: + $$MatchTableTableReferences + ._gameIdTable(db) + .id, + ) + as T; + } + if (groupId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.groupId, + referencedTable: $$MatchTableTableReferences + ._groupIdTable(db), + referencedColumn: + $$MatchTableTableReferences + ._groupIdTable(db) + .id, + ) + as T; + } + + return state; + }, getPrefetchedDataCallback: (items) async { return [ if (playerMatchTableRefs) @@ -2684,21 +4646,21 @@ class $$MatchTableTableTableManager ), typedResults: items, ), - if (groupMatchTableRefs) + if (scoreTableRefs) await $_getPrefetchedData< MatchTableData, $MatchTableTable, - GroupMatchTableData + ScoreTableData >( currentTable: table, referencedTable: $$MatchTableTableReferences - ._groupMatchTableRefsTable(db), + ._scoreTableRefsTable(db), managerFromTypedResult: (p0) => $$MatchTableTableReferences( db, table, p0, - ).groupMatchTableRefs, + ).scoreTableRefs, referencedItemsForCurrentItem: (item, referencedItems) => referencedItems.where( (e) => e.matchId == item.id, @@ -2726,8 +4688,10 @@ typedef $$MatchTableTableProcessedTableManager = (MatchTableData, $$MatchTableTableReferences), MatchTableData, PrefetchHooks Function({ + bool gameId, + bool groupId, bool playerMatchTableRefs, - bool groupMatchTableRefs, + bool scoreTableRefs, }) >; typedef $$PlayerGroupTableTableCreateCompanionBuilder = @@ -3095,16 +5059,290 @@ typedef $$PlayerGroupTableTableProcessedTableManager = PlayerGroupTableData, PrefetchHooks Function({bool playerId, bool groupId}) >; +typedef $$TeamTableTableCreateCompanionBuilder = + TeamTableCompanion Function({ + required String id, + required String name, + required DateTime createdAt, + Value rowid, + }); +typedef $$TeamTableTableUpdateCompanionBuilder = + TeamTableCompanion Function({ + Value id, + Value name, + Value createdAt, + Value rowid, + }); + +final class $$TeamTableTableReferences + extends BaseReferences<_$AppDatabase, $TeamTableTable, TeamTableData> { + $$TeamTableTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static MultiTypedResultKey<$PlayerMatchTableTable, List> + _playerMatchTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable( + db.playerMatchTable, + aliasName: $_aliasNameGenerator( + db.teamTable.id, + db.playerMatchTable.teamId, + ), + ); + + $$PlayerMatchTableTableProcessedTableManager get playerMatchTableRefs { + final manager = $$PlayerMatchTableTableTableManager( + $_db, + $_db.playerMatchTable, + ).filter((f) => f.teamId.id.sqlEquals($_itemColumn('id')!)); + + final cache = $_typedResult.readTableOrNull( + _playerMatchTableRefsTable($_db), + ); + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: cache), + ); + } +} + +class $$TeamTableTableFilterComposer + extends Composer<_$AppDatabase, $TeamTableTable> { + $$TeamTableTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnFilters(column), + ); + + Expression playerMatchTableRefs( + Expression Function($$PlayerMatchTableTableFilterComposer f) f, + ) { + final $$PlayerMatchTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.playerMatchTable, + getReferencedColumn: (t) => t.teamId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerMatchTableTableFilterComposer( + $db: $db, + $table: $db.playerMatchTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$TeamTableTableOrderingComposer + extends Composer<_$AppDatabase, $TeamTableTable> { + $$TeamTableTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get createdAt => $composableBuilder( + column: $table.createdAt, + builder: (column) => ColumnOrderings(column), + ); +} + +class $$TeamTableTableAnnotationComposer + extends Composer<_$AppDatabase, $TeamTableTable> { + $$TeamTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get createdAt => + $composableBuilder(column: $table.createdAt, builder: (column) => column); + + Expression playerMatchTableRefs( + Expression Function($$PlayerMatchTableTableAnnotationComposer a) f, + ) { + final $$PlayerMatchTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.id, + referencedTable: $db.playerMatchTable, + getReferencedColumn: (t) => t.teamId, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerMatchTableTableAnnotationComposer( + $db: $db, + $table: $db.playerMatchTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return f(composer); + } +} + +class $$TeamTableTableTableManager + extends + RootTableManager< + _$AppDatabase, + $TeamTableTable, + TeamTableData, + $$TeamTableTableFilterComposer, + $$TeamTableTableOrderingComposer, + $$TeamTableTableAnnotationComposer, + $$TeamTableTableCreateCompanionBuilder, + $$TeamTableTableUpdateCompanionBuilder, + (TeamTableData, $$TeamTableTableReferences), + TeamTableData, + PrefetchHooks Function({bool playerMatchTableRefs}) + > { + $$TeamTableTableTableManager(_$AppDatabase db, $TeamTableTable table) + : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$TeamTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$TeamTableTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$TeamTableTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value createdAt = const Value.absent(), + Value rowid = const Value.absent(), + }) => TeamTableCompanion( + id: id, + name: name, + createdAt: createdAt, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String id, + required String name, + required DateTime createdAt, + Value rowid = const Value.absent(), + }) => TeamTableCompanion.insert( + id: id, + name: name, + createdAt: createdAt, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$TeamTableTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: ({playerMatchTableRefs = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [ + if (playerMatchTableRefs) db.playerMatchTable, + ], + addJoins: null, + getPrefetchedDataCallback: (items) async { + return [ + if (playerMatchTableRefs) + await $_getPrefetchedData< + TeamTableData, + $TeamTableTable, + PlayerMatchTableData + >( + currentTable: table, + referencedTable: $$TeamTableTableReferences + ._playerMatchTableRefsTable(db), + managerFromTypedResult: (p0) => + $$TeamTableTableReferences( + db, + table, + p0, + ).playerMatchTableRefs, + referencedItemsForCurrentItem: (item, referencedItems) => + referencedItems.where((e) => e.teamId == item.id), + typedResults: items, + ), + ]; + }, + ); + }, + ), + ); +} + +typedef $$TeamTableTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $TeamTableTable, + TeamTableData, + $$TeamTableTableFilterComposer, + $$TeamTableTableOrderingComposer, + $$TeamTableTableAnnotationComposer, + $$TeamTableTableCreateCompanionBuilder, + $$TeamTableTableUpdateCompanionBuilder, + (TeamTableData, $$TeamTableTableReferences), + TeamTableData, + PrefetchHooks Function({bool playerMatchTableRefs}) + >; typedef $$PlayerMatchTableTableCreateCompanionBuilder = PlayerMatchTableCompanion Function({ required String playerId, required String matchId, + Value teamId, + required int score, Value rowid, }); typedef $$PlayerMatchTableTableUpdateCompanionBuilder = PlayerMatchTableCompanion Function({ Value playerId, Value matchId, + Value teamId, + Value score, Value rowid, }); @@ -3158,6 +5396,25 @@ final class $$PlayerMatchTableTableReferences manager.$state.copyWith(prefetchedData: [item]), ); } + + static $TeamTableTable _teamIdTable(_$AppDatabase db) => + db.teamTable.createAlias( + $_aliasNameGenerator(db.playerMatchTable.teamId, db.teamTable.id), + ); + + $$TeamTableTableProcessedTableManager? get teamId { + final $_column = $_itemColumn('team_id'); + if ($_column == null) return null; + final manager = $$TeamTableTableTableManager( + $_db, + $_db.teamTable, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_teamIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } } class $$PlayerMatchTableTableFilterComposer @@ -3169,6 +5426,489 @@ class $$PlayerMatchTableTableFilterComposer super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); + ColumnFilters get score => $composableBuilder( + column: $table.score, + builder: (column) => ColumnFilters(column), + ); + + $$PlayerTableTableFilterComposer get playerId { + final $$PlayerTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.playerId, + referencedTable: $db.playerTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerTableTableFilterComposer( + $db: $db, + $table: $db.playerTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$MatchTableTableFilterComposer get matchId { + final $$MatchTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.matchId, + referencedTable: $db.matchTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$MatchTableTableFilterComposer( + $db: $db, + $table: $db.matchTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$TeamTableTableFilterComposer get teamId { + final $$TeamTableTableFilterComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.teamId, + referencedTable: $db.teamTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$TeamTableTableFilterComposer( + $db: $db, + $table: $db.teamTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$PlayerMatchTableTableOrderingComposer + extends Composer<_$AppDatabase, $PlayerMatchTableTable> { + $$PlayerMatchTableTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get score => $composableBuilder( + column: $table.score, + builder: (column) => ColumnOrderings(column), + ); + + $$PlayerTableTableOrderingComposer get playerId { + final $$PlayerTableTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.playerId, + referencedTable: $db.playerTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerTableTableOrderingComposer( + $db: $db, + $table: $db.playerTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$MatchTableTableOrderingComposer get matchId { + final $$MatchTableTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.matchId, + referencedTable: $db.matchTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$MatchTableTableOrderingComposer( + $db: $db, + $table: $db.matchTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$TeamTableTableOrderingComposer get teamId { + final $$TeamTableTableOrderingComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.teamId, + referencedTable: $db.teamTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$TeamTableTableOrderingComposer( + $db: $db, + $table: $db.teamTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$PlayerMatchTableTableAnnotationComposer + extends Composer<_$AppDatabase, $PlayerMatchTableTable> { + $$PlayerMatchTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get score => + $composableBuilder(column: $table.score, builder: (column) => column); + + $$PlayerTableTableAnnotationComposer get playerId { + final $$PlayerTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.playerId, + referencedTable: $db.playerTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$PlayerTableTableAnnotationComposer( + $db: $db, + $table: $db.playerTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$MatchTableTableAnnotationComposer get matchId { + final $$MatchTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.matchId, + referencedTable: $db.matchTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$MatchTableTableAnnotationComposer( + $db: $db, + $table: $db.matchTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } + + $$TeamTableTableAnnotationComposer get teamId { + final $$TeamTableTableAnnotationComposer composer = $composerBuilder( + composer: this, + getCurrentColumn: (t) => t.teamId, + referencedTable: $db.teamTable, + getReferencedColumn: (t) => t.id, + builder: + ( + joinBuilder, { + $addJoinBuilderToRootComposer, + $removeJoinBuilderFromRootComposer, + }) => $$TeamTableTableAnnotationComposer( + $db: $db, + $table: $db.teamTable, + $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, + joinBuilder: joinBuilder, + $removeJoinBuilderFromRootComposer: + $removeJoinBuilderFromRootComposer, + ), + ); + return composer; + } +} + +class $$PlayerMatchTableTableTableManager + extends + RootTableManager< + _$AppDatabase, + $PlayerMatchTableTable, + PlayerMatchTableData, + $$PlayerMatchTableTableFilterComposer, + $$PlayerMatchTableTableOrderingComposer, + $$PlayerMatchTableTableAnnotationComposer, + $$PlayerMatchTableTableCreateCompanionBuilder, + $$PlayerMatchTableTableUpdateCompanionBuilder, + (PlayerMatchTableData, $$PlayerMatchTableTableReferences), + PlayerMatchTableData, + PrefetchHooks Function({bool playerId, bool matchId, bool teamId}) + > { + $$PlayerMatchTableTableTableManager( + _$AppDatabase db, + $PlayerMatchTableTable table, + ) : super( + TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$PlayerMatchTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$PlayerMatchTableTableOrderingComposer($db: db, $table: table), + createComputedFieldComposer: () => + $$PlayerMatchTableTableAnnotationComposer($db: db, $table: table), + updateCompanionCallback: + ({ + Value playerId = const Value.absent(), + Value matchId = const Value.absent(), + Value teamId = const Value.absent(), + Value score = const Value.absent(), + Value rowid = const Value.absent(), + }) => PlayerMatchTableCompanion( + playerId: playerId, + matchId: matchId, + teamId: teamId, + score: score, + rowid: rowid, + ), + createCompanionCallback: + ({ + required String playerId, + required String matchId, + Value teamId = const Value.absent(), + required int score, + Value rowid = const Value.absent(), + }) => PlayerMatchTableCompanion.insert( + playerId: playerId, + matchId: matchId, + teamId: teamId, + score: score, + rowid: rowid, + ), + withReferenceMapper: (p0) => p0 + .map( + (e) => ( + e.readTable(table), + $$PlayerMatchTableTableReferences(db, table, e), + ), + ) + .toList(), + prefetchHooksCallback: + ({playerId = false, matchId = false, teamId = false}) { + return PrefetchHooks( + db: db, + explicitlyWatchedTables: [], + addJoins: + < + T extends TableManagerState< + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic, + dynamic + > + >(state) { + if (playerId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.playerId, + referencedTable: + $$PlayerMatchTableTableReferences + ._playerIdTable(db), + referencedColumn: + $$PlayerMatchTableTableReferences + ._playerIdTable(db) + .id, + ) + as T; + } + if (matchId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.matchId, + referencedTable: + $$PlayerMatchTableTableReferences + ._matchIdTable(db), + referencedColumn: + $$PlayerMatchTableTableReferences + ._matchIdTable(db) + .id, + ) + as T; + } + if (teamId) { + state = + state.withJoin( + currentTable: table, + currentColumn: table.teamId, + referencedTable: + $$PlayerMatchTableTableReferences + ._teamIdTable(db), + referencedColumn: + $$PlayerMatchTableTableReferences + ._teamIdTable(db) + .id, + ) + as T; + } + + return state; + }, + getPrefetchedDataCallback: (items) async { + return []; + }, + ); + }, + ), + ); +} + +typedef $$PlayerMatchTableTableProcessedTableManager = + ProcessedTableManager< + _$AppDatabase, + $PlayerMatchTableTable, + PlayerMatchTableData, + $$PlayerMatchTableTableFilterComposer, + $$PlayerMatchTableTableOrderingComposer, + $$PlayerMatchTableTableAnnotationComposer, + $$PlayerMatchTableTableCreateCompanionBuilder, + $$PlayerMatchTableTableUpdateCompanionBuilder, + (PlayerMatchTableData, $$PlayerMatchTableTableReferences), + PlayerMatchTableData, + PrefetchHooks Function({bool playerId, bool matchId, bool teamId}) + >; +typedef $$ScoreTableTableCreateCompanionBuilder = + ScoreTableCompanion Function({ + required String playerId, + required String matchId, + required int roundNumber, + required int score, + required int change, + Value rowid, + }); +typedef $$ScoreTableTableUpdateCompanionBuilder = + ScoreTableCompanion Function({ + Value playerId, + Value matchId, + Value roundNumber, + Value score, + Value change, + Value rowid, + }); + +final class $$ScoreTableTableReferences + extends BaseReferences<_$AppDatabase, $ScoreTableTable, ScoreTableData> { + $$ScoreTableTableReferences(super.$_db, super.$_table, super.$_typedResult); + + static $PlayerTableTable _playerIdTable(_$AppDatabase db) => + db.playerTable.createAlias( + $_aliasNameGenerator(db.scoreTable.playerId, db.playerTable.id), + ); + + $$PlayerTableTableProcessedTableManager get playerId { + final $_column = $_itemColumn('player_id')!; + + final manager = $$PlayerTableTableTableManager( + $_db, + $_db.playerTable, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_playerIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } + + static $MatchTableTable _matchIdTable(_$AppDatabase db) => + db.matchTable.createAlias( + $_aliasNameGenerator(db.scoreTable.matchId, db.matchTable.id), + ); + + $$MatchTableTableProcessedTableManager get matchId { + final $_column = $_itemColumn('match_id')!; + + final manager = $$MatchTableTableTableManager( + $_db, + $_db.matchTable, + ).filter((f) => f.id.sqlEquals($_column)); + final item = $_typedResult.readTableOrNull(_matchIdTable($_db)); + if (item == null) return manager; + return ProcessedTableManager( + manager.$state.copyWith(prefetchedData: [item]), + ); + } +} + +class $$ScoreTableTableFilterComposer + extends Composer<_$AppDatabase, $ScoreTableTable> { + $$ScoreTableTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get roundNumber => $composableBuilder( + column: $table.roundNumber, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get score => $composableBuilder( + column: $table.score, + builder: (column) => ColumnFilters(column), + ); + + ColumnFilters get change => $composableBuilder( + column: $table.change, + builder: (column) => ColumnFilters(column), + ); + $$PlayerTableTableFilterComposer get playerId { final $$PlayerTableTableFilterComposer composer = $composerBuilder( composer: this, @@ -3216,15 +5956,30 @@ class $$PlayerMatchTableTableFilterComposer } } -class $$PlayerMatchTableTableOrderingComposer - extends Composer<_$AppDatabase, $PlayerMatchTableTable> { - $$PlayerMatchTableTableOrderingComposer({ +class $$ScoreTableTableOrderingComposer + extends Composer<_$AppDatabase, $ScoreTableTable> { + $$ScoreTableTableOrderingComposer({ required super.$db, required super.$table, super.joinBuilder, super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); + ColumnOrderings get roundNumber => $composableBuilder( + column: $table.roundNumber, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get score => $composableBuilder( + column: $table.score, + builder: (column) => ColumnOrderings(column), + ); + + ColumnOrderings get change => $composableBuilder( + column: $table.change, + builder: (column) => ColumnOrderings(column), + ); + $$PlayerTableTableOrderingComposer get playerId { final $$PlayerTableTableOrderingComposer composer = $composerBuilder( composer: this, @@ -3272,15 +6027,26 @@ class $$PlayerMatchTableTableOrderingComposer } } -class $$PlayerMatchTableTableAnnotationComposer - extends Composer<_$AppDatabase, $PlayerMatchTableTable> { - $$PlayerMatchTableTableAnnotationComposer({ +class $$ScoreTableTableAnnotationComposer + extends Composer<_$AppDatabase, $ScoreTableTable> { + $$ScoreTableTableAnnotationComposer({ required super.$db, required super.$table, super.joinBuilder, super.$addJoinBuilderToRootComposer, super.$removeJoinBuilderFromRootComposer, }); + GeneratedColumn get roundNumber => $composableBuilder( + column: $table.roundNumber, + builder: (column) => column, + ); + + GeneratedColumn get score => + $composableBuilder(column: $table.score, builder: (column) => column); + + GeneratedColumn get change => + $composableBuilder(column: $table.change, builder: (column) => column); + $$PlayerTableTableAnnotationComposer get playerId { final $$PlayerTableTableAnnotationComposer composer = $composerBuilder( composer: this, @@ -3328,59 +6094,69 @@ class $$PlayerMatchTableTableAnnotationComposer } } -class $$PlayerMatchTableTableTableManager +class $$ScoreTableTableTableManager extends RootTableManager< _$AppDatabase, - $PlayerMatchTableTable, - PlayerMatchTableData, - $$PlayerMatchTableTableFilterComposer, - $$PlayerMatchTableTableOrderingComposer, - $$PlayerMatchTableTableAnnotationComposer, - $$PlayerMatchTableTableCreateCompanionBuilder, - $$PlayerMatchTableTableUpdateCompanionBuilder, - (PlayerMatchTableData, $$PlayerMatchTableTableReferences), - PlayerMatchTableData, + $ScoreTableTable, + ScoreTableData, + $$ScoreTableTableFilterComposer, + $$ScoreTableTableOrderingComposer, + $$ScoreTableTableAnnotationComposer, + $$ScoreTableTableCreateCompanionBuilder, + $$ScoreTableTableUpdateCompanionBuilder, + (ScoreTableData, $$ScoreTableTableReferences), + ScoreTableData, PrefetchHooks Function({bool playerId, bool matchId}) > { - $$PlayerMatchTableTableTableManager( - _$AppDatabase db, - $PlayerMatchTableTable table, - ) : super( + $$ScoreTableTableTableManager(_$AppDatabase db, $ScoreTableTable table) + : super( TableManagerState( db: db, table: table, createFilteringComposer: () => - $$PlayerMatchTableTableFilterComposer($db: db, $table: table), + $$ScoreTableTableFilterComposer($db: db, $table: table), createOrderingComposer: () => - $$PlayerMatchTableTableOrderingComposer($db: db, $table: table), + $$ScoreTableTableOrderingComposer($db: db, $table: table), createComputedFieldComposer: () => - $$PlayerMatchTableTableAnnotationComposer($db: db, $table: table), + $$ScoreTableTableAnnotationComposer($db: db, $table: table), updateCompanionCallback: ({ Value playerId = const Value.absent(), Value matchId = const Value.absent(), + Value roundNumber = const Value.absent(), + Value score = const Value.absent(), + Value change = const Value.absent(), Value rowid = const Value.absent(), - }) => PlayerMatchTableCompanion( + }) => ScoreTableCompanion( playerId: playerId, matchId: matchId, + roundNumber: roundNumber, + score: score, + change: change, rowid: rowid, ), createCompanionCallback: ({ required String playerId, required String matchId, + required int roundNumber, + required int score, + required int change, Value rowid = const Value.absent(), - }) => PlayerMatchTableCompanion.insert( + }) => ScoreTableCompanion.insert( playerId: playerId, matchId: matchId, + roundNumber: roundNumber, + score: score, + change: change, rowid: rowid, ), withReferenceMapper: (p0) => p0 .map( (e) => ( e.readTable(table), - $$PlayerMatchTableTableReferences(db, table, e), + $$ScoreTableTableReferences(db, table, e), ), ) .toList(), @@ -3409,13 +6185,11 @@ class $$PlayerMatchTableTableTableManager state.withJoin( currentTable: table, currentColumn: table.playerId, - referencedTable: - $$PlayerMatchTableTableReferences - ._playerIdTable(db), - referencedColumn: - $$PlayerMatchTableTableReferences - ._playerIdTable(db) - .id, + referencedTable: $$ScoreTableTableReferences + ._playerIdTable(db), + referencedColumn: $$ScoreTableTableReferences + ._playerIdTable(db) + .id, ) as T; } @@ -3424,13 +6198,11 @@ class $$PlayerMatchTableTableTableManager state.withJoin( currentTable: table, currentColumn: table.matchId, - referencedTable: - $$PlayerMatchTableTableReferences - ._matchIdTable(db), - referencedColumn: - $$PlayerMatchTableTableReferences - ._matchIdTable(db) - .id, + referencedTable: $$ScoreTableTableReferences + ._matchIdTable(db), + referencedColumn: $$ScoreTableTableReferences + ._matchIdTable(db) + .id, ) as T; } @@ -3446,385 +6218,20 @@ class $$PlayerMatchTableTableTableManager ); } -typedef $$PlayerMatchTableTableProcessedTableManager = +typedef $$ScoreTableTableProcessedTableManager = ProcessedTableManager< _$AppDatabase, - $PlayerMatchTableTable, - PlayerMatchTableData, - $$PlayerMatchTableTableFilterComposer, - $$PlayerMatchTableTableOrderingComposer, - $$PlayerMatchTableTableAnnotationComposer, - $$PlayerMatchTableTableCreateCompanionBuilder, - $$PlayerMatchTableTableUpdateCompanionBuilder, - (PlayerMatchTableData, $$PlayerMatchTableTableReferences), - PlayerMatchTableData, + $ScoreTableTable, + ScoreTableData, + $$ScoreTableTableFilterComposer, + $$ScoreTableTableOrderingComposer, + $$ScoreTableTableAnnotationComposer, + $$ScoreTableTableCreateCompanionBuilder, + $$ScoreTableTableUpdateCompanionBuilder, + (ScoreTableData, $$ScoreTableTableReferences), + ScoreTableData, PrefetchHooks Function({bool playerId, bool matchId}) >; -typedef $$GroupMatchTableTableCreateCompanionBuilder = - GroupMatchTableCompanion Function({ - required String groupId, - required String matchId, - Value rowid, - }); -typedef $$GroupMatchTableTableUpdateCompanionBuilder = - GroupMatchTableCompanion Function({ - Value groupId, - Value matchId, - Value rowid, - }); - -final class $$GroupMatchTableTableReferences - extends - BaseReferences< - _$AppDatabase, - $GroupMatchTableTable, - GroupMatchTableData - > { - $$GroupMatchTableTableReferences( - super.$_db, - super.$_table, - super.$_typedResult, - ); - - static $GroupTableTable _groupIdTable(_$AppDatabase db) => - db.groupTable.createAlias( - $_aliasNameGenerator(db.groupMatchTable.groupId, db.groupTable.id), - ); - - $$GroupTableTableProcessedTableManager get groupId { - final $_column = $_itemColumn('group_id')!; - - final manager = $$GroupTableTableTableManager( - $_db, - $_db.groupTable, - ).filter((f) => f.id.sqlEquals($_column)); - final item = $_typedResult.readTableOrNull(_groupIdTable($_db)); - if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item]), - ); - } - - static $MatchTableTable _matchIdTable(_$AppDatabase db) => - db.matchTable.createAlias( - $_aliasNameGenerator(db.groupMatchTable.matchId, db.matchTable.id), - ); - - $$MatchTableTableProcessedTableManager get matchId { - final $_column = $_itemColumn('match_id')!; - - final manager = $$MatchTableTableTableManager( - $_db, - $_db.matchTable, - ).filter((f) => f.id.sqlEquals($_column)); - final item = $_typedResult.readTableOrNull(_matchIdTable($_db)); - if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item]), - ); - } -} - -class $$GroupMatchTableTableFilterComposer - extends Composer<_$AppDatabase, $GroupMatchTableTable> { - $$GroupMatchTableTableFilterComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - $$GroupTableTableFilterComposer get groupId { - final $$GroupTableTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.groupId, - referencedTable: $db.groupTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GroupTableTableFilterComposer( - $db: $db, - $table: $db.groupTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } - - $$MatchTableTableFilterComposer get matchId { - final $$MatchTableTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.matchId, - referencedTable: $db.matchTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$MatchTableTableFilterComposer( - $db: $db, - $table: $db.matchTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } -} - -class $$GroupMatchTableTableOrderingComposer - extends Composer<_$AppDatabase, $GroupMatchTableTable> { - $$GroupMatchTableTableOrderingComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - $$GroupTableTableOrderingComposer get groupId { - final $$GroupTableTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.groupId, - referencedTable: $db.groupTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GroupTableTableOrderingComposer( - $db: $db, - $table: $db.groupTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } - - $$MatchTableTableOrderingComposer get matchId { - final $$MatchTableTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.matchId, - referencedTable: $db.matchTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$MatchTableTableOrderingComposer( - $db: $db, - $table: $db.matchTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } -} - -class $$GroupMatchTableTableAnnotationComposer - extends Composer<_$AppDatabase, $GroupMatchTableTable> { - $$GroupMatchTableTableAnnotationComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - $$GroupTableTableAnnotationComposer get groupId { - final $$GroupTableTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.groupId, - referencedTable: $db.groupTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$GroupTableTableAnnotationComposer( - $db: $db, - $table: $db.groupTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } - - $$MatchTableTableAnnotationComposer get matchId { - final $$MatchTableTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.matchId, - referencedTable: $db.matchTable, - getReferencedColumn: (t) => t.id, - builder: - ( - joinBuilder, { - $addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer, - }) => $$MatchTableTableAnnotationComposer( - $db: $db, - $table: $db.matchTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - ), - ); - return composer; - } -} - -class $$GroupMatchTableTableTableManager - extends - RootTableManager< - _$AppDatabase, - $GroupMatchTableTable, - GroupMatchTableData, - $$GroupMatchTableTableFilterComposer, - $$GroupMatchTableTableOrderingComposer, - $$GroupMatchTableTableAnnotationComposer, - $$GroupMatchTableTableCreateCompanionBuilder, - $$GroupMatchTableTableUpdateCompanionBuilder, - (GroupMatchTableData, $$GroupMatchTableTableReferences), - GroupMatchTableData, - PrefetchHooks Function({bool groupId, bool matchId}) - > { - $$GroupMatchTableTableTableManager( - _$AppDatabase db, - $GroupMatchTableTable table, - ) : super( - TableManagerState( - db: db, - table: table, - createFilteringComposer: () => - $$GroupMatchTableTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$GroupMatchTableTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$GroupMatchTableTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: - ({ - Value groupId = const Value.absent(), - Value matchId = const Value.absent(), - Value rowid = const Value.absent(), - }) => GroupMatchTableCompanion( - groupId: groupId, - matchId: matchId, - rowid: rowid, - ), - createCompanionCallback: - ({ - required String groupId, - required String matchId, - Value rowid = const Value.absent(), - }) => GroupMatchTableCompanion.insert( - groupId: groupId, - matchId: matchId, - rowid: rowid, - ), - withReferenceMapper: (p0) => p0 - .map( - (e) => ( - e.readTable(table), - $$GroupMatchTableTableReferences(db, table, e), - ), - ) - .toList(), - prefetchHooksCallback: ({groupId = false, matchId = false}) { - return PrefetchHooks( - db: db, - explicitlyWatchedTables: [], - addJoins: - < - T extends TableManagerState< - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic - > - >(state) { - if (groupId) { - state = - state.withJoin( - currentTable: table, - currentColumn: table.groupId, - referencedTable: - $$GroupMatchTableTableReferences - ._groupIdTable(db), - referencedColumn: - $$GroupMatchTableTableReferences - ._groupIdTable(db) - .id, - ) - as T; - } - if (matchId) { - state = - state.withJoin( - currentTable: table, - currentColumn: table.matchId, - referencedTable: - $$GroupMatchTableTableReferences - ._matchIdTable(db), - referencedColumn: - $$GroupMatchTableTableReferences - ._matchIdTable(db) - .id, - ) - as T; - } - - return state; - }, - getPrefetchedDataCallback: (items) async { - return []; - }, - ); - }, - ), - ); -} - -typedef $$GroupMatchTableTableProcessedTableManager = - ProcessedTableManager< - _$AppDatabase, - $GroupMatchTableTable, - GroupMatchTableData, - $$GroupMatchTableTableFilterComposer, - $$GroupMatchTableTableOrderingComposer, - $$GroupMatchTableTableAnnotationComposer, - $$GroupMatchTableTableCreateCompanionBuilder, - $$GroupMatchTableTableUpdateCompanionBuilder, - (GroupMatchTableData, $$GroupMatchTableTableReferences), - GroupMatchTableData, - PrefetchHooks Function({bool groupId, bool matchId}) - >; class $AppDatabaseManager { final _$AppDatabase _db; @@ -3833,12 +6240,16 @@ class $AppDatabaseManager { $$PlayerTableTableTableManager(_db, _db.playerTable); $$GroupTableTableTableManager get groupTable => $$GroupTableTableTableManager(_db, _db.groupTable); + $$GameTableTableTableManager get gameTable => + $$GameTableTableTableManager(_db, _db.gameTable); $$MatchTableTableTableManager get matchTable => $$MatchTableTableTableManager(_db, _db.matchTable); $$PlayerGroupTableTableTableManager get playerGroupTable => $$PlayerGroupTableTableTableManager(_db, _db.playerGroupTable); + $$TeamTableTableTableManager get teamTable => + $$TeamTableTableTableManager(_db, _db.teamTable); $$PlayerMatchTableTableTableManager get playerMatchTable => $$PlayerMatchTableTableTableManager(_db, _db.playerMatchTable); - $$GroupMatchTableTableTableManager get groupMatchTable => - $$GroupMatchTableTableTableManager(_db, _db.groupMatchTable); + $$ScoreTableTableTableManager get scoreTable => + $$ScoreTableTableTableManager(_db, _db.scoreTable); } diff --git a/lib/data/db/tables/game_table.dart b/lib/data/db/tables/game_table.dart new file mode 100644 index 0000000..a55b8fc --- /dev/null +++ b/lib/data/db/tables/game_table.dart @@ -0,0 +1,14 @@ +import 'package:drift/drift.dart'; + +class GameTable extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + TextColumn get ruleset => text()(); + TextColumn get description => text()(); + TextColumn get color => text()(); + TextColumn get icon => text()(); + DateTimeColumn get createdAt => dateTime()(); + + @override + Set> get primaryKey => {id}; +} diff --git a/lib/data/db/tables/group_match_table.dart b/lib/data/db/tables/group_match_table.dart deleted file mode 100644 index 3f77dcb..0000000 --- a/lib/data/db/tables/group_match_table.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:drift/drift.dart'; -import 'package:game_tracker/data/db/tables/group_table.dart'; -import 'package:game_tracker/data/db/tables/match_table.dart'; - -class GroupMatchTable extends Table { - TextColumn get groupId => - text().references(GroupTable, #id, onDelete: KeyAction.cascade)(); - TextColumn get matchId => - text().references(MatchTable, #id, onDelete: KeyAction.cascade)(); - - @override - Set> get primaryKey => {groupId, matchId}; -} diff --git a/lib/data/db/tables/group_table.dart b/lib/data/db/tables/group_table.dart index 5c52355..2f30cce 100644 --- a/lib/data/db/tables/group_table.dart +++ b/lib/data/db/tables/group_table.dart @@ -3,6 +3,7 @@ import 'package:drift/drift.dart'; class GroupTable extends Table { TextColumn get id => text()(); TextColumn get name => text()(); + TextColumn get description => text()(); DateTimeColumn get createdAt => dateTime()(); @override diff --git a/lib/data/db/tables/match_table.dart b/lib/data/db/tables/match_table.dart index 96aff2a..191e72c 100644 --- a/lib/data/db/tables/match_table.dart +++ b/lib/data/db/tables/match_table.dart @@ -1,11 +1,19 @@ import 'package:drift/drift.dart'; +import 'package:tallee/data/db/tables/game_table.dart'; +import 'package:tallee/data/db/tables/group_table.dart'; class MatchTable extends Table { TextColumn get id => text()(); - TextColumn get name => text()(); - late final winnerId = text().nullable()(); + TextColumn get gameId => + text().references(GameTable, #id, onDelete: KeyAction.cascade)(); + // Nullable if there is no group associated with the match + TextColumn get groupId => + text().references(GroupTable, #id, onDelete: KeyAction.cascade).nullable()(); + TextColumn get name => text().nullable()(); + TextColumn get notes => text().nullable()(); DateTimeColumn get createdAt => dateTime()(); + DateTimeColumn get endedAt => dateTime().nullable()(); @override Set> get primaryKey => {id}; -} +} \ No newline at end of file diff --git a/lib/data/db/tables/player_group_table.dart b/lib/data/db/tables/player_group_table.dart index da2521b..8d484ef 100644 --- a/lib/data/db/tables/player_group_table.dart +++ b/lib/data/db/tables/player_group_table.dart @@ -1,6 +1,6 @@ import 'package:drift/drift.dart'; -import 'package:game_tracker/data/db/tables/group_table.dart'; -import 'package:game_tracker/data/db/tables/player_table.dart'; +import 'package:tallee/data/db/tables/group_table.dart'; +import 'package:tallee/data/db/tables/player_table.dart'; class PlayerGroupTable extends Table { TextColumn get playerId => diff --git a/lib/data/db/tables/player_match_table.dart b/lib/data/db/tables/player_match_table.dart index e155cd5..3ff55ea 100644 --- a/lib/data/db/tables/player_match_table.dart +++ b/lib/data/db/tables/player_match_table.dart @@ -1,12 +1,16 @@ import 'package:drift/drift.dart'; -import 'package:game_tracker/data/db/tables/match_table.dart'; -import 'package:game_tracker/data/db/tables/player_table.dart'; +import 'package:tallee/data/db/tables/match_table.dart'; +import 'package:tallee/data/db/tables/player_table.dart'; +import 'package:tallee/data/db/tables/team_table.dart'; class PlayerMatchTable extends Table { TextColumn get playerId => text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); TextColumn get matchId => text().references(MatchTable, #id, onDelete: KeyAction.cascade)(); + TextColumn get teamId => + text().references(TeamTable, #id).nullable()(); + IntColumn get score => integer()(); @override Set> get primaryKey => {playerId, matchId}; diff --git a/lib/data/db/tables/player_table.dart b/lib/data/db/tables/player_table.dart index 794958e..15b29a5 100644 --- a/lib/data/db/tables/player_table.dart +++ b/lib/data/db/tables/player_table.dart @@ -3,6 +3,7 @@ import 'package:drift/drift.dart'; class PlayerTable extends Table { TextColumn get id => text()(); TextColumn get name => text()(); + TextColumn get description => text()(); DateTimeColumn get createdAt => dateTime()(); @override diff --git a/lib/data/db/tables/score_table.dart b/lib/data/db/tables/score_table.dart new file mode 100644 index 0000000..a7bf122 --- /dev/null +++ b/lib/data/db/tables/score_table.dart @@ -0,0 +1,16 @@ +import 'package:drift/drift.dart'; +import 'package:tallee/data/db/tables/match_table.dart'; +import 'package:tallee/data/db/tables/player_table.dart'; + +class ScoreTable extends Table { + TextColumn get playerId => + text().references(PlayerTable, #id, onDelete: KeyAction.cascade)(); + TextColumn get matchId => + text().references(MatchTable, #id, onDelete: KeyAction.cascade)(); + IntColumn get roundNumber => integer()(); + IntColumn get score => integer()(); + IntColumn get change => integer()(); + + @override + Set> get primaryKey => {playerId, matchId, roundNumber}; +} \ No newline at end of file diff --git a/lib/data/db/tables/team_table.dart b/lib/data/db/tables/team_table.dart new file mode 100644 index 0000000..b1a24a9 --- /dev/null +++ b/lib/data/db/tables/team_table.dart @@ -0,0 +1,10 @@ +import 'package:drift/drift.dart'; + +class TeamTable extends Table { + TextColumn get id => text()(); + TextColumn get name => text()(); + DateTimeColumn get createdAt => dateTime()(); + + @override + Set> get primaryKey => {id}; +} diff --git a/lib/data/dto/game.dart b/lib/data/dto/game.dart new file mode 100644 index 0000000..2eeee1e --- /dev/null +++ b/lib/data/dto/game.dart @@ -0,0 +1,52 @@ +import 'package:clock/clock.dart'; +import 'package:uuid/uuid.dart'; +import 'package:tallee/core/enums.dart'; + +class Game { + final String id; + final DateTime createdAt; + final String name; + final Ruleset ruleset; + final String description; + final GameColor color; + final String icon; + + Game({ + String? id, + DateTime? createdAt, + required this.name, + required this.ruleset, + String? description, + required this.color, + required this.icon, + }) : id = id ?? const Uuid().v4(), + createdAt = createdAt ?? clock.now(), + description = description ?? ''; + + @override + String toString() { + return 'Game{id: $id, name: $name, ruleset: $ruleset, description: $description, color: $color, icon: $icon}'; + } + + /// Creates a Game instance from a JSON object. + Game.fromJson(Map json) + : id = json['id'], + createdAt = DateTime.parse(json['createdAt']), + name = json['name'], + ruleset = Ruleset.values.firstWhere((e) => e.name == json['ruleset']), + description = json['description'], + color = GameColor.values.firstWhere((e) => e.name == json['color']), + icon = json['icon']; + + /// Converts the Game instance to a JSON object. + Map toJson() => { + 'id': id, + 'createdAt': createdAt.toIso8601String(), + 'name': name, + 'ruleset': ruleset.name, + 'description': description, + 'color': color.name, + 'icon': icon, + }; +} + diff --git a/lib/data/dto/group.dart b/lib/data/dto/group.dart index 92dbd09..7676b1e 100644 --- a/lib/data/dto/group.dart +++ b/lib/data/dto/group.dart @@ -1,40 +1,44 @@ import 'package:clock/clock.dart'; -import 'package:game_tracker/data/dto/player.dart'; +import 'package:tallee/data/dto/player.dart'; import 'package:uuid/uuid.dart'; class Group { final String id; - final DateTime createdAt; final String name; + final String description; + final DateTime createdAt; final List members; Group({ String? id, DateTime? createdAt, required this.name, + String? description, required this.members, }) : id = id ?? const Uuid().v4(), - createdAt = createdAt ?? clock.now(); + createdAt = createdAt ?? clock.now(), + description = description ?? ''; @override String toString() { - return 'Group{id: $id, name: $name,members: $members}'; + return 'Group{id: $id, name: $name, description: $description, members: $members}'; } - /// Creates a Group instance from a JSON object. + /// Creates a Group instance from a JSON object (memberIds format). + /// Player objects are reconstructed from memberIds by the DataTransferService. Group.fromJson(Map json) : id = json['id'], createdAt = DateTime.parse(json['createdAt']), name = json['name'], - members = (json['members'] as List) - .map((memberJson) => Player.fromJson(memberJson)) - .toList(); + description = json['description'], + members = []; // Populated during import via DataTransferService - /// Converts the Group instance to a JSON object. + /// Converts the Group instance to a JSON object using normalized format (memberIds only). Map toJson() => { 'id': id, 'createdAt': createdAt.toIso8601String(), 'name': name, - 'members': members.map((member) => member.toJson()).toList(), + 'description': description, + 'memberIds': members.map((member) => member.id).toList(), }; } diff --git a/lib/data/dto/match.dart b/lib/data/dto/match.dart index 9570f66..3976d36 100644 --- a/lib/data/dto/match.dart +++ b/lib/data/dto/match.dart @@ -1,51 +1,61 @@ import 'package:clock/clock.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/player.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/dto/game.dart'; +import 'package:tallee/data/dto/group.dart'; +import 'package:tallee/data/dto/player.dart'; import 'package:uuid/uuid.dart'; class Match { final String id; final DateTime createdAt; + final DateTime? endedAt; final String name; - final List? players; + final Game game; final Group? group; + final List players; + final String notes; Player? winner; Match({ String? id, DateTime? createdAt, + this.endedAt, required this.name, - this.players, + required this.game, this.group, + this.players = const [], + String? notes, this.winner, }) : id = id ?? const Uuid().v4(), - createdAt = createdAt ?? clock.now(); + createdAt = createdAt ?? clock.now(), + notes = notes ?? ''; @override String toString() { - return 'Match{\n\tid: $id,\n\tname: $name,\n\tplayers: $players,\n\tgroup: $group,\n\twinner: $winner\n}'; + return 'Match{id: $id, name: $name, game: $game, group: $group, players: $players, notes: $notes, endedAt: $endedAt}'; } - /// Creates a Match instance from a JSON object. + /// Creates a Match instance from a JSON object (ID references format). + /// Related objects are reconstructed from IDs by the DataTransferService. Match.fromJson(Map json) - : id = json['id'], - name = json['name'], - createdAt = DateTime.parse(json['createdAt']), - players = json['players'] != null - ? (json['players'] as List) - .map((playerJson) => Player.fromJson(playerJson)) - .toList() - : null, - group = json['group'] != null ? Group.fromJson(json['group']) : null, - winner = json['winner'] != null ? Player.fromJson(json['winner']) : null; + : id = json['id'], + createdAt = DateTime.parse(json['createdAt']), + endedAt = json['endedAt'] != null ? DateTime.parse(json['endedAt']) : null, + name = json['name'], + game = Game(name: '', ruleset: Ruleset.singleWinner, description: '', color: GameColor.blue, icon: ''), // Populated during import via DataTransferService + group = null, // Populated during import via DataTransferService + players = [], // Populated during import via DataTransferService + notes = json['notes'] ?? ''; - /// Converts the Match instance to a JSON object. + /// Converts the Match instance to a JSON object using normalized format (ID references only). Map toJson() => { 'id': id, 'createdAt': createdAt.toIso8601String(), + 'endedAt': endedAt?.toIso8601String(), 'name': name, - 'players': players?.map((player) => player.toJson()).toList(), - 'group': group?.toJson(), - 'winner': winner?.toJson(), + 'gameId': game.id, + 'groupId': group?.id, + 'playerIds': players.map((player) => player.id).toList(), + 'notes': notes, }; } diff --git a/lib/data/dto/player.dart b/lib/data/dto/player.dart index cfb4f4b..c405de9 100644 --- a/lib/data/dto/player.dart +++ b/lib/data/dto/player.dart @@ -5,26 +5,34 @@ class Player { final String id; final DateTime createdAt; final String name; + final String description; - Player({String? id, DateTime? createdAt, required this.name}) - : id = id ?? const Uuid().v4(), - createdAt = createdAt ?? clock.now(); + Player({ + String? id, + DateTime? createdAt, + required this.name, + String? description, + }) : id = id ?? const Uuid().v4(), + createdAt = createdAt ?? clock.now(), + description = description ?? ''; @override String toString() { - return 'Player{id: $id,name: $name}'; + return 'Player{id: $id, name: $name, description: $description}'; } /// Creates a Player instance from a JSON object. Player.fromJson(Map json) : id = json['id'], createdAt = DateTime.parse(json['createdAt']), - name = json['name']; + name = json['name'], + description = json['description']; /// Converts the Player instance to a JSON object. Map toJson() => { 'id': id, 'createdAt': createdAt.toIso8601String(), 'name': name, + 'description': description, }; } diff --git a/lib/data/dto/team.dart b/lib/data/dto/team.dart new file mode 100644 index 0000000..56036b2 --- /dev/null +++ b/lib/data/dto/team.dart @@ -0,0 +1,40 @@ +import 'package:clock/clock.dart'; +import 'package:tallee/data/dto/player.dart'; +import 'package:uuid/uuid.dart'; + +class Team { + final String id; + final String name; + final DateTime createdAt; + final List members; + + Team({ + String? id, + required this.name, + DateTime? createdAt, + required this.members, + }) : id = id ?? const Uuid().v4(), + createdAt = createdAt ?? clock.now(); + + @override + String toString() { + return 'Team{id: $id, name: $name, members: $members}'; + } + + /// Creates a Team instance from a JSON object (memberIds format). + /// Player objects are reconstructed from memberIds by the DataTransferService. + Team.fromJson(Map json) + : id = json['id'], + name = json['name'], + createdAt = DateTime.parse(json['createdAt']), + members = []; // Populated during import via DataTransferService + + /// Converts the Team instance to a JSON object using normalized format (memberIds only). + Map toJson() => { + 'id': id, + 'name': name, + 'createdAt': createdAt.toIso8601String(), + 'memberIds': members.map((member) => member.id).toList(), + }; +} + diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 0c85634..d0bacfc 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -3,7 +3,7 @@ "all_players": "Alle Spieler:innen", "all_players_selected": "Alle Spieler:innen ausgewählt", "amount_of_matches": "Anzahl der Spiele", - "app_name": "Game Tracker", + "app_name": "Tallee", "best_player": "Beste:r Spieler:in", "cancel": "Abbrechen", "choose_game": "Spielvorlage wählen", @@ -86,6 +86,9 @@ "settings": "Einstellungen", "single_loser": "Ein:e Verlierer:in", "single_winner": "Ein:e Gewinner:in", + "highest_score": "Höchste Punkte", + "lowest_score": "Niedrigste Punkte", + "multiple_winners": "Mehrere Gewinner:innen", "statistics": "Statistiken", "stats": "Statistiken", "successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt", diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 6756ec5..e15f0c6 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -307,7 +307,7 @@ "all_players": "All players", "all_players_selected": "All players selected", "amount_of_matches": "Amount of Matches", - "app_name": "Game Tracker", + "app_name": "Tallee", "best_player": "Best Player", "cancel": "Cancel", "choose_game": "Choose Game", @@ -388,6 +388,9 @@ "settings": "Settings", "single_loser": "Single Loser", "single_winner": "Single Winner", + "highest_score": "Highest Score", + "lowest_score": "Lowest Score", + "multiple_winners": "Multiple Winners", "statistics": "Statistics", "stats": "Stats", "successfully_added_player": "Successfully added player {playerName}", diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index b8f80db..af7f534 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -119,7 +119,7 @@ abstract class AppLocalizations { /// The name of the App /// /// In en, this message translates to: - /// **'Game Tracker'** + /// **'Tallee'** String get app_name; /// Label for best player statistic @@ -602,6 +602,24 @@ abstract class AppLocalizations { /// **'Single Winner'** String get single_winner; + /// No description provided for @highest_score. + /// + /// In en, this message translates to: + /// **'Highest Score'** + String get highest_score; + + /// No description provided for @lowest_score. + /// + /// In en, this message translates to: + /// **'Lowest Score'** + String get lowest_score; + + /// No description provided for @multiple_winners. + /// + /// In en, this message translates to: + /// **'Multiple Winners'** + String get multiple_winners; + /// Statistics tab label /// /// In en, this message translates to: diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 20b1ffb..b20ced8 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -18,7 +18,7 @@ class AppLocalizationsDe extends AppLocalizations { String get amount_of_matches => 'Anzahl der Spiele'; @override - String get app_name => 'Game Tracker'; + String get app_name => 'Tallee'; @override String get best_player => 'Beste:r Spieler:in'; @@ -274,6 +274,15 @@ class AppLocalizationsDe extends AppLocalizations { @override String get single_winner => 'Ein:e Gewinner:in'; + @override + String get highest_score => 'Höchste Punkte'; + + @override + String get lowest_score => 'Niedrigste Punkte'; + + @override + String get multiple_winners => 'Mehrere Gewinner:innen'; + @override String get statistics => 'Statistiken'; diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 35d2d2a..f1b2479 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -18,7 +18,7 @@ class AppLocalizationsEn extends AppLocalizations { String get amount_of_matches => 'Amount of Matches'; @override - String get app_name => 'Game Tracker'; + String get app_name => 'Tallee'; @override String get best_player => 'Best Player'; @@ -274,6 +274,15 @@ class AppLocalizationsEn extends AppLocalizations { @override String get single_winner => 'Single Winner'; + @override + String get highest_score => 'Highest Score'; + + @override + String get lowest_score => 'Lowest Score'; + + @override + String get multiple_winners => 'Multiple Winners'; + @override String get statistics => 'Statistics'; diff --git a/lib/main.dart b/lib/main.dart index 2f64e2e..59384ac 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/views/main_menu/custom_navigation_bar.dart'; import 'package:provider/provider.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/views/main_menu/custom_navigation_bar.dart'; void main() { runApp( @@ -29,9 +29,7 @@ class GameTracker extends StatelessWidget { return supportedLocale; } } - return supportedLocales.firstWhere( - (locale) => locale.languageCode == 'en', - ); + return supportedLocales.firstWhere((locale) => locale.languageCode == 'en'); }, debugShowCheckedModeBanner: false, onGenerateTitle: (context) => AppLocalizations.of(context).app_name, diff --git a/lib/presentation/views/main_menu/custom_navigation_bar.dart b/lib/presentation/views/main_menu/custom_navigation_bar.dart index b17f63d..cbea02a 100644 --- a/lib/presentation/views/main_menu/custom_navigation_bar.dart +++ b/lib/presentation/views/main_menu/custom_navigation_bar.dart @@ -1,15 +1,13 @@ -import 'dart:ui'; - import 'package:flutter/material.dart'; -import 'package:game_tracker/core/adaptive_page_route.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/views/main_menu/group_view/group_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/home_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/match_view/match_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/settings_view/settings_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/statistics_view.dart'; -import 'package:game_tracker/presentation/widgets/navbar_item.dart'; +import 'package:tallee/core/adaptive_page_route.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/views/main_menu/group_view/group_view.dart'; +import 'package:tallee/presentation/views/main_menu/home_view.dart'; +import 'package:tallee/presentation/views/main_menu/match_view/match_view.dart'; +import 'package:tallee/presentation/views/main_menu/settings_view/settings_view.dart'; +import 'package:tallee/presentation/views/main_menu/statistics_view.dart'; +import 'package:tallee/presentation/widgets/navbar_item.dart'; class CustomNavigationBar extends StatefulWidget { /// A custom navigation bar widget that provides tabbed navigation @@ -75,103 +73,62 @@ class _CustomNavigationBarState extends State backgroundColor: CustomTheme.backgroundColor, body: tabs[currentIndex], extendBody: true, - bottomNavigationBar: SizedBox( - height: 70 + MediaQuery.of(context).padding.bottom, - child: Stack( - children: [ - // Dynamically generated blur layers for ultra-smooth transition - ...List.generate(34, (index) { - // Use cubic curve for an even more natural, smoother transition - final progress = index / 34.0; // 0.0 to 1.0 - final cubic = progress * progress * progress; // cubic curve - final blurStrength = - 0.5 + (cubic * 50.0); // Very smooth from 0.5 to 50.5 - - // Height goes completely from 100% to 0% (all the way down) - // With extra density at the bottom for softer transition - final heightFactor = index < 25 - // First 25 layers: 100% to 30% - ? 1.0 - (progress * 0.7) - // Last 10 layers: 30% to 0% (denser) - : 0.3 - ((index - 25) / 34.0); - - return Positioned( - left: 0, - right: 0, - bottom: 0, - height: - (70 + MediaQuery.of(context).padding.bottom) * - heightFactor.clamp(0.05, 1.0), - child: ClipRect( - child: BackdropFilter( - filter: ImageFilter.blur( - sigmaX: blurStrength, - sigmaY: blurStrength, - ), - child: Container(color: Colors.transparent), - ), - ), - ); - }), - // Gradient overlay - Positioned.fill( - child: Container( - decoration: BoxDecoration( - gradient: LinearGradient( - begin: Alignment.bottomCenter, - end: Alignment.topCenter, - colors: [ - CustomTheme.boxColor.withValues(alpha: 1), - CustomTheme.boxColor.withValues(alpha: 0.5), - CustomTheme.boxColor.withValues(alpha: 0.2), - CustomTheme.boxColor.withValues(alpha: 0.0), - ], - stops: const [0.0, 0.4, 0.8, 1], - ), - ), - ), - ), - // Navbar content - SafeArea( - child: SizedBox( - height: 70, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - NavbarItem( - index: 0, - isSelected: currentIndex == 0, - icon: Icons.home_rounded, - label: loc.home, - onTabTapped: onTabTapped, - ), - NavbarItem( - index: 1, - isSelected: currentIndex == 1, - icon: Icons.gamepad_rounded, - label: loc.matches, - onTabTapped: onTabTapped, - ), - NavbarItem( - index: 2, - isSelected: currentIndex == 2, - icon: Icons.group_rounded, - label: loc.groups, - onTabTapped: onTabTapped, - ), - NavbarItem( - index: 3, - isSelected: currentIndex == 3, - icon: Icons.bar_chart_rounded, - label: loc.statistics, - onTabTapped: onTabTapped, - ), - ], - ), - ), + bottomNavigationBar: Container( + height: 115, + decoration: BoxDecoration( + color: CustomTheme.navBarBackgroundColor, + border: Border.all( + strokeAlign: BorderSide.strokeAlignOutside, + color: CustomTheme.boxBorderColor, + width: 2, + ), + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(30), + topRight: Radius.circular(30), + ), + boxShadow: [ + BoxShadow( + color: Colors.black.withValues(alpha: 0.1), + blurRadius: 20, + offset: const Offset(0, -5), ), ], ), + child: SafeArea( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + NavbarItem( + index: 0, + isSelected: currentIndex == 0, + icon: Icons.home_rounded, + label: loc.home, + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 1, + isSelected: currentIndex == 1, + icon: Icons.gamepad_rounded, + label: loc.matches, + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 2, + isSelected: currentIndex == 2, + icon: Icons.group_rounded, + label: loc.groups, + onTabTapped: onTabTapped, + ), + NavbarItem( + index: 3, + isSelected: currentIndex == 3, + icon: Icons.bar_chart_rounded, + label: loc.statistics, + onTabTapped: onTabTapped, + ), + ], + ), + ), ), ); } diff --git a/lib/presentation/views/main_menu/group_view/create_group_view.dart b/lib/presentation/views/main_menu/group_view/create_group_view.dart index 678872a..42a11f3 100644 --- a/lib/presentation/views/main_menu/group_view/create_group_view.dart +++ b/lib/presentation/views/main_menu/group_view/create_group_view.dart @@ -1,14 +1,15 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/core/enums.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; -import 'package:game_tracker/presentation/widgets/player_selection.dart'; -import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart'; import 'package:provider/provider.dart'; +import 'package:tallee/core/constants.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/group.dart'; +import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart'; +import 'package:tallee/presentation/widgets/player_selection.dart'; +import 'package:tallee/presentation/widgets/text_input/text_input_field.dart'; class CreateGroupView extends StatefulWidget { const CreateGroupView({super.key, this.groupToEdit}); @@ -39,7 +40,7 @@ class _CreateGroupViewState extends State { void initState() { super.initState(); db = Provider.of(context, listen: false); - if(widget.groupToEdit != null) { + if (widget.groupToEdit != null) { _groupNameController.text = widget.groupToEdit!.name; setState(() { initialSelectedPlayers = widget.groupToEdit!.members; @@ -65,38 +66,54 @@ class _CreateGroupViewState extends State { child: Scaffold( resizeToAvoidBottomInset: false, backgroundColor: CustomTheme.backgroundColor, - appBar: AppBar(title: Text(widget.groupToEdit == null ? loc.create_new_group : loc.edit_group), actions: widget.groupToEdit == null ? [] : [IconButton(icon: const Icon(Icons.delete), onPressed: () async { - if(widget.groupToEdit != null) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text(loc.delete_group), - content: Text(loc.this_cannot_be_undone), - actions: [ - TextButton( - onPressed: () => Navigator.of(context).pop(false), - child: Text(loc.cancel), - ), - TextButton( - onPressed: () => Navigator.of(context).pop(true), - child: Text(loc.delete), + appBar: AppBar( + title: Text( + widget.groupToEdit == null ? loc.create_new_group : loc.edit_group, + ), + actions: widget.groupToEdit == null + ? [] + : [ + IconButton( + icon: const Icon(Icons.delete), + onPressed: () async { + if (widget.groupToEdit != null) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(loc.delete_group), + content: Text(loc.this_cannot_be_undone), + actions: [ + TextButton( + onPressed: () => + Navigator.of(context).pop(false), + child: Text(loc.cancel), + ), + TextButton( + onPressed: () => + Navigator.of(context).pop(true), + child: Text(loc.delete), + ), + ], + ), + ).then((confirmed) async { + if (confirmed == true && context.mounted) { + bool success = await db.groupDao.deleteGroup( + groupId: widget.groupToEdit!.id, + ); + if (!context.mounted) return; + if (success) { + Navigator.pop(context); + } else { + if (!mounted) return; + showSnackbar(message: loc.error_deleting_group); + } + } + }); + } + }, ), ], - ), - ).then((confirmed) async { - if (confirmed == true && context.mounted) { - bool success = await db.groupDao.deleteGroup(groupId: widget.groupToEdit!.id); - if (!context.mounted) return; - if (success) { - Navigator.pop(context); - } else { - if (!mounted) return; - showSnackbar(message: loc.error_deleting_group); - } - } - }); - } - },)],), + ), body: SafeArea( child: Column( mainAxisAlignment: MainAxisAlignment.start, @@ -106,6 +123,7 @@ class _CreateGroupViewState extends State { child: TextInputField( controller: _groupNameController, hintText: loc.group_name, + maxLength: Constants.MAX_GROUP_NAME_LENGTH, ), ), Expanded( @@ -119,7 +137,9 @@ class _CreateGroupViewState extends State { ), ), CustomWidthButton( - text: widget.groupToEdit == null ? loc.create_group : loc.edit_group, + text: widget.groupToEdit == null + ? loc.create_group + : loc.edit_group, sizeRelativeToWidth: 0.95, buttonType: ButtonType.primary, onPressed: @@ -127,12 +147,15 @@ class _CreateGroupViewState extends State { (selectedPlayers.length < 2)) ? null : () async { - late Group? updatedGroup; + Group? updatedGroup = null; + bool successfullNameChange = true; + bool successfullMemberChange = true; late bool success; if (widget.groupToEdit == null) { success = await db.groupDao.addGroup( group: Group( name: _groupNameController.text.trim(), + description: '', members: selectedPlayers, ), ); @@ -140,21 +163,37 @@ class _CreateGroupViewState extends State { updatedGroup = Group( id: widget.groupToEdit!.id, name: _groupNameController.text.trim(), + description: '', members: selectedPlayers, ); - //TODO: Implement group editing in database - /* - success = await db.groupDao.updateGroup( - group: updatedGroup, - ); - */ - success = true; + if (widget.groupToEdit!.name != + _groupNameController.text.trim()) { + successfullNameChange = await db.groupDao + .updateGroupName( + groupId: widget.groupToEdit!.id, + newName: _groupNameController.text.trim(), + ); + } + + if (widget.groupToEdit!.members != selectedPlayers) { + successfullMemberChange = await db.groupDao + .replaceGroupPlayers( + groupId: widget.groupToEdit!.id, + newPlayers: selectedPlayers, + ); + } + success = + successfullNameChange && successfullMemberChange; } if (!context.mounted) return; if (success) { Navigator.pop(context, updatedGroup); } else { - showSnackbar(message: widget.groupToEdit == null ? loc.error_creating_group : loc.error_editing_group); + showSnackbar( + message: widget.groupToEdit == null + ? loc.error_creating_group + : loc.error_editing_group, + ); } }, ), @@ -165,12 +204,11 @@ class _CreateGroupViewState extends State { ), ); } + /// Displays a snackbar with the given message and optional action. /// /// [message] The message to display in the snackbar. - void showSnackbar({ - required String message, - }) { + void showSnackbar({required String message}) { final messenger = _scaffoldMessengerKey.currentState; if (messenger != null) { messenger.hideCurrentSnackBar(); diff --git a/lib/presentation/views/main_menu/group_view/group_detail_view.dart b/lib/presentation/views/main_menu/group_view/group_detail_view.dart index 81a1c6e..6e145ea 100644 --- a/lib/presentation/views/main_menu/group_view/group_detail_view.dart +++ b/lib/presentation/views/main_menu/group_view/group_detail_view.dart @@ -1,21 +1,21 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/adaptive_page_route.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/match.dart'; -import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/views/main_menu/group_view/create_group_view.dart'; -import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; -import 'package:game_tracker/presentation/widgets/buttons/animated_dialog_button.dart'; -import 'package:game_tracker/presentation/widgets/buttons/main_menu_button.dart'; -import 'package:game_tracker/presentation/widgets/colored_icon_container.dart'; -import 'package:game_tracker/presentation/widgets/custom_alert_dialog.dart'; -import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; -import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; +import 'package:tallee/core/adaptive_page_route.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/group.dart'; +import 'package:tallee/data/dto/match.dart'; +import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/views/main_menu/group_view/create_group_view.dart'; +import 'package:tallee/presentation/widgets/app_skeleton.dart'; +import 'package:tallee/presentation/widgets/buttons/animated_dialog_button.dart'; +import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart'; +import 'package:tallee/presentation/widgets/colored_icon_container.dart'; +import 'package:tallee/presentation/widgets/custom_alert_dialog.dart'; +import 'package:tallee/presentation/widgets/tiles/info_tile.dart'; +import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart'; class GroupDetailView extends StatefulWidget { /// A view that displays the profile of a group @@ -82,7 +82,9 @@ class _GroupDetailViewState extends State { onPressed: () => Navigator.of(context).pop(true), child: Text( loc.delete, - style: TextStyle(color: CustomTheme.secondaryColor), + style: const TextStyle( + color: CustomTheme.secondaryColor, + ), ), ), ], @@ -188,9 +190,7 @@ class _GroupDetailViewState extends State { context, adaptivePageRoute( builder: (context) { - return CreateGroupView( - groupToEdit: _group, - ); + return CreateGroupView(groupToEdit: _group); }, ), ); @@ -242,8 +242,9 @@ class _GroupDetailViewState extends State { /// Loads statistics for this group Future _loadStatistics() async { final matches = await db.matchDao.getAllMatches(); - final groupMatches = - matches.where((match) => match.group?.id == _group.id).toList(); + final groupMatches = matches + .where((match) => match.group?.id == _group.id) + .toList(); setState(() { totalMatches = groupMatches.length; @@ -261,7 +262,7 @@ class _GroupDetailViewState extends State { if (match.winner != null) { bestPlayerCounts.update( match.winner!, - (value) => value + 1, + (value) => value + 1, ifAbsent: () => 1, ); } @@ -276,4 +277,4 @@ class _GroupDetailViewState extends State { return bestPlayer; } -} \ No newline at end of file +} diff --git a/lib/presentation/views/main_menu/group_view/group_view.dart b/lib/presentation/views/main_menu/group_view/group_view.dart index a995f12..551f3ec 100644 --- a/lib/presentation/views/main_menu/group_view/group_view.dart +++ b/lib/presentation/views/main_menu/group_view/group_view.dart @@ -1,18 +1,18 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/adaptive_page_route.dart'; -import 'package:game_tracker/core/constants.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/views/main_menu/group_view/group_detail_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/group_view/create_group_view.dart'; -import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; -import 'package:game_tracker/presentation/widgets/buttons/main_menu_button.dart'; -import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; -import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; +import 'package:tallee/core/adaptive_page_route.dart'; +import 'package:tallee/core/constants.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/group.dart'; +import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/views/main_menu/group_view/create_group_view.dart'; +import 'package:tallee/presentation/views/main_menu/group_view/group_detail_view.dart'; +import 'package:tallee/presentation/widgets/app_skeleton.dart'; +import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart'; +import 'package:tallee/presentation/widgets/tiles/group_tile.dart'; +import 'package:tallee/presentation/widgets/top_centered_message.dart'; class GroupsView extends StatefulWidget { /// A view that displays a list of groups @@ -35,7 +35,8 @@ class _GroupsViewState extends State { 7, Group( name: 'Skeleton Group', - members: List.filled(6, Player(name: 'Skeleton Player')), + description: '', + members: List.filled(6, Player(name: 'Skeleton Player', description: '')), ), ); diff --git a/lib/presentation/views/main_menu/home_view.dart b/lib/presentation/views/main_menu/home_view.dart index f28341e..a7f5cfa 100644 --- a/lib/presentation/views/main_menu/home_view.dart +++ b/lib/presentation/views/main_menu/home_view.dart @@ -1,18 +1,20 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/adaptive_page_route.dart'; -import 'package:game_tracker/core/constants.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/match.dart'; -import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart'; -import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; -import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart'; -import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; -import 'package:game_tracker/presentation/widgets/tiles/match_tile.dart'; -import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart'; import 'package:provider/provider.dart'; +import 'package:tallee/core/adaptive_page_route.dart'; +import 'package:tallee/core/constants.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/game.dart'; +import 'package:tallee/data/dto/group.dart'; +import 'package:tallee/data/dto/match.dart'; +import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart'; +import 'package:tallee/presentation/widgets/app_skeleton.dart'; +import 'package:tallee/presentation/widgets/buttons/quick_create_button.dart'; +import 'package:tallee/presentation/widgets/tiles/info_tile.dart'; +import 'package:tallee/presentation/widgets/tiles/match_tile.dart'; +import 'package:tallee/presentation/widgets/tiles/quick_info_tile.dart'; class HomeView extends StatefulWidget { /// The main home view of the application, displaying quick info, @@ -40,13 +42,16 @@ class _HomeViewState extends State { 2, Match( name: 'Skeleton Match', + game: Game(name: '', ruleset: Ruleset.singleWinner, description: '', color: GameColor.blue, icon: ''), group: Group( name: 'Skeleton Group', + description: '', members: [ - Player(name: 'Skeleton Player 1'), - Player(name: 'Skeleton Player 2'), + Player(name: 'Skeleton Player 1', description: ''), + Player(name: 'Skeleton Player 2', description: ''), ], ), + notes: '', ), ); @@ -99,9 +104,7 @@ class _HomeViewState extends State { if (recentMatches.isNotEmpty) for (Match match in recentMatches) Padding( - padding: const EdgeInsets.symmetric( - vertical: 6.0, - ), + padding: const EdgeInsets.symmetric(vertical: 6.0), child: MatchTile( compact: true, width: constraints.maxWidth * 0.9, @@ -110,19 +113,15 @@ class _HomeViewState extends State { await Navigator.of(context).push( adaptivePageRoute( fullscreenDialog: true, - builder: (context) => - MatchResultView(match: match), + builder: (context) => MatchResultView(match: match), ), ); - await updatedWinnerinRecentMatches(match.id); + await updatedWinnerInRecentMatches(match.id); }, ), ) else - Center( - heightFactor: 5, - child: Text(loc.no_recent_matches_available), - ), + Center(heightFactor: 5, child: Text(loc.no_recent_matches_available)), ], ), ), @@ -138,40 +137,22 @@ class _HomeViewState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - QuickCreateButton( - text: 'Category 1', - onPressed: () {}, - ), - QuickCreateButton( - text: 'Category 2', - onPressed: () {}, - ), + QuickCreateButton(text: 'Category 1', onPressed: () {}), + QuickCreateButton(text: 'Category 2', onPressed: () {}), ], ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - QuickCreateButton( - text: 'Category 3', - onPressed: () {}, - ), - QuickCreateButton( - text: 'Category 4', - onPressed: () {}, - ), + QuickCreateButton(text: 'Category 3', onPressed: () {}), + QuickCreateButton(text: 'Category 4', onPressed: () {}), ], ), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - QuickCreateButton( - text: 'Category 5', - onPressed: () {}, - ), - QuickCreateButton( - text: 'Category 6', - onPressed: () {}, - ), + QuickCreateButton(text: 'Category 5', onPressed: () {}), + QuickCreateButton(text: 'Category 6', onPressed: () {}), ], ), ], @@ -200,11 +181,9 @@ class _HomeViewState extends State { matchCount = results[0] as int; groupCount = results[1] as int; loadedRecentMatches = results[2] as List; - recentMatches = - (loadedRecentMatches - ..sort((a, b) => b.createdAt.compareTo(a.createdAt))) - .take(2) - .toList(); + recentMatches = (loadedRecentMatches..sort((a, b) => b.createdAt.compareTo(a.createdAt))) + .take(2) + .toList(); if (mounted) { setState(() { isLoading = false; @@ -214,7 +193,7 @@ class _HomeViewState extends State { } /// Updates the winner information for a specific match in the recent matches list. - Future updatedWinnerinRecentMatches(String matchId) async { + Future updatedWinnerInRecentMatches(String matchId) async { final db = Provider.of(context, listen: false); final winner = await db.matchDao.getWinner(matchId: matchId); final matchIndex = recentMatches.indexWhere((match) => match.id == matchId); diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart index 3ff6e79..447b9c5 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_game_view.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/core/enums.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; -import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart'; +import 'package:tallee/presentation/widgets/tiles/title_description_list_tile.dart'; class ChooseGameView extends StatefulWidget { /// A view that allows the user to choose a game from a list of available games diff --git a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart index 592d765..9c60b16 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/choose_group_view.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; -import 'package:game_tracker/presentation/widgets/tiles/group_tile.dart'; -import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/data/dto/group.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart'; +import 'package:tallee/presentation/widgets/tiles/group_tile.dart'; +import 'package:tallee/presentation/widgets/top_centered_message.dart'; class ChooseGroupView extends StatefulWidget { /// A view that allows the user to choose a group from a list of groups. diff --git a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart index 3ad5032..5720606 100644 --- a/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart +++ b/lib/presentation/views/main_menu/match_view/create_match/create_match_view.dart @@ -1,21 +1,22 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/adaptive_page_route.dart'; -import 'package:game_tracker/core/constants.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/core/enums.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/match.dart'; -import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_game_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/choose_group_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart'; -import 'package:game_tracker/presentation/widgets/buttons/custom_width_button.dart'; -import 'package:game_tracker/presentation/widgets/player_selection.dart'; -import 'package:game_tracker/presentation/widgets/text_input/text_input_field.dart'; -import 'package:game_tracker/presentation/widgets/tiles/choose_tile.dart'; import 'package:provider/provider.dart'; +import 'package:tallee/core/adaptive_page_route.dart'; +import 'package:tallee/core/constants.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/game.dart'; +import 'package:tallee/data/dto/group.dart'; +import 'package:tallee/data/dto/match.dart'; +import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/views/main_menu/match_view/create_match/choose_game_view.dart'; +import 'package:tallee/presentation/views/main_menu/match_view/create_match/choose_group_view.dart'; +import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart'; +import 'package:tallee/presentation/widgets/buttons/custom_width_button.dart'; +import 'package:tallee/presentation/widgets/player_selection.dart'; +import 'package:tallee/presentation/widgets/text_input/text_input_field.dart'; +import 'package:tallee/presentation/widgets/tiles/choose_tile.dart'; class CreateMatchView extends StatefulWidget { /// A view that allows creating a new match @@ -62,7 +63,7 @@ class _CreateMatchViewState extends State { int selectedGameIndex = -1; /// The currently selected players - List? selectedPlayers; + List selectedPlayers = []; /// GlobalKey for ScaffoldMessenger to show snackbars final _scaffoldMessengerKey = GlobalKey(); @@ -102,7 +103,7 @@ class _CreateMatchViewState extends State { } List<(String, String, Ruleset)> games = [ - ('Example Game 1', 'This is a description', Ruleset.leastPoints), + ('Example Game 1', 'This is a description', Ruleset.lowestScore), ('Example Game 2', '', Ruleset.singleWinner), ]; @@ -169,8 +170,8 @@ class _CreateMatchViewState extends State { filteredPlayerList = playerList .where( (p) => - !selectedGroup!.members.any((m) => m.id == p.id), - ) + !selectedGroup!.members.any((m) => m.id == p.id), + ) .toList(); } else { filteredPlayerList = List.from(playerList); @@ -181,7 +182,7 @@ class _CreateMatchViewState extends State { Expanded( child: PlayerSelection( key: ValueKey(selectedGroup?.id ?? 'no_group'), - initialSelectedPlayers: selectedPlayers ?? [], + initialSelectedPlayers: selectedPlayers, availablePlayers: filteredPlayerList, onChanged: (value) { setState(() { @@ -196,28 +197,56 @@ class _CreateMatchViewState extends State { buttonType: ButtonType.primary, onPressed: _enableCreateGameButton() ? () async { - Match match = Match( - name: _matchNameController.text.isEmpty - ? (hintText ?? '') - : _matchNameController.text.trim(), - createdAt: DateTime.now(), - group: selectedGroup, - players: selectedPlayers, - ); - await db.matchDao.addMatch(match: match); - if (context.mounted) { - Navigator.pushReplacement( - context, - adaptivePageRoute( - fullscreenDialog: true, - builder: (context) => MatchResultView( - match: match, - onWinnerChanged: widget.onWinnerChanged, - ), - ), - ); - } - } + // Use a game from the games list + Game? gameToUse; + if (selectedGameIndex == -1) { + // Use the first game as default if none selected + final selectedGame = games[0]; + gameToUse = Game( + name: selectedGame.$1, + description: selectedGame.$2, + ruleset: selectedGame.$3, + color: GameColor.blue, + icon: '', + ); + } else { + // Use the selected game from the list + final selectedGame = games[selectedGameIndex]; + gameToUse = Game( + name: selectedGame.$1, + description: selectedGame.$2, + ruleset: selectedGame.$3, + color: GameColor.blue, + icon: '', + ); + } + // Add the game to the database if it doesn't exist + await db.gameDao.addGame(game: gameToUse); + + Match match = Match( + name: _matchNameController.text.isEmpty + ? (hintText ?? '') + : _matchNameController.text.trim(), + createdAt: DateTime.now(), + game: gameToUse, + group: selectedGroup, + players: selectedPlayers, + notes: '', + ); + await db.matchDao.addMatch(match: match); + if (context.mounted) { + Navigator.pushReplacement( + context, + adaptivePageRoute( + fullscreenDialog: true, + builder: (context) => MatchResultView( + match: match, + onWinnerChanged: widget.onWinnerChanged, + ), + ), + ); + } + } : null, ), ], @@ -234,6 +263,6 @@ class _CreateMatchViewState extends State { /// - Either a group is selected OR at least 2 players are selected bool _enableCreateGameButton() { return (selectedGroup != null || - (selectedPlayers != null && selectedPlayers!.length > 1)); + (selectedPlayers.length > 1)); } -} +} \ No newline at end of file diff --git a/lib/presentation/views/main_menu/match_view/match_result_view.dart b/lib/presentation/views/main_menu/match_view/match_result_view.dart index 1deb385..4f3f0c0 100644 --- a/lib/presentation/views/main_menu/match_view/match_result_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_result_view.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/match.dart'; -import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/widgets/tiles/custom_radio_list_tile.dart'; import 'package:provider/provider.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/match.dart'; +import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/widgets/tiles/custom_radio_list_tile.dart'; class MatchResultView extends StatefulWidget { /// A view that allows selecting and saving the winner of a match @@ -74,7 +74,7 @@ class _MatchResultViewState extends State { ), decoration: BoxDecoration( color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), + border: Border.all(color: CustomTheme.boxBorderColor), borderRadius: BorderRadius.circular(12), ), child: Column( @@ -153,12 +153,10 @@ class _MatchResultViewState extends State { List getAllPlayers(Match match) { List players = []; - if (match.group == null && match.players != null) { - players = [...match.players!]; - } else if (match.group != null && match.players != null) { - players = [...match.players!, ...match.group!.members]; + if (match.group == null) { + players = [...match.players]; } else { - players = [...match.group!.members]; + players = [...match.players, ...match.group!.members]; } players.sort((a, b) => a.name.compareTo(b.name)); diff --git a/lib/presentation/views/main_menu/match_view/match_view.dart b/lib/presentation/views/main_menu/match_view/match_view.dart index e85bf77..1f50342 100644 --- a/lib/presentation/views/main_menu/match_view/match_view.dart +++ b/lib/presentation/views/main_menu/match_view/match_view.dart @@ -2,21 +2,23 @@ import 'dart:core' hide Match; import 'package:flutter/material.dart'; import 'package:fluttericon/rpg_awesome_icons.dart'; -import 'package:game_tracker/core/adaptive_page_route.dart'; -import 'package:game_tracker/core/constants.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/match.dart'; -import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/views/main_menu/match_view/create_match/create_match_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/match_view/match_result_view.dart'; -import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; -import 'package:game_tracker/presentation/widgets/buttons/main_menu_button.dart'; -import 'package:game_tracker/presentation/widgets/tiles/match_tile.dart'; -import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; +import 'package:tallee/core/adaptive_page_route.dart'; +import 'package:tallee/core/constants.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/game.dart'; +import 'package:tallee/data/dto/group.dart'; +import 'package:tallee/data/dto/match.dart'; +import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/views/main_menu/match_view/create_match/create_match_view.dart'; +import 'package:tallee/presentation/views/main_menu/match_view/match_result_view.dart'; +import 'package:tallee/presentation/widgets/app_skeleton.dart'; +import 'package:tallee/presentation/widgets/buttons/main_menu_button.dart'; +import 'package:tallee/presentation/widgets/tiles/match_tile.dart'; +import 'package:tallee/presentation/widgets/top_centered_message.dart'; class MatchView extends StatefulWidget { /// A view that displays a list of matches @@ -36,12 +38,15 @@ class _MatchViewState extends State { 4, Match( name: 'Skeleton match name', + game: Game(name: '', ruleset: Ruleset.singleWinner, description: '', color: GameColor.blue, icon: ''), group: Group( name: 'Group name', - members: List.filled(5, Player(name: 'Player')), + description: '', + members: List.filled(5, Player(name: 'Player', description: '')), ), - winner: Player(name: 'Player'), - players: [Player(name: 'Player')], + winner: Player(name: 'Player', description: ''), + players: [Player(name: 'Player', description: '')], + notes: '', ), ); diff --git a/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart index 54ff34e..6e58108 100644 --- a/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart +++ b/lib/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart'; -import 'package:game_tracker/presentation/widgets/colored_icon_container.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart'; +import 'package:tallee/presentation/widgets/colored_icon_container.dart'; import 'package:url_launcher/url_launcher.dart'; class LicenseDetailView extends StatelessWidget { @@ -89,7 +89,7 @@ class LicenseDetailView extends StatelessWidget { maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, - style: TextStyle( + style: const TextStyle( fontSize: 12, color: CustomTheme.secondaryColor, decoration: TextDecoration.underline, diff --git a/lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart b/lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart index ef1109c..8fb2f2c 100644 --- a/lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart +++ b/lib/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart @@ -8,7 +8,7 @@ // https://pub.dev/packages/dart_pubspec_licenses /// This package. -const thisPackage = _game_tracker; +const thisPackage = _tallee; /// All dependencies including transitive dependencies. const allDependencies = [ @@ -31,6 +31,7 @@ const allDependencies = [ _cli_config, _cli_util, _clock, + _code_assets, _code_builder, _collection, _convert, @@ -57,10 +58,12 @@ const allDependencies = [ _flutter_lints, _flutter_plugin_android_lifecycle, _flutter_web_plugins, + _fluttericon, _font_awesome_flutter, _frontend_server_client, _glob, _graphs, + _hooks, _html, _http, _http_multi_server, @@ -80,8 +83,10 @@ const allDependencies = [ _material_color_utilities, _meta, _mime, + _native_toolchain_c, _nested, _node_preamble, + _objective_c, _package_config, _package_info_plus, _package_info_plus_platform_interface, @@ -160,6 +165,7 @@ const dependencies = [ _drift_flutter, _file_picker, _file_saver, + _fluttericon, _font_awesome_flutter, _intl, _json_schema, @@ -517,13 +523,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// build_daemon 4.1.0 +/// build_daemon 4.1.1 const _build_daemon = Package( name: 'build_daemon', description: 'A daemon for running Dart builds.', repository: 'https://github.com/dart-lang/build/tree/master/build_daemon', authors: [], - version: '4.1.0', + version: '4.1.1', spdxIdentifiers: ['BSD-3-Clause'], isMarkdown: false, isSdk: false, @@ -724,14 +730,14 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// built_value 8.12.0 +/// built_value 8.12.3 const _built_value = Package( name: 'built_value', description: '''Value types with builders, Dart classes as enums, and serialization. This library is the runtime dependency. ''', repository: 'https://github.com/google/built_value.dart/tree/master/built_value', authors: [], - version: '8.12.0', + version: '8.12.3', spdxIdentifiers: ['BSD-3-Clause'], isMarkdown: false, isSdk: false, @@ -1185,13 +1191,54 @@ const _clock = Package( limitations under the License.''', ); -/// code_builder 4.11.0 +/// code_assets 1.0.0 +const _code_assets = Package( + name: 'code_assets', + description: 'This library contains the hook protocol specification for bundling native code with Dart packages.', + repository: 'https://github.com/dart-lang/native/tree/main/pkgs/code_assets', + authors: [], + version: '1.0.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('collection'), PackageRef('hooks')], + devDependencies: [PackageRef('json_schema'), PackageRef('test')], + license: '''Copyright 2025, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + +/// code_builder 4.11.1 const _code_builder = Package( name: 'code_builder', description: 'A fluent, builder-based library for generating valid Dart code.', repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/code_builder', authors: [], - version: '4.11.0', + version: '4.11.1', spdxIdentifiers: ['BSD-3-Clause'], isMarkdown: false, isSdk: false, @@ -1349,13 +1396,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// cross_file 0.3.5+1 +/// cross_file 0.3.5+2 const _cross_file = Package( name: 'cross_file', description: 'An abstraction to allow working with files across multiple platforms.', repository: 'https://github.com/flutter/packages/tree/main/packages/cross_file', authors: [], - version: '0.3.5+1', + version: '0.3.5+2', spdxIdentifiers: ['BSD-3-Clause'], isMarkdown: false, isSdk: false, @@ -1581,13 +1628,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// dbus 0.7.11 +/// dbus 0.7.12 const _dbus = Package( name: 'dbus', description: 'A native Dart implementation of the D-Bus message bus client. This package allows Dart applications to directly access services on the Linux desktop.', homepage: 'https://github.com/canonical/dbus.dart', authors: [], - version: '0.7.11', + version: '0.7.12', spdxIdentifiers: ['MPL-2.0'], isMarkdown: false, isSdk: false, @@ -1968,7 +2015,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice defined by the Mozilla Public License, v. 2.0.''', ); -/// dio 5.9.0 +/// dio 5.9.1 const _dio = Package( name: 'dio', description: '''A powerful HTTP networking package, @@ -1979,7 +2026,7 @@ Custom adapters, Transformers, etc. homepage: 'https://github.com/cfug/dio', repository: 'https://github.com/cfug/dio/blob/main/dio', authors: [], - version: '5.9.0', + version: '5.9.1', spdxIdentifiers: ['MIT'], isMarkdown: false, isSdk: false, @@ -2369,13 +2416,13 @@ const _fake_async = Package( limitations under the License.''', ); -/// ffi 2.1.4 +/// ffi 2.1.5 const _ffi = Package( name: 'ffi', description: 'Utilities for working with Foreign Function Interface (FFI) code.', repository: 'https://github.com/dart-lang/native/tree/main/pkgs/ffi', authors: [], - version: '2.1.4', + version: '2.1.5', spdxIdentifiers: ['BSD-3-Clause'], isMarkdown: false, isSdk: false, @@ -2450,14 +2497,14 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// file_picker 10.3.7 +/// file_picker 10.3.10 const _file_picker = Package( name: 'file_picker', description: 'A package that allows you to use a native file explorer to pick single or multiple absolute file paths, with extension filtering support.', homepage: 'https://github.com/miguelpruivo/plugins_flutter_file_picker', repository: 'https://github.com/miguelpruivo/flutter_file_picker', authors: [], - version: '10.3.7', + version: '10.3.10', spdxIdentifiers: ['MIT'], isMarkdown: false, isSdk: false, @@ -2571,13 +2618,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// flutter 3.38.4 +/// flutter 3.38.6 const _flutter = Package( name: 'flutter', description: 'A framework for writing Flutter applications', homepage: 'https://flutter.dev', authors: [], - version: '3.38.4', + version: '3.38.6', spdxIdentifiers: ['BSD-3-Clause'], isMarkdown: false, isSdk: true, @@ -2701,6 +2748,44 @@ const _flutter_web_plugins = Package( devDependencies: [], ); +/// fluttericon 2.0.0 +const _fluttericon = Package( + name: 'fluttericon', + description: 'Flutter icons from popular web icon fonts. Customize your final icon package using fluttericon.com.', + homepage: 'https://github.com/ilikerobots/fluttericon_pkg', + authors: [], + version: '2.0.0', + spdxIdentifiers: [], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('flutter')], + devDependencies: [PackageRef('test'), PackageRef('recase')], + license: '''Copyright (c) 2020, Mike Hoolehan, StarHeight Media +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the StarHeight Media nor the + names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL HOOLEHAN OR STARHEIGHT MEDIA BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + /// font_awesome_flutter 10.12.0 const _font_awesome_flutter = Package( name: 'font_awesome_flutter', @@ -2862,6 +2947,47 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); +/// hooks 1.0.1 +const _hooks = Package( + name: 'hooks', + description: 'A library that contains a Dart API for the JSON-based protocol for `hook/build.dart` and `hook/link.dart`.', + repository: 'https://github.com/dart-lang/native/tree/main/pkgs/hooks', + authors: [], + version: '1.0.1', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('collection'), PackageRef('crypto'), PackageRef('logging'), PackageRef('meta'), PackageRef('pub_semver'), PackageRef('yaml')], + devDependencies: [PackageRef('args'), PackageRef('code_assets'), PackageRef('glob'), PackageRef('json_schema'), PackageRef('path'), PackageRef('test')], + license: '''Copyright 2025, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + /// html 0.15.6 const _html = Package( name: 'html', @@ -3145,13 +3271,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// json_annotation 4.9.0 +/// json_annotation 4.10.0 const _json_annotation = Package( name: 'json_annotation', description: 'Classes and helper functions that support JSON code generation via the `json_serializable` package.', repository: 'https://github.com/google/json_serializable.dart/tree/master/json_annotation', authors: [], - version: '4.9.0', + version: '4.10.0', spdxIdentifiers: ['BSD-3-Clause'], isMarkdown: false, isSdk: false, @@ -3821,6 +3947,47 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); +/// native_toolchain_c 0.17.4 +const _native_toolchain_c = Package( + name: 'native_toolchain_c', + description: 'A library to invoke the native C compiler installed on the host machine.', + repository: 'https://github.com/dart-lang/native/tree/main/pkgs/native_toolchain_c', + authors: [], + version: '0.17.4', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('code_assets'), PackageRef('glob'), PackageRef('hooks'), PackageRef('logging'), PackageRef('meta'), PackageRef('pub_semver')], + devDependencies: [PackageRef('collection'), PackageRef('test')], + license: '''Copyright 2023, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + /// nested 1.0.0 const _nested = Package( name: 'nested', @@ -3918,6 +4085,47 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); +/// objective_c 9.3.0 +const _objective_c = Package( + name: 'objective_c', + description: 'A library to access Objective C from Flutter that acts as a support library for package:ffigen.', + repository: 'https://github.com/dart-lang/native/tree/main/pkgs/objective_c', + authors: [], + version: '9.3.0', + spdxIdentifiers: ['BSD-3-Clause'], + isMarkdown: false, + isSdk: false, + dependencies: [PackageRef('code_assets'), PackageRef('collection'), PackageRef('ffi'), PackageRef('hooks'), PackageRef('logging'), PackageRef('native_toolchain_c'), PackageRef('pub_semver')], + devDependencies: [PackageRef('args'), PackageRef('path'), PackageRef('test'), PackageRef('yaml')], + license: '''Copyright 2024, the Dart project authors. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google LLC nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', + ); + /// package_config 2.2.0 const _package_config = Package( name: 'package_config', @@ -4164,13 +4372,13 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// path_provider_android 2.2.20 +/// path_provider_android 2.2.22 const _path_provider_android = Package( name: 'path_provider_android', description: 'Android implementation of the path_provider plugin.', repository: 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_android', authors: [], - version: '2.2.20', + version: '2.2.22', spdxIdentifiers: ['BSD-3-Clause'], isMarkdown: false, isSdk: false, @@ -4203,18 +4411,18 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// path_provider_foundation 2.4.3 +/// path_provider_foundation 2.6.0 const _path_provider_foundation = Package( name: 'path_provider_foundation', description: 'iOS and macOS implementation of the path_provider plugin', repository: 'https://github.com/flutter/packages/tree/main/packages/path_provider/path_provider_foundation', authors: [], - version: '2.4.3', + version: '2.6.0', spdxIdentifiers: ['BSD-3-Clause'], isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('path_provider_platform_interface')], - devDependencies: [PackageRef('build_runner'), PackageRef('path')], + dependencies: [PackageRef('ffi'), PackageRef('flutter'), PackageRef('objective_c'), PackageRef('path_provider_platform_interface')], + devDependencies: [], license: '''Copyright 2013 The Flutter Authors Redistribution and use in source and binary forms, with or without modification, @@ -5503,18 +5711,18 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// skeletonizer 2.1.1 +/// skeletonizer 2.1.2 const _skeletonizer = Package( name: 'skeletonizer', description: 'Converts already built widgets into skeleton loaders with no extra effort.', homepage: 'https://github.com/Milad-Akarie/skeletonizer', authors: [], - version: '2.1.1', + version: '2.1.2', spdxIdentifiers: ['MIT'], isMarkdown: false, isSdk: false, dependencies: [PackageRef('flutter')], - devDependencies: [PackageRef('flutter_lints')], + devDependencies: [PackageRef('flutter_lints'), PackageRef('coverage')], license: '''MIT License Copyright (c) 2023 Milad Akarie @@ -5661,13 +5869,13 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// source_span 1.10.1 +/// source_span 1.10.2 const _source_span = Package( name: 'source_span', description: 'Provides a standard representation for source code locations and spans.', repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/source_span', authors: [], - version: '1.10.1', + version: '1.10.2', spdxIdentifiers: ['BSD-3-Clause'], isMarkdown: false, isSdk: false, @@ -5737,13 +5945,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', ); -/// sqlite3_flutter_libs 0.5.40 +/// sqlite3_flutter_libs 0.5.41 const _sqlite3_flutter_libs = Package( name: 'sqlite3_flutter_libs', description: 'Flutter plugin to include native sqlite3 libraries with your app', - homepage: 'https://github.com/simolus3/sqlite3.dart/tree/main/sqlite3_flutter_libs', + homepage: 'https://github.com/simolus3/sqlite3.dart/tree/v2/sqlite3_flutter_libs', authors: [], - version: '0.5.40', + version: '0.5.41', spdxIdentifiers: ['MIT'], isMarkdown: false, isSdk: false, @@ -5772,14 +5980,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', ); -/// sqlparser 0.42.0 +/// sqlparser 0.42.1 const _sqlparser = Package( name: 'sqlparser', description: 'Parses sqlite statements and performs static analysis on them', homepage: 'https://github.com/simolus3/drift/tree/develop/sqlparser', repository: 'https://github.com/simolus3/drift', authors: [], - version: '0.42.0', + version: '0.42.1', spdxIdentifiers: ['MIT'], isMarkdown: false, isSdk: false, @@ -6525,13 +6733,13 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// url_launcher_web 2.4.1 +/// url_launcher_web 2.4.2 const _url_launcher_web = Package( name: 'url_launcher_web', description: 'Web platform implementation of url_launcher', repository: 'https://github.com/flutter/packages/tree/main/packages/url_launcher/url_launcher_web', authors: [], - version: '2.4.1', + version: '2.4.2', spdxIdentifiers: ['Apache-2.0', 'BSD-3-Clause'], isMarkdown: false, isSdk: false, @@ -6539,7 +6747,7 @@ const _url_launcher_web = Package( devDependencies: [], license: '''url_launcher_web -Copyright 2013 The Flutter Authors. All rights reserved. +Copyright 2013 The Flutter Authors Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: @@ -6932,18 +7140,18 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''', ); -/// watcher 1.1.4 +/// watcher 1.2.1 const _watcher = Package( name: 'watcher', description: 'A file system watcher. It monitors changes to contents of directories and sends notifications when files have been added, removed, or modified.', repository: 'https://github.com/dart-lang/tools/tree/main/pkgs/watcher', authors: [], - version: '1.1.4', + version: '1.2.1', spdxIdentifiers: ['BSD-3-Clause'], isMarkdown: false, isSdk: false, dependencies: [PackageRef('async'), PackageRef('path')], - devDependencies: [PackageRef('test')], + devDependencies: [PackageRef('clock'), PackageRef('fake_async'), PackageRef('test')], license: '''Copyright 2014, the Dart project authors. Redistribution and use in source and binary forms, with or without @@ -7151,7 +7359,7 @@ const _win32 = Package( isMarkdown: false, isSdk: false, dependencies: [PackageRef('ffi')], - devDependencies: [PackageRef('args'), PackageRef('path'), PackageRef('test')], + devDependencies: [PackageRef('args'), PackageRef('hooks'), PackageRef('path'), PackageRef('test')], license: '''BSD 3-Clause License Copyright (c) 2024, Halil Durmus @@ -7291,16 +7499,181 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''', ); -/// game_tracker 0.0.5+127 -const _game_tracker = Package( - name: 'game_tracker', - description: 'Game Tracking App for Card Games', +/// tallee 0.0.16+250 +const _tallee = Package( + name: 'tallee', + description: 'Tracking App for Card Games', authors: [], - version: '0.0.5+127', - spdxIdentifiers: [], + version: '0.0.16+250', + spdxIdentifiers: ['LGPL-3.0'], isMarkdown: false, isSdk: false, - dependencies: [PackageRef('flutter'), PackageRef('clock'), PackageRef('cupertino_icons'), PackageRef('drift'), PackageRef('drift_flutter'), PackageRef('file_picker'), PackageRef('file_saver'), PackageRef('font_awesome_flutter'), PackageRef('intl'), PackageRef('json_schema'), PackageRef('package_info_plus'), PackageRef('path_provider'), PackageRef('provider'), PackageRef('skeletonizer'), PackageRef('url_launcher'), PackageRef('uuid')], + dependencies: [PackageRef('flutter'), PackageRef('clock'), PackageRef('cupertino_icons'), PackageRef('drift'), PackageRef('drift_flutter'), PackageRef('file_picker'), PackageRef('file_saver'), PackageRef('fluttericon'), PackageRef('font_awesome_flutter'), PackageRef('intl'), PackageRef('json_schema'), PackageRef('package_info_plus'), PackageRef('path_provider'), PackageRef('provider'), PackageRef('skeletonizer'), PackageRef('url_launcher'), PackageRef('uuid')], devDependencies: [PackageRef('build_runner'), PackageRef('dart_pubspec_licenses'), PackageRef('drift_dev'), PackageRef('flutter_lints')], + license: '''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.''', ); diff --git a/lib/presentation/views/main_menu/settings_view/licenses_view.dart b/lib/presentation/views/main_menu/settings_view/licenses_view.dart index 58aae5b..ed98c26 100644 --- a/lib/presentation/views/main_menu/settings_view/licenses_view.dart +++ b/lib/presentation/views/main_menu/settings_view/licenses_view.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart'; -import 'package:game_tracker/presentation/widgets/tiles/license_tile.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart'; +import 'package:tallee/presentation/widgets/tiles/license_tile.dart'; class LicensesView extends StatelessWidget { /// A view that displays a list of open source licenses used in the app diff --git a/lib/presentation/views/main_menu/settings_view/settings_view.dart b/lib/presentation/views/main_menu/settings_view/settings_view.dart index 78c7efc..6a558ad 100644 --- a/lib/presentation/views/main_menu/settings_view/settings_view.dart +++ b/lib/presentation/views/main_menu/settings_view/settings_view.dart @@ -3,16 +3,16 @@ import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/core/enums.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses_view.dart'; -import 'package:game_tracker/presentation/widgets/buttons/animated_dialog_button.dart'; -import 'package:game_tracker/presentation/widgets/custom_alert_dialog.dart'; -import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart'; -import 'package:game_tracker/services/data_transfer_service.dart'; import 'package:intl/intl.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/views/main_menu/settings_view/licenses_view.dart'; +import 'package:tallee/presentation/widgets/buttons/animated_dialog_button.dart'; +import 'package:tallee/presentation/widgets/custom_alert_dialog.dart'; +import 'package:tallee/presentation/widgets/tiles/settings_list_tile.dart'; +import 'package:tallee/services/data_transfer_service.dart'; import 'package:url_launcher/url_launcher.dart'; class SettingsView extends StatefulWidget { @@ -89,7 +89,7 @@ class _SettingsViewState extends State { ); final result = await DataTransferService.exportData( json, - 'game_tracker-data', + 'tallee-data', ); if (!scaffoldMessengerContext.mounted) return; showExportSnackBar( @@ -137,7 +137,7 @@ class _SettingsViewState extends State { onPressed: () => Navigator.of(context).pop(true), child: Text( loc.delete, - style: TextStyle( + style: const TextStyle( color: CustomTheme.secondaryColor, ), ), diff --git a/lib/presentation/views/main_menu/statistics_view.dart b/lib/presentation/views/main_menu/statistics_view.dart index f87a3fb..5b3ff22 100644 --- a/lib/presentation/views/main_menu/statistics_view.dart +++ b/lib/presentation/views/main_menu/statistics_view.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/constants.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/match.dart'; -import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; -import 'package:game_tracker/presentation/widgets/tiles/statistics_tile.dart'; -import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; +import 'package:tallee/core/constants.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/match.dart'; +import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/widgets/app_skeleton.dart'; +import 'package:tallee/presentation/widgets/tiles/statistics_tile.dart'; +import 'package:tallee/presentation/widgets/top_centered_message.dart'; class StatisticsView extends StatefulWidget { /// A view that displays player statistics @@ -167,7 +167,7 @@ class _StatisticsViewState extends State { final playerId = winCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, - orElse: () => Player(id: playerId, name: loc.not_available), + orElse: () => Player(id: playerId, name: loc.not_available, description: ''), ); winCounts[i] = (player.name, winCounts[i].$2); } @@ -202,19 +202,17 @@ class _StatisticsViewState extends State { } } } - if (match.players != null) { - final members = match.players!.map((p) => p.id).toList(); - for (var playerId in members) { - final index = matchCounts.indexWhere((entry) => entry.$1 == playerId); - // -1 means player not found in matchCounts - if (index != -1) { - final current = matchCounts[index].$2; + final members = match.players.map((p) => p.id).toList(); + for (var playerId in members) { + final index = matchCounts.indexWhere((entry) => entry.$1 == playerId); + // -1 means player not found in matchCounts + if (index != -1) { + final current = matchCounts[index].$2; matchCounts[index] = (playerId, current + 1); } else { matchCounts.add((playerId, 1)); } } - } } // Adding all players with zero matches @@ -231,7 +229,7 @@ class _StatisticsViewState extends State { final playerId = matchCounts[i].$1; final player = players.firstWhere( (p) => p.id == playerId, - orElse: () => Player(id: playerId, name: loc.not_available), + orElse: () => Player(id: playerId, name: loc.not_available, description: ''), ); matchCounts[i] = (player.name, matchCounts[i].$2); } diff --git a/lib/presentation/widgets/buttons/animated_dialog_button.dart b/lib/presentation/widgets/buttons/animated_dialog_button.dart index 65c0510..798edfa 100644 --- a/lib/presentation/widgets/buttons/animated_dialog_button.dart +++ b/lib/presentation/widgets/buttons/animated_dialog_button.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; +import 'package:tallee/core/custom_theme.dart'; class AnimatedDialogButton extends StatefulWidget { /// A custom animated button widget that provides a scaling and opacity effect diff --git a/lib/presentation/widgets/buttons/custom_width_button.dart b/lib/presentation/widgets/buttons/custom_width_button.dart index 8d45540..489ceae 100644 --- a/lib/presentation/widgets/buttons/custom_width_button.dart +++ b/lib/presentation/widgets/buttons/custom_width_button.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/core/enums.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/core/enums.dart'; class CustomWidthButton extends StatelessWidget { /// A custom button widget that is designed to have a width relative to the screen size. diff --git a/lib/presentation/widgets/buttons/quick_create_button.dart b/lib/presentation/widgets/buttons/quick_create_button.dart index e013186..f3aa588 100644 --- a/lib/presentation/widgets/buttons/quick_create_button.dart +++ b/lib/presentation/widgets/buttons/quick_create_button.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; +import 'package:tallee/core/custom_theme.dart'; class QuickCreateButton extends StatefulWidget { /// A button widget designed for quick creating matches in the [HomeView] @@ -28,14 +28,18 @@ class _QuickCreateButtonState extends State { onPressed: widget.onPressed, style: ElevatedButton.styleFrom( minimumSize: const Size(140, 45), - backgroundColor: CustomTheme.primaryColor, + backgroundColor: CustomTheme.primaryColor.withAlpha(200).withBlue(40), shape: RoundedRectangleBorder( borderRadius: CustomTheme.standardBorderRadiusAll, ), ), child: Text( widget.text, - style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + style: const TextStyle( + color: CustomTheme.textColor, + fontWeight: FontWeight.bold, + fontSize: 16, + ), ), ); } diff --git a/lib/presentation/widgets/colored_icon_container.dart b/lib/presentation/widgets/colored_icon_container.dart index be51cd2..fe0659f 100644 --- a/lib/presentation/widgets/colored_icon_container.dart +++ b/lib/presentation/widgets/colored_icon_container.dart @@ -1,5 +1,5 @@ import 'package:flutter/cupertino.dart'; -import 'package:game_tracker/core/custom_theme.dart'; +import 'package:tallee/core/custom_theme.dart'; class ColoredIconContainer extends StatelessWidget { /// A customizable container widget that displays an icon with a colored background. @@ -48,7 +48,7 @@ class ColoredIconContainer extends StatelessWidget { child: Icon( icon, size: iconSize, - color: CustomTheme.primaryColor.withGreen(40), + color: CustomTheme.primaryColor.withBlue(40), ), ), ], diff --git a/lib/presentation/widgets/custom_alert_dialog.dart b/lib/presentation/widgets/custom_alert_dialog.dart index af5b45a..bf98f2c 100644 --- a/lib/presentation/widgets/custom_alert_dialog.dart +++ b/lib/presentation/widgets/custom_alert_dialog.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; +import 'package:tallee/core/custom_theme.dart'; class CustomAlertDialog extends StatelessWidget { /// A custom alert dialog widget that provides a os unspecific AlertDialog, @@ -32,7 +32,7 @@ class CustomAlertDialog extends StatelessWidget { actionsAlignment: MainAxisAlignment.spaceAround, shape: RoundedRectangleBorder( borderRadius: CustomTheme.standardBorderRadiusAll, - side: BorderSide(color: CustomTheme.boxBorder), + side: const BorderSide(color: CustomTheme.boxBorderColor), ), ); } diff --git a/lib/presentation/widgets/navbar_item.dart b/lib/presentation/widgets/navbar_item.dart index 45f2976..17c055c 100644 --- a/lib/presentation/widgets/navbar_item.dart +++ b/lib/presentation/widgets/navbar_item.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; +import 'package:tallee/core/custom_theme.dart'; class NavbarItem extends StatefulWidget { /// A navigation bar item widget that represents a single tab in a navigation bar. @@ -87,19 +87,29 @@ class _NavbarItemState extends State mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.center, children: [ - ScaleTransition( - scale: widget.isSelected - ? _scaleAnimation - : const AlwaysStoppedAnimation(1.0), - child: Icon( - widget.icon, + AnimatedContainer( + width: 50, + height: 50, + decoration: BoxDecoration( color: widget.isSelected - ? CustomTheme.navBarItemSelectedColor - : CustomTheme.navBarItemUnselectedColor, - size: 32, + ? CustomTheme.primaryColor.withAlpha(50) + : Colors.transparent, + borderRadius: const BorderRadius.all(Radius.circular(15)), + ), + duration: const Duration(milliseconds: 200), + child: ScaleTransition( + scale: widget.isSelected + ? _scaleAnimation + : const AlwaysStoppedAnimation(1.0), + child: Icon( + widget.icon, + color: widget.isSelected + ? CustomTheme.navBarItemSelectedColor + : CustomTheme.navBarItemUnselectedColor, + size: 32, + ), ), ), - const SizedBox(height: 4), Text( widget.label, style: TextStyle( diff --git a/lib/presentation/widgets/player_selection.dart b/lib/presentation/widgets/player_selection.dart index 784b973..cc3a68c 100644 --- a/lib/presentation/widgets/player_selection.dart +++ b/lib/presentation/widgets/player_selection.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/constants.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/widgets/app_skeleton.dart'; -import 'package:game_tracker/presentation/widgets/text_input/custom_search_bar.dart'; -import 'package:game_tracker/presentation/widgets/tiles/text_icon_list_tile.dart'; -import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; -import 'package:game_tracker/presentation/widgets/top_centered_message.dart'; import 'package:provider/provider.dart'; +import 'package:tallee/core/constants.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/widgets/app_skeleton.dart'; +import 'package:tallee/presentation/widgets/text_input/custom_search_bar.dart'; +import 'package:tallee/presentation/widgets/tiles/text_icon_list_tile.dart'; +import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart'; +import 'package:tallee/presentation/widgets/top_centered_message.dart'; class PlayerSelection extends StatefulWidget { /// A widget that allows users to select players from a list, @@ -62,7 +62,7 @@ class _PlayerSelectionState extends State { /// Skeleton data used while loading players. late final List skeletonData = List.filled( 7, - Player(name: 'Player 0'), + Player(name: 'Player 0', description: ''), ); @override @@ -85,6 +85,7 @@ class _PlayerSelectionState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ CustomSearchBar( + maxLength: Constants.MAX_PLAYER_NAME_LENGTH, controller: _searchBarController, constraints: const BoxConstraints(maxHeight: 45, minHeight: 45), hintText: loc.search_for_players, @@ -275,7 +276,7 @@ class _PlayerSelectionState extends State { final loc = AppLocalizations.of(context); final playerName = _searchBarController.text.trim(); - final createdPlayer = Player(name: playerName); + final createdPlayer = Player(name: playerName, description: ''); final success = await db.playerDao.addPlayer(player: createdPlayer); if (!context.mounted) return; diff --git a/lib/presentation/widgets/text_input/custom_search_bar.dart b/lib/presentation/widgets/text_input/custom_search_bar.dart index 76bb6e5..313fc1a 100644 --- a/lib/presentation/widgets/text_input/custom_search_bar.dart +++ b/lib/presentation/widgets/text_input/custom_search_bar.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/constants.dart'; -import 'package:game_tracker/core/custom_theme.dart'; +import 'package:tallee/core/custom_theme.dart'; class CustomSearchBar extends StatelessWidget { /// A custom search bar widget that encapsulates a [SearchBar] with additional customization options. @@ -22,6 +21,7 @@ class CustomSearchBar extends StatelessWidget { this.onTrailingButtonPressed, this.onChanged, this.constraints, + this.maxLength, }); /// The controller for the search bar's text input. @@ -48,15 +48,19 @@ class CustomSearchBar extends StatelessWidget { /// The constraints for the search bar. final BoxConstraints? constraints; + /// Optional parameter for maximum length of the input text. + final int? maxLength; + @override Widget build(BuildContext context) { /// Enforce maximum length on the input text - const maxLength = Constants.MAX_PLAYER_NAME_LENGTH; - if (controller.text.length > maxLength) { - controller.text = controller.text.substring(0, maxLength); - controller.selection = TextSelection.fromPosition( - TextPosition(offset: controller.text.length), - ); + if (maxLength != null) { + if (controller.text.length > maxLength!) { + controller.text = controller.text.substring(0, maxLength); + controller.selection = TextSelection.fromPosition( + TextPosition(offset: controller.text.length), + ); + } } return SearchBar( @@ -83,7 +87,9 @@ class CustomSearchBar extends StatelessWidget { const SizedBox(width: 5), ], backgroundColor: WidgetStateProperty.all(CustomTheme.boxColor), - side: WidgetStateProperty.all(BorderSide(color: CustomTheme.boxBorder)), + side: WidgetStateProperty.all( + const BorderSide(color: CustomTheme.boxBorderColor), + ), shape: WidgetStateProperty.all( RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), ), diff --git a/lib/presentation/widgets/text_input/text_input_field.dart b/lib/presentation/widgets/text_input/text_input_field.dart index 8f7a597..541ae6f 100644 --- a/lib/presentation/widgets/text_input/text_input_field.dart +++ b/lib/presentation/widgets/text_input/text_input_field.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; +import 'package:flutter/services.dart'; +import 'package:tallee/core/custom_theme.dart'; class TextInputField extends StatelessWidget { /// A custom text input field widget that encapsulates a [TextField] with specific styling. @@ -33,18 +34,21 @@ class TextInputField extends StatelessWidget { controller: controller, onChanged: onChanged, maxLength: maxLength, + maxLengthEnforcement: MaxLengthEnforcement.truncateAfterCompositionEnds, decoration: InputDecoration( filled: true, fillColor: CustomTheme.boxColor, hintText: hintText, hintStyle: const TextStyle(fontSize: 18), - enabledBorder: OutlineInputBorder( - borderRadius: const BorderRadius.all(Radius.circular(12)), - borderSide: BorderSide(color: CustomTheme.boxBorder), + // Hides the character counter + counterText: '', + enabledBorder: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide(color: CustomTheme.boxBorderColor), ), - focusedBorder: OutlineInputBorder( - borderRadius: const BorderRadius.all(Radius.circular(12)), - borderSide: BorderSide(color: CustomTheme.boxBorder), + focusedBorder: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide(color: CustomTheme.boxBorderColor), ), floatingLabelBehavior: FloatingLabelBehavior.never, ), diff --git a/lib/presentation/widgets/tiles/choose_tile.dart b/lib/presentation/widgets/tiles/choose_tile.dart index 595816e..10ded6b 100644 --- a/lib/presentation/widgets/tiles/choose_tile.dart +++ b/lib/presentation/widgets/tiles/choose_tile.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; +import 'package:tallee/core/custom_theme.dart'; class ChooseTile extends StatefulWidget { /// A tile widget that allows users to choose an option by tapping on it. diff --git a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart index 2d8dc7a..53d0a03 100644 --- a/lib/presentation/widgets/tiles/custom_radio_list_tile.dart +++ b/lib/presentation/widgets/tiles/custom_radio_list_tile.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; +import 'package:tallee/core/custom_theme.dart'; class CustomRadioListTile extends StatelessWidget { /// A custom radio list tile widget that encapsulates a [Radio] button with additional styling and functionality. @@ -31,7 +31,7 @@ class CustomRadioListTile extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 2), decoration: BoxDecoration( color: CustomTheme.boxColor, - border: Border.all(color: CustomTheme.boxBorder), + border: Border.all(color: CustomTheme.boxBorderColor), borderRadius: CustomTheme.standardBorderRadiusAll, ), child: Row( diff --git a/lib/presentation/widgets/tiles/group_tile.dart b/lib/presentation/widgets/tiles/group_tile.dart index c035a04..d662918 100644 --- a/lib/presentation/widgets/tiles/group_tile.dart +++ b/lib/presentation/widgets/tiles/group_tile.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/data/dto/group.dart'; +import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart'; class GroupTile extends StatefulWidget { /// A tile widget that displays information about a group, including its name and members. diff --git a/lib/presentation/widgets/tiles/info_tile.dart b/lib/presentation/widgets/tiles/info_tile.dart index 78d7f28..fdbd88c 100644 --- a/lib/presentation/widgets/tiles/info_tile.dart +++ b/lib/presentation/widgets/tiles/info_tile.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; +import 'package:tallee/core/custom_theme.dart'; class InfoTile extends StatefulWidget { /// A tile widget that displays a title with an icon and some content below it. diff --git a/lib/presentation/widgets/tiles/license_tile.dart b/lib/presentation/widgets/tiles/license_tile.dart index 33e5a45..9289ed5 100644 --- a/lib/presentation/widgets/tiles/license_tile.dart +++ b/lib/presentation/widgets/tiles/license_tile.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart'; -import 'package:game_tracker/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart'; -import 'package:game_tracker/presentation/widgets/colored_icon_container.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/presentation/views/main_menu/settings_view/licenses/license_detail_view.dart'; +import 'package:tallee/presentation/views/main_menu/settings_view/licenses/oss_licenses.dart'; +import 'package:tallee/presentation/widgets/colored_icon_container.dart'; class LicenseTile extends StatelessWidget { /// A tile widget that displays information about a software package license. diff --git a/lib/presentation/widgets/tiles/match_tile.dart b/lib/presentation/widgets/tiles/match_tile.dart index e1365c1..7862000 100644 --- a/lib/presentation/widgets/tiles/match_tile.dart +++ b/lib/presentation/widgets/tiles/match_tile.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/data/dto/match.dart'; -import 'package:game_tracker/data/dto/player.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/widgets/tiles/text_icon_tile.dart'; import 'package:intl/intl.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/data/dto/match.dart'; +import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/widgets/tiles/text_icon_tile.dart'; class MatchTile extends StatefulWidget { /// A tile widget that displays information about a match, including its name, @@ -91,7 +91,7 @@ class _MatchTileState extends State { const SizedBox(width: 6), Expanded( child: Text( - '${group.name}${widget.match.players != null ? ' + ${widget.match.players?.length}' : ''}', + '${group.name} + ${widget.match.players.length}', style: const TextStyle(fontSize: 14, color: Colors.grey), overflow: TextOverflow.ellipsis, ), @@ -106,7 +106,7 @@ class _MatchTileState extends State { const SizedBox(width: 6), Expanded( child: Text( - '${widget.match.players!.length} ${loc.players}', + '${widget.match.players.length} ${loc.players}', style: const TextStyle(fontSize: 14, color: Colors.grey), overflow: TextOverflow.ellipsis, ), @@ -241,12 +241,10 @@ class _MatchTileState extends State { final playerIds = {}; // Add players from game.players - if (widget.match.players != null) { - for (var player in widget.match.players!) { - if (!playerIds.contains(player.id)) { - allPlayers.add(player); - playerIds.add(player.id); - } + for (var player in widget.match.players) { + if (!playerIds.contains(player.id)) { + allPlayers.add(player); + playerIds.add(player.id); } } diff --git a/lib/presentation/widgets/tiles/quick_info_tile.dart b/lib/presentation/widgets/tiles/quick_info_tile.dart index 4d6ef2e..5646fa5 100644 --- a/lib/presentation/widgets/tiles/quick_info_tile.dart +++ b/lib/presentation/widgets/tiles/quick_info_tile.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; +import 'package:tallee/core/custom_theme.dart'; class QuickInfoTile extends StatefulWidget { /// A tile widget that displays a title with an icon and a numeric value below it. diff --git a/lib/presentation/widgets/tiles/settings_list_tile.dart b/lib/presentation/widgets/tiles/settings_list_tile.dart index d4bc6dc..de805cd 100644 --- a/lib/presentation/widgets/tiles/settings_list_tile.dart +++ b/lib/presentation/widgets/tiles/settings_list_tile.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; -import 'package:game_tracker/presentation/widgets/colored_icon_container.dart'; +import 'package:tallee/core/custom_theme.dart'; +import 'package:tallee/presentation/widgets/colored_icon_container.dart'; class SettingsListTile extends StatelessWidget { /// A customizable settings list tile widget that displays an icon, title, and an optional suffix widget. diff --git a/lib/presentation/widgets/tiles/statistics_tile.dart b/lib/presentation/widgets/tiles/statistics_tile.dart index 2ac0dfd..bc2f7b6 100644 --- a/lib/presentation/widgets/tiles/statistics_tile.dart +++ b/lib/presentation/widgets/tiles/statistics_tile.dart @@ -1,8 +1,8 @@ import 'dart:math'; import 'package:flutter/material.dart'; -import 'package:game_tracker/l10n/generated/app_localizations.dart'; -import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart'; +import 'package:tallee/l10n/generated/app_localizations.dart'; +import 'package:tallee/presentation/widgets/tiles/info_tile.dart'; class StatisticsTile extends StatelessWidget { /// A tile widget that displays statistical data using horizontal bars. diff --git a/lib/presentation/widgets/tiles/text_icon_list_tile.dart b/lib/presentation/widgets/tiles/text_icon_list_tile.dart index e468e95..2b29d41 100644 --- a/lib/presentation/widgets/tiles/text_icon_list_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_list_tile.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; +import 'package:tallee/core/custom_theme.dart'; class TextIconListTile extends StatelessWidget { /// A list tile widget that displays text with an optional icon button. diff --git a/lib/presentation/widgets/tiles/text_icon_tile.dart b/lib/presentation/widgets/tiles/text_icon_tile.dart index 90c32b7..f98e0a7 100644 --- a/lib/presentation/widgets/tiles/text_icon_tile.dart +++ b/lib/presentation/widgets/tiles/text_icon_tile.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; +import 'package:tallee/core/custom_theme.dart'; class TextIconTile extends StatelessWidget { /// A tile widget that displays text with an optional icon that can be tapped. diff --git a/lib/presentation/widgets/tiles/title_description_list_tile.dart b/lib/presentation/widgets/tiles/title_description_list_tile.dart index a963d16..9dc8f33 100644 --- a/lib/presentation/widgets/tiles/title_description_list_tile.dart +++ b/lib/presentation/widgets/tiles/title_description_list_tile.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:game_tracker/core/custom_theme.dart'; +import 'package:tallee/core/custom_theme.dart'; class TitleDescriptionListTile extends StatelessWidget { /// A list tile widget that displays a title and description, with optional highlighting and badge. diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 8767c59..526a459 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -4,52 +4,70 @@ import 'dart:io'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:game_tracker/core/enums.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/match.dart'; -import 'package:game_tracker/data/dto/player.dart'; import 'package:json_schema/json_schema.dart'; import 'package:provider/provider.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/game.dart'; +import 'package:tallee/data/dto/group.dart'; +import 'package:tallee/data/dto/match.dart'; +import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/data/dto/team.dart'; class DataTransferService { /// Deletes all data from the database. static Future deleteAllData(BuildContext context) async { final db = Provider.of(context, listen: false); await db.matchDao.deleteAllMatches(); + await db.teamDao.deleteAllTeams(); await db.groupDao.deleteAllGroups(); + await db.gameDao.deleteAllGames(); await db.playerDao.deleteAllPlayers(); } /// Retrieves all application data and converts it to a JSON string. - /// Returns the JSON string representation of the data. + /// Returns the JSON string representation of the data in normalized format. static Future getAppDataAsJson(BuildContext context) async { final db = Provider.of(context, listen: false); final matches = await db.matchDao.getAllMatches(); final groups = await db.groupDao.getAllGroups(); final players = await db.playerDao.getAllPlayers(); + final games = await db.gameDao.getAllGames(); + final teams = await db.teamDao.getAllTeams(); - // Construct a JSON representation of the data + // Construct a JSON representation of the data in normalized format final Map jsonMap = { 'players': players.map((p) => p.toJson()).toList(), - + 'games': games.map((g) => g.toJson()).toList(), 'groups': groups .map((g) => { 'id': g.id, 'name': g.name, + 'description': g.description, 'createdAt': g.createdAt.toIso8601String(), 'memberIds': (g.members).map((m) => m.id).toList(), - }).toList(), - + }) + .toList(), + 'teams': teams + .map((t) => { + 'id': t.id, + 'name': t.name, + 'createdAt': t.createdAt.toIso8601String(), + 'memberIds': (t.members).map((m) => m.id).toList(), + }) + .toList(), 'matches': matches .map((m) => { 'id': m.id, 'name': m.name, 'createdAt': m.createdAt.toIso8601String(), + 'endedAt': m.endedAt?.toIso8601String(), + 'gameId': m.game.id, 'groupId': m.group?.id, - 'playerIds': (m.players ?? []).map((p) => p.id).toList(), - 'winnerId': m.winner?.id, - }).toList(), + 'playerIds': m.players.map((p) => p.id).toList(), + 'notes': m.notes, + }) + .toList(), }; return json.encode(jsonMap); @@ -61,9 +79,9 @@ class DataTransferService { /// [jsonString] The JSON string to be exported. /// [fileName] The desired name for the exported file (without extension). static Future exportData( - String jsonString, - String fileName - ) async { + String jsonString, + String fileName + ) async { try { final bytes = Uint8List.fromList(utf8.encode(jsonString)); final path = await FilePicker.platform.saveFile( @@ -107,10 +125,12 @@ class DataTransferService { final Map decoded = json.decode(jsonString) as Map; final List playersJson = (decoded['players'] as List?) ?? []; + final List gamesJson = (decoded['games'] as List?) ?? []; final List groupsJson = (decoded['groups'] as List?) ?? []; + final List teamsJson = (decoded['teams'] as List?) ?? []; final List matchesJson = (decoded['matches'] as List?) ?? []; - // Players + // Import Players final List importedPlayers = playersJson .map((p) => Player.fromJson(p as Map)) .toList(); @@ -119,7 +139,16 @@ class DataTransferService { for (final p in importedPlayers) p.id: p, }; - // Groups + // Import Games + final List importedGames = gamesJson + .map((g) => Game.fromJson(g as Map)) + .toList(); + + final Map gameById = { + for (final g in importedGames) g.id: g, + }; + + // Import Groups final List importedGroups = groupsJson.map((g) { final map = g as Map; final memberIds = (map['memberIds'] as List? ?? []).cast(); @@ -132,6 +161,7 @@ class DataTransferService { return Group( id: map['id'] as String, name: map['name'] as String, + description: map['description'] as String, members: members, createdAt: DateTime.parse(map['createdAt'] as String), ); @@ -141,33 +171,57 @@ class DataTransferService { for (final g in importedGroups) g.id: g, }; - // Matches + // Import Teams + final List importedTeams = teamsJson.map((t) { + final map = t as Map; + final memberIds = (map['memberIds'] as List? ?? []).cast(); + + final members = memberIds + .map((id) => playerById[id]) + .whereType() + .toList(); + + return Team( + id: map['id'] as String, + name: map['name'] as String, + members: members, + createdAt: DateTime.parse(map['createdAt'] as String), + ); + }).toList(); + + // Import Matches final List importedMatches = matchesJson.map((m) { final map = m as Map; + final String gameId = map['gameId'] as String; final String? groupId = map['groupId'] as String?; final List playerIds = (map['playerIds'] as List? ?? []).cast(); - final String? winnerId = map['winnerId'] as String?; + final DateTime? endedAt = map['endedAt'] != null ? DateTime.parse(map['endedAt'] as String) : null; + final game = gameById[gameId]; final group = (groupId == null) ? null : groupById[groupId]; final players = playerIds .map((id) => playerById[id]) .whereType() .toList(); - final winner = (winnerId == null) ? null : playerById[winnerId]; return Match( id: map['id'] as String, name: map['name'] as String, + game: game ?? Game(name: 'Unknown', ruleset: Ruleset.singleWinner, description: '', color: GameColor.blue, icon: ''), group: group, players: players, createdAt: DateTime.parse(map['createdAt'] as String), - winner: winner, + endedAt: endedAt, + notes: map['notes'] as String? ?? '', ); }).toList(); + // Import all data into the database await db.playerDao.addPlayersAsList(players: importedPlayers); + await db.gameDao.addGamesAsList(games: importedGames); await db.groupDao.addGroupsAsList(groups: importedGroups); + await db.teamDao.addTeamsAsList(teams: importedTeams); await db.matchDao.addMatchAsList(matches: importedMatches); return ImportResult.success; diff --git a/pubspec.yaml b/pubspec.yaml index 54ff72b..6192380 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ -name: game_tracker -description: "Game Tracking App for Card Games" +name: tallee +description: "Tracking App for Card Games" publish_to: 'none' -version: 0.0.9+242 +version: 0.0.17+251 environment: sdk: ^3.8.1 @@ -34,7 +34,7 @@ dev_dependencies: build_runner: ^2.5.4 dart_pubspec_licenses: ^3.0.14 drift_dev: ^2.27.0 - flutter_lints: ^5.0.0 + flutter_lints: ^6.0.0 flutter: uses-material-design: true diff --git a/test/db_tests/aggregates/group_test.dart b/test/db_tests/aggregates/group_test.dart new file mode 100644 index 0000000..a671232 --- /dev/null +++ b/test/db_tests/aggregates/group_test.dart @@ -0,0 +1,374 @@ +import 'package:clock/clock.dart'; +import 'package:drift/drift.dart' hide isNull; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/group.dart'; +import 'package:tallee/data/dto/player.dart'; + +void main() { + late AppDatabase database; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; + late Group testGroup1; + late Group testGroup2; + late Group testGroup3; + late Group testGroup4; + final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); + final fakeClock = Clock(() => fixedDate); + + setUp(() { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + withClock(fakeClock, () { + testPlayer1 = Player(name: 'Alice', description: ''); + testPlayer2 = Player(name: 'Bob', description: ''); + testPlayer3 = Player(name: 'Charlie', description: ''); + testPlayer4 = Player(name: 'Diana', description: ''); + testGroup1 = Group( + name: 'Test Group', + description: '', + members: [testPlayer1, testPlayer2, testPlayer3], + ); + testGroup2 = Group( + id: 'gr2', + name: 'Second Group', + description: '', + members: [testPlayer2, testPlayer3, testPlayer4], + ); + testGroup3 = Group( + id: 'gr2', + name: 'Second Group', + description: '', + members: [testPlayer2, testPlayer4], + ); + testGroup4 = Group( + id: 'gr2', + name: 'Second Group', + description: '', + members: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], + ); + }); + }); + tearDown(() async { + await database.close(); + }); + group('Group Tests', () { + + // Verifies that a single group can be added and retrieved with all fields and members intact. + test('Adding and fetching a single group works correctly', () async { + await database.groupDao.addGroup(group: testGroup1); + + final fetchedGroup = await database.groupDao.getGroupById( + groupId: testGroup1.id, + ); + + expect(fetchedGroup.id, testGroup1.id); + expect(fetchedGroup.name, testGroup1.name); + expect(fetchedGroup.createdAt, testGroup1.createdAt); + + expect(fetchedGroup.members.length, testGroup1.members.length); + for (int i = 0; i < testGroup1.members.length; i++) { + expect(fetchedGroup.members[i].id, testGroup1.members[i].id); + expect(fetchedGroup.members[i].name, testGroup1.members[i].name); + expect( + fetchedGroup.members[i].createdAt, + testGroup1.members[i].createdAt, + ); + } + }); + + // Verifies that multiple groups can be added and retrieved with correct members. + test('Adding and fetching multiple groups works correctly', () async { + await database.groupDao.addGroupsAsList( + groups: [testGroup1, testGroup2, testGroup3, testGroup4], + ); + + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups.length, 2); + + final testGroups = {testGroup1.id: testGroup1, testGroup2.id: testGroup2}; + + for (final group in allGroups) { + final testGroup = testGroups[group.id]!; + + expect(group.id, testGroup.id); + expect(group.name, testGroup.name); + expect(group.createdAt, testGroup.createdAt); + + expect(group.members.length, testGroup.members.length); + for (int i = 0; i < testGroup.members.length; i++) { + expect(group.members[i].id, testGroup.members[i].id); + expect(group.members[i].name, testGroup.members[i].name); + expect(group.members[i].createdAt, testGroup.members[i].createdAt); + } + } + }); + + // Verifies that adding the same group twice does not create duplicates. + test('Adding the same group twice does not create duplicates', () async { + await database.groupDao.addGroup(group: testGroup1); + await database.groupDao.addGroup(group: testGroup1); + + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups.length, 1); + }); + + // Verifies that groupExists returns correct boolean based on group presence. + test('Group existence check works correctly', () async { + var groupExists = await database.groupDao.groupExists( + groupId: testGroup1.id, + ); + expect(groupExists, false); + + await database.groupDao.addGroup(group: testGroup1); + + groupExists = await database.groupDao.groupExists(groupId: testGroup1.id); + expect(groupExists, true); + }); + + // Verifies that deleteGroup removes the group and returns true. + test('Deleting a group works correctly', () async { + await database.groupDao.addGroup(group: testGroup1); + + final groupDeleted = await database.groupDao.deleteGroup( + groupId: testGroup1.id, + ); + expect(groupDeleted, true); + + final groupExists = await database.groupDao.groupExists( + groupId: testGroup1.id, + ); + expect(groupExists, false); + }); + + // Verifies that updateGroupName correctly updates only the name field. + test('Updating a group name works correctly', () async { + await database.groupDao.addGroup(group: testGroup1); + + const newGroupName = 'new group name'; + + await database.groupDao.updateGroupName( + groupId: testGroup1.id, + newName: newGroupName, + ); + + final result = await database.groupDao.getGroupById( + groupId: testGroup1.id, + ); + expect(result.name, newGroupName); + }); + + // Verifies that getGroupCount returns correct count through add/delete operations. + test('Getting the group count works correctly', () async { + final initialCount = await database.groupDao.getGroupCount(); + expect(initialCount, 0); + + await database.groupDao.addGroup(group: testGroup1); + + final groupAdded = await database.groupDao.getGroupCount(); + expect(groupAdded, 1); + + final groupRemoved = await database.groupDao.deleteGroup( + groupId: testGroup1.id, + ); + expect(groupRemoved, true); + + final finalCount = await database.groupDao.getGroupCount(); + expect(finalCount, 0); + }); + + // Verifies that getAllGroups returns an empty list when no groups exist. + test('getAllGroups returns empty list when no groups exist', () async { + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups, isEmpty); + }); + + // Verifies that getGroupById throws StateError for non-existent group ID. + test('getGroupById throws exception for non-existent group', () async { + expect( + () => database.groupDao.getGroupById(groupId: 'non-existent-id'), + throwsA(isA()), + ); + }); + + // Verifies that addGroup returns false when trying to add a duplicate group. + test('addGroup returns false when group already exists', () async { + final firstAdd = await database.groupDao.addGroup(group: testGroup1); + expect(firstAdd, true); + + final secondAdd = await database.groupDao.addGroup(group: testGroup1); + expect(secondAdd, false); + + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups.length, 1); + }); + + // Verifies that addGroupsAsList handles an empty list without errors. + test('addGroupsAsList handles empty list correctly', () async { + await database.groupDao.addGroupsAsList(groups: []); + + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups.length, 0); + }); + + // Verifies that deleteGroup returns false for a non-existent group ID. + test('deleteGroup returns false for non-existent group', () async { + final deleted = await database.groupDao.deleteGroup( + groupId: 'non-existent-id', + ); + expect(deleted, false); + }); + + // Verifies that updateGroupName returns false for a non-existent group ID. + test('updateGroupName returns false for non-existent group', () async { + final updated = await database.groupDao.updateGroupName( + groupId: 'non-existent-id', + newName: 'New Name', + ); + expect(updated, false); + }); + + // Verifies that updateGroupDescription correctly updates the description field. + test('Updating a group description works correctly', () async { + await database.groupDao.addGroup(group: testGroup1); + + const newDescription = 'This is a new description'; + + final updated = await database.groupDao.updateGroupDescription( + groupId: testGroup1.id, + newDescription: newDescription, + ); + expect(updated, true); + + final result = await database.groupDao.getGroupById( + groupId: testGroup1.id, + ); + expect(result.description, newDescription); + }); + + // Verifies that updateGroupDescription can set the description to null. + test('updateGroupDescription can set description to null', () async { + final groupWithDescription = Group( + name: 'Group with description', + description: 'Initial description', + members: [testPlayer1], + ); + await database.groupDao.addGroup(group: groupWithDescription); + + final updated = await database.groupDao.updateGroupDescription( + groupId: groupWithDescription.id, + newDescription: 'Updated description', + ); + expect(updated, true); + + final result = await database.groupDao.getGroupById( + groupId: groupWithDescription.id, + ); + expect(result.description, 'Updated description'); + }); + + // Verifies that updateGroupDescription returns false for a non-existent group. + test('updateGroupDescription returns false for non-existent group', + () async { + final updated = await database.groupDao.updateGroupDescription( + groupId: 'non-existent-id', + newDescription: 'New Description', + ); + expect(updated, false); + }); + + // Verifies that deleteAllGroups removes all groups from the database. + test('deleteAllGroups removes all groups', () async { + await database.groupDao.addGroupsAsList( + groups: [testGroup1, testGroup2], + ); + + final countBefore = await database.groupDao.getGroupCount(); + expect(countBefore, 2); + + final deleted = await database.groupDao.deleteAllGroups(); + expect(deleted, true); + + final countAfter = await database.groupDao.getGroupCount(); + expect(countAfter, 0); + }); + + // Verifies that deleteAllGroups returns false when no groups exist. + test('deleteAllGroups returns false when no groups exist', () async { + final deleted = await database.groupDao.deleteAllGroups(); + expect(deleted, false); + }); + + // Verifies that groups with special characters (quotes, emojis) are stored correctly. + test('Group with special characters in name is stored correctly', () async { + final specialGroup = Group( + name: 'Group\'s & "Special" ', + description: 'Description with émojis 🎮🎲', + members: [testPlayer1], + ); + await database.groupDao.addGroup(group: specialGroup); + + final fetchedGroup = await database.groupDao.getGroupById( + groupId: specialGroup.id, + ); + expect(fetchedGroup.name, 'Group\'s & "Special" '); + expect(fetchedGroup.description, 'Description with émojis 🎮🎲'); + }); + + // Verifies that a group with an empty members list can be stored and retrieved. + test('Group with empty members list is stored correctly', () async { + final emptyGroup = Group( + name: 'Empty Group', + description: '', + members: [], + ); + await database.groupDao.addGroup(group: emptyGroup); + + final fetchedGroup = await database.groupDao.getGroupById( + groupId: emptyGroup.id, + ); + expect(fetchedGroup.name, 'Empty Group'); + expect(fetchedGroup.members, isEmpty); + }); + + // Verifies that multiple sequential updates to the same group work correctly. + test('Multiple updates to the same group work correctly', () async { + await database.groupDao.addGroup(group: testGroup1); + + await database.groupDao.updateGroupName( + groupId: testGroup1.id, + newName: 'Updated Name', + ); + await database.groupDao.updateGroupDescription( + groupId: testGroup1.id, + newDescription: 'Updated Description', + ); + + final updatedGroup = await database.groupDao.getGroupById( + groupId: testGroup1.id, + ); + expect(updatedGroup.name, 'Updated Name'); + expect(updatedGroup.description, 'Updated Description'); + expect(updatedGroup.members.length, testGroup1.members.length); + }); + + // Verifies that addGroupsAsList with duplicate groups only adds unique ones. + test('addGroupsAsList with duplicate groups only adds once', () async { + await database.groupDao.addGroupsAsList( + groups: [testGroup1, testGroup1, testGroup1], + ); + + final allGroups = await database.groupDao.getAllGroups(); + expect(allGroups.length, 1); + }); + }); +} diff --git a/test/db_tests/game_test.dart b/test/db_tests/aggregates/match_test.dart similarity index 61% rename from test/db_tests/game_test.dart rename to test/db_tests/aggregates/match_test.dart index 0ec2cfc..0718e0d 100644 --- a/test/db_tests/game_test.dart +++ b/test/db_tests/aggregates/match_test.dart @@ -2,10 +2,12 @@ import 'package:clock/clock.dart'; import 'package:drift/drift.dart'; import 'package:drift/native.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/match.dart'; -import 'package:game_tracker/data/dto/player.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/game.dart'; +import 'package:tallee/data/dto/group.dart'; +import 'package:tallee/data/dto/match.dart'; +import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/core/enums.dart'; void main() { late AppDatabase database; @@ -16,6 +18,7 @@ void main() { late Player testPlayer5; late Group testGroup1; late Group testGroup2; + late Game testGame; late Match testMatch1; late Match testMatch2; late Match testMatchOnlyPlayers; @@ -33,39 +36,50 @@ void main() { ); withClock(fakeClock, () { - testPlayer1 = Player(name: 'Alice'); - testPlayer2 = Player(name: 'Bob'); - testPlayer3 = Player(name: 'Charlie'); - testPlayer4 = Player(name: 'Diana'); - testPlayer5 = Player(name: 'Eve'); + testPlayer1 = Player(name: 'Alice', description: ''); + testPlayer2 = Player(name: 'Bob', description: ''); + testPlayer3 = Player(name: 'Charlie', description: ''); + testPlayer4 = Player(name: 'Diana', description: ''); + testPlayer5 = Player(name: 'Eve', description: ''); testGroup1 = Group( name: 'Test Group 2', + description: '', members: [testPlayer1, testPlayer2, testPlayer3], ); testGroup2 = Group( name: 'Test Group 2', + description: '', members: [testPlayer4, testPlayer5], ); + testGame = Game(name: 'Test Game', ruleset: Ruleset.singleWinner, description: 'A test game', color: GameColor.blue, icon: ''); testMatch1 = Match( name: 'First Test Match', + game: testGame, group: testGroup1, players: [testPlayer4, testPlayer5], winner: testPlayer4, + notes: '', ); testMatch2 = Match( name: 'Second Test Match', + game: testGame, group: testGroup2, players: [testPlayer1, testPlayer2, testPlayer3], winner: testPlayer2, + notes: '', ); testMatchOnlyPlayers = Match( name: 'Test Match with Players', + game: testGame, players: [testPlayer1, testPlayer2, testPlayer3], winner: testPlayer3, + notes: '', ); testMatchOnlyGroup = Match( name: 'Test Match with Group', + game: testGame, group: testGroup2, + notes: '', ); }); await database.playerDao.addPlayersAsList( @@ -78,12 +92,15 @@ void main() { ], ); await database.groupDao.addGroupsAsList(groups: [testGroup1, testGroup2]); + await database.gameDao.addGame(game: testGame); }); tearDown(() async { await database.close(); }); group('Match Tests', () { + + // Verifies that a single match can be added and retrieved with all fields, group, and players intact. test('Adding and fetching single match works correctly', () async { await database.matchDao.addMatch(match: testMatch1); @@ -95,14 +112,6 @@ void main() { expect(result.name, testMatch1.name); expect(result.createdAt, testMatch1.createdAt); - if (result.winner != null && testMatch1.winner != null) { - expect(result.winner!.id, testMatch1.winner!.id); - expect(result.winner!.name, testMatch1.winner!.name); - expect(result.winner!.createdAt, testMatch1.winner!.createdAt); - } else { - expect(result.winner, testMatch1.winner); - } - if (result.group != null) { expect(result.group!.members.length, testGroup1.members.length); @@ -113,22 +122,19 @@ void main() { } else { fail('Group is null'); } - if (result.players != null) { - expect(result.players!.length, testMatch1.players!.length); + expect(result.players.length, testMatch1.players.length); - for (int i = 0; i < testMatch1.players!.length; i++) { - expect(result.players![i].id, testMatch1.players![i].id); - expect(result.players![i].name, testMatch1.players![i].name); - expect( - result.players![i].createdAt, - testMatch1.players![i].createdAt, - ); - } - } else { - fail('Players is null'); + for (int i = 0; i < testMatch1.players.length; i++) { + expect(result.players[i].id, testMatch1.players[i].id); + expect(result.players[i].name, testMatch1.players[i].name); + expect( + result.players[i].createdAt, + testMatch1.players[i].createdAt, + ); } }); + // Verifies that multiple matches can be added and retrieved with correct groups and players. test('Adding and fetching multiple matches works correctly', () async { await database.matchDao.addMatchAsList( matches: [ @@ -156,13 +162,6 @@ void main() { expect(match.id, testMatch.id); expect(match.name, testMatch.name); expect(match.createdAt, testMatch.createdAt); - if (match.winner != null && testMatch.winner != null) { - expect(match.winner!.id, testMatch.winner!.id); - expect(match.winner!.name, testMatch.winner!.name); - expect(match.winner!.createdAt, testMatch.winner!.createdAt); - } else { - expect(match.winner, testMatch.winner); - } // Group-Checks if (testMatch.group != null) { @@ -188,22 +187,19 @@ void main() { } // Players-Checks - if (testMatch.players != null) { - expect(match.players!.length, testMatch.players!.length); - for (int i = 0; i < testMatch.players!.length; i++) { - expect(match.players![i].id, testMatch.players![i].id); - expect(match.players![i].name, testMatch.players![i].name); - expect( - match.players![i].createdAt, - testMatch.players![i].createdAt, - ); - } - } else { - expect(match.players, null); + expect(match.players.length, testMatch.players.length); + for (int i = 0; i < testMatch.players.length; i++) { + expect(match.players[i].id, testMatch.players[i].id); + expect(match.players[i].name, testMatch.players[i].name); + expect( + match.players[i].createdAt, + testMatch.players[i].createdAt, + ); } } }); + // Verifies that adding the same match twice does not create duplicates. test('Adding the same match twice does not create duplicates', () async { await database.matchDao.addMatch(match: testMatch1); await database.matchDao.addMatch(match: testMatch1); @@ -212,6 +208,7 @@ void main() { expect(matchCount, 1); }); + // Verifies that matchExists returns correct boolean based on match presence. test('Match existence check works correctly', () async { var matchExists = await database.matchDao.matchExists( matchId: testMatch1.id, @@ -224,6 +221,7 @@ void main() { expect(matchExists, true); }); + // Verifies that deleteMatch removes the match and returns true. test('Deleting a match works correctly', () async { await database.matchDao.addMatch(match: testMatch1); @@ -238,6 +236,7 @@ void main() { expect(matchExists, false); }); + // Verifies that getMatchCount returns correct count through add/delete operations. test('Getting the match count works correctly', () async { var matchCount = await database.matchDao.getMatchCount(); expect(matchCount, 0); @@ -263,82 +262,7 @@ void main() { expect(matchCount, 0); }); - test('Checking if match has winner works correctly', () async { - await database.matchDao.addMatch(match: testMatch1); - await database.matchDao.addMatch(match: testMatchOnlyGroup); - - var hasWinner = await database.matchDao.hasWinner(matchId: testMatch1.id); - expect(hasWinner, true); - - hasWinner = await database.matchDao.hasWinner( - matchId: testMatchOnlyGroup.id, - ); - expect(hasWinner, false); - }); - - test('Fetching the winner of a match works correctly', () async { - await database.matchDao.addMatch(match: testMatch1); - - final winner = await database.matchDao.getWinner(matchId: testMatch1.id); - if (winner == null) { - fail('Winner is null'); - } else { - expect(winner.id, testMatch1.winner!.id); - expect(winner.name, testMatch1.winner!.name); - expect(winner.createdAt, testMatch1.winner!.createdAt); - } - }); - - test('Updating the winner of a match works correctly', () async { - await database.matchDao.addMatch(match: testMatch1); - - final winner = await database.matchDao.getWinner(matchId: testMatch1.id); - if (winner == null) { - fail('Winner is null'); - } else { - expect(winner.id, testMatch1.winner!.id); - expect(winner.name, testMatch1.winner!.name); - expect(winner.createdAt, testMatch1.winner!.createdAt); - expect(winner.id, testPlayer4.id); - expect(winner.id != testPlayer5.id, true); - } - - await database.matchDao.setWinner( - matchId: testMatch1.id, - winnerId: testPlayer5.id, - ); - - final newWinner = await database.matchDao.getWinner( - matchId: testMatch1.id, - ); - - if (newWinner == null) { - fail('New winner is null'); - } else { - expect(newWinner.id, testPlayer5.id); - expect(newWinner.name, testPlayer5.name); - expect(newWinner.createdAt, testPlayer5.createdAt); - } - }); - - test('Removing a winner works correctly', () async { - await database.matchDao.addMatch(match: testMatch2); - - var hasWinner = await database.matchDao.hasWinner(matchId: testMatch2.id); - expect(hasWinner, true); - - await database.matchDao.removeWinner(matchId: testMatch2.id); - - hasWinner = await database.matchDao.hasWinner(matchId: testMatch2.id); - expect(hasWinner, false); - - final removedWinner = await database.matchDao.getWinner( - matchId: testMatch2.id, - ); - - expect(removedWinner, null); - }); - + // Verifies that updateMatchName correctly updates only the name field. test('Renaming a match works correctly', () async { await database.matchDao.addMatch(match: testMatch1); diff --git a/test/db_tests/aggregates/team_test.dart b/test/db_tests/aggregates/team_test.dart new file mode 100644 index 0000000..17ceff9 --- /dev/null +++ b/test/db_tests/aggregates/team_test.dart @@ -0,0 +1,527 @@ +import 'package:clock/clock.dart'; +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/game.dart'; +import 'package:tallee/data/dto/match.dart'; +import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/data/dto/team.dart'; +import 'package:tallee/core/enums.dart'; + +void main() { + late AppDatabase database; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; + late Team testTeam1; + late Team testTeam2; + late Team testTeam3; + late Game testGame1; + late Game testGame2; + final fixedDate = DateTime(2025, 11, 19, 00, 11, 23); + final fakeClock = Clock(() => fixedDate); + + setUp(() async { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + withClock(fakeClock, () { + testPlayer1 = Player(name: 'Alice', description: ''); + testPlayer2 = Player(name: 'Bob', description: ''); + testPlayer3 = Player(name: 'Charlie', description: ''); + testPlayer4 = Player(name: 'Diana', description: ''); + testTeam1 = Team( + name: 'Team Alpha', + members: [testPlayer1, testPlayer2], + ); + testTeam2 = Team( + name: 'Team Beta', + members: [testPlayer3, testPlayer4], + ); + testTeam3 = Team( + name: 'Team Gamma', + members: [testPlayer1, testPlayer3], + ); + testGame1 = Game(name: 'Game 1', ruleset: Ruleset.singleWinner, description: 'Test game 1', color: GameColor.blue, icon: ''); + testGame2 = Game(name: 'Game 2', ruleset: Ruleset.highestScore, description: 'Test game 2', color: GameColor.red, icon: ''); + }); + + await database.playerDao.addPlayersAsList( + players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], + ); + await database.gameDao.addGame(game: testGame1); + await database.gameDao.addGame(game: testGame2); + }); + + tearDown(() async { + await database.close(); + }); + + group('Team Tests', () { + + // Verifies that a single team can be added and retrieved with all fields intact. + test('Adding and fetching a single team works correctly', () async { + final added = await database.teamDao.addTeam(team: testTeam1); + expect(added, true); + + final fetchedTeam = await database.teamDao.getTeamById( + teamId: testTeam1.id, + ); + + expect(fetchedTeam.id, testTeam1.id); + expect(fetchedTeam.name, testTeam1.name); + expect(fetchedTeam.createdAt, testTeam1.createdAt); + }); + + // Verifies that multiple teams can be added at once and retrieved correctly. + test('Adding and fetching multiple teams works correctly', () async { + await database.teamDao.addTeamsAsList( + teams: [testTeam1, testTeam2, testTeam3], + ); + + final allTeams = await database.teamDao.getAllTeams(); + expect(allTeams.length, 3); + + final testTeams = { + testTeam1.id: testTeam1, + testTeam2.id: testTeam2, + testTeam3.id: testTeam3, + }; + + for (final team in allTeams) { + final testTeam = testTeams[team.id]!; + + expect(team.id, testTeam.id); + expect(team.name, testTeam.name); + expect(team.createdAt, testTeam.createdAt); + } + }); + + // Verifies that adding the same team twice does not create duplicates and returns false. + test('Adding the same team twice does not create duplicates', () async { + await database.teamDao.addTeam(team: testTeam1); + final addedAgain = await database.teamDao.addTeam(team: testTeam1); + + expect(addedAgain, false); + + final teamCount = await database.teamDao.getTeamCount(); + expect(teamCount, 1); + }); + + // Verifies that teamExists returns correct boolean based on team presence. + test('Team existence check works correctly', () async { + var teamExists = await database.teamDao.teamExists(teamId: testTeam1.id); + expect(teamExists, false); + + await database.teamDao.addTeam(team: testTeam1); + + teamExists = await database.teamDao.teamExists(teamId: testTeam1.id); + expect(teamExists, true); + }); + + // Verifies that deleteTeam removes the team and returns true. + test('Deleting a team works correctly', () async { + await database.teamDao.addTeam(team: testTeam1); + + final teamDeleted = await database.teamDao.deleteTeam( + teamId: testTeam1.id, + ); + expect(teamDeleted, true); + + final teamExists = await database.teamDao.teamExists( + teamId: testTeam1.id, + ); + expect(teamExists, false); + }); + + // Verifies that deleteTeam returns false for a non-existent team ID. + test('Deleting a non-existent team returns false', () async { + final teamDeleted = await database.teamDao.deleteTeam( + teamId: 'non-existent-id', + ); + expect(teamDeleted, false); + }); + + // Verifies that getTeamCount returns correct count through add/delete operations. + test('Getting the team count works correctly', () async { + var teamCount = await database.teamDao.getTeamCount(); + expect(teamCount, 0); + + await database.teamDao.addTeam(team: testTeam1); + + teamCount = await database.teamDao.getTeamCount(); + expect(teamCount, 1); + + await database.teamDao.addTeam(team: testTeam2); + + teamCount = await database.teamDao.getTeamCount(); + expect(teamCount, 2); + + await database.teamDao.deleteTeam(teamId: testTeam1.id); + + teamCount = await database.teamDao.getTeamCount(); + expect(teamCount, 1); + + await database.teamDao.deleteTeam(teamId: testTeam2.id); + + teamCount = await database.teamDao.getTeamCount(); + expect(teamCount, 0); + }); + + // Verifies that updateTeamName correctly updates only the name field. + test('Updating team name works correctly', () async { + await database.teamDao.addTeam(team: testTeam1); + + var fetchedTeam = await database.teamDao.getTeamById( + teamId: testTeam1.id, + ); + expect(fetchedTeam.name, testTeam1.name); + + const newName = 'Updated Team Name'; + await database.teamDao.updateTeamName( + teamId: testTeam1.id, + newName: newName, + ); + + fetchedTeam = await database.teamDao.getTeamById(teamId: testTeam1.id); + expect(fetchedTeam.name, newName); + }); + + // Verifies that deleteAllTeams removes all teams from the database. + test('Deleting all teams works correctly', () async { + await database.teamDao.addTeamsAsList( + teams: [testTeam1, testTeam2, testTeam3], + ); + + var teamCount = await database.teamDao.getTeamCount(); + expect(teamCount, 3); + + final deleted = await database.teamDao.deleteAllTeams(); + expect(deleted, true); + + teamCount = await database.teamDao.getTeamCount(); + expect(teamCount, 0); + }); + + // Verifies that deleteAllTeams returns false when no teams exist. + test('Deleting all teams when empty returns false', () async { + final deleted = await database.teamDao.deleteAllTeams(); + expect(deleted, false); + }); + + // Verifies that addTeamsAsList returns false when given an empty list. + test('Adding teams as list with empty list returns false', () async { + final added = await database.teamDao.addTeamsAsList(teams: []); + expect(added, false); + }); + + // Verifies that addTeamsAsList with duplicate IDs ignores duplicates and keeps the first. + test('Adding teams with duplicate IDs ignores duplicates', () async { + final duplicateTeam = Team( + id: testTeam1.id, + name: 'Duplicate Team', + members: [testPlayer4], + ); + + await database.teamDao.addTeamsAsList( + teams: [testTeam1, duplicateTeam, testTeam2], + ); + + final teamCount = await database.teamDao.getTeamCount(); + expect(teamCount, 2); + + // The first one should be kept (insertOrIgnore) + final fetchedTeam = await database.teamDao.getTeamById( + teamId: testTeam1.id, + ); + expect(fetchedTeam.name, testTeam1.name); + }); + + // Verifies that getAllTeams returns empty list when no teams exist. + test('Getting all teams when empty returns empty list', () async { + final allTeams = await database.teamDao.getAllTeams(); + expect(allTeams.isEmpty, true); + }); + + // Verifies that getTeamById throws exception for non-existent team. + test('Getting non-existent team throws exception', () async { + expect( + () => database.teamDao.getTeamById(teamId: 'non-existent-id'), + throwsA(isA()), + ); + }); + + // Verifies that updating team name preserves other fields. + test('Updating team name preserves other team fields', () async { + await database.teamDao.addTeam(team: testTeam1); + final originalTeam = await database.teamDao.getTeamById( + teamId: testTeam1.id, + ); + final originalCreatedAt = originalTeam.createdAt; + + const newName = 'Brand New Team Name'; + await database.teamDao.updateTeamName( + teamId: testTeam1.id, + newName: newName, + ); + + final updatedTeam = await database.teamDao.getTeamById( + teamId: testTeam1.id, + ); + + expect(updatedTeam.name, newName); + expect(updatedTeam.id, testTeam1.id); + expect(updatedTeam.createdAt, originalCreatedAt); + }); + + // Verifies that team name can be updated to an empty string. + test('Updating team name to empty string works', () async { + await database.teamDao.addTeam(team: testTeam1); + + await database.teamDao.updateTeamName( + teamId: testTeam1.id, + newName: '', + ); + + final updatedTeam = await database.teamDao.getTeamById( + teamId: testTeam1.id, + ); + + expect(updatedTeam.name, ''); + }); + + // Verifies that team name can be updated to a very long string. + test('Updating team name to long string works', () async { + await database.teamDao.addTeam(team: testTeam1); + final longName = 'A' * 500; // 500 character name + + await database.teamDao.updateTeamName( + teamId: testTeam1.id, + newName: longName, + ); + + final updatedTeam = await database.teamDao.getTeamById( + teamId: testTeam1.id, + ); + + expect(updatedTeam.name, longName); + expect(updatedTeam.name.length, 500); + }); + + // Verifies that updating non-existent team name doesn't throw error. + test('Updating non-existent team name completes without error', () async { + expect( + () => database.teamDao.updateTeamName( + teamId: 'non-existent-id', + newName: 'New Name', + ), + returnsNormally, + ); + }); + + // Verifies that deleteTeam only affects the specified team. + test('Deleting one team does not affect other teams', () async { + await database.teamDao.addTeamsAsList( + teams: [testTeam1, testTeam2, testTeam3], + ); + + await database.teamDao.deleteTeam(teamId: testTeam2.id); + + final allTeams = await database.teamDao.getAllTeams(); + expect(allTeams.length, 2); + expect(allTeams.any((t) => t.id == testTeam1.id), true); + expect(allTeams.any((t) => t.id == testTeam2.id), false); + expect(allTeams.any((t) => t.id == testTeam3.id), true); + }); + + // Verifies that teams with overlapping members are independent. + test('Teams with overlapping members are independent', () async { + // Create two matches since player_match has primary key {playerId, matchId} + final match1 = Match(name: 'Match 1', game: testGame1, notes: ''); + final match2 = Match(name: 'Match 2', game: testGame2, notes: ''); + await database.matchDao.addMatch(match: match1); + await database.matchDao.addMatch(match: match2); + + // Add teams to database + await database.teamDao.addTeamsAsList( + teams: [testTeam1, testTeam3], + ); + + // Associate players with teams through match1 + // testTeam1: player1, player2 + await database.playerMatchDao.addPlayerToMatch( + playerId: testPlayer1.id, + matchId: match1.id, + teamId: testTeam1.id, + score: 0, + ); + await database.playerMatchDao.addPlayerToMatch( + playerId: testPlayer2.id, + matchId: match1.id, + teamId: testTeam1.id, + score: 0, + ); + + // Associate players with teams through match2 + // testTeam3: player1, player3 (overlapping player1) + await database.playerMatchDao.addPlayerToMatch( + playerId: testPlayer1.id, + matchId: match2.id, + teamId: testTeam3.id, + score: 0, + ); + await database.playerMatchDao.addPlayerToMatch( + playerId: testPlayer3.id, + matchId: match2.id, + teamId: testTeam3.id, + score: 0, + ); + + final team1 = await database.teamDao.getTeamById(teamId: testTeam1.id); + final team3 = await database.teamDao.getTeamById(teamId: testTeam3.id); + + expect(team1.members.length, 2); + expect(team3.members.length, 2); + expect(team1.members.any((p) => p.id == testPlayer1.id), true); + expect(team3.members.any((p) => p.id == testPlayer1.id), true); + }); + + // Verifies that adding teams sequentially works correctly. + test('Adding teams sequentially maintains correct count', () async { + var count = await database.teamDao.getTeamCount(); + expect(count, 0); + + await database.teamDao.addTeam(team: testTeam1); + count = await database.teamDao.getTeamCount(); + expect(count, 1); + + await database.teamDao.addTeam(team: testTeam2); + count = await database.teamDao.getTeamCount(); + expect(count, 2); + + await database.teamDao.addTeam(team: testTeam3); + count = await database.teamDao.getTeamCount(); + expect(count, 3); + }); + + // Verifies that getAllTeams returns all teams with correct data. + test('Getting all teams returns all teams with correct data', () async { + await database.teamDao.addTeamsAsList( + teams: [testTeam1, testTeam2, testTeam3], + ); + + final allTeams = await database.teamDao.getAllTeams(); + + expect(allTeams.length, 3); + expect( + allTeams.map((t) => t.id).toSet(), + {testTeam1.id, testTeam2.id, testTeam3.id}, + ); + }); + + // Verifies that teamExists returns false for deleted teams. + test('Team existence returns false after deletion', () async { + await database.teamDao.addTeam(team: testTeam1); + expect(await database.teamDao.teamExists(teamId: testTeam1.id), true); + + await database.teamDao.deleteTeam(teamId: testTeam1.id); + expect(await database.teamDao.teamExists(teamId: testTeam1.id), false); + }); + + // Verifies that adding multiple teams in batch then deleting returns correct count. + test('Batch add then partial delete maintains correct count', () async { + await database.teamDao.addTeamsAsList( + teams: [testTeam1, testTeam2, testTeam3], + ); + + expect(await database.teamDao.getTeamCount(), 3); + + await database.teamDao.deleteTeam(teamId: testTeam1.id); + expect(await database.teamDao.getTeamCount(), 2); + + await database.teamDao.deleteTeam(teamId: testTeam3.id); + expect(await database.teamDao.getTeamCount(), 1); + }); + + // Verifies that deleteAllTeams with single team works. + test('Deleting all teams with single team returns true', () async { + await database.teamDao.addTeam(team: testTeam1); + expect(await database.teamDao.getTeamCount(), 1); + + final deleted = await database.teamDao.deleteAllTeams(); + expect(deleted, true); + expect(await database.teamDao.getTeamCount(), 0); + }); + + // Verifies that addTeam after deleteAllTeams works correctly. + test('Adding team after deleteAllTeams works correctly', () async { + await database.teamDao.addTeamsAsList( + teams: [testTeam1, testTeam2], + ); + expect(await database.teamDao.getTeamCount(), 2); + + await database.teamDao.deleteAllTeams(); + expect(await database.teamDao.getTeamCount(), 0); + + final added = await database.teamDao.addTeam(team: testTeam3); + expect(added, true); + expect(await database.teamDao.getTeamCount(), 1); + + final fetchedTeam = await database.teamDao.getTeamById( + teamId: testTeam3.id, + ); + expect(fetchedTeam.name, testTeam3.name); + }); + + // Verifies that addTeamsAsList with partial duplicates ignores duplicates. + test('Adding teams with some duplicates ignores only duplicates', () async { + await database.teamDao.addTeam(team: testTeam1); + + final duplicateTeam1 = Team( + id: testTeam1.id, + name: 'Different Name', + members: [testPlayer3], + ); + + await database.teamDao.addTeamsAsList( + teams: [duplicateTeam1, testTeam2, testTeam3], + ); + + final allTeams = await database.teamDao.getAllTeams(); + expect(allTeams.length, 3); + + // Verify testTeam1 retained original name (was inserted first) + final team1 = await database.teamDao.getTeamById(teamId: testTeam1.id); + expect(team1.name, testTeam1.name); + }); + + // Verifies that team IDs are preserved correctly. + test('Team IDs are preserved through add and retrieve', () async { + await database.teamDao.addTeam(team: testTeam1); + + final fetchedTeam = await database.teamDao.getTeamById( + teamId: testTeam1.id, + ); + + expect(fetchedTeam.id, testTeam1.id); + }); + + // Verifies that createdAt timestamps are preserved. + test('Team createdAt timestamps are preserved', () async { + await database.teamDao.addTeam(team: testTeam1); + + final fetchedTeam = await database.teamDao.getTeamById( + teamId: testTeam1.id, + ); + + expect(fetchedTeam.createdAt, testTeam1.createdAt); + }); + }); +} \ No newline at end of file diff --git a/test/db_tests/entities/game_test.dart b/test/db_tests/entities/game_test.dart new file mode 100644 index 0000000..924a60b --- /dev/null +++ b/test/db_tests/entities/game_test.dart @@ -0,0 +1,542 @@ +import 'package:clock/clock.dart'; +import 'package:drift/drift.dart' hide isNull; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/game.dart'; + +void main() { + late AppDatabase database; + late Game testGame1; + late Game testGame2; + late Game testGame3; + final fixedDate = DateTime(2025, 11, 19, 00, 11, 23); + final fakeClock = Clock(() => fixedDate); + + setUp(() { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + closeStreamsSynchronously: true, + ), + ); + + withClock(fakeClock, () { + testGame1 = Game( + name: 'Chess', + ruleset: Ruleset.singleWinner, + description: 'A classic strategy game', + color: GameColor.blue, + icon: 'chess_icon', + ); + testGame2 = Game( + id: 'game2', + name: 'Poker', + ruleset: Ruleset.multipleWinners, + description: 'Card game with multiple winners', + color: GameColor.red, + icon: 'poker_icon', + ); + testGame3 = Game( + id: 'game3', + name: 'Monopoly', + ruleset: Ruleset.highestScore, + description: 'A board game about real estate', + color: GameColor.orange, + icon: '', + ); + }); + }); + + tearDown(() async { + await database.close(); + }); + + group('Game Tests', () { + + // Verifies that getAllGames returns an empty list when the database has no games. + test('getAllGames returns empty list when no games exist', () async { + final allGames = await database.gameDao.getAllGames(); + expect(allGames, isEmpty); + }); + + // Verifies that a single game can be added and retrieved with all fields intact. + test('Adding and fetching a single game works correctly', () async { + await database.gameDao.addGame(game: testGame1); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames.length, 1); + expect(allGames.first.id, testGame1.id); + expect(allGames.first.name, testGame1.name); + expect(allGames.first.ruleset, testGame1.ruleset); + expect(allGames.first.description, testGame1.description); + expect(allGames.first.color, testGame1.color); + expect(allGames.first.icon, testGame1.icon); + expect(allGames.first.createdAt, testGame1.createdAt); + }); + + // Verifies that multiple games can be added and retrieved correctly. + test('Adding and fetching multiple games works correctly', () async { + await database.gameDao.addGame(game: testGame1); + await database.gameDao.addGame(game: testGame2); + await database.gameDao.addGame(game: testGame3); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames.length, 3); + + final names = allGames.map((g) => g.name).toList(); + expect(names, containsAll(['Chess', 'Poker', 'Monopoly'])); + }); + + // Verifies that getGameById returns the correct game with all properties. + test('getGameById returns correct game', () async { + await database.gameDao.addGame(game: testGame1); + await database.gameDao.addGame(game: testGame2); + + final game = await database.gameDao.getGameById(gameId: testGame2.id); + expect(game.id, testGame2.id); + expect(game.name, testGame2.name); + expect(game.ruleset, testGame2.ruleset); + expect(game.description, testGame2.description); + expect(game.color, testGame2.color); + expect(game.icon, testGame2.icon); + }); + + // Verifies that getGameById throws a StateError when the game doesn't exist. + test('getGameById throws exception for non-existent game', () async { + expect( + () => database.gameDao.getGameById(gameId: 'non-existent-id'), + throwsA(isA()), + ); + }); + + // Verifies that addGame returns true when a game is successfully added. + test('addGame returns true when game is added successfully', () async { + final result = await database.gameDao.addGame(game: testGame1); + expect(result, true); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames.length, 1); + }); + + // Verifies that addGame returns false when trying to add a duplicate game. + test('addGame returns false when game already exists', () async { + final firstAdd = await database.gameDao.addGame(game: testGame1); + expect(firstAdd, true); + + final secondAdd = await database.gameDao.addGame(game: testGame1); + expect(secondAdd, false); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames.length, 1); + }); + + // Verifies that a game with empty optional fields can be added and retrieved. + test('addGame handles game with null optional fields', () async { + final gameWithNulls = Game(name: 'Simple Game', ruleset: Ruleset.lowestScore, description: 'A simple game', color: GameColor.green, icon: ''); + final result = await database.gameDao.addGame(game: gameWithNulls); + expect(result, true); + + final fetchedGame = await database.gameDao.getGameById( + gameId: gameWithNulls.id, + ); + expect(fetchedGame.name, 'Simple Game'); + expect(fetchedGame.description, 'A simple game'); + expect(fetchedGame.color, GameColor.green); + expect(fetchedGame.icon, ''); + }); + + // Verifies that multiple games can be added at once using addGamesAsList. + test('addGamesAsList adds multiple games correctly', () async { + final result = await database.gameDao.addGamesAsList( + games: [testGame1, testGame2, testGame3], + ); + expect(result, true); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames.length, 3); + }); + + // Verifies that addGamesAsList returns false when given an empty list. + test('addGamesAsList returns false for empty list', () async { + final result = await database.gameDao.addGamesAsList(games: []); + expect(result, false); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames.length, 0); + }); + + // Verifies that addGamesAsList ignores duplicate games when adding. + test('addGamesAsList ignores duplicate games', () async { + await database.gameDao.addGame(game: testGame1); + + final result = await database.gameDao.addGamesAsList( + games: [testGame1, testGame2], + ); + expect(result, true); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames.length, 2); + }); + + // Verifies that deleteGame returns true and removes the game from database. + test('deleteGame returns true when game is deleted', () async { + await database.gameDao.addGame(game: testGame1); + + final result = await database.gameDao.deleteGame(gameId: testGame1.id); + expect(result, true); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames, isEmpty); + }); + + // Verifies that deleteGame returns false for a non-existent game ID. + test('deleteGame returns false for non-existent game', () async { + final result = await database.gameDao.deleteGame( + gameId: 'non-existent-id', + ); + expect(result, false); + }); + + // Verifies that deleteGame only removes the specified game, leaving others intact. + test('deleteGame only deletes the specified game', () async { + await database.gameDao.addGamesAsList( + games: [testGame1, testGame2, testGame3], + ); + + await database.gameDao.deleteGame(gameId: testGame2.id); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames.length, 2); + expect(allGames.any((g) => g.id == testGame2.id), false); + expect(allGames.any((g) => g.id == testGame1.id), true); + expect(allGames.any((g) => g.id == testGame3.id), true); + }); + + // Verifies that gameExists returns true when the game exists in database. + test('gameExists returns true for existing game', () async { + await database.gameDao.addGame(game: testGame1); + + final exists = await database.gameDao.gameExists(gameId: testGame1.id); + expect(exists, true); + }); + + // Verifies that gameExists returns false for a non-existent game ID. + test('gameExists returns false for non-existent game', () async { + final exists = await database.gameDao.gameExists( + gameId: 'non-existent-id', + ); + expect(exists, false); + }); + + // Verifies that gameExists returns false after a game has been deleted. + test('gameExists returns false after game is deleted', () async { + await database.gameDao.addGame(game: testGame1); + await database.gameDao.deleteGame(gameId: testGame1.id); + + final exists = await database.gameDao.gameExists(gameId: testGame1.id); + expect(exists, false); + }); + + // Verifies that updateGameName correctly updates only the name field. + test('updateGameName updates the name correctly', () async { + await database.gameDao.addGame(game: testGame1); + + await database.gameDao.updateGameName( + gameId: testGame1.id, + newName: 'Updated Chess', + ); + + final updatedGame = await database.gameDao.getGameById( + gameId: testGame1.id, + ); + expect(updatedGame.name, 'Updated Chess'); + expect(updatedGame.ruleset, testGame1.ruleset); + }); + + // Verifies that updateGameName does nothing when game doesn't exist. + test('updateGameName does nothing for non-existent game', () async { + await database.gameDao.updateGameName( + gameId: 'non-existent-id', + newName: 'New Name', + ); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames, isEmpty); + }); + + // Verifies that updateGameRuleset correctly updates only the ruleset field. + test('updateGameRuleset updates the ruleset correctly', () async { + await database.gameDao.addGame(game: testGame1); + + await database.gameDao.updateGameRuleset( + gameId: testGame1.id, + newRuleset: Ruleset.highestScore, + ); + + final updatedGame = await database.gameDao.getGameById( + gameId: testGame1.id, + ); + expect(updatedGame.ruleset, Ruleset.highestScore); + expect(updatedGame.name, testGame1.name); + }); + + // Verifies that updateGameRuleset does nothing when game doesn't exist. + test('updateGameRuleset does nothing for non-existent game', () async { + await database.gameDao.updateGameRuleset( + gameId: 'non-existent-id', + newRuleset: Ruleset.lowestScore, + ); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames, isEmpty); + }); + + // Verifies that updateGameDescription correctly updates the description. + test('updateGameDescription updates the description correctly', () async { + await database.gameDao.addGame(game: testGame1); + + await database.gameDao.updateGameDescription( + gameId: testGame1.id, + newDescription: 'An updated description', + ); + + final updatedGame = await database.gameDao.getGameById( + gameId: testGame1.id, + ); + expect(updatedGame.description, 'An updated description'); + }); + + // Verifies that updateGameDescription can set the description to an empty string. + test('updateGameDescription can set description to empty string', () async { + await database.gameDao.addGame(game: testGame1); + + await database.gameDao.updateGameDescription( + gameId: testGame1.id, + newDescription: '', + ); + + final updatedGame = await database.gameDao.getGameById( + gameId: testGame1.id, + ); + expect(updatedGame.description, ''); + }); + + // Verifies that updateGameDescription does nothing when game doesn't exist. + test('updateGameDescription does nothing for non-existent game', () async { + await database.gameDao.updateGameDescription( + gameId: 'non-existent-id', + newDescription: 'New Description', + ); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames, isEmpty); + }); + + // Verifies that updateGameColor correctly updates the color value. + test('updateGameColor updates the color correctly', () async { + await database.gameDao.addGame(game: testGame1); + + await database.gameDao.updateGameColor( + gameId: testGame1.id, + newColor: GameColor.green, + ); + + final updatedGame = await database.gameDao.getGameById( + gameId: testGame1.id, + ); + expect(updatedGame.color, GameColor.green); + }); + + // Verifies that updateGameColor does nothing when game doesn't exist. + test('updateGameColor does nothing for non-existent game', () async { + await database.gameDao.updateGameColor( + gameId: 'non-existent-id', + newColor: GameColor.green, + ); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames, isEmpty); + }); + + // Verifies that updateGameIcon correctly updates the icon value. + test('updateGameIcon updates the icon correctly', () async { + await database.gameDao.addGame(game: testGame1); + + await database.gameDao.updateGameIcon( + gameId: testGame1.id, + newIcon: 'new_chess_icon', + ); + + final updatedGame = await database.gameDao.getGameById( + gameId: testGame1.id, + ); + expect(updatedGame.icon, 'new_chess_icon'); + }); + + // Verifies that updateGameIcon can update the icon. + test('updateGameIcon updates icon correctly', () async { + await database.gameDao.addGame(game: testGame1); + + await database.gameDao.updateGameIcon( + gameId: testGame1.id, + newIcon: 'new_icon', + ); + + final updatedGame = await database.gameDao.getGameById( + gameId: testGame1.id, + ); + expect(updatedGame.icon, 'new_icon'); + }); + + // Verifies that updateGameIcon does nothing when game doesn't exist. + test('updateGameIcon does nothing for non-existent game', () async { + await database.gameDao.updateGameIcon( + gameId: 'non-existent-id', + newIcon: 'some_icon', + ); + + final allGames = await database.gameDao.getAllGames(); + expect(allGames, isEmpty); + }); + + // Verifies that getGameCount returns 0 when no games exist. + test('getGameCount returns 0 when no games exist', () async { + final count = await database.gameDao.getGameCount(); + expect(count, 0); + }); + + // Verifies that getGameCount returns the correct count after adding games. + test('getGameCount returns correct count after adding games', () async { + await database.gameDao.addGamesAsList( + games: [testGame1, testGame2, testGame3], + ); + + final count = await database.gameDao.getGameCount(); + expect(count, 3); + }); + + // Verifies that getGameCount updates correctly after deleting a game. + test('getGameCount updates correctly after deletion', () async { + await database.gameDao.addGamesAsList( + games: [testGame1, testGame2], + ); + + final countBefore = await database.gameDao.getGameCount(); + expect(countBefore, 2); + + await database.gameDao.deleteGame(gameId: testGame1.id); + + final countAfter = await database.gameDao.getGameCount(); + expect(countAfter, 1); + }); + + // Verifies that deleteAllGames removes all games from the database. + test('deleteAllGames removes all games', () async { + await database.gameDao.addGamesAsList( + games: [testGame1, testGame2, testGame3], + ); + + final countBefore = await database.gameDao.getGameCount(); + expect(countBefore, 3); + + final result = await database.gameDao.deleteAllGames(); + expect(result, true); + + final countAfter = await database.gameDao.getGameCount(); + expect(countAfter, 0); + }); + + // Verifies that deleteAllGames returns false when no games exist. + test('deleteAllGames returns false when no games exist', () async { + final result = await database.gameDao.deleteAllGames(); + expect(result, false); + }); + + // Verifies that games with special characters (quotes, emojis) are stored correctly. + test('Game with special characters in name is stored correctly', () async { + final specialGame = Game( + name: 'Game\'s & "Special" ', + ruleset: Ruleset.multipleWinners, + description: 'Description with émojis 🎮🎲', + color: GameColor.purple, + icon: '', + ); + await database.gameDao.addGame(game: specialGame); + + final fetchedGame = await database.gameDao.getGameById( + gameId: specialGame.id, + ); + expect(fetchedGame.name, 'Game\'s & "Special" '); + expect(fetchedGame.description, 'Description with émojis 🎮🎲'); + }); + + // Verifies that games with empty string fields are stored and retrieved correctly. + test('Game with empty string fields is stored correctly', () async { + final emptyGame = Game( + name: '', + ruleset: Ruleset.singleWinner, + description: '', + icon: '', + color: GameColor.red, + ); + await database.gameDao.addGame(game: emptyGame); + + final fetchedGame = await database.gameDao.getGameById( + gameId: emptyGame.id, + ); + expect(fetchedGame.name, ''); + expect(fetchedGame.ruleset, Ruleset.singleWinner); + expect(fetchedGame.description, ''); + expect(fetchedGame.icon, ''); + }); + + // Verifies that games with very long strings (10000 chars) are handled correctly. + test('Game with very long strings is stored correctly', () async { + final longString = 'A' * 10000; + final longGame = Game( + name: longString, + description: longString, + ruleset: Ruleset.multipleWinners, + color: GameColor.yellow, + icon: '', + ); + await database.gameDao.addGame(game: longGame); + + final fetchedGame = await database.gameDao.getGameById( + gameId: longGame.id, + ); + expect(fetchedGame.name.length, 10000); + expect(fetchedGame.description.length, 10000); + expect(fetchedGame.ruleset, Ruleset.multipleWinners); + }); + + // Verifies that multiple sequential updates to the same game work correctly. + test('Multiple updates to the same game work correctly', () async { + await database.gameDao.addGame(game: testGame1); + + await database.gameDao.updateGameName( + gameId: testGame1.id, + newName: 'Updated Name', + ); + await database.gameDao.updateGameColor( + gameId: testGame1.id, + newColor: GameColor.teal, + ); + await database.gameDao.updateGameDescription( + gameId: testGame1.id, + newDescription: 'Updated Description', + ); + + final updatedGame = await database.gameDao.getGameById( + gameId: testGame1.id, + ); + expect(updatedGame.name, 'Updated Name'); + expect(updatedGame.color, GameColor.teal); + expect(updatedGame.description, 'Updated Description'); + expect(updatedGame.ruleset, testGame1.ruleset); + expect(updatedGame.icon, testGame1.icon); + }); + }); +} diff --git a/test/db_tests/entities/player_test.dart b/test/db_tests/entities/player_test.dart new file mode 100644 index 0000000..9d7fa77 --- /dev/null +++ b/test/db_tests/entities/player_test.dart @@ -0,0 +1,377 @@ +import 'package:clock/clock.dart'; +import 'package:drift/drift.dart' hide isNull; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/player.dart'; + +void main() { + late AppDatabase database; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; + final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); + final fakeClock = Clock(() => fixedDate); + + setUp(() { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + withClock(fakeClock, () { + testPlayer1 = Player(name: 'Test Player', description: ''); + testPlayer2 = Player(name: 'Second Player', description: ''); + testPlayer3 = Player(name: 'Charlie', description: ''); + testPlayer4 = Player(name: 'Diana', description: ''); + }); + }); + tearDown(() async { + await database.close(); + }); + + group('Player Tests', () { + + // Verifies that players can be added and retrieved with all fields intact. + test('Adding and fetching single player works correctly', () async { + await database.playerDao.addPlayer(player: testPlayer1); + await database.playerDao.addPlayer(player: testPlayer2); + + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers.length, 2); + + final fetchedPlayer1 = allPlayers.firstWhere( + (g) => g.id == testPlayer1.id, + ); + expect(fetchedPlayer1.name, testPlayer1.name); + expect(fetchedPlayer1.createdAt, testPlayer1.createdAt); + + final fetchedPlayer2 = allPlayers.firstWhere( + (g) => g.id == testPlayer2.id, + ); + expect(fetchedPlayer2.name, testPlayer2.name); + expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); + }); + + // Verifies that multiple players can be added at once and retrieved correctly. + test('Adding and fetching multiple players works correctly', () async { + await database.playerDao.addPlayersAsList( + players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], + ); + + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers.length, 4); + + // Map for connecting fetched players with expected players + final testPlayers = { + testPlayer1.id: testPlayer1, + testPlayer2.id: testPlayer2, + testPlayer3.id: testPlayer3, + testPlayer4.id: testPlayer4, + }; + + for (final player in allPlayers) { + final testPlayer = testPlayers[player.id]!; + + expect(player.id, testPlayer.id); + expect(player.name, testPlayer.name); + expect(player.createdAt, testPlayer.createdAt); + } + }); + + // Verifies that adding the same player twice does not create duplicates. + test('Adding the same player twice does not create duplicates', () async { + await database.playerDao.addPlayer(player: testPlayer1); + await database.playerDao.addPlayer(player: testPlayer1); + + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers.length, 1); + }); + + // Verifies that playerExists returns correct boolean based on player presence. + test('Player existence check works correctly', () async { + var playerExists = await database.playerDao.playerExists( + playerId: testPlayer1.id, + ); + expect(playerExists, false); + + await database.playerDao.addPlayer(player: testPlayer1); + + playerExists = await database.playerDao.playerExists( + playerId: testPlayer1.id, + ); + expect(playerExists, true); + }); + + // Verifies that deletePlayer removes the player and returns true. + test('Deleting a player works correctly', () async { + await database.playerDao.addPlayer(player: testPlayer1); + final playerDeleted = await database.playerDao.deletePlayer( + playerId: testPlayer1.id, + ); + expect(playerDeleted, true); + + final playerExists = await database.playerDao.playerExists( + playerId: testPlayer1.id, + ); + expect(playerExists, false); + }); + + // Verifies that updatePlayerName correctly updates only the name field. + test('Updating a player name works correctly', () async { + await database.playerDao.addPlayer(player: testPlayer1); + + const newPlayerName = 'new player name'; + + await database.playerDao.updatePlayerName( + playerId: testPlayer1.id, + newName: newPlayerName, + ); + + final result = await database.playerDao.getPlayerById( + playerId: testPlayer1.id, + ); + expect(result.name, newPlayerName); + }); + + // Verifies that getPlayerCount returns correct count through add/delete operations. + test('Getting the player count works correctly', () async { + var playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 0); + + await database.playerDao.addPlayer(player: testPlayer1); + + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 1); + + await database.playerDao.addPlayer(player: testPlayer2); + + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 2); + + await database.playerDao.deletePlayer(playerId: testPlayer1.id); + + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 1); + + await database.playerDao.deletePlayer(playerId: testPlayer2.id); + + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 0); + }); + + // Verifies that getAllPlayers returns an empty list when no players exist. + test('getAllPlayers returns empty list when no players exist', () async { + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers, isEmpty); + }); + + // Verifies that getPlayerById returns the correct player. + test('getPlayerById returns correct player', () async { + await database.playerDao.addPlayer(player: testPlayer1); + await database.playerDao.addPlayer(player: testPlayer2); + + final fetchedPlayer = await database.playerDao.getPlayerById( + playerId: testPlayer1.id, + ); + + expect(fetchedPlayer.id, testPlayer1.id); + expect(fetchedPlayer.name, testPlayer1.name); + expect(fetchedPlayer.createdAt, testPlayer1.createdAt); + expect(fetchedPlayer.description, testPlayer1.description); + }); + + // Verifies that getPlayerById throws StateError for non-existent player ID. + test('getPlayerById throws exception for non-existent player', () async { + expect( + () => database.playerDao.getPlayerById(playerId: 'non-existent-id'), + throwsA(isA()), + ); + }); + + // Verifies that addPlayer returns false when trying to add a duplicate player. + test('addPlayer returns false when player already exists', () async { + final firstAdd = await database.playerDao.addPlayer(player: testPlayer1); + expect(firstAdd, true); + + final secondAdd = await database.playerDao.addPlayer(player: testPlayer1); + expect(secondAdd, false); + }); + + // Verifies that addPlayersAsList handles empty list correctly. + test('addPlayersAsList handles empty list correctly', () async { + final result = await database.playerDao.addPlayersAsList(players: []); + expect(result, false); + + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers, isEmpty); + }); + + // Verifies that addPlayersAsList ignores duplicate player IDs. + test('addPlayersAsList with duplicate IDs ignores duplicates', () async { + await database.playerDao.addPlayersAsList( + players: [testPlayer1, testPlayer1, testPlayer2], + ); + + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers.length, 2); + }); + + // Verifies that deletePlayer returns false for non-existent player. + test('deletePlayer returns false for non-existent player', () async { + final result = await database.playerDao.deletePlayer( + playerId: 'non-existent-id', + ); + expect(result, false); + }); + + // Verifies that updatePlayerName does nothing for non-existent player (no exception). + test('updatePlayerName does nothing for non-existent player', () async { + // Should not throw, just do nothing + await database.playerDao.updatePlayerName( + playerId: 'non-existent-id', + newName: 'New Name', + ); + + final allPlayers = await database.playerDao.getAllPlayers(); + expect(allPlayers, isEmpty); + }); + + // Verifies that deleteAllPlayers removes all players. + test('deleteAllPlayers removes all players', () async { + await database.playerDao.addPlayersAsList( + players: [testPlayer1, testPlayer2, testPlayer3], + ); + + var playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 3); + + final result = await database.playerDao.deleteAllPlayers(); + expect(result, true); + + playerCount = await database.playerDao.getPlayerCount(); + expect(playerCount, 0); + }); + + // Verifies that deleteAllPlayers returns false when no players exist. + test('deleteAllPlayers returns false when no players exist', () async { + final result = await database.playerDao.deleteAllPlayers(); + expect(result, false); + }); + + // Verifies that a player with special characters in name is stored correctly. + test('Player with special characters in name is stored correctly', () async { + final specialPlayer = Player(name: 'Test!@#\$%^&*()_+-=[]{}|;\':",.<>?/`~', description: ''); + + await database.playerDao.addPlayer(player: specialPlayer); + + final fetchedPlayer = await database.playerDao.getPlayerById( + playerId: specialPlayer.id, + ); + expect(fetchedPlayer.name, specialPlayer.name); + }); + + // Verifies that a player with description is stored correctly. + test('Player with description is stored correctly', () async { + final playerWithDescription = Player( + name: 'Described Player', + description: 'This is a test description', + ); + + await database.playerDao.addPlayer(player: playerWithDescription); + + final fetchedPlayer = await database.playerDao.getPlayerById( + playerId: playerWithDescription.id, + ); + expect(fetchedPlayer.name, playerWithDescription.name); + expect(fetchedPlayer.description, playerWithDescription.description); + }); + + // Verifies that a player with null description is stored correctly. + test('Player with null description is stored correctly', () async { + final playerWithoutDescription = Player(name: 'No Description Player', description: ''); + + await database.playerDao.addPlayer(player: playerWithoutDescription); + + final fetchedPlayer = await database.playerDao.getPlayerById( + playerId: playerWithoutDescription.id, + ); + expect(fetchedPlayer.description, ''); + }); + + // Verifies that multiple updates to the same player work correctly. + test('Multiple updates to the same player work correctly', () async { + await database.playerDao.addPlayer(player: testPlayer1); + + await database.playerDao.updatePlayerName( + playerId: testPlayer1.id, + newName: 'First Update', + ); + + var fetchedPlayer = await database.playerDao.getPlayerById( + playerId: testPlayer1.id, + ); + expect(fetchedPlayer.name, 'First Update'); + + await database.playerDao.updatePlayerName( + playerId: testPlayer1.id, + newName: 'Second Update', + ); + + fetchedPlayer = await database.playerDao.getPlayerById( + playerId: testPlayer1.id, + ); + expect(fetchedPlayer.name, 'Second Update'); + + await database.playerDao.updatePlayerName( + playerId: testPlayer1.id, + newName: 'Third Update', + ); + + fetchedPlayer = await database.playerDao.getPlayerById( + playerId: testPlayer1.id, + ); + expect(fetchedPlayer.name, 'Third Update'); + }); + + // Verifies that a player with empty string name is stored correctly. + test('Player with empty string name is stored correctly', () async { + final emptyNamePlayer = Player(name: '', description: ''); + + await database.playerDao.addPlayer(player: emptyNamePlayer); + + final fetchedPlayer = await database.playerDao.getPlayerById( + playerId: emptyNamePlayer.id, + ); + expect(fetchedPlayer.name, ''); + }); + + // Verifies that a player with very long name is stored correctly. + test('Player with very long name is stored correctly', () async { + final longName = 'A' * 1000; + final longNamePlayer = Player(name: longName, description: ''); + + await database.playerDao.addPlayer(player: longNamePlayer); + + final fetchedPlayer = await database.playerDao.getPlayerById( + playerId: longNamePlayer.id, + ); + expect(fetchedPlayer.name, longName); + }); + + // Verifies that addPlayer returns true on first add. + test('addPlayer returns true when player is added successfully', () async { + final result = await database.playerDao.addPlayer(player: testPlayer1); + expect(result, true); + + final playerExists = await database.playerDao.playerExists( + playerId: testPlayer1.id, + ); + expect(playerExists, true); + }); + }); +} diff --git a/test/db_tests/group_match_test.dart b/test/db_tests/group_match_test.dart deleted file mode 100644 index 7d812bd..0000000 --- a/test/db_tests/group_match_test.dart +++ /dev/null @@ -1,221 +0,0 @@ -import 'package:clock/clock.dart'; -import 'package:drift/drift.dart' hide isNotNull; -import 'package:drift/native.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/match.dart'; -import 'package:game_tracker/data/dto/player.dart'; - -void main() { - late AppDatabase database; - late Player testPlayer1; - late Player testPlayer2; - late Player testPlayer3; - late Player testPlayer4; - late Player testPlayer5; - late Group testGroup1; - late Group testGroup2; - late Match testMatchWithGroup; - late Match testMatchWithPlayers; - final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); - final fakeClock = Clock(() => fixedDate); - - setUp(() async { - database = AppDatabase( - DatabaseConnection( - NativeDatabase.memory(), - // Recommended for widget tests to avoid test errors. - closeStreamsSynchronously: true, - ), - ); - - withClock(fakeClock, () { - testPlayer1 = Player(name: 'Alice'); - testPlayer2 = Player(name: 'Bob'); - testPlayer3 = Player(name: 'Charlie'); - testPlayer4 = Player(name: 'Diana'); - testPlayer5 = Player(name: 'Eve'); - testGroup1 = Group( - name: 'Test Group', - members: [testPlayer1, testPlayer2, testPlayer3], - ); - testGroup2 = Group( - name: 'Test Group', - members: [testPlayer3, testPlayer2], - ); - testMatchWithPlayers = Match( - name: 'Test Match with Players', - players: [testPlayer4, testPlayer5], - ); - testMatchWithGroup = Match( - name: 'Test Match with Group', - group: testGroup1, - ); - }); - await database.playerDao.addPlayersAsList( - players: [ - testPlayer1, - testPlayer2, - testPlayer3, - testPlayer4, - testPlayer5, - ], - ); - await database.groupDao.addGroupsAsList(groups: [testGroup1, testGroup2]); - }); - tearDown(() async { - await database.close(); - }); - group('Group-Match Tests', () { - test('matchHasGroup() has group works correctly', () async { - await database.matchDao.addMatch(match: testMatchWithPlayers); - await database.groupDao.addGroup(group: testGroup1); - - var matchHasGroup = await database.groupMatchDao.matchHasGroup( - matchId: testMatchWithPlayers.id, - ); - - expect(matchHasGroup, false); - - await database.groupMatchDao.addGroupToMatch( - matchId: testMatchWithPlayers.id, - groupId: testGroup1.id, - ); - - matchHasGroup = await database.groupMatchDao.matchHasGroup( - matchId: testMatchWithPlayers.id, - ); - - expect(matchHasGroup, true); - }); - - test('Adding a group to a match works correctly', () async { - await database.matchDao.addMatch(match: testMatchWithPlayers); - await database.groupDao.addGroup(group: testGroup1); - await database.groupMatchDao.addGroupToMatch( - matchId: testMatchWithPlayers.id, - groupId: testGroup1.id, - ); - - var groupAdded = await database.groupMatchDao.isGroupInMatch( - matchId: testMatchWithPlayers.id, - groupId: testGroup1.id, - ); - expect(groupAdded, true); - - groupAdded = await database.groupMatchDao.isGroupInMatch( - matchId: testMatchWithPlayers.id, - groupId: '', - ); - expect(groupAdded, false); - }); - - test('Removing group from match works correctly', () async { - await database.matchDao.addMatch(match: testMatchWithGroup); - - final groupToRemove = testMatchWithGroup.group!; - - final removed = await database.groupMatchDao.removeGroupFromMatch( - groupId: groupToRemove.id, - matchId: testMatchWithGroup.id, - ); - expect(removed, true); - - final result = await database.matchDao.getMatchById( - matchId: testMatchWithGroup.id, - ); - expect(result.group, null); - }); - - test('Retrieving group of a match works correctly', () async { - await database.matchDao.addMatch(match: testMatchWithGroup); - final group = await database.groupMatchDao.getGroupOfMatch( - matchId: testMatchWithGroup.id, - ); - - if (group == null) { - fail('Group should not be null'); - } - - expect(group.id, testGroup1.id); - expect(group.name, testGroup1.name); - expect(group.createdAt, testGroup1.createdAt); - expect(group.members.length, testGroup1.members.length); - for (int i = 0; i < group.members.length; i++) { - expect(group.members[i].id, testGroup1.members[i].id); - expect(group.members[i].name, testGroup1.members[i].name); - expect(group.members[i].createdAt, testGroup1.members[i].createdAt); - } - }); - - test('Updating the group of a match works correctly', () async { - await database.matchDao.addMatch(match: testMatchWithGroup); - - var group = await database.groupMatchDao.getGroupOfMatch( - matchId: testMatchWithGroup.id, - ); - - if (group == null) { - fail('Initial group should not be null'); - } else { - expect(group.id, testGroup1.id); - expect(group.name, testGroup1.name); - expect(group.createdAt, testGroup1.createdAt); - expect(group.members.length, testGroup1.members.length); - } - - await database.groupDao.addGroup(group: testGroup2); - await database.groupMatchDao.updateGroupOfMatch( - matchId: testMatchWithGroup.id, - newGroupId: testGroup2.id, - ); - - group = await database.groupMatchDao.getGroupOfMatch( - matchId: testMatchWithGroup.id, - ); - - if (group == null) { - fail('Updated group should not be null'); - } else { - expect(group.id, testGroup2.id); - expect(group.name, testGroup2.name); - expect(group.createdAt, testGroup2.createdAt); - expect(group.members.length, testGroup2.members.length); - for (int i = 0; i < group.members.length; i++) { - expect(group.members[i].id, testGroup2.members[i].id); - expect(group.members[i].name, testGroup2.members[i].name); - expect(group.members[i].createdAt, testGroup2.members[i].createdAt); - } - } - }); - - test('Adding the same group to seperate matches works correctly', () async { - final match1 = Match(name: 'Match 1', group: testGroup1); - final match2 = Match(name: 'Match 2', group: testGroup1); - - await Future.wait([ - database.matchDao.addMatch(match: match1), - database.matchDao.addMatch(match: match2), - ]); - - final group1 = await database.groupMatchDao.getGroupOfMatch( - matchId: match1.id, - ); - final group2 = await database.groupMatchDao.getGroupOfMatch( - matchId: match2.id, - ); - - expect(group1, isNotNull); - expect(group2, isNotNull); - - final groups = [group1!, group2!]; - for (final group in groups) { - expect(group.members.length, testGroup1.members.length); - expect(group.id, testGroup1.id); - expect(group.name, testGroup1.name); - expect(group.createdAt, testGroup1.createdAt); - } - }); - }); -} diff --git a/test/db_tests/group_test.dart b/test/db_tests/group_test.dart deleted file mode 100644 index 5104c65..0000000 --- a/test/db_tests/group_test.dart +++ /dev/null @@ -1,177 +0,0 @@ -import 'package:clock/clock.dart'; -import 'package:drift/drift.dart'; -import 'package:drift/native.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/player.dart'; - -void main() { - late AppDatabase database; - late Player testPlayer1; - late Player testPlayer2; - late Player testPlayer3; - late Player testPlayer4; - late Group testGroup1; - late Group testGroup2; - late Group testGroup3; - late Group testGroup4; - final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); - final fakeClock = Clock(() => fixedDate); - - setUp(() { - database = AppDatabase( - DatabaseConnection( - NativeDatabase.memory(), - // Recommended for widget tests to avoid test errors. - closeStreamsSynchronously: true, - ), - ); - - withClock(fakeClock, () { - testPlayer1 = Player(name: 'Alice'); - testPlayer2 = Player(name: 'Bob'); - testPlayer3 = Player(name: 'Charlie'); - testPlayer4 = Player(name: 'Diana'); - testGroup1 = Group( - name: 'Test Group', - members: [testPlayer1, testPlayer2, testPlayer3], - ); - testGroup2 = Group( - id: 'gr2', - name: 'Second Group', - members: [testPlayer2, testPlayer3, testPlayer4], - ); - testGroup3 = Group( - id: 'gr2', - name: 'Second Group', - members: [testPlayer2, testPlayer4], - ); - testGroup4 = Group( - id: 'gr2', - name: 'Second Group', - members: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], - ); - }); - }); - tearDown(() async { - await database.close(); - }); - group('Group Tests', () { - test('Adding and fetching a single group works correctly', () async { - await database.groupDao.addGroup(group: testGroup1); - - final fetchedGroup = await database.groupDao.getGroupById( - groupId: testGroup1.id, - ); - - expect(fetchedGroup.id, testGroup1.id); - expect(fetchedGroup.name, testGroup1.name); - expect(fetchedGroup.createdAt, testGroup1.createdAt); - - expect(fetchedGroup.members.length, testGroup1.members.length); - for (int i = 0; i < testGroup1.members.length; i++) { - expect(fetchedGroup.members[i].id, testGroup1.members[i].id); - expect(fetchedGroup.members[i].name, testGroup1.members[i].name); - expect( - fetchedGroup.members[i].createdAt, - testGroup1.members[i].createdAt, - ); - } - }); - - test('Adding and fetching multiple groups works correctly', () async { - await database.groupDao.addGroupsAsList( - groups: [testGroup1, testGroup2, testGroup3, testGroup4], - ); - - final allGroups = await database.groupDao.getAllGroups(); - expect(allGroups.length, 2); - - final testGroups = {testGroup1.id: testGroup1, testGroup2.id: testGroup2}; - - for (final group in allGroups) { - final testGroup = testGroups[group.id]!; - - expect(group.id, testGroup.id); - expect(group.name, testGroup.name); - expect(group.createdAt, testGroup.createdAt); - - expect(group.members.length, testGroup.members.length); - for (int i = 0; i < testGroup.members.length; i++) { - expect(group.members[i].id, testGroup.members[i].id); - expect(group.members[i].name, testGroup.members[i].name); - expect(group.members[i].createdAt, testGroup.members[i].createdAt); - } - } - }); - - test('Adding the same group twice does not create duplicates', () async { - await database.groupDao.addGroup(group: testGroup1); - await database.groupDao.addGroup(group: testGroup1); - - final allGroups = await database.groupDao.getAllGroups(); - expect(allGroups.length, 1); - }); - - test('Group existence check works correctly', () async { - var groupExists = await database.groupDao.groupExists( - groupId: testGroup1.id, - ); - expect(groupExists, false); - - await database.groupDao.addGroup(group: testGroup1); - - groupExists = await database.groupDao.groupExists(groupId: testGroup1.id); - expect(groupExists, true); - }); - - test('Deleting a group works correctly', () async { - await database.groupDao.addGroup(group: testGroup1); - - final groupDeleted = await database.groupDao.deleteGroup( - groupId: testGroup1.id, - ); - expect(groupDeleted, true); - - final groupExists = await database.groupDao.groupExists( - groupId: testGroup1.id, - ); - expect(groupExists, false); - }); - - test('Updating a group name works correcly', () async { - await database.groupDao.addGroup(group: testGroup1); - - const newGroupName = 'new group name'; - - await database.groupDao.updateGroupname( - groupId: testGroup1.id, - newName: newGroupName, - ); - - final result = await database.groupDao.getGroupById( - groupId: testGroup1.id, - ); - expect(result.name, newGroupName); - }); - - test('Getting the group count works correctly', () async { - final initialCount = await database.groupDao.getGroupCount(); - expect(initialCount, 0); - - await database.groupDao.addGroup(group: testGroup1); - - final groupAdded = await database.groupDao.getGroupCount(); - expect(groupAdded, 1); - - final groupRemoved = await database.groupDao.deleteGroup( - groupId: testGroup1.id, - ); - expect(groupRemoved, true); - - final finalCount = await database.groupDao.getGroupCount(); - expect(finalCount, 0); - }); - }); -} diff --git a/test/db_tests/player_group_test.dart b/test/db_tests/player_group_test.dart deleted file mode 100644 index 2783430..0000000 --- a/test/db_tests/player_group_test.dart +++ /dev/null @@ -1,103 +0,0 @@ -import 'package:clock/clock.dart'; -import 'package:drift/drift.dart'; -import 'package:drift/native.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/player.dart'; - -void main() { - late AppDatabase database; - late Player testPlayer1; - late Player testPlayer2; - late Player testPlayer3; - late Player testPlayer4; - late Group testgroup; - final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); - final fakeClock = Clock(() => fixedDate); - - setUp(() { - database = AppDatabase( - DatabaseConnection( - NativeDatabase.memory(), - // Recommended for widget tests to avoid test errors. - closeStreamsSynchronously: true, - ), - ); - - withClock(fakeClock, () { - testPlayer1 = Player(name: 'Alice'); - testPlayer2 = Player(name: 'Bob'); - testPlayer3 = Player(name: 'Charlie'); - testPlayer4 = Player(name: 'Diana'); - testgroup = Group( - name: 'Test Group', - members: [testPlayer1, testPlayer2, testPlayer3], - ); - }); - }); - tearDown(() async { - await database.close(); - }); - - group('Player-Group Tests', () { - /// No need to test if group has players since the members attribute is - /// not nullable - - test('Adding a player to a group works correctly', () async { - await database.groupDao.addGroup(group: testgroup); - await database.playerDao.addPlayer(player: testPlayer4); - await database.playerGroupDao.addPlayerToGroup( - groupId: testgroup.id, - player: testPlayer4, - ); - - var playerAdded = await database.playerGroupDao.isPlayerInGroup( - groupId: testgroup.id, - playerId: testPlayer4.id, - ); - - expect(playerAdded, true); - - playerAdded = await database.playerGroupDao.isPlayerInGroup( - groupId: testgroup.id, - playerId: '', - ); - - expect(playerAdded, false); - }); - - test('Removing player from group works correctly', () async { - await database.groupDao.addGroup(group: testgroup); - - final playerToRemove = testgroup.members[0]; - - final removed = await database.playerGroupDao.removePlayerFromGroup( - playerId: playerToRemove.id, - groupId: testgroup.id, - ); - expect(removed, true); - - final result = await database.groupDao.getGroupById( - groupId: testgroup.id, - ); - expect(result.members.length, testgroup.members.length - 1); - - final playerExists = result.members.any((p) => p.id == playerToRemove.id); - expect(playerExists, false); - }); - - test('Retrieving players of a group works correctly', () async { - await database.groupDao.addGroup(group: testgroup); - final players = await database.playerGroupDao.getPlayersOfGroup( - groupId: testgroup.id, - ); - - for (int i = 0; i < players.length; i++) { - expect(players[i].id, testgroup.members[i].id); - expect(players[i].name, testgroup.members[i].name); - expect(players[i].createdAt, testgroup.members[i].createdAt); - } - }); - }); -} diff --git a/test/db_tests/player_match_test.dart b/test/db_tests/player_match_test.dart deleted file mode 100644 index 8a4f569..0000000 --- a/test/db_tests/player_match_test.dart +++ /dev/null @@ -1,237 +0,0 @@ -import 'package:clock/clock.dart'; -import 'package:drift/drift.dart' hide isNotNull; -import 'package:drift/native.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/group.dart'; -import 'package:game_tracker/data/dto/match.dart'; -import 'package:game_tracker/data/dto/player.dart'; - -void main() { - late AppDatabase database; - late Player testPlayer1; - late Player testPlayer2; - late Player testPlayer3; - late Player testPlayer4; - late Player testPlayer5; - late Player testPlayer6; - late Group testgroup; - late Match testMatchOnlyGroup; - late Match testMatchOnlyPlayers; - final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); - final fakeClock = Clock(() => fixedDate); - - setUp(() async { - database = AppDatabase( - DatabaseConnection( - NativeDatabase.memory(), - // Recommended for widget tests to avoid test errors. - closeStreamsSynchronously: true, - ), - ); - - withClock(fakeClock, () { - testPlayer1 = Player(name: 'Alice'); - testPlayer2 = Player(name: 'Bob'); - testPlayer3 = Player(name: 'Charlie'); - testPlayer4 = Player(name: 'Diana'); - testPlayer5 = Player(name: 'Eve'); - testPlayer6 = Player(name: 'Frank'); - testgroup = Group( - name: 'Test Group', - members: [testPlayer1, testPlayer2, testPlayer3], - ); - testMatchOnlyGroup = Match( - name: 'Test Match with Group', - group: testgroup, - ); - testMatchOnlyPlayers = Match( - name: 'Test Match with Players', - players: [testPlayer4, testPlayer5, testPlayer6], - ); - }); - await database.playerDao.addPlayersAsList( - players: [ - testPlayer1, - testPlayer2, - testPlayer3, - testPlayer4, - testPlayer5, - testPlayer6, - ], - ); - await database.groupDao.addGroup(group: testgroup); - }); - tearDown(() async { - await database.close(); - }); - - group('Player-Match Tests', () { - test('Match has player works correctly', () async { - await database.matchDao.addMatch(match: testMatchOnlyGroup); - await database.playerDao.addPlayer(player: testPlayer1); - - var matchHasPlayers = await database.playerMatchDao.matchHasPlayers( - matchId: testMatchOnlyGroup.id, - ); - - expect(matchHasPlayers, false); - - await database.playerMatchDao.addPlayerToMatch( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer1.id, - ); - - matchHasPlayers = await database.playerMatchDao.matchHasPlayers( - matchId: testMatchOnlyGroup.id, - ); - - expect(matchHasPlayers, true); - }); - - test('Adding a player to a match works correctly', () async { - await database.matchDao.addMatch(match: testMatchOnlyGroup); - await database.playerDao.addPlayer(player: testPlayer5); - await database.playerMatchDao.addPlayerToMatch( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer5.id, - ); - - var playerAdded = await database.playerMatchDao.isPlayerInMatch( - matchId: testMatchOnlyGroup.id, - playerId: testPlayer5.id, - ); - - expect(playerAdded, true); - - playerAdded = await database.playerMatchDao.isPlayerInMatch( - matchId: testMatchOnlyGroup.id, - playerId: '', - ); - - expect(playerAdded, false); - }); - - test('Removing player from match works correctly', () async { - await database.matchDao.addMatch(match: testMatchOnlyPlayers); - - final playerToRemove = testMatchOnlyPlayers.players![0]; - - final removed = await database.playerMatchDao.removePlayerFromMatch( - playerId: playerToRemove.id, - matchId: testMatchOnlyPlayers.id, - ); - expect(removed, true); - - final result = await database.matchDao.getMatchById( - matchId: testMatchOnlyPlayers.id, - ); - expect(result.players!.length, testMatchOnlyPlayers.players!.length - 1); - - final playerExists = result.players!.any( - (p) => p.id == playerToRemove.id, - ); - expect(playerExists, false); - }); - - test('Retrieving players of a match works correctly', () async { - await database.matchDao.addMatch(match: testMatchOnlyPlayers); - final players = await database.playerMatchDao.getPlayersOfMatch( - matchId: testMatchOnlyPlayers.id, - ); - - if (players == null) { - fail('Players should not be null'); - } - - for (int i = 0; i < players.length; i++) { - expect(players[i].id, testMatchOnlyPlayers.players![i].id); - expect(players[i].name, testMatchOnlyPlayers.players![i].name); - expect( - players[i].createdAt, - testMatchOnlyPlayers.players![i].createdAt, - ); - } - }); - - test('Updating the match players works coreclty', () async { - await database.matchDao.addMatch(match: testMatchOnlyPlayers); - - final newPlayers = [testPlayer1, testPlayer2, testPlayer4]; - await database.playerDao.addPlayersAsList(players: newPlayers); - - // First, remove all existing players - final existingPlayers = await database.playerMatchDao.getPlayersOfMatch( - matchId: testMatchOnlyPlayers.id, - ); - - if (existingPlayers == null || existingPlayers.isEmpty) { - fail('Existing players should not be null or empty'); - } - - await database.playerMatchDao.updatePlayersFromMatch( - matchId: testMatchOnlyPlayers.id, - newPlayer: newPlayers, - ); - - final updatedPlayers = await database.playerMatchDao.getPlayersOfMatch( - matchId: testMatchOnlyPlayers.id, - ); - - if (updatedPlayers == null) { - fail('Updated players should not be null'); - } - - expect(updatedPlayers.length, newPlayers.length); - - /// Create a map of new players for easy lookup - final testPlayers = {for (var p in newPlayers) p.id: p}; - - /// Verify each updated player matches the new players - for (final player in updatedPlayers) { - final testPlayer = testPlayers[player.id]!; - - expect(player.id, testPlayer.id); - expect(player.name, testPlayer.name); - expect(player.createdAt, testPlayer.createdAt); - } - }); - - test( - 'Adding the same player to seperate matches works correctly', - () async { - final playersList = [testPlayer1, testPlayer2, testPlayer3]; - final match1 = Match(name: 'Match 1', players: playersList); - final match2 = Match(name: 'Match 2', players: playersList); - - await Future.wait([ - database.matchDao.addMatch(match: match1), - database.matchDao.addMatch(match: match2), - ]); - - final players1 = await database.playerMatchDao.getPlayersOfMatch( - matchId: match1.id, - ); - final players2 = await database.playerMatchDao.getPlayersOfMatch( - matchId: match2.id, - ); - - expect(players1, isNotNull); - expect(players2, isNotNull); - - expect( - players1!.map((p) => p.id).toList(), - equals(players2!.map((p) => p.id).toList()), - ); - expect( - players1.map((p) => p.name).toList(), - equals(players2.map((p) => p.name).toList()), - ); - expect( - players1.map((p) => p.createdAt).toList(), - equals(players2.map((p) => p.createdAt).toList()), - ); - }, - ); - }); -} diff --git a/test/db_tests/player_test.dart b/test/db_tests/player_test.dart deleted file mode 100644 index 5bd10ad..0000000 --- a/test/db_tests/player_test.dart +++ /dev/null @@ -1,159 +0,0 @@ -import 'package:clock/clock.dart'; -import 'package:drift/drift.dart'; -import 'package:drift/native.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:game_tracker/data/db/database.dart'; -import 'package:game_tracker/data/dto/player.dart'; - -void main() { - late AppDatabase database; - late Player testPlayer1; - late Player testPlayer2; - late Player testPlayer3; - late Player testPlayer4; - final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); - final fakeClock = Clock(() => fixedDate); - - setUp(() { - database = AppDatabase( - DatabaseConnection( - NativeDatabase.memory(), - // Recommended for widget tests to avoid test errors. - closeStreamsSynchronously: true, - ), - ); - - withClock(fakeClock, () { - testPlayer1 = Player(name: 'Test Player'); - testPlayer2 = Player(name: 'Second Player'); - testPlayer3 = Player(name: 'Charlie'); - testPlayer4 = Player(name: 'Diana'); - }); - }); - tearDown(() async { - await database.close(); - }); - - group('Player Tests', () { - test('Adding and fetching single player works correctly', () async { - await database.playerDao.addPlayer(player: testPlayer1); - await database.playerDao.addPlayer(player: testPlayer2); - - final allPlayers = await database.playerDao.getAllPlayers(); - expect(allPlayers.length, 2); - - final fetchedPlayer1 = allPlayers.firstWhere( - (g) => g.id == testPlayer1.id, - ); - expect(fetchedPlayer1.name, testPlayer1.name); - expect(fetchedPlayer1.createdAt, testPlayer1.createdAt); - - final fetchedPlayer2 = allPlayers.firstWhere( - (g) => g.id == testPlayer2.id, - ); - expect(fetchedPlayer2.name, testPlayer2.name); - expect(fetchedPlayer2.createdAt, testPlayer2.createdAt); - }); - - test('Adding and fetching multiple players works correctly', () async { - await database.playerDao.addPlayersAsList( - players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4], - ); - - final allPlayers = await database.playerDao.getAllPlayers(); - expect(allPlayers.length, 4); - - // Map for connencting fetched players with expected players - final testPlayers = { - testPlayer1.id: testPlayer1, - testPlayer2.id: testPlayer2, - testPlayer3.id: testPlayer3, - testPlayer4.id: testPlayer4, - }; - - for (final player in allPlayers) { - final testPlayer = testPlayers[player.id]!; - - expect(player.id, testPlayer.id); - expect(player.name, testPlayer.name); - expect(player.createdAt, testPlayer.createdAt); - } - }); - - test('Adding the same player twice does not create duplicates', () async { - await database.playerDao.addPlayer(player: testPlayer1); - await database.playerDao.addPlayer(player: testPlayer1); - - final allPlayers = await database.playerDao.getAllPlayers(); - expect(allPlayers.length, 1); - }); - - test('Player existence check works correctly', () async { - var playerExists = await database.playerDao.playerExists( - playerId: testPlayer1.id, - ); - expect(playerExists, false); - - await database.playerDao.addPlayer(player: testPlayer1); - - playerExists = await database.playerDao.playerExists( - playerId: testPlayer1.id, - ); - expect(playerExists, true); - }); - - test('Deleting a player works correctly', () async { - await database.playerDao.addPlayer(player: testPlayer1); - final playerDeleted = await database.playerDao.deletePlayer( - playerId: testPlayer1.id, - ); - expect(playerDeleted, true); - - final playerExists = await database.playerDao.playerExists( - playerId: testPlayer1.id, - ); - expect(playerExists, false); - }); - - test('Updating a player name works correcly', () async { - await database.playerDao.addPlayer(player: testPlayer1); - - const newPlayerName = 'new player name'; - - await database.playerDao.updatePlayername( - playerId: testPlayer1.id, - newName: newPlayerName, - ); - - final result = await database.playerDao.getPlayerById( - playerId: testPlayer1.id, - ); - expect(result.name, newPlayerName); - }); - - test('Getting the player count works correctly', () async { - var playerCount = await database.playerDao.getPlayerCount(); - expect(playerCount, 0); - - await database.playerDao.addPlayer(player: testPlayer1); - - playerCount = await database.playerDao.getPlayerCount(); - expect(playerCount, 1); - - await database.playerDao.addPlayer(player: testPlayer2); - - playerCount = await database.playerDao.getPlayerCount(); - expect(playerCount, 2); - - await database.playerDao.deletePlayer(playerId: testPlayer1.id); - - playerCount = await database.playerDao.getPlayerCount(); - expect(playerCount, 1); - - await database.playerDao.deletePlayer(playerId: testPlayer2.id); - - playerCount = await database.playerDao.getPlayerCount(); - expect(playerCount, 0); - }); - }); -} diff --git a/test/db_tests/relationships/player_group_test.dart b/test/db_tests/relationships/player_group_test.dart new file mode 100644 index 0000000..051daf8 --- /dev/null +++ b/test/db_tests/relationships/player_group_test.dart @@ -0,0 +1,342 @@ +import 'package:clock/clock.dart'; +import 'package:drift/drift.dart'; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/group.dart'; +import 'package:tallee/data/dto/player.dart'; + +void main() { + late AppDatabase database; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; + late Group testGroup; + final fixedDate = DateTime(2025, 19, 11, 00, 11, 23); + final fakeClock = Clock(() => fixedDate); + + setUp(() { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + withClock(fakeClock, () { + testPlayer1 = Player(name: 'Alice', description: ''); + testPlayer2 = Player(name: 'Bob', description: ''); + testPlayer3 = Player(name: 'Charlie', description: ''); + testPlayer4 = Player(name: 'Diana', description: ''); + testGroup = Group( + name: 'Test Group', + description: '', + members: [testPlayer1, testPlayer2, testPlayer3], + ); + }); + }); + tearDown(() async { + await database.close(); + }); + + group('Player-Group Tests', () { + + // Verifies that a player can be added to an existing group and isPlayerInGroup returns true. + test('Adding a player to a group works correctly', () async { + await database.groupDao.addGroup(group: testGroup); + await database.playerDao.addPlayer(player: testPlayer4); + await database.playerGroupDao.addPlayerToGroup( + groupId: testGroup.id, + player: testPlayer4, + ); + + var playerAdded = await database.playerGroupDao.isPlayerInGroup( + groupId: testGroup.id, + playerId: testPlayer4.id, + ); + + expect(playerAdded, true); + + playerAdded = await database.playerGroupDao.isPlayerInGroup( + groupId: testGroup.id, + playerId: '', + ); + + expect(playerAdded, false); + }); + + // Verifies that a player can be removed from a group and the group's member count decreases. + test('Removing player from group works correctly', () async { + await database.groupDao.addGroup(group: testGroup); + + final playerToRemove = testGroup.members[0]; + + final removed = await database.playerGroupDao.removePlayerFromGroup( + playerId: playerToRemove.id, + groupId: testGroup.id, + ); + expect(removed, true); + + final result = await database.groupDao.getGroupById( + groupId: testGroup.id, + ); + expect(result.members.length, testGroup.members.length - 1); + + final playerExists = result.members.any((p) => p.id == playerToRemove.id); + expect(playerExists, false); + }); + + // Verifies that getPlayersOfGroup returns all members of a group with correct data. + test('Retrieving players of a group works correctly', () async { + await database.groupDao.addGroup(group: testGroup); + final players = await database.playerGroupDao.getPlayersOfGroup( + groupId: testGroup.id, + ); + + for (int i = 0; i < players.length; i++) { + expect(players[i].id, testGroup.members[i].id); + expect(players[i].name, testGroup.members[i].name); + expect(players[i].createdAt, testGroup.members[i].createdAt); + } + }); + + // Verifies that isPlayerInGroup returns false for non-existent player. + test('isPlayerInGroup returns false for non-existent player', () async { + await database.groupDao.addGroup(group: testGroup); + + final result = await database.playerGroupDao.isPlayerInGroup( + playerId: 'non-existent-player-id', + groupId: testGroup.id, + ); + + expect(result, false); + }); + + // Verifies that isPlayerInGroup returns false for non-existent group. + test('isPlayerInGroup returns false for non-existent group', () async { + await database.playerDao.addPlayer(player: testPlayer1); + + final result = await database.playerGroupDao.isPlayerInGroup( + playerId: testPlayer1.id, + groupId: 'non-existent-group-id', + ); + + expect(result, false); + }); + + // Verifies that addPlayerToGroup returns false when player already in group. + test('addPlayerToGroup returns false when player already in group', () async { + await database.groupDao.addGroup(group: testGroup); + + // testPlayer1 is already in testGroup via group creation + final result = await database.playerGroupDao.addPlayerToGroup( + player: testPlayer1, + groupId: testGroup.id, + ); + + expect(result, false); + }); + + // Verifies that addPlayerToGroup adds player to player table if not exists. + test('addPlayerToGroup adds player to player table if not exists', () async { + await database.groupDao.addGroup(group: testGroup); + + // testPlayer4 is not in the database yet + var playerExists = await database.playerDao.playerExists( + playerId: testPlayer4.id, + ); + expect(playerExists, false); + + await database.playerGroupDao.addPlayerToGroup( + player: testPlayer4, + groupId: testGroup.id, + ); + + // Now player should exist in player table + playerExists = await database.playerDao.playerExists( + playerId: testPlayer4.id, + ); + expect(playerExists, true); + }); + + // Verifies that removePlayerFromGroup returns false for non-existent player. + test('removePlayerFromGroup returns false for non-existent player', () async { + await database.groupDao.addGroup(group: testGroup); + + final result = await database.playerGroupDao.removePlayerFromGroup( + playerId: 'non-existent-player-id', + groupId: testGroup.id, + ); + + expect(result, false); + }); + + // Verifies that removePlayerFromGroup returns false for non-existent group. + test('removePlayerFromGroup returns false for non-existent group', () async { + await database.playerDao.addPlayer(player: testPlayer1); + + final result = await database.playerGroupDao.removePlayerFromGroup( + playerId: testPlayer1.id, + groupId: 'non-existent-group-id', + ); + + expect(result, false); + }); + + // Verifies that getPlayersOfGroup returns empty list for group with no members. + test('getPlayersOfGroup returns empty list for empty group', () async { + final emptyGroup = Group(name: 'Empty Group', description: '', members: []); + await database.groupDao.addGroup(group: emptyGroup); + + final players = await database.playerGroupDao.getPlayersOfGroup( + groupId: emptyGroup.id, + ); + + expect(players, isEmpty); + }); + + // Verifies that getPlayersOfGroup returns empty list for non-existent group. + test('getPlayersOfGroup returns empty list for non-existent group', () async { + final players = await database.playerGroupDao.getPlayersOfGroup( + groupId: 'non-existent-group-id', + ); + + expect(players, isEmpty); + }); + + // Verifies that removing all players from a group leaves the group empty. + test('Removing all players from a group leaves group empty', () async { + await database.groupDao.addGroup(group: testGroup); + + for (final player in testGroup.members) { + await database.playerGroupDao.removePlayerFromGroup( + playerId: player.id, + groupId: testGroup.id, + ); + } + + final players = await database.playerGroupDao.getPlayersOfGroup( + groupId: testGroup.id, + ); + expect(players, isEmpty); + + // Group should still exist + final groupExists = await database.groupDao.groupExists( + groupId: testGroup.id, + ); + expect(groupExists, true); + }); + + // Verifies that a player can be in multiple groups. + test('Player can be in multiple groups', () async { + final secondGroup = Group(name: 'Second Group', description: '', members: []); + await database.groupDao.addGroup(group: testGroup); + await database.groupDao.addGroup(group: secondGroup); + + // Add testPlayer1 to second group (already in testGroup) + await database.playerGroupDao.addPlayerToGroup( + player: testPlayer1, + groupId: secondGroup.id, + ); + + final inFirstGroup = await database.playerGroupDao.isPlayerInGroup( + playerId: testPlayer1.id, + groupId: testGroup.id, + ); + final inSecondGroup = await database.playerGroupDao.isPlayerInGroup( + playerId: testPlayer1.id, + groupId: secondGroup.id, + ); + + expect(inFirstGroup, true); + expect(inSecondGroup, true); + }); + + // Verifies that removing player from one group doesn't affect other groups. + test('Removing player from one group does not affect other groups', () async { + final secondGroup = Group(name: 'Second Group', description: '', members: [testPlayer1]); + await database.groupDao.addGroup(group: testGroup); + await database.groupDao.addGroup(group: secondGroup); + + // Remove testPlayer1 from testGroup + await database.playerGroupDao.removePlayerFromGroup( + playerId: testPlayer1.id, + groupId: testGroup.id, + ); + + final inFirstGroup = await database.playerGroupDao.isPlayerInGroup( + playerId: testPlayer1.id, + groupId: testGroup.id, + ); + final inSecondGroup = await database.playerGroupDao.isPlayerInGroup( + playerId: testPlayer1.id, + groupId: secondGroup.id, + ); + + expect(inFirstGroup, false); + expect(inSecondGroup, true); + }); + + // Verifies that addPlayerToGroup returns true on successful addition. + test('addPlayerToGroup returns true on successful addition', () async { + await database.groupDao.addGroup(group: testGroup); + await database.playerDao.addPlayer(player: testPlayer4); + + final result = await database.playerGroupDao.addPlayerToGroup( + player: testPlayer4, + groupId: testGroup.id, + ); + + expect(result, true); + }); + + // Verifies that removing the same player twice returns false on second attempt. + test('Removing same player twice returns false on second attempt', () async { + await database.groupDao.addGroup(group: testGroup); + + final firstRemoval = await database.playerGroupDao.removePlayerFromGroup( + playerId: testPlayer1.id, + groupId: testGroup.id, + ); + expect(firstRemoval, true); + + final secondRemoval = await database.playerGroupDao.removePlayerFromGroup( + playerId: testPlayer1.id, + groupId: testGroup.id, + ); + expect(secondRemoval, false); + }); + + // Verifies that replaceGroupPlayers removes all existing players and replaces with new list. + test('replaceGroupPlayers replaces all group members correctly', () async { + // Create initial group with 3 players + await database.groupDao.addGroup(group: testGroup); + + // Verify initial members + var groupMembers = await database.groupDao.getGroupById( + groupId: testGroup.id, + ); + expect(groupMembers.members.length, 3); + + // Replace with new list containing 2 different players + final newPlayersList = [testPlayer3, testPlayer4]; + await database.groupDao.replaceGroupPlayers( + groupId: testGroup.id, + newPlayers: newPlayersList, + ); + + // Get updated group and verify members + groupMembers = await database.groupDao.getGroupById( + groupId: testGroup.id, + ); + + expect(groupMembers.members.length, 2); + expect(groupMembers.members.any((p) => p.id == testPlayer3.id), true); + expect(groupMembers.members.any((p) => p.id == testPlayer4.id), true); + expect(groupMembers.members.any((p) => p.id == testPlayer1.id), false); + expect(groupMembers.members.any((p) => p.id == testPlayer2.id), false); + }); + }); +} diff --git a/test/db_tests/relationships/player_match_test.dart b/test/db_tests/relationships/player_match_test.dart new file mode 100644 index 0000000..890e74e --- /dev/null +++ b/test/db_tests/relationships/player_match_test.dart @@ -0,0 +1,915 @@ +import 'package:clock/clock.dart'; +import 'package:drift/drift.dart' hide isNotNull, isNull; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tallee/core/enums.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/game.dart'; +import 'package:tallee/data/dto/group.dart'; +import 'package:tallee/data/dto/match.dart'; +import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/data/dto/team.dart'; + +void main() { + late AppDatabase database; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Player testPlayer4; + late Player testPlayer5; + late Player testPlayer6; + late Group testGroup; + late Game testGame; + late Match testMatchOnlyGroup; + late Match testMatchOnlyPlayers; + late Team testTeam1; + late Team testTeam2; + final fixedDate = DateTime(2025, 11, 19, 00, 11, 23); + final fakeClock = Clock(() => fixedDate); + + setUp(() async { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + withClock(fakeClock, () { + testPlayer1 = Player(name: 'Alice', description: ''); + testPlayer2 = Player(name: 'Bob', description: ''); + testPlayer3 = Player(name: 'Charlie', description: ''); + testPlayer4 = Player(name: 'Diana', description: ''); + testPlayer5 = Player(name: 'Eve', description: ''); + testPlayer6 = Player(name: 'Frank', description: ''); + testGroup = Group( + name: 'Test Group', + description: '', + members: [testPlayer1, testPlayer2, testPlayer3], + ); + testGame = Game(name: 'Test Game', ruleset: Ruleset.singleWinner, description: 'A test game', color: GameColor.blue, icon: ''); + testMatchOnlyGroup = Match( + name: 'Test Match with Group', + game: testGame, + group: testGroup, + notes: '', + ); + testMatchOnlyPlayers = Match( + name: 'Test Match with Players', + game: testGame, + players: [testPlayer4, testPlayer5, testPlayer6], + notes: '', + ); + testTeam1 = Team( + name: 'Team Alpha', + members: [testPlayer1, testPlayer2], + ); + testTeam2 = Team( + name: 'Team Beta', + members: [testPlayer3, testPlayer4], + ); + }); + await database.playerDao.addPlayersAsList( + players: [ + testPlayer1, + testPlayer2, + testPlayer3, + testPlayer4, + testPlayer5, + testPlayer6, + ], + ); + await database.groupDao.addGroup(group: testGroup); + await database.gameDao.addGame(game: testGame); + }); + tearDown(() async { + await database.close(); + }); + + group('Player-Match Tests', () { + + // Verifies that matchHasPlayers returns false initially and true after adding a player. + test('Match has player works correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); + await database.playerDao.addPlayer(player: testPlayer1); + + var matchHasPlayers = await database.playerMatchDao.matchHasPlayers( + matchId: testMatchOnlyGroup.id, + ); + + expect(matchHasPlayers, false); + + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer1.id, + ); + + matchHasPlayers = await database.playerMatchDao.matchHasPlayers( + matchId: testMatchOnlyGroup.id, + ); + + expect(matchHasPlayers, true); + }); + + // Verifies that a player can be added to a match and isPlayerInMatch returns true. + test('Adding a player to a match works correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); + await database.playerDao.addPlayer(player: testPlayer5); + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer5.id, + ); + + var playerAdded = await database.playerMatchDao.isPlayerInMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer5.id, + ); + + expect(playerAdded, true); + + playerAdded = await database.playerMatchDao.isPlayerInMatch( + matchId: testMatchOnlyGroup.id, + playerId: '', + ); + + expect(playerAdded, false); + }); + + // Verifies that a player can be removed from a match and the player count decreases. + test('Removing player from match works correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); + + final playerToRemove = testMatchOnlyPlayers.players[0]; + + final removed = await database.playerMatchDao.removePlayerFromMatch( + playerId: playerToRemove.id, + matchId: testMatchOnlyPlayers.id, + ); + expect(removed, true); + + final result = await database.matchDao.getMatchById( + matchId: testMatchOnlyPlayers.id, + ); + expect(result.players.length, testMatchOnlyPlayers.players.length - 1); + + final playerExists = result.players.any( + (p) => p.id == playerToRemove.id, + ); + expect(playerExists, false); + }); + + // Verifies that getPlayersOfMatch returns all players of a match with correct data. + test('Retrieving players of a match works correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); + final players = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatchOnlyPlayers.id, + ) ?? []; + + for (int i = 0; i < players.length; i++) { + expect(players[i].id, testMatchOnlyPlayers.players[i].id); + expect(players[i].name, testMatchOnlyPlayers.players[i].name); + expect( + players[i].createdAt, + testMatchOnlyPlayers.players[i].createdAt, + ); + } + }); + + // Verifies that updatePlayersFromMatch replaces all existing players with new ones. + test('Updating the match players works correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); + + final newPlayers = [testPlayer1, testPlayer2, testPlayer4]; + await database.playerDao.addPlayersAsList(players: newPlayers); + + // First, remove all existing players + final existingPlayers = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatchOnlyPlayers.id, + ); + + if (existingPlayers == null || existingPlayers.isEmpty) { + fail('Existing players should not be null or empty'); + } + + await database.playerMatchDao.updatePlayersFromMatch( + matchId: testMatchOnlyPlayers.id, + newPlayer: newPlayers, + ); + + final updatedPlayers = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatchOnlyPlayers.id, + ); + + if (updatedPlayers == null) { + fail('Updated players should not be null'); + } + + expect(updatedPlayers.length, newPlayers.length); + + /// Create a map of new players for easy lookup + final testPlayers = {for (var p in newPlayers) p.id: p}; + + /// Verify each updated player matches the new players + for (final player in updatedPlayers) { + final testPlayer = testPlayers[player.id]!; + + expect(player.id, testPlayer.id); + expect(player.name, testPlayer.name); + expect(player.createdAt, testPlayer.createdAt); + } + }); + + // Verifies that the same player can be added to multiple different matches. + test( + 'Adding the same player to separate matches works correctly', + () async { + final playersList = [testPlayer1, testPlayer2, testPlayer3]; + final match1 = Match(name: 'Match 1', game: testGame, players: playersList, notes: ''); + final match2 = Match(name: 'Match 2', game: testGame, players: playersList, notes: ''); + + await Future.wait([ + database.matchDao.addMatch(match: match1), + database.matchDao.addMatch(match: match2), + ]); + + final players1 = await database.playerMatchDao.getPlayersOfMatch( + matchId: match1.id, + ); + final players2 = await database.playerMatchDao.getPlayersOfMatch( + matchId: match2.id, + ); + + expect(players1, isNotNull); + expect(players2, isNotNull); + + expect( + players1!.map((p) => p.id).toList(), + equals(players2!.map((p) => p.id).toList()), + ); + expect( + players1.map((p) => p.name).toList(), + equals(players2.map((p) => p.name).toList()), + ); + expect( + players1.map((p) => p.createdAt).toList(), + equals(players2.map((p) => p.createdAt).toList()), + ); + }, + ); + + // Verifies that getPlayersOfMatch returns null for a non-existent match. + test('getPlayersOfMatch returns null for non-existent match', () async { + final players = await database.playerMatchDao.getPlayersOfMatch( + matchId: 'non-existent-match-id', + ); + + expect(players, isNull); + }); + + // Verifies that adding a player with initial score works correctly. + test('Adding player with initial score works correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); + + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer1.id, + score: 100, + ); + + final score = await database.playerMatchDao.getPlayerScore( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer1.id, + ); + + expect(score, 100); + }); + + // Verifies that getPlayerScore returns the correct score. + test('getPlayerScore returns correct score', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); + + // Default score should be 0 when added through match + final score = await database.playerMatchDao.getPlayerScore( + matchId: testMatchOnlyPlayers.id, + playerId: testPlayer4.id, + ); + + expect(score, 0); + }); + + // Verifies that getPlayerScore returns null for non-existent player-match combination. + test('getPlayerScore returns null for non-existent player in match', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); + + final score = await database.playerMatchDao.getPlayerScore( + matchId: testMatchOnlyGroup.id, + playerId: 'non-existent-player-id', + ); + + expect(score, isNull); + }); + + // Verifies that updatePlayerScore updates the score correctly. + test('updatePlayerScore updates score correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); + + final updated = await database.playerMatchDao.updatePlayerScore( + matchId: testMatchOnlyPlayers.id, + playerId: testPlayer4.id, + newScore: 50, + ); + + expect(updated, true); + + final score = await database.playerMatchDao.getPlayerScore( + matchId: testMatchOnlyPlayers.id, + playerId: testPlayer4.id, + ); + + expect(score, 50); + }); + + // Verifies that updatePlayerScore returns false for non-existent player-match. + test('updatePlayerScore returns false for non-existent player-match', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); + + final updated = await database.playerMatchDao.updatePlayerScore( + matchId: testMatchOnlyGroup.id, + playerId: 'non-existent-player-id', + newScore: 50, + ); + + expect(updated, false); + }); + + // Verifies that adding a player with teamId works correctly. + test('Adding player with teamId works correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); + await database.teamDao.addTeam(team: testTeam1); + + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer1.id, + teamId: testTeam1.id, + ); + + final playersInTeam = await database.playerMatchDao.getPlayersInTeam( + matchId: testMatchOnlyGroup.id, + teamId: testTeam1.id, + ); + + expect(playersInTeam.length, 1); + expect(playersInTeam[0].id, testPlayer1.id); + }); + + // Verifies that updatePlayerTeam updates the team correctly. + test('updatePlayerTeam updates team correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); + await database.teamDao.addTeam(team: testTeam1); + await database.teamDao.addTeam(team: testTeam2); + + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer1.id, + teamId: testTeam1.id, + ); + + // Update player's team + final updated = await database.playerMatchDao.updatePlayerTeam( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer1.id, + teamId: testTeam2.id, + ); + + expect(updated, true); + + // Verify player is now in testTeam2 + final playersInTeam2 = await database.playerMatchDao.getPlayersInTeam( + matchId: testMatchOnlyGroup.id, + teamId: testTeam2.id, + ); + + expect(playersInTeam2.length, 1); + expect(playersInTeam2[0].id, testPlayer1.id); + + // Verify player is no longer in testTeam1 + final playersInTeam1 = await database.playerMatchDao.getPlayersInTeam( + matchId: testMatchOnlyGroup.id, + teamId: testTeam1.id, + ); + + expect(playersInTeam1.isEmpty, true); + }); + + // Verifies that updatePlayerTeam can set team to null. + test('updatePlayerTeam can remove player from team', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); + await database.teamDao.addTeam(team: testTeam1); + + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer1.id, + teamId: testTeam1.id, + ); + + // Remove player from team by setting teamId to null + final updated = await database.playerMatchDao.updatePlayerTeam( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer1.id, + teamId: null, + ); + + expect(updated, true); + + final playersInTeam = await database.playerMatchDao.getPlayersInTeam( + matchId: testMatchOnlyGroup.id, + teamId: testTeam1.id, + ); + + expect(playersInTeam.isEmpty, true); + }); + + // Verifies that updatePlayerTeam returns false for non-existent player-match. + test('updatePlayerTeam returns false for non-existent player-match', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); + + final updated = await database.playerMatchDao.updatePlayerTeam( + matchId: testMatchOnlyGroup.id, + playerId: 'non-existent-player-id', + teamId: testTeam1.id, + ); + + expect(updated, false); + }); + + // Verifies that getPlayersInTeam returns empty list for non-existent team. + test('getPlayersInTeam returns empty list for non-existent team', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); + + final players = await database.playerMatchDao.getPlayersInTeam( + matchId: testMatchOnlyPlayers.id, + teamId: 'non-existent-team-id', + ); + + expect(players.isEmpty, true); + }); + + // Verifies that getPlayersInTeam returns all players of a team. + test('getPlayersInTeam returns all players of a team', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); + await database.teamDao.addTeam(team: testTeam1); + + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer1.id, + teamId: testTeam1.id, + ); + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer2.id, + teamId: testTeam1.id, + ); + + final playersInTeam = await database.playerMatchDao.getPlayersInTeam( + matchId: testMatchOnlyGroup.id, + teamId: testTeam1.id, + ); + + expect(playersInTeam.length, 2); + final playerIds = playersInTeam.map((p) => p.id).toSet(); + expect(playerIds.contains(testPlayer1.id), true); + expect(playerIds.contains(testPlayer2.id), true); + }); + + // Verifies that removePlayerFromMatch returns false for non-existent player. + test('removePlayerFromMatch returns false for non-existent player', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); + + final removed = await database.playerMatchDao.removePlayerFromMatch( + playerId: 'non-existent-player-id', + matchId: testMatchOnlyPlayers.id, + ); + + expect(removed, false); + }); + + // Verifies that adding the same player twice to the same match is ignored. + test('Adding same player twice to same match is ignored', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); + + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer1.id, + score: 10, + ); + + // Try to add the same player again with different score + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer1.id, + score: 100, + ); + + // Score should still be 10 because insert was ignored + final score = await database.playerMatchDao.getPlayerScore( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer1.id, + ); + + expect(score, 10); + + // Verify player count is still 1 + final players = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatchOnlyGroup.id, + ); + + expect(players?.length, 1); + }); + + // Verifies that updatePlayersFromMatch with empty list removes all players. + test('updatePlayersFromMatch with empty list removes all players', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); + + // Verify players exist initially + var players = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatchOnlyPlayers.id, + ); + expect(players?.length, 3); + + // Update with empty list + await database.playerMatchDao.updatePlayersFromMatch( + matchId: testMatchOnlyPlayers.id, + newPlayer: [], + ); + + // Verify all players are removed + players = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatchOnlyPlayers.id, + ); + expect(players, isNull); + }); + + // Verifies that updatePlayersFromMatch with same players makes no changes. + test('updatePlayersFromMatch with same players makes no changes', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); + + final originalPlayers = [testPlayer4, testPlayer5, testPlayer6]; + + await database.playerMatchDao.updatePlayersFromMatch( + matchId: testMatchOnlyPlayers.id, + newPlayer: originalPlayers, + ); + + final players = await database.playerMatchDao.getPlayersOfMatch( + matchId: testMatchOnlyPlayers.id, + ); + + expect(players?.length, originalPlayers.length); + final playerIds = players!.map((p) => p.id).toSet(); + for (final originalPlayer in originalPlayers) { + expect(playerIds.contains(originalPlayer.id), true); + } + }); + + // Verifies that matchHasPlayers returns false for non-existent match. + test('matchHasPlayers returns false for non-existent match', () async { + final hasPlayers = await database.playerMatchDao.matchHasPlayers( + matchId: 'non-existent-match-id', + ); + + expect(hasPlayers, false); + }); + + // Verifies that isPlayerInMatch returns false for non-existent match. + test('isPlayerInMatch returns false for non-existent match', () async { + final isInMatch = await database.playerMatchDao.isPlayerInMatch( + matchId: 'non-existent-match-id', + playerId: testPlayer1.id, + ); + + expect(isInMatch, false); + }); + + // Verifies that updatePlayersFromMatch preserves scores for existing players. + test('updatePlayersFromMatch only modifies player associations', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); + + // Update score for existing player + await database.playerMatchDao.updatePlayerScore( + matchId: testMatchOnlyPlayers.id, + playerId: testPlayer4.id, + newScore: 75, + ); + + // Update players, keeping testPlayer4 and adding testPlayer1 + await database.playerMatchDao.updatePlayersFromMatch( + matchId: testMatchOnlyPlayers.id, + newPlayer: [testPlayer4, testPlayer1], + ); + + // Verify testPlayer4's score is preserved + final score = await database.playerMatchDao.getPlayerScore( + matchId: testMatchOnlyPlayers.id, + playerId: testPlayer4.id, + ); + + expect(score, 75); + + // Verify testPlayer1 was added with default score + final newPlayerScore = await database.playerMatchDao.getPlayerScore( + matchId: testMatchOnlyPlayers.id, + playerId: testPlayer1.id, + ); + + expect(newPlayerScore, 0); + }); + + // Verifies that adding a player with both score and teamId works correctly. + test('Adding player with score and teamId works correctly', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); + await database.teamDao.addTeam(team: testTeam1); + + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer1.id, + teamId: testTeam1.id, + score: 150, + ); + + // Verify score + final score = await database.playerMatchDao.getPlayerScore( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer1.id, + ); + expect(score, 150); + + // Verify team assignment + final playersInTeam = await database.playerMatchDao.getPlayersInTeam( + matchId: testMatchOnlyGroup.id, + teamId: testTeam1.id, + ); + expect(playersInTeam.length, 1); + expect(playersInTeam[0].id, testPlayer1.id); + }); + + // Verifies that updating score with negative value works. + test('updatePlayerScore with negative score works', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); + + final updated = await database.playerMatchDao.updatePlayerScore( + matchId: testMatchOnlyPlayers.id, + playerId: testPlayer4.id, + newScore: -10, + ); + + expect(updated, true); + + final score = await database.playerMatchDao.getPlayerScore( + matchId: testMatchOnlyPlayers.id, + playerId: testPlayer4.id, + ); + + expect(score, -10); + }); + + // Verifies that updating score with zero value works. + test('updatePlayerScore with zero score works', () async { + await database.matchDao.addMatch(match: testMatchOnlyPlayers); + + // First set a non-zero score + await database.playerMatchDao.updatePlayerScore( + matchId: testMatchOnlyPlayers.id, + playerId: testPlayer4.id, + newScore: 100, + ); + + // Then update to zero + final updated = await database.playerMatchDao.updatePlayerScore( + matchId: testMatchOnlyPlayers.id, + playerId: testPlayer4.id, + newScore: 0, + ); + + expect(updated, true); + + final score = await database.playerMatchDao.getPlayerScore( + matchId: testMatchOnlyPlayers.id, + playerId: testPlayer4.id, + ); + + expect(score, 0); + }); + + // Verifies that getPlayersInTeam returns empty list for non-existent match. + test('getPlayersInTeam returns empty list for non-existent match', () async { + await database.teamDao.addTeam(team: testTeam1); + + final players = await database.playerMatchDao.getPlayersInTeam( + matchId: 'non-existent-match-id', + teamId: testTeam1.id, + ); + + expect(players.isEmpty, true); + }); + + // Verifies that players in different teams within the same match are returned correctly. + test('Players in different teams within same match are separate', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); + await database.teamDao.addTeam(team: testTeam1); + await database.teamDao.addTeam(team: testTeam2); + + // Add players to different teams + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer1.id, + teamId: testTeam1.id, + ); + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer2.id, + teamId: testTeam1.id, + ); + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer3.id, + teamId: testTeam2.id, + ); + + // Verify team 1 players + final playersInTeam1 = await database.playerMatchDao.getPlayersInTeam( + matchId: testMatchOnlyGroup.id, + teamId: testTeam1.id, + ); + expect(playersInTeam1.length, 2); + final team1Ids = playersInTeam1.map((p) => p.id).toSet(); + expect(team1Ids.contains(testPlayer1.id), true); + expect(team1Ids.contains(testPlayer2.id), true); + expect(team1Ids.contains(testPlayer3.id), false); + + // Verify team 2 players + final playersInTeam2 = await database.playerMatchDao.getPlayersInTeam( + matchId: testMatchOnlyGroup.id, + teamId: testTeam2.id, + ); + expect(playersInTeam2.length, 1); + expect(playersInTeam2[0].id, testPlayer3.id); + }); + + // Verifies that removePlayerFromMatch does not affect other matches. + test('removePlayerFromMatch does not affect other matches', () async { + final playersList = [testPlayer1, testPlayer2]; + final match1 = Match(name: 'Match 1', game: testGame, players: playersList, notes: ''); + final match2 = Match(name: 'Match 2', game: testGame, players: playersList, notes: ''); + + await Future.wait([ + database.matchDao.addMatch(match: match1), + database.matchDao.addMatch(match: match2), + ]); + + // Remove player from match1 + final removed = await database.playerMatchDao.removePlayerFromMatch( + playerId: testPlayer1.id, + matchId: match1.id, + ); + expect(removed, true); + + // Verify player is removed from match1 + final isInMatch1 = await database.playerMatchDao.isPlayerInMatch( + matchId: match1.id, + playerId: testPlayer1.id, + ); + expect(isInMatch1, false); + + // Verify player still exists in match2 + final isInMatch2 = await database.playerMatchDao.isPlayerInMatch( + matchId: match2.id, + playerId: testPlayer1.id, + ); + expect(isInMatch2, true); + }); + + // Verifies that updating scores for players in different matches are independent. + test('Player scores are independent across matches', () async { + final playersList = [testPlayer1]; + final match1 = Match(name: 'Match 1', game: testGame, players: playersList, notes: ''); + final match2 = Match(name: 'Match 2', game: testGame, players: playersList, notes: ''); + + await Future.wait([ + database.matchDao.addMatch(match: match1), + database.matchDao.addMatch(match: match2), + ]); + + // Update score in match1 + await database.playerMatchDao.updatePlayerScore( + matchId: match1.id, + playerId: testPlayer1.id, + newScore: 100, + ); + + // Update score in match2 + await database.playerMatchDao.updatePlayerScore( + matchId: match2.id, + playerId: testPlayer1.id, + newScore: 50, + ); + + // Verify scores are independent + final scoreInMatch1 = await database.playerMatchDao.getPlayerScore( + matchId: match1.id, + playerId: testPlayer1.id, + ); + final scoreInMatch2 = await database.playerMatchDao.getPlayerScore( + matchId: match2.id, + playerId: testPlayer1.id, + ); + + expect(scoreInMatch1, 100); + expect(scoreInMatch2, 50); + }); + + // Verifies that updatePlayersFromMatch on non-existent match fails with constraint error. + test('updatePlayersFromMatch on non-existent match fails with foreign key constraint', () async { + // Should throw due to foreign key constraint - match doesn't exist + await expectLater( + database.playerMatchDao.updatePlayersFromMatch( + matchId: 'non-existent-match-id', + newPlayer: [testPlayer1, testPlayer2], + ), + throwsA(anything), + ); + }); + + // Verifies that a player can be in a match without being assigned to a team. + test('Player can exist in match without team assignment', () async { + await database.matchDao.addMatch(match: testMatchOnlyGroup); + await database.teamDao.addTeam(team: testTeam1); + + // Add player to match without team + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer1.id, + ); + + // Add another player to match with team + await database.playerMatchDao.addPlayerToMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer2.id, + teamId: testTeam1.id, + ); + + // Verify both players are in the match + final isPlayer1InMatch = await database.playerMatchDao.isPlayerInMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer1.id, + ); + final isPlayer2InMatch = await database.playerMatchDao.isPlayerInMatch( + matchId: testMatchOnlyGroup.id, + playerId: testPlayer2.id, + ); + + expect(isPlayer1InMatch, true); + expect(isPlayer2InMatch, true); + + // Verify only player2 is in the team + final playersInTeam = await database.playerMatchDao.getPlayersInTeam( + matchId: testMatchOnlyGroup.id, + teamId: testTeam1.id, + ); + + expect(playersInTeam.length, 1); + expect(playersInTeam[0].id, testPlayer2.id); + }); + + // Verifies that replaceMatchPlayers removes all existing players and replaces with new list. + test('replaceMatchPlayers replaces all match players correctly', () async { + // Create initial match with 3 players + await database.matchDao.addMatch(match: testMatchOnlyPlayers); + + // Verify initial players + var matchPlayers = await database.matchDao.getMatchById( + matchId: testMatchOnlyPlayers.id, + ); + expect(matchPlayers.players.length, 3); + + // Replace with new list containing 2 different players + final newPlayersList = [testPlayer1, testPlayer2]; + await database.matchDao.replaceMatchPlayers( + matchId: testMatchOnlyPlayers.id, + newPlayers: newPlayersList, + ); + + // Get updated match and verify players + matchPlayers = await database.matchDao.getMatchById( + matchId: testMatchOnlyPlayers.id, + ); + + expect(matchPlayers.players.length, 2); + expect(matchPlayers.players.any((p) => p.id == testPlayer1.id), true); + expect(matchPlayers.players.any((p) => p.id == testPlayer2.id), true); + expect(matchPlayers.players.any((p) => p.id == testPlayer4.id), false); + expect(matchPlayers.players.any((p) => p.id == testPlayer5.id), false); + expect(matchPlayers.players.any((p) => p.id == testPlayer6.id), false); + }); + }); +} diff --git a/test/db_tests/values/score_test.dart b/test/db_tests/values/score_test.dart new file mode 100644 index 0000000..2ad4671 --- /dev/null +++ b/test/db_tests/values/score_test.dart @@ -0,0 +1,739 @@ +import 'package:clock/clock.dart'; +import 'package:drift/drift.dart' hide isNull, isNotNull; +import 'package:drift/native.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:tallee/data/db/database.dart'; +import 'package:tallee/data/dto/game.dart'; +import 'package:tallee/data/dto/match.dart'; +import 'package:tallee/data/dto/player.dart'; +import 'package:tallee/core/enums.dart'; + +void main() { + late AppDatabase database; + late Player testPlayer1; + late Player testPlayer2; + late Player testPlayer3; + late Game testGame; + late Match testMatch1; + late Match testMatch2; + final fixedDate = DateTime(2025, 11, 19, 00, 11, 23); + final fakeClock = Clock(() => fixedDate); + + setUp(() async { + database = AppDatabase( + DatabaseConnection( + NativeDatabase.memory(), + // Recommended for widget tests to avoid test errors. + closeStreamsSynchronously: true, + ), + ); + + withClock(fakeClock, () { + testPlayer1 = Player(name: 'Alice', description: ''); + testPlayer2 = Player(name: 'Bob', description: ''); + testPlayer3 = Player(name: 'Charlie', description: ''); + testGame = Game(name: 'Test Game', ruleset: Ruleset.singleWinner, description: 'A test game', color: GameColor.blue, icon: ''); + testMatch1 = Match( + name: 'Test Match 1', + game: testGame, + players: [testPlayer1, testPlayer2], + notes: '', + ); + testMatch2 = Match( + name: 'Test Match 2', + game: testGame, + players: [testPlayer2, testPlayer3], + notes: '', + ); + }); + + await database.playerDao.addPlayersAsList( + players: [testPlayer1, testPlayer2, testPlayer3], + ); + await database.gameDao.addGame(game: testGame); + await database.matchDao.addMatch(match: testMatch1); + await database.matchDao.addMatch(match: testMatch2); + }); + + tearDown(() async { + await database.close(); + }); + + group('Score Tests', () { + + // Verifies that a score can be added and retrieved with all fields intact. + test('Adding and fetching a score works correctly', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 10, + change: 10, + ); + + final score = await database.scoreDao.getScoreForRound( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + ); + + expect(score, isNotNull); + expect(score!.playerId, testPlayer1.id); + expect(score.matchId, testMatch1.id); + expect(score.roundNumber, 1); + expect(score.score, 10); + expect(score.change, 10); + }); + + // Verifies that getScoresForMatch returns all scores for a given match. + test('Getting scores for a match works correctly', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 10, + change: 10, + ); + await database.scoreDao.addScore( + playerId: testPlayer2.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 20, + change: 20, + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 2, + score: 25, + change: 15, + ); + + final scores = await database.scoreDao.getScoresForMatch( + matchId: testMatch1.id, + ); + + expect(scores.length, 3); + }); + + // Verifies that getPlayerScoresInMatch returns all scores for a player in a match, ordered by round. + test('Getting player scores in a match works correctly', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 10, + change: 10, + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 2, + score: 25, + change: 15, + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 3, + score: 30, + change: 5, + ); + + final playerScores = await database.scoreDao.getPlayerScoresInMatch( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + + expect(playerScores.length, 3); + expect(playerScores[0].roundNumber, 1); + expect(playerScores[1].roundNumber, 2); + expect(playerScores[2].roundNumber, 3); + expect(playerScores[0].score, 10); + expect(playerScores[1].score, 25); + expect(playerScores[2].score, 30); + }); + + // Verifies that getScoreForRound returns null for a non-existent round number. + test('Getting score for a non-existent round returns null', () async { + final score = await database.scoreDao.getScoreForRound( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 999, + ); + + expect(score, isNull); + }); + + // Verifies that updateScore correctly updates the score and change values. + test('Updating a score works correctly', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 10, + change: 10, + ); + + final updated = await database.scoreDao.updateScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + newScore: 50, + newChange: 40, + ); + + expect(updated, true); + + final score = await database.scoreDao.getScoreForRound( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + ); + + expect(score, isNotNull); + expect(score!.score, 50); + expect(score.change, 40); + }); + + // Verifies that updateScore returns false for a non-existent score entry. + test('Updating a non-existent score returns false', () async { + final updated = await database.scoreDao.updateScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 999, + newScore: 50, + newChange: 40, + ); + + expect(updated, false); + }); + + // Verifies that deleteScore removes the score entry and returns true. + test('Deleting a score works correctly', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 10, + change: 10, + ); + + final deleted = await database.scoreDao.deleteScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + ); + + expect(deleted, true); + + final score = await database.scoreDao.getScoreForRound( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + ); + + expect(score, isNull); + }); + + // Verifies that deleteScore returns false for a non-existent score entry. + test('Deleting a non-existent score returns false', () async { + final deleted = await database.scoreDao.deleteScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 999, + ); + + expect(deleted, false); + }); + + // Verifies that deleteScoresForMatch removes all scores for a match but keeps other match scores. + test('Deleting scores for a match works correctly', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 10, + change: 10, + ); + await database.scoreDao.addScore( + playerId: testPlayer2.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 20, + change: 20, + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch2.id, + roundNumber: 1, + score: 15, + change: 15, + ); + + final deleted = await database.scoreDao.deleteScoresForMatch( + matchId: testMatch1.id, + ); + + expect(deleted, true); + + final match1Scores = await database.scoreDao.getScoresForMatch( + matchId: testMatch1.id, + ); + expect(match1Scores.length, 0); + + final match2Scores = await database.scoreDao.getScoresForMatch( + matchId: testMatch2.id, + ); + expect(match2Scores.length, 1); + }); + + // Verifies that deleteScoresForPlayer removes all scores for a player across all matches. + test('Deleting scores for a player works correctly', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 10, + change: 10, + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch2.id, + roundNumber: 1, + score: 15, + change: 15, + ); + await database.scoreDao.addScore( + playerId: testPlayer2.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 20, + change: 20, + ); + + final deleted = await database.scoreDao.deleteScoresForPlayer( + playerId: testPlayer1.id, + ); + + expect(deleted, true); + + final player1Scores = await database.scoreDao.getPlayerScoresInMatch( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + expect(player1Scores.length, 0); + + final player2Scores = await database.scoreDao.getPlayerScoresInMatch( + playerId: testPlayer2.id, + matchId: testMatch1.id, + ); + expect(player2Scores.length, 1); + }); + + // Verifies that getLatestRoundNumber returns the highest round number for a match. + test('Getting latest round number works correctly', () async { + var latestRound = await database.scoreDao.getLatestRoundNumber( + matchId: testMatch1.id, + ); + expect(latestRound, 0); + + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 10, + change: 10, + ); + + latestRound = await database.scoreDao.getLatestRoundNumber( + matchId: testMatch1.id, + ); + expect(latestRound, 1); + + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 5, + score: 50, + change: 40, + ); + + latestRound = await database.scoreDao.getLatestRoundNumber( + matchId: testMatch1.id, + ); + expect(latestRound, 5); + }); + + // Verifies that getTotalScoreForPlayer returns the latest score (cumulative) for a player. + test('Getting total score for a player works correctly', () async { + var totalScore = await database.scoreDao.getTotalScoreForPlayer( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + expect(totalScore, 0); + + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 10, + change: 10, + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 2, + score: 25, + change: 15, + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 3, + score: 40, + change: 15, + ); + + totalScore = await database.scoreDao.getTotalScoreForPlayer( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + expect(totalScore, 40); + }); + + // Verifies that adding a score with the same player/match/round replaces the existing one. + test('Adding the same score twice replaces the existing one', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 10, + change: 10, + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 99, + change: 99, + ); + + final score = await database.scoreDao.getScoreForRound( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + ); + + expect(score, isNotNull); + expect(score!.score, 99); + expect(score.change, 99); + }); + + // Verifies that getScoresForMatch returns empty list for match with no scores. + test('Getting scores for match with no scores returns empty list', () async { + final scores = await database.scoreDao.getScoresForMatch( + matchId: testMatch1.id, + ); + + expect(scores.isEmpty, true); + }); + + // Verifies that getPlayerScoresInMatch returns empty list when player has no scores. + test('Getting player scores with no scores returns empty list', () async { + final playerScores = await database.scoreDao.getPlayerScoresInMatch( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + + expect(playerScores.isEmpty, true); + }); + + // Verifies that scores can have negative values. + test('Score can have negative values', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: -10, + change: -10, + ); + + final score = await database.scoreDao.getScoreForRound( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + ); + + expect(score, isNotNull); + expect(score!.score, -10); + expect(score.change, -10); + }); + + // Verifies that scores can have zero values. + test('Score can have zero values', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 0, + change: 0, + ); + + final score = await database.scoreDao.getScoreForRound( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + ); + + expect(score, isNotNull); + expect(score!.score, 0); + expect(score.change, 0); + }); + + // Verifies that very large round numbers are supported. + test('Score supports very large round numbers', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 999999, + score: 100, + change: 100, + ); + + final score = await database.scoreDao.getScoreForRound( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 999999, + ); + + expect(score, isNotNull); + expect(score!.roundNumber, 999999); + }); + + // Verifies that getLatestRoundNumber returns max correctly for non-consecutive rounds. + test('Getting latest round number with non-consecutive rounds', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 10, + change: 10, + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 5, + score: 50, + change: 40, + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 3, + score: 30, + change: 20, + ); + + final latestRound = await database.scoreDao.getLatestRoundNumber( + matchId: testMatch1.id, + ); + + expect(latestRound, 5); + }); + + // Verifies that deleteScoresForMatch returns false when no scores exist. + test('Deleting scores for empty match returns false', () async { + final deleted = await database.scoreDao.deleteScoresForMatch( + matchId: testMatch1.id, + ); + + expect(deleted, false); + }); + + // Verifies that deleteScoresForPlayer returns false when player has no scores. + test('Deleting scores for player with no scores returns false', () async { + final deleted = await database.scoreDao.deleteScoresForPlayer( + playerId: testPlayer1.id, + ); + + expect(deleted, false); + }); + + // Verifies that multiple players in same match can have independent score updates. + test('Multiple players in same match have independent scores', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 10, + change: 10, + ); + await database.scoreDao.addScore( + playerId: testPlayer2.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 20, + change: 20, + ); + + await database.scoreDao.updateScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + newScore: 100, + newChange: 90, + ); + + final player1Score = await database.scoreDao.getScoreForRound( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + ); + final player2Score = await database.scoreDao.getScoreForRound( + playerId: testPlayer2.id, + matchId: testMatch1.id, + roundNumber: 1, + ); + + expect(player1Score!.score, 100); + expect(player2Score!.score, 20); + }); + + // Verifies that scores are isolated across different matches. + test('Scores are isolated across different matches', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 10, + change: 10, + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch2.id, + roundNumber: 1, + score: 50, + change: 50, + ); + + final match1Scores = await database.scoreDao.getPlayerScoresInMatch( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + final match2Scores = await database.scoreDao.getPlayerScoresInMatch( + playerId: testPlayer1.id, + matchId: testMatch2.id, + ); + + expect(match1Scores.length, 1); + expect(match2Scores.length, 1); + expect(match1Scores[0].score, 10); + expect(match2Scores[0].score, 50); + }); + + // Verifies that getTotalScoreForPlayer returns latest score across multiple rounds. + test('Total score for player returns latest cumulative score', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 2, + score: 25, + change: 25, + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 10, + change: 10, + ); + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 3, + score: 50, + change: 25, + ); + + final totalScore = await database.scoreDao.getTotalScoreForPlayer( + playerId: testPlayer1.id, + matchId: testMatch1.id, + ); + + // Should return the highest round's score + expect(totalScore, 50); + }); + + // Verifies that updating one player's score doesn't affect another player's score in same round. + test('Updating one player score does not affect other players in same round', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 10, + change: 10, + ); + await database.scoreDao.addScore( + playerId: testPlayer2.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 20, + change: 20, + ); + await database.scoreDao.addScore( + playerId: testPlayer3.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 30, + change: 30, + ); + + await database.scoreDao.updateScore( + playerId: testPlayer2.id, + matchId: testMatch1.id, + roundNumber: 1, + newScore: 99, + newChange: 89, + ); + + final scores = await database.scoreDao.getScoresForMatch( + matchId: testMatch1.id, + ); + + expect(scores.length, 3); + expect(scores.where((s) => s.playerId == testPlayer1.id).first.score, 10); + expect(scores.where((s) => s.playerId == testPlayer2.id).first.score, 99); + expect(scores.where((s) => s.playerId == testPlayer3.id).first.score, 30); + }); + + // Verifies that deleting a player's scores only affects that specific player. + test('Deleting player scores only affects target player', () async { + await database.scoreDao.addScore( + playerId: testPlayer1.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 10, + change: 10, + ); + await database.scoreDao.addScore( + playerId: testPlayer2.id, + matchId: testMatch1.id, + roundNumber: 1, + score: 20, + change: 20, + ); + + await database.scoreDao.deleteScoresForPlayer( + playerId: testPlayer1.id, + ); + + final match1Scores = await database.scoreDao.getScoresForMatch( + matchId: testMatch1.id, + ); + + expect(match1Scores.length, 1); + expect(match1Scores[0].playerId, testPlayer2.id); + }); + }); +}