Merge pull request 'MVP' (#141) from development into main
Reviewed-on: #141 Reviewed-by: gelbeinhalb <spam@yannick-weigert.de>
57
.gitea/workflows/pull_request.yaml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
name: Pull Request Pipeline
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- 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: Get dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Analyze Formatting
|
||||||
|
run: flutter analyze lib test
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
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: Get dependencies
|
||||||
|
run: flutter pub get
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: flutter test
|
||||||
50
.gitea/workflows/push.yaml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
name: Push Pipeline
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "development"
|
||||||
|
- "main"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
format:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: false # Needs bot user
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
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: Get & upgrade dependencies
|
||||||
|
run: |
|
||||||
|
flutter pub get
|
||||||
|
flutter pub upgrade --major-versions
|
||||||
|
|
||||||
|
- name: Auto-format
|
||||||
|
run: |
|
||||||
|
dart format lib
|
||||||
|
dart fix --apply lib
|
||||||
|
|
||||||
|
# Needs credentials, push access and the right files need to be staged
|
||||||
|
- name: Commit Changes
|
||||||
|
run: |
|
||||||
|
git config --global user.name "Gitea Actions"
|
||||||
|
git config --global user.email "actions@gitea.com"
|
||||||
|
git status
|
||||||
|
git add lib/
|
||||||
|
git status
|
||||||
|
git commit -m "Actions: Auto-formatting [skip ci]"
|
||||||
|
git push
|
||||||
1
.gitignore
vendored
@@ -195,3 +195,4 @@ app.*.map.json
|
|||||||
/android/app/debug
|
/android/app/debug
|
||||||
/android/app/profile
|
/android/app/profile
|
||||||
/android/app/release
|
/android/app/release
|
||||||
|
/devtools_options.yaml
|
||||||
|
|||||||
14
.metadata
@@ -15,19 +15,7 @@ migration:
|
|||||||
- platform: root
|
- platform: root
|
||||||
create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
||||||
base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
||||||
- platform: android
|
- platform: ios
|
||||||
create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
|
||||||
base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
|
||||||
- platform: linux
|
|
||||||
create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
|
||||||
base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
|
||||||
- platform: macos
|
|
||||||
create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
|
||||||
base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
|
||||||
- platform: web
|
|
||||||
create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
|
||||||
base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
|
||||||
- platform: windows
|
|
||||||
create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
create_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
||||||
base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
base_revision: b25305a8832cfc6ba632a7f87ad455e319dccce8
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:theme="@style/LaunchTheme"
|
android:theme="@style/LaunchTheme"
|
||||||
|
|||||||
BIN
android/app/src/main/ic_launcher-playstore.png
Normal file
|
After Width: | Height: | Size: 8.0 KiB |
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Modify this file to customize your launch splash screen -->
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:drawable="@android:color/white" />
|
<item android:drawable="@color/launch_background" />
|
||||||
|
|
||||||
<!-- You can insert your own image assets here -->
|
<!-- You can insert your own image assets here -->
|
||||||
<!-- <item>
|
<!-- <item>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
Before Width: | Height: | Size: 544 B |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 828 B |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 448 B |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 442 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 656 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp
Normal file
|
After Width: | Height: | Size: 348 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 721 B |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 508 B |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 704 B |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 824 B |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
5
android/app/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="app_icon_background">#E6F1E4</color>
|
||||||
|
<color name="launch_background">#0B0B0B</color>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Referenz unbedingt als @color/launch_background (nicht @colors/...) -->
|
||||||
|
<color name="ic_launcher_background">@color/app_icon_background</color>
|
||||||
|
</resources>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
the Flutter engine draws its first frame -->
|
the Flutter engine draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@color/launch_background</item>
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
This theme determines the color of the Android Window while your
|
This theme determines the color of the Android Window while your
|
||||||
|
|||||||
104
assets/schema.json
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
{
|
||||||
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"players": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"createdAt",
|
||||||
|
"name"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"groups": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"memberIds": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"createdAt",
|
||||||
|
"memberIds"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"matches": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"groupId": {
|
||||||
|
"anyOf": [
|
||||||
|
{"type": "string"},
|
||||||
|
{"type": "null"}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"playerIds": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"winnerId": {
|
||||||
|
"anyOf": [
|
||||||
|
{"type": "string"},
|
||||||
|
{"type": "null"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"createdAt",
|
||||||
|
"groupId",
|
||||||
|
"playerIds"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"players",
|
||||||
|
"groups",
|
||||||
|
"matches"
|
||||||
|
]
|
||||||
|
}
|
||||||
34
ios/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
**/dgph
|
||||||
|
*.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
*.moved-aside
|
||||||
|
*.pbxuser
|
||||||
|
*.perspectivev3
|
||||||
|
**/*sync/
|
||||||
|
.sconsign.dblite
|
||||||
|
.tags*
|
||||||
|
**/.vagrant/
|
||||||
|
**/DerivedData/
|
||||||
|
Icon?
|
||||||
|
**/Pods/
|
||||||
|
**/.symlinks/
|
||||||
|
profile
|
||||||
|
xcuserdata
|
||||||
|
**/.generated/
|
||||||
|
Flutter/App.framework
|
||||||
|
Flutter/Flutter.framework
|
||||||
|
Flutter/Flutter.podspec
|
||||||
|
Flutter/Generated.xcconfig
|
||||||
|
Flutter/ephemeral/
|
||||||
|
Flutter/app.flx
|
||||||
|
Flutter/app.zip
|
||||||
|
Flutter/flutter_assets/
|
||||||
|
Flutter/flutter_export_environment.sh
|
||||||
|
ServiceDefinitions.json
|
||||||
|
Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!default.mode1v3
|
||||||
|
!default.mode2v3
|
||||||
|
!default.pbxuser
|
||||||
|
!default.perspectivev3
|
||||||
26
ios/Flutter/AppFrameworkInfo.plist
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>io.flutter.flutter.app</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>MinimumOSVersion</key>
|
||||||
|
<string>13.0</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
2
ios/Flutter/Debug.xcconfig
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
2
ios/Flutter/Release.xcconfig
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
43
ios/Podfile
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# Uncomment this line to define a global platform for your project
|
||||||
|
# platform :ios, '13.0'
|
||||||
|
|
||||||
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutter_root
|
||||||
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||||
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
|
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||||
|
end
|
||||||
|
|
||||||
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
|
return matches[1].strip if matches
|
||||||
|
end
|
||||||
|
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_ios_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
|
||||||
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
target 'RunnerTests' do
|
||||||
|
inherit! :search_paths
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_ios_build_settings(target)
|
||||||
|
end
|
||||||
|
end
|
||||||
731
ios/Runner.xcodeproj/project.pbxproj
Normal file
@@ -0,0 +1,731 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 54;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
8AD879B4BA24BC1EB84E1092 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8543AAE6520EA0C0B3AF8FEE /* Pods_RunnerTests.framework */; };
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
DDD6907F99188C9B97C6B11F /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D622CF241440C10C19C0D397 /* Pods_Runner.framework */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||||
|
remoteInfo = Runner;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Embed Frameworks";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
13301BC306FBFE16F253F2B9 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
32DDFE3349B038E1CA758D7B /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
8543AAE6520EA0C0B3AF8FEE /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
96CDE41BAA7259C918DB326B /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
B194217AD06D15D90AAF9056 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
B68CF4A64F0B5E45B43D6900 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
D622CF241440C10C19C0D397 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
E754D1191B3E54E52B6DCC49 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
6F6FEDCE9772FEF7A6255134 /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
8AD879B4BA24BC1EB84E1092 /* Pods_RunnerTests.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
DDD6907F99188C9B97C6B11F /* Pods_Runner.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||||
|
);
|
||||||
|
path = RunnerTests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146E51CF9000F007C117D = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||||
|
ABF0E17C36D6999806C09130 /* Pods */,
|
||||||
|
F14326E3F17437DD2E32AB7B /* Frameworks */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146EF1CF9000F007C117D /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */,
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
|
);
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
ABF0E17C36D6999806C09130 /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
B194217AD06D15D90AAF9056 /* Pods-Runner.debug.xcconfig */,
|
||||||
|
32DDFE3349B038E1CA758D7B /* Pods-Runner.release.xcconfig */,
|
||||||
|
13301BC306FBFE16F253F2B9 /* Pods-Runner.profile.xcconfig */,
|
||||||
|
96CDE41BAA7259C918DB326B /* Pods-RunnerTests.debug.xcconfig */,
|
||||||
|
B68CF4A64F0B5E45B43D6900 /* Pods-RunnerTests.release.xcconfig */,
|
||||||
|
E754D1191B3E54E52B6DCC49 /* Pods-RunnerTests.profile.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Pods;
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
F14326E3F17437DD2E32AB7B /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
D622CF241440C10C19C0D397 /* Pods_Runner.framework */,
|
||||||
|
8543AAE6520EA0C0B3AF8FEE /* Pods_RunnerTests.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
|
buildPhases = (
|
||||||
|
F7D5E29C2C77E2E8925BBB8A /* [CP] Check Pods Manifest.lock */,
|
||||||
|
331C807D294A63A400263BE5 /* Sources */,
|
||||||
|
331C807F294A63A400263BE5 /* Resources */,
|
||||||
|
6F6FEDCE9772FEF7A6255134 /* Frameworks */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = RunnerTests;
|
||||||
|
productName = RunnerTests;
|
||||||
|
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
|
buildPhases = (
|
||||||
|
8947D8DE27F8CB7D5A5F265C /* [CP] Check Pods Manifest.lock */,
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
0CC58B149CD3F41CF94E1C52 /* [CP] Embed Pods Frameworks */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Runner;
|
||||||
|
productName = Runner;
|
||||||
|
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
BuildIndependentTargetsInParallel = YES;
|
||||||
|
LastUpgradeCheck = 1510;
|
||||||
|
ORGANIZATIONNAME = "";
|
||||||
|
TargetAttributes = {
|
||||||
|
331C8080294A63A400263BE5 = {
|
||||||
|
CreatedOnToolsVersion = 14.0;
|
||||||
|
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D = {
|
||||||
|
CreatedOnToolsVersion = 7.3.1;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
331C807F294A63A400263BE5 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
0CC58B149CD3F41CF94E1C52 /* [CP] Embed Pods Frameworks */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||||
|
);
|
||||||
|
name = "Thin Binary";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
|
};
|
||||||
|
8947D8DE27F8CB7D5A5F265C /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Run Script";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
|
};
|
||||||
|
F7D5E29C2C77E2E8925BBB8A /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
331C807D294A63A400263BE5 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||||
|
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C146FB1CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = Main.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C147001CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = LaunchScreen.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = RJB4MM6RVS;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
331C8088294A63A400263BE5 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 96CDE41BAA7259C918DB326B /* Pods-RunnerTests.debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
331C8089294A63A400263BE5 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = B68CF4A64F0B5E45B43D6900 /* Pods-RunnerTests.release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
331C808A294A63A400263BE5 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = E754D1191B3E54E52B6DCC49 /* Pods-RunnerTests.profile.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
97C147031CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147041CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
97C147061CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = RJB4MM6RVS;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147071CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
DEVELOPMENT_TEAM = RJB4MM6RVS;
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.gameTracker;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
331C8088294A63A400263BE5 /* Debug */,
|
||||||
|
331C8089294A63A400263BE5 /* Release */,
|
||||||
|
331C808A294A63A400263BE5 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147031CF9000F007C117D /* Debug */,
|
||||||
|
97C147041CF9000F007C117D /* Release */,
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147061CF9000F007C117D /* Debug */,
|
||||||
|
97C147071CF9000F007C117D /* Release */,
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
}
|
||||||
7
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
101
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1510"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||||
|
BuildableName = "RunnerTests.xctest"
|
||||||
|
BlueprintName = "RunnerTests"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
enableGPUValidationMode = "1"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
10
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
13
ios/Runner/AppDelegate.swift
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@main
|
||||||
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
|
) -> Bool {
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
14
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "icon_x1024.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"platform" : "ios",
|
||||||
|
"size" : "1024x1024"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
ios/Runner/Assets.xcassets/AppIcon.appiconset/icon_x1024.png
Normal file
|
After Width: | Height: | Size: 8.8 KiB |
6
ios/Runner/Assets.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"colors" : [
|
||||||
|
{
|
||||||
|
"color" : {
|
||||||
|
"color-space" : "srgb",
|
||||||
|
"components" : {
|
||||||
|
"alpha" : "1.000",
|
||||||
|
"blue" : "0.043",
|
||||||
|
"green" : "0.043",
|
||||||
|
"red" : "0.043"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"idiom" : "universal"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
21
ios/Runner/Assets.xcassets/LauncherIcon.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "icon.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
ios/Runner/Assets.xcassets/LauncherIcon.imageset/icon.png
vendored
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
43
ios/Runner/Base.lproj/LaunchScreen.storyboard
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24412" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24405"/>
|
||||||
|
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<subviews>
|
||||||
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="LauncherIcon" translatesAutoresizingMaskIntoConstraints="NO" id="ygV-Op-Bu5">
|
||||||
|
<rect key="frame" x="46" y="334" width="301" height="184"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||||
|
</imageView>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" name="LauncherBackgroundColor"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="80.152671755725194" y="264.08450704225356"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<color key="tintColor" red="0.90196078431372551" green="0.94509803921568625" blue="0.89411764705882346" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<resources>
|
||||||
|
<image name="LauncherIcon" width="1000" height="1000"/>
|
||||||
|
<namedColor name="LauncherBackgroundColor">
|
||||||
|
<color red="0.90196078431372551" green="0.94509803921568625" blue="0.89411764705882346" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</namedColor>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
29
ios/Runner/Base.lproj/Main.storyboard
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24412" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
|
||||||
|
<device id="retina6_12" orientation="portrait" appearance="light"/>
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24405"/>
|
||||||
|
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Flutter View Controller-->
|
||||||
|
<scene sceneID="tne-QT-ifu">
|
||||||
|
<objects>
|
||||||
|
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="141" y="131"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
||||||
47
ios/Runner/Info.plist
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Game Tracker</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>game_tracker</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
1
ios/Runner/Runner-Bridging-Header.h
Normal file
@@ -0,0 +1 @@
|
|||||||
|
#import "GeneratedPluginRegistrant.h"
|
||||||
12
ios/RunnerTests/RunnerTests.swift
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class RunnerTests: XCTestCase {
|
||||||
|
|
||||||
|
func testExample() {
|
||||||
|
// If you add code to the Runner application, consider adding tests here.
|
||||||
|
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
5
l10n.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
arb-dir: lib/l10n/arb
|
||||||
|
template-arb-file: app_en.arb
|
||||||
|
output-localization-file: app_localizations.dart
|
||||||
|
output-dir: lib/l10n/generated
|
||||||
|
nullable-getter: false
|
||||||
6
lib/core/constants.dart
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
class Constants {
|
||||||
|
Constants._(); // Private constructor to prevent instantiation
|
||||||
|
|
||||||
|
/// Minimum duration of all app skeletons
|
||||||
|
static Duration minimumSkeletonDuration = const Duration(milliseconds: 250);
|
||||||
|
}
|
||||||
59
lib/core/custom_theme.dart
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class CustomTheme {
|
||||||
|
CustomTheme._(); // Private constructor to prevent instantiation
|
||||||
|
|
||||||
|
// ==================== Colors ====================
|
||||||
|
static Color primaryColor = const Color(0xFF7505E4);
|
||||||
|
static Color secondaryColor = const Color(0xFFAFA2FF);
|
||||||
|
static Color backgroundColor = const Color(0xFF0B0B0B);
|
||||||
|
static Color boxColor = const Color(0xFF101010);
|
||||||
|
static Color onBoxColor = const Color(0xFF181818);
|
||||||
|
static Color boxBorder = const Color(0xFF272727);
|
||||||
|
static const Color textColor = Colors.white;
|
||||||
|
|
||||||
|
// ==================== Border Radius ====================
|
||||||
|
static const double standardBorderRadius = 12.0;
|
||||||
|
static BorderRadius get standardBorderRadiusAll =>
|
||||||
|
BorderRadius.circular(standardBorderRadius);
|
||||||
|
|
||||||
|
// ==================== Padding & Margins ====================
|
||||||
|
static const EdgeInsets standardMargin = EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 10,
|
||||||
|
);
|
||||||
|
static const EdgeInsets tileMargin = EdgeInsets.symmetric(
|
||||||
|
horizontal: 12,
|
||||||
|
vertical: 5,
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==================== Decorations ====================
|
||||||
|
static BoxDecoration standardBoxDecoration = BoxDecoration(
|
||||||
|
color: boxColor,
|
||||||
|
border: Border.all(color: boxBorder),
|
||||||
|
borderRadius: standardBorderRadiusAll,
|
||||||
|
);
|
||||||
|
|
||||||
|
static BoxDecoration highlightedBoxDecoration = BoxDecoration(
|
||||||
|
color: boxColor,
|
||||||
|
border: Border.all(color: primaryColor),
|
||||||
|
borderRadius: standardBorderRadiusAll,
|
||||||
|
boxShadow: [BoxShadow(color: primaryColor.withAlpha(120), blurRadius: 12)],
|
||||||
|
);
|
||||||
|
|
||||||
|
// ==================== App Bar Theme ====================
|
||||||
|
static AppBarTheme appBarTheme = AppBarTheme(
|
||||||
|
backgroundColor: backgroundColor,
|
||||||
|
foregroundColor: textColor,
|
||||||
|
elevation: 0,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
centerTitle: true,
|
||||||
|
titleTextStyle: const TextStyle(
|
||||||
|
color: textColor,
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
iconTheme: const IconThemeData(color: textColor),
|
||||||
|
);
|
||||||
|
}
|
||||||
52
lib/core/enums.dart
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||||
|
|
||||||
|
/// Button types used for styling the [CustomWidthButton]
|
||||||
|
/// - [ButtonType.primary]: Primary button style.
|
||||||
|
/// - [ButtonType.secondary]: Secondary button style.
|
||||||
|
/// - [ButtonType.tertiary]: Tertiary button style.
|
||||||
|
enum ButtonType { primary, secondary, tertiary }
|
||||||
|
|
||||||
|
/// Result types for import operations in the [SettingsView]
|
||||||
|
/// - [ImportResult.success]: The import operation was successful.
|
||||||
|
/// - [ImportResult.canceled]: The import operation was canceled by the user.
|
||||||
|
/// - [ImportResult.fileReadError]: There was an error reading the selected file.
|
||||||
|
/// - [ImportResult.invalidSchema]: The JSON schema of the imported data is invalid.
|
||||||
|
/// - [ImportResult.formatException]: A format exception occurred during import.
|
||||||
|
/// - [ImportResult.unknownException]: An exception occurred during import.
|
||||||
|
enum ImportResult {
|
||||||
|
success,
|
||||||
|
canceled,
|
||||||
|
fileReadError,
|
||||||
|
invalidSchema,
|
||||||
|
formatException,
|
||||||
|
unknownException,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result types for export operations in the [SettingsView]
|
||||||
|
/// - [ExportResult.success]: The export operation was successful.
|
||||||
|
/// - [ExportResult.canceled]: The export operation was canceled by the user.
|
||||||
|
/// - [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 }
|
||||||
|
|
||||||
|
/// 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.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
214
lib/data/dao/group_dao.dart
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
part 'group_dao.g.dart';
|
||||||
|
|
||||||
|
@DriftAccessor(tables: [GroupTable, PlayerGroupTable])
|
||||||
|
class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||||
|
GroupDao(super.db);
|
||||||
|
|
||||||
|
/// Retrieves all groups from the database.
|
||||||
|
Future<List<Group>> getAllGroups() async {
|
||||||
|
final query = select(groupTable);
|
||||||
|
final result = await query.get();
|
||||||
|
return Future.wait(
|
||||||
|
result.map((groupData) async {
|
||||||
|
final members = await db.playerGroupDao.getPlayersOfGroup(
|
||||||
|
groupId: groupData.id,
|
||||||
|
);
|
||||||
|
return Group(
|
||||||
|
id: groupData.id,
|
||||||
|
name: groupData.name,
|
||||||
|
members: members,
|
||||||
|
createdAt: groupData.createdAt,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves a [Group] by its [groupId], including its members.
|
||||||
|
Future<Group> getGroupById({required String groupId}) async {
|
||||||
|
final query = select(groupTable)..where((g) => g.id.equals(groupId));
|
||||||
|
final result = await query.getSingle();
|
||||||
|
|
||||||
|
List<Player> members = await db.playerGroupDao.getPlayersOfGroup(
|
||||||
|
groupId: groupId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Group(
|
||||||
|
id: result.id,
|
||||||
|
name: result.name,
|
||||||
|
members: members,
|
||||||
|
createdAt: result.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new group with the given [id] and [name] to the database.
|
||||||
|
/// This method also adds the group's members to the [PlayerGroupTable].
|
||||||
|
Future<bool> addGroup({required Group group}) async {
|
||||||
|
if (!await groupExists(groupId: group.id)) {
|
||||||
|
await db.transaction(() async {
|
||||||
|
await into(groupTable).insert(
|
||||||
|
GroupTableCompanion.insert(
|
||||||
|
id: group.id,
|
||||||
|
name: group.name,
|
||||||
|
createdAt: group.createdAt,
|
||||||
|
),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
await Future.wait(
|
||||||
|
group.members.map((player) => db.playerDao.addPlayer(player: player)),
|
||||||
|
);
|
||||||
|
await db.batch(
|
||||||
|
(b) => b.insertAll(
|
||||||
|
db.playerGroupTable,
|
||||||
|
group.members
|
||||||
|
.map(
|
||||||
|
(member) => PlayerGroupTableCompanion.insert(
|
||||||
|
playerId: member.id,
|
||||||
|
groupId: group.id,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds multiple groups to the database.
|
||||||
|
/// Also adds the group's members to the [PlayerGroupTable].
|
||||||
|
Future<void> addGroupsAsList({required List<Group> groups}) async {
|
||||||
|
if (groups.isEmpty) return;
|
||||||
|
await db.transaction(() async {
|
||||||
|
// Deduplicate groups by id - keep first occurrence
|
||||||
|
final Map<String, Group> uniqueGroups = {};
|
||||||
|
for (final g in groups) {
|
||||||
|
uniqueGroups.putIfAbsent(g.id, () => g);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert unique groups in batch
|
||||||
|
// Using insertOrIgnore to avoid triggering cascade deletes on
|
||||||
|
// player_group associations when groups already exist
|
||||||
|
await db.batch(
|
||||||
|
(b) => b.insertAll(
|
||||||
|
groupTable,
|
||||||
|
uniqueGroups.values
|
||||||
|
.map(
|
||||||
|
(group) => GroupTableCompanion.insert(
|
||||||
|
id: group.id,
|
||||||
|
name: group.name,
|
||||||
|
createdAt: group.createdAt,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
mode: InsertMode.insertOrIgnore,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Collect unique players from all groups
|
||||||
|
final uniquePlayers = <String, Player>{};
|
||||||
|
for (final g in uniqueGroups.values) {
|
||||||
|
for (final m in g.members) {
|
||||||
|
uniquePlayers[m.id] = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uniquePlayers.isNotEmpty) {
|
||||||
|
// Using insertOrIgnore to avoid triggering cascade deletes on
|
||||||
|
// player_group associations when players already exist
|
||||||
|
await db.batch(
|
||||||
|
(b) => b.insertAll(
|
||||||
|
db.playerTable,
|
||||||
|
uniquePlayers.values
|
||||||
|
.map(
|
||||||
|
(p) => PlayerTableCompanion.insert(
|
||||||
|
id: p.id,
|
||||||
|
name: p.name,
|
||||||
|
createdAt: p.createdAt,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
mode: InsertMode.insertOrIgnore,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare all player-group associations in one list (unique pairs)
|
||||||
|
final Set<String> seenPairs = {};
|
||||||
|
final List<PlayerGroupTableCompanion> pgRows = [];
|
||||||
|
for (final g in uniqueGroups.values) {
|
||||||
|
for (final m in g.members) {
|
||||||
|
final key = '${m.id}|${g.id}';
|
||||||
|
if (!seenPairs.contains(key)) {
|
||||||
|
seenPairs.add(key);
|
||||||
|
pgRows.add(
|
||||||
|
PlayerGroupTableCompanion.insert(playerId: m.id, groupId: g.id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pgRows.isNotEmpty) {
|
||||||
|
await db.batch((b) {
|
||||||
|
for (final pg in pgRows) {
|
||||||
|
b.insert(db.playerGroupTable, pg, mode: InsertMode.insertOrReplace);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes the group with the given [id] from the database.
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> deleteGroup({required String groupId}) async {
|
||||||
|
final query = (delete(groupTable)..where((g) => g.id.equals(groupId)));
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the name of the group with the given [id] to [newName].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> updateGroupname({
|
||||||
|
required String groupId,
|
||||||
|
required String newName,
|
||||||
|
}) async {
|
||||||
|
final rowsAffected =
|
||||||
|
await (update(groupTable)..where((g) => g.id.equals(groupId))).write(
|
||||||
|
GroupTableCompanion(name: Value(newName)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the number of groups in the database.
|
||||||
|
Future<int> getGroupCount() async {
|
||||||
|
final count =
|
||||||
|
await (selectOnly(groupTable)..addColumns([groupTable.id.count()]))
|
||||||
|
.map((row) => row.read(groupTable.id.count()))
|
||||||
|
.getSingle();
|
||||||
|
return count ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a group with the given [groupId] exists in the database.
|
||||||
|
/// Returns `true` if the group exists, `false` otherwise.
|
||||||
|
Future<bool> groupExists({required String groupId}) async {
|
||||||
|
final query = select(groupTable)..where((g) => g.id.equals(groupId));
|
||||||
|
final result = await query.getSingleOrNull();
|
||||||
|
return result != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes all groups from the database.
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> deleteAllGroups() async {
|
||||||
|
final query = delete(groupTable);
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
lib/data/dao/group_dao.g.dart
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'group_dao.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
mixin _$GroupDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||||
|
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
||||||
|
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
||||||
|
$PlayerGroupTableTable get playerGroupTable =>
|
||||||
|
attachedDatabase.playerGroupTable;
|
||||||
|
}
|
||||||
98
lib/data/dao/group_match_dao.dart
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:game_tracker/data/db/database.dart';
|
||||||
|
import 'package:game_tracker/data/db/tables/group_match_table.dart';
|
||||||
|
import 'package:game_tracker/data/dto/group.dart';
|
||||||
|
|
||||||
|
part 'group_match_dao.g.dart';
|
||||||
|
|
||||||
|
@DriftAccessor(tables: [GroupMatchTable])
|
||||||
|
class GroupMatchDao extends DatabaseAccessor<AppDatabase>
|
||||||
|
with _$GroupMatchDaoMixin {
|
||||||
|
GroupMatchDao(super.db);
|
||||||
|
|
||||||
|
/// Associates a group with a match by inserting a record into the
|
||||||
|
/// [GroupMatchTable].
|
||||||
|
Future<void> addGroupToMatch({
|
||||||
|
required String matchId,
|
||||||
|
required String groupId,
|
||||||
|
}) async {
|
||||||
|
if (await matchHasGroup(matchId: matchId)) {
|
||||||
|
throw Exception('Match already has a group');
|
||||||
|
}
|
||||||
|
await into(groupMatchTable).insert(
|
||||||
|
GroupMatchTableCompanion.insert(groupId: groupId, matchId: matchId),
|
||||||
|
mode: InsertMode.insertOrIgnore,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the [Group] associated with the given [matchId].
|
||||||
|
/// Returns `null` if no group is found.
|
||||||
|
Future<Group?> getGroupOfMatch({required String matchId}) async {
|
||||||
|
final result = await (select(
|
||||||
|
groupMatchTable,
|
||||||
|
)..where((g) => g.matchId.equals(matchId))).getSingleOrNull();
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final group = await db.groupDao.getGroupById(groupId: result.groupId);
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if there is a group associated with the given [matchId].
|
||||||
|
/// Returns `true` if there is a group, otherwise `false`.
|
||||||
|
Future<bool> matchHasGroup({required String matchId}) async {
|
||||||
|
final count =
|
||||||
|
await (selectOnly(groupMatchTable)
|
||||||
|
..where(groupMatchTable.matchId.equals(matchId))
|
||||||
|
..addColumns([groupMatchTable.groupId.count()]))
|
||||||
|
.map((row) => row.read(groupMatchTable.groupId.count()))
|
||||||
|
.getSingle();
|
||||||
|
return (count ?? 0) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a specific group is associated with a specific match.
|
||||||
|
/// Returns `true` if the group is in the match, otherwise `false`.
|
||||||
|
Future<bool> isGroupInMatch({
|
||||||
|
required String matchId,
|
||||||
|
required String groupId,
|
||||||
|
}) async {
|
||||||
|
final count =
|
||||||
|
await (selectOnly(groupMatchTable)
|
||||||
|
..where(
|
||||||
|
groupMatchTable.matchId.equals(matchId) &
|
||||||
|
groupMatchTable.groupId.equals(groupId),
|
||||||
|
)
|
||||||
|
..addColumns([groupMatchTable.groupId.count()]))
|
||||||
|
.map((row) => row.read(groupMatchTable.groupId.count()))
|
||||||
|
.getSingle();
|
||||||
|
return (count ?? 0) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the association of a group from a match based on [groupId] and
|
||||||
|
/// [matchId].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> removeGroupFromMatch({
|
||||||
|
required String matchId,
|
||||||
|
required String groupId,
|
||||||
|
}) async {
|
||||||
|
final query = delete(groupMatchTable)
|
||||||
|
..where((g) => g.matchId.equals(matchId) & g.groupId.equals(groupId));
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the group associated with a match to [newGroupId] based on
|
||||||
|
/// [matchId].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> updateGroupOfMatch({
|
||||||
|
required String matchId,
|
||||||
|
required String newGroupId,
|
||||||
|
}) async {
|
||||||
|
final updatedRows =
|
||||||
|
await (update(groupMatchTable)..where((g) => g.matchId.equals(matchId)))
|
||||||
|
.write(GroupMatchTableCompanion(groupId: Value(newGroupId)));
|
||||||
|
return updatedRows > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
lib/data/dao/group_match_dao.g.dart
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'group_match_dao.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
mixin _$GroupMatchDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||||
|
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
||||||
|
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
||||||
|
$GroupMatchTableTable get groupMatchTable => attachedDatabase.groupMatchTable;
|
||||||
|
}
|
||||||
326
lib/data/dao/match_dao.dart
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
part 'match_dao.g.dart';
|
||||||
|
|
||||||
|
@DriftAccessor(tables: [MatchTable])
|
||||||
|
class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
|
||||||
|
MatchDao(super.db);
|
||||||
|
|
||||||
|
/// Retrieves all matches from the database.
|
||||||
|
Future<List<Match>> getAllMatches() async {
|
||||||
|
final query = select(matchTable);
|
||||||
|
final result = await query.get();
|
||||||
|
|
||||||
|
return Future.wait(
|
||||||
|
result.map((row) async {
|
||||||
|
final group = await db.groupMatchDao.getGroupOfMatch(matchId: row.id);
|
||||||
|
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,
|
||||||
|
group: group,
|
||||||
|
players: players,
|
||||||
|
createdAt: row.createdAt,
|
||||||
|
winner: winner,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves a [Match] by its [matchId].
|
||||||
|
Future<Match> getMatchById({required String matchId}) async {
|
||||||
|
final query = select(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
|
final result = await query.getSingle();
|
||||||
|
|
||||||
|
List<Player>? players;
|
||||||
|
if (await db.playerMatchDao.matchHasPlayers(matchId: matchId)) {
|
||||||
|
players = await db.playerMatchDao.getPlayersOfMatch(matchId: matchId);
|
||||||
|
}
|
||||||
|
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!);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Match(
|
||||||
|
id: result.id,
|
||||||
|
name: result.name,
|
||||||
|
players: players,
|
||||||
|
group: group,
|
||||||
|
winner: winner,
|
||||||
|
createdAt: result.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
Future<void> addMatch({required Match match}) async {
|
||||||
|
await db.transaction(() async {
|
||||||
|
await into(matchTable).insert(
|
||||||
|
MatchTableCompanion.insert(
|
||||||
|
id: match.id,
|
||||||
|
name: match.name,
|
||||||
|
winnerId: Value(match.winner?.id),
|
||||||
|
createdAt: match.createdAt,
|
||||||
|
),
|
||||||
|
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(
|
||||||
|
matchId: match.id,
|
||||||
|
groupId: match.group!.id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds multiple [Match]s 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.
|
||||||
|
Future<void> addMatchAsList({required List<Match> 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 groups of the matches in batch
|
||||||
|
// Using insertOrIgnore to avoid overwriting existing groups (which would
|
||||||
|
// trigger cascade deletes on player_group associations)
|
||||||
|
await db.batch(
|
||||||
|
(b) => b.insertAll(
|
||||||
|
db.groupTable,
|
||||||
|
matches
|
||||||
|
.where((match) => match.group != null)
|
||||||
|
.map(
|
||||||
|
(matches) => GroupTableCompanion.insert(
|
||||||
|
id: matches.group!.id,
|
||||||
|
name: matches.group!.name,
|
||||||
|
createdAt: matches.group!.createdAt,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
mode: InsertMode.insertOrIgnore,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add all players of the matches in batch (unique)
|
||||||
|
final uniquePlayers = <String, Player>{};
|
||||||
|
for (final match in matches) {
|
||||||
|
if (match.players != null) {
|
||||||
|
for (final p in match.players!) {
|
||||||
|
uniquePlayers[p.id] = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Also include members of groups
|
||||||
|
if (match.group != null) {
|
||||||
|
for (final m in match.group!.members) {
|
||||||
|
uniquePlayers[m.id] = m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uniquePlayers.isNotEmpty) {
|
||||||
|
// Using insertOrIgnore to avoid triggering cascade deletes on
|
||||||
|
// player_group/player_match associations when players already exist
|
||||||
|
await db.batch(
|
||||||
|
(b) => b.insertAll(
|
||||||
|
db.playerTable,
|
||||||
|
uniquePlayers.values
|
||||||
|
.map(
|
||||||
|
(p) => PlayerTableCompanion.insert(
|
||||||
|
id: p.id,
|
||||||
|
name: p.name,
|
||||||
|
createdAt: p.createdAt,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
mode: InsertMode.insertOrIgnore,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add all player-group associations in batch
|
||||||
|
await db.batch((b) {
|
||||||
|
for (final match in matches) {
|
||||||
|
if (match.group != null) {
|
||||||
|
for (final m in match.group!.members) {
|
||||||
|
b.insert(
|
||||||
|
db.playerGroupTable,
|
||||||
|
PlayerGroupTableCompanion.insert(
|
||||||
|
playerId: m.id,
|
||||||
|
groupId: match.group!.id,
|
||||||
|
),
|
||||||
|
mode: InsertMode.insertOrIgnore,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes the match with the given [matchId] from the database.
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> deleteMatch({required String matchId}) async {
|
||||||
|
final query = delete(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the number of matches in the database.
|
||||||
|
Future<int> getMatchCount() async {
|
||||||
|
final count =
|
||||||
|
await (selectOnly(matchTable)..addColumns([matchTable.id.count()]))
|
||||||
|
.map((row) => row.read(matchTable.id.count()))
|
||||||
|
.getSingle();
|
||||||
|
return count ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a match with the given [matchId] exists in the database.
|
||||||
|
/// Returns `true` if the match exists, otherwise `false`.
|
||||||
|
Future<bool> matchExists({required String matchId}) async {
|
||||||
|
final query = select(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
|
final result = await query.getSingleOrNull();
|
||||||
|
return result != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes all matches from the database.
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> deleteAllMatches() async {
|
||||||
|
final query = delete(matchTable);
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sets the winner of the match with the given [matchId] to the player with
|
||||||
|
/// the given [winnerId].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> setWinner({
|
||||||
|
required String matchId,
|
||||||
|
required String winnerId,
|
||||||
|
}) async {
|
||||||
|
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
|
final rowsAffected = await query.write(
|
||||||
|
MatchTableCompanion(winnerId: Value(winnerId)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the winner of the match with the given [matchId].
|
||||||
|
/// Returns the [Player] who won the match, or `null` if no winner is set.
|
||||||
|
Future<Player?> getWinner({required String matchId}) async {
|
||||||
|
final query = select(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
|
final result = await query.getSingleOrNull();
|
||||||
|
if (result == null || result.winnerId == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final winner = await db.playerDao.getPlayerById(playerId: result.winnerId!);
|
||||||
|
return winner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the winner of the match with the given [matchId].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> removeWinner({required String matchId}) async {
|
||||||
|
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
|
final rowsAffected = await query.write(
|
||||||
|
const MatchTableCompanion(winnerId: Value(null)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if the match with the given [matchId] has a winner set.
|
||||||
|
/// Returns `true` if a winner is set, otherwise `false`.
|
||||||
|
Future<bool> hasWinner({required String matchId}) async {
|
||||||
|
final query = select(matchTable)
|
||||||
|
..where((g) => g.id.equals(matchId) & g.winnerId.isNotNull());
|
||||||
|
final result = await query.getSingleOrNull();
|
||||||
|
return result != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Changes the title of the match with the given [matchId] to [newName].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> updateMatchName({
|
||||||
|
required String matchId,
|
||||||
|
required String newName,
|
||||||
|
}) async {
|
||||||
|
final query = update(matchTable)..where((g) => g.id.equals(matchId));
|
||||||
|
final rowsAffected = await query.write(
|
||||||
|
MatchTableCompanion(name: Value(newName)),
|
||||||
|
);
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
lib/data/dao/match_dao.g.dart
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'match_dao.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
mixin _$MatchDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||||
|
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
||||||
|
}
|
||||||
119
lib/data/dao/player_dao.dart
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
part 'player_dao.g.dart';
|
||||||
|
|
||||||
|
@DriftAccessor(tables: [PlayerTable])
|
||||||
|
class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
||||||
|
PlayerDao(super.db);
|
||||||
|
|
||||||
|
/// Retrieves all players from the database.
|
||||||
|
Future<List<Player>> getAllPlayers() async {
|
||||||
|
final query = select(playerTable);
|
||||||
|
final result = await query.get();
|
||||||
|
return result
|
||||||
|
.map(
|
||||||
|
(row) => Player(id: row.id, name: row.name, createdAt: row.createdAt),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves a [Player] by their [id].
|
||||||
|
Future<Player> getPlayerById({required String playerId}) async {
|
||||||
|
final query = select(playerTable)..where((p) => p.id.equals(playerId));
|
||||||
|
final result = await query.getSingle();
|
||||||
|
return Player(
|
||||||
|
id: result.id,
|
||||||
|
name: result.name,
|
||||||
|
createdAt: result.createdAt,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a new [player] to the database.
|
||||||
|
/// If a player with the same ID already exists, updates their name to
|
||||||
|
/// the new one.
|
||||||
|
Future<bool> addPlayer({required Player player}) async {
|
||||||
|
if (!await playerExists(playerId: player.id)) {
|
||||||
|
await into(playerTable).insert(
|
||||||
|
PlayerTableCompanion.insert(
|
||||||
|
id: player.id,
|
||||||
|
name: player.name,
|
||||||
|
createdAt: player.createdAt,
|
||||||
|
),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds multiple [players] to the database in a batch operation.
|
||||||
|
/// Uses insertOrIgnore to avoid triggering cascade deletes on
|
||||||
|
/// player_group associations when players already exist.
|
||||||
|
Future<bool> addPlayersAsList({required List<Player> players}) async {
|
||||||
|
if (players.isEmpty) return false;
|
||||||
|
|
||||||
|
await db.batch(
|
||||||
|
(b) => b.insertAll(
|
||||||
|
playerTable,
|
||||||
|
players
|
||||||
|
.map(
|
||||||
|
(player) => PlayerTableCompanion.insert(
|
||||||
|
id: player.id,
|
||||||
|
name: player.name,
|
||||||
|
createdAt: player.createdAt,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
mode: InsertMode.insertOrIgnore,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes the player with the given [id] from the database.
|
||||||
|
/// Returns `true` if the player was deleted, `false` if the player did not exist.
|
||||||
|
Future<bool> deletePlayer({required String playerId}) async {
|
||||||
|
final query = delete(playerTable)..where((p) => p.id.equals(playerId));
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a player with the given [id] exists in the database.
|
||||||
|
/// Returns `true` if the player exists, `false` otherwise.
|
||||||
|
Future<bool> playerExists({required String playerId}) async {
|
||||||
|
final query = select(playerTable)..where((p) => p.id.equals(playerId));
|
||||||
|
final result = await query.getSingleOrNull();
|
||||||
|
return result != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the name of the player with the given [playerId] to [newName].
|
||||||
|
Future<void> updatePlayername({
|
||||||
|
required String playerId,
|
||||||
|
required String newName,
|
||||||
|
}) async {
|
||||||
|
await (update(playerTable)..where((p) => p.id.equals(playerId))).write(
|
||||||
|
PlayerTableCompanion(name: Value(newName)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the total count of players in the database.
|
||||||
|
Future<int> getPlayerCount() async {
|
||||||
|
final count =
|
||||||
|
await (selectOnly(playerTable)..addColumns([playerTable.id.count()]))
|
||||||
|
.map((row) => row.read(playerTable.id.count()))
|
||||||
|
.getSingle();
|
||||||
|
return count ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Deletes all players from the database.
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> deleteAllPlayers() async {
|
||||||
|
final query = delete(playerTable);
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
lib/data/dao/player_dao.g.dart
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'player_dao.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
mixin _$PlayerDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||||
|
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
||||||
|
}
|
||||||
79
lib/data/dao/player_group_dao.dart
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
part 'player_group_dao.g.dart';
|
||||||
|
|
||||||
|
@DriftAccessor(tables: [PlayerGroupTable, PlayerTable])
|
||||||
|
class PlayerGroupDao extends DatabaseAccessor<AppDatabase>
|
||||||
|
with _$PlayerGroupDaoMixin {
|
||||||
|
PlayerGroupDao(super.db);
|
||||||
|
|
||||||
|
/// No need for a groupHasPlayers method since the members attribute is
|
||||||
|
/// not nullable
|
||||||
|
|
||||||
|
/// Adds a [player] to a group with the given [groupId].
|
||||||
|
/// If the player is already in the group, no action is taken.
|
||||||
|
/// If the player does not exist in the player table, they are added.
|
||||||
|
/// Returns `true` if the player was added, otherwise `false`.
|
||||||
|
Future<bool> addPlayerToGroup({
|
||||||
|
required Player player,
|
||||||
|
required String groupId,
|
||||||
|
}) async {
|
||||||
|
if (await isPlayerInGroup(playerId: player.id, groupId: groupId)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!await db.playerDao.playerExists(playerId: player.id)) {
|
||||||
|
db.playerDao.addPlayer(player: player);
|
||||||
|
}
|
||||||
|
|
||||||
|
await into(playerGroupTable).insert(
|
||||||
|
PlayerGroupTableCompanion.insert(playerId: player.id, groupId: groupId),
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves all players belonging to a specific group by [groupId].
|
||||||
|
Future<List<Player>> getPlayersOfGroup({required String groupId}) async {
|
||||||
|
final query = select(playerGroupTable)
|
||||||
|
..where((pG) => pG.groupId.equals(groupId));
|
||||||
|
final result = await query.get();
|
||||||
|
|
||||||
|
List<Player> groupMembers = List.empty(growable: true);
|
||||||
|
|
||||||
|
for (var entry in result) {
|
||||||
|
final player = await db.playerDao.getPlayerById(playerId: entry.playerId);
|
||||||
|
groupMembers.add(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
return groupMembers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes a player from a group based on [playerId] and [groupId].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> removePlayerFromGroup({
|
||||||
|
required String playerId,
|
||||||
|
required String groupId,
|
||||||
|
}) async {
|
||||||
|
final query = delete(playerGroupTable)
|
||||||
|
..where((p) => p.playerId.equals(playerId) & p.groupId.equals(groupId));
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a player with [playerId] is in the group with [groupId].
|
||||||
|
/// Returns `true` if the player is in the group, otherwise `false`.
|
||||||
|
Future<bool> isPlayerInGroup({
|
||||||
|
required String playerId,
|
||||||
|
required String groupId,
|
||||||
|
}) async {
|
||||||
|
final query = select(playerGroupTable)
|
||||||
|
..where((p) => p.playerId.equals(playerId) & p.groupId.equals(groupId));
|
||||||
|
final result = await query.getSingleOrNull();
|
||||||
|
return result != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
lib/data/dao/player_group_dao.g.dart
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'player_group_dao.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
mixin _$PlayerGroupDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||||
|
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
||||||
|
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
||||||
|
$PlayerGroupTableTable get playerGroupTable =>
|
||||||
|
attachedDatabase.playerGroupTable;
|
||||||
|
}
|
||||||
130
lib/data/dao/player_match_dao.dart
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
part 'player_match_dao.g.dart';
|
||||||
|
|
||||||
|
@DriftAccessor(tables: [PlayerMatchTable])
|
||||||
|
class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
|
||||||
|
with _$PlayerMatchDaoMixin {
|
||||||
|
PlayerMatchDao(super.db);
|
||||||
|
|
||||||
|
/// Associates a player with a match by inserting a record into the
|
||||||
|
/// [PlayerMatchTable].
|
||||||
|
Future<void> addPlayerToMatch({
|
||||||
|
required String matchId,
|
||||||
|
required String playerId,
|
||||||
|
}) async {
|
||||||
|
await into(playerMatchTable).insert(
|
||||||
|
PlayerMatchTableCompanion.insert(playerId: playerId, matchId: matchId),
|
||||||
|
mode: InsertMode.insertOrIgnore,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves a list of [Player]s associated with the given [matchId].
|
||||||
|
/// Returns null if no players are found.
|
||||||
|
Future<List<Player>?> getPlayersOfMatch({required String matchId}) async {
|
||||||
|
final result = await (select(
|
||||||
|
playerMatchTable,
|
||||||
|
)..where((p) => p.matchId.equals(matchId))).get();
|
||||||
|
|
||||||
|
if (result.isEmpty) return null;
|
||||||
|
|
||||||
|
final futures = result.map(
|
||||||
|
(row) => db.playerDao.getPlayerById(playerId: row.playerId),
|
||||||
|
);
|
||||||
|
final players = await Future.wait(futures);
|
||||||
|
return players;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if there are any players associated with the given [matchId].
|
||||||
|
/// Returns `true` if there are players, otherwise `false`.
|
||||||
|
Future<bool> matchHasPlayers({required String matchId}) async {
|
||||||
|
final count =
|
||||||
|
await (selectOnly(playerMatchTable)
|
||||||
|
..where(playerMatchTable.matchId.equals(matchId))
|
||||||
|
..addColumns([playerMatchTable.playerId.count()]))
|
||||||
|
.map((row) => row.read(playerMatchTable.playerId.count()))
|
||||||
|
.getSingle();
|
||||||
|
return (count ?? 0) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks if a specific player is associated with a specific match.
|
||||||
|
/// Returns `true` if the player is in the match, otherwise `false`.
|
||||||
|
Future<bool> isPlayerInMatch({
|
||||||
|
required String matchId,
|
||||||
|
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();
|
||||||
|
return (count ?? 0) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes the association of a player with a match by deleting the record
|
||||||
|
/// from the [PlayerMatchTable].
|
||||||
|
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||||
|
Future<bool> removePlayerFromMatch({
|
||||||
|
required String matchId,
|
||||||
|
required String playerId,
|
||||||
|
}) async {
|
||||||
|
final query = delete(playerMatchTable)
|
||||||
|
..where((pg) => pg.matchId.equals(matchId))
|
||||||
|
..where((pg) => pg.playerId.equals(playerId));
|
||||||
|
final rowsAffected = await query.go();
|
||||||
|
return rowsAffected > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the players associated with a match based on the provided
|
||||||
|
/// [newPlayer] list. It adds new players and removes players that are no
|
||||||
|
/// longer associated with the match.
|
||||||
|
Future<void> updatePlayersFromMatch({
|
||||||
|
required String matchId,
|
||||||
|
required List<Player> newPlayer,
|
||||||
|
}) async {
|
||||||
|
final currentPlayers = await getPlayersOfMatch(matchId: matchId);
|
||||||
|
// Create sets of player IDs for easy comparison
|
||||||
|
final currentPlayerIds = currentPlayers?.map((p) => p.id).toSet() ?? {};
|
||||||
|
final newPlayerIdsSet = newPlayer.map((p) => p.id).toSet();
|
||||||
|
|
||||||
|
// Determine players to add and remove
|
||||||
|
final playersToAdd = newPlayerIdsSet.difference(currentPlayerIds);
|
||||||
|
final playersToRemove = currentPlayerIds.difference(newPlayerIdsSet);
|
||||||
|
|
||||||
|
db.transaction(() async {
|
||||||
|
// Remove old players
|
||||||
|
if (playersToRemove.isNotEmpty) {
|
||||||
|
await (delete(playerMatchTable)..where(
|
||||||
|
(pg) =>
|
||||||
|
pg.matchId.equals(matchId) &
|
||||||
|
pg.playerId.isIn(playersToRemove.toList()),
|
||||||
|
))
|
||||||
|
.go();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new players
|
||||||
|
if (playersToAdd.isNotEmpty) {
|
||||||
|
final inserts = playersToAdd
|
||||||
|
.map(
|
||||||
|
(id) => PlayerMatchTableCompanion.insert(
|
||||||
|
playerId: id,
|
||||||
|
matchId: matchId,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
await Future.wait(
|
||||||
|
inserts.map(
|
||||||
|
(c) => into(
|
||||||
|
playerMatchTable,
|
||||||
|
).insert(c, mode: InsertMode.insertOrIgnore),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
11
lib/data/dao/player_match_dao.g.dart
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'player_match_dao.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
mixin _$PlayerMatchDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||||
|
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
||||||
|
$MatchTableTable get matchTable => attachedDatabase.matchTable;
|
||||||
|
$PlayerMatchTableTable get playerMatchTable =>
|
||||||
|
attachedDatabase.playerMatchTable;
|
||||||
|
}
|
||||||
60
lib/data/db/database.dart
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
part 'database.g.dart';
|
||||||
|
|
||||||
|
@DriftDatabase(
|
||||||
|
tables: [
|
||||||
|
PlayerTable,
|
||||||
|
GroupTable,
|
||||||
|
MatchTable,
|
||||||
|
PlayerGroupTable,
|
||||||
|
PlayerMatchTable,
|
||||||
|
GroupMatchTable,
|
||||||
|
],
|
||||||
|
daos: [
|
||||||
|
PlayerDao,
|
||||||
|
GroupDao,
|
||||||
|
MatchDao,
|
||||||
|
PlayerGroupDao,
|
||||||
|
PlayerMatchDao,
|
||||||
|
GroupMatchDao,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
class AppDatabase extends _$AppDatabase {
|
||||||
|
AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get schemaVersion => 1;
|
||||||
|
|
||||||
|
@override
|
||||||
|
MigrationStrategy get migration {
|
||||||
|
return MigrationStrategy(
|
||||||
|
beforeOpen: (details) async {
|
||||||
|
await customStatement('PRAGMA foreign_keys = ON');
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static QueryExecutor _openConnection() {
|
||||||
|
return driftDatabase(
|
||||||
|
name: 'gametracker_db',
|
||||||
|
native: const DriftNativeOptions(
|
||||||
|
databaseDirectory: getApplicationSupportDirectory,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
3844
lib/data/db/database.g.dart
Normal file
13
lib/data/db/tables/group_match_table.dart
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:game_tracker/data/db/tables/group_table.dart';
|
||||||
|
import 'package:game_tracker/data/db/tables/match_table.dart';
|
||||||
|
|
||||||
|
class GroupMatchTable extends Table {
|
||||||
|
TextColumn get groupId =>
|
||||||
|
text().references(GroupTable, #id, onDelete: KeyAction.cascade)();
|
||||||
|
TextColumn get matchId =>
|
||||||
|
text().references(MatchTable, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column<Object>> get primaryKey => {groupId, matchId};
|
||||||
|
}
|
||||||
10
lib/data/db/tables/group_table.dart
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
class GroupTable extends Table {
|
||||||
|
TextColumn get id => text()();
|
||||||
|
TextColumn get name => text()();
|
||||||
|
DateTimeColumn get createdAt => dateTime()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column<Object>> get primaryKey => {id};
|
||||||
|
}
|
||||||
11
lib/data/db/tables/match_table.dart
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
class MatchTable extends Table {
|
||||||
|
TextColumn get id => text()();
|
||||||
|
TextColumn get name => text()();
|
||||||
|
late final winnerId = text().nullable()();
|
||||||
|
DateTimeColumn get createdAt => dateTime()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column<Object>> get primaryKey => {id};
|
||||||
|
}
|
||||||
13
lib/data/db/tables/player_group_table.dart
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:game_tracker/data/db/tables/group_table.dart';
|
||||||
|
import 'package:game_tracker/data/db/tables/player_table.dart';
|
||||||
|
|
||||||
|
class PlayerGroupTable extends Table {
|
||||||
|
TextColumn get playerId =>
|
||||||
|
text().references(PlayerTable, #id, onDelete: KeyAction.cascade)();
|
||||||
|
TextColumn get groupId =>
|
||||||
|
text().references(GroupTable, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column<Object>> get primaryKey => {playerId, groupId};
|
||||||
|
}
|
||||||
13
lib/data/db/tables/player_match_table.dart
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
class PlayerMatchTable extends Table {
|
||||||
|
TextColumn get playerId =>
|
||||||
|
text().references(PlayerTable, #id, onDelete: KeyAction.cascade)();
|
||||||
|
TextColumn get matchId =>
|
||||||
|
text().references(MatchTable, #id, onDelete: KeyAction.cascade)();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column<Object>> get primaryKey => {playerId, matchId};
|
||||||
|
}
|
||||||
10
lib/data/db/tables/player_table.dart
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
class PlayerTable extends Table {
|
||||||
|
TextColumn get id => text()();
|
||||||
|
TextColumn get name => text()();
|
||||||
|
DateTimeColumn get createdAt => dateTime()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column<Object>> get primaryKey => {id};
|
||||||
|
}
|
||||||
40
lib/data/dto/group.dart
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import 'package:clock/clock.dart';
|
||||||
|
import 'package:game_tracker/data/dto/player.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
class Group {
|
||||||
|
final String id;
|
||||||
|
final DateTime createdAt;
|
||||||
|
final String name;
|
||||||
|
final List<Player> members;
|
||||||
|
|
||||||
|
Group({
|
||||||
|
String? id,
|
||||||
|
DateTime? createdAt,
|
||||||
|
required this.name,
|
||||||
|
required this.members,
|
||||||
|
}) : id = id ?? const Uuid().v4(),
|
||||||
|
createdAt = createdAt ?? clock.now();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'Group{id: $id, name: $name,members: $members}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a Group instance from a JSON object.
|
||||||
|
Group.fromJson(Map<String, dynamic> json)
|
||||||
|
: id = json['id'],
|
||||||
|
createdAt = DateTime.parse(json['createdAt']),
|
||||||
|
name = json['name'],
|
||||||
|
members = (json['members'] as List)
|
||||||
|
.map((memberJson) => Player.fromJson(memberJson))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
/// Converts the Group instance to a JSON object.
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'id': id,
|
||||||
|
'createdAt': createdAt.toIso8601String(),
|
||||||
|
'name': name,
|
||||||
|
'members': members.map((member) => member.toJson()).toList(),
|
||||||
|
};
|
||||||
|
}
|
||||||
51
lib/data/dto/match.dart
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import 'package:clock/clock.dart';
|
||||||
|
import 'package:game_tracker/data/dto/group.dart';
|
||||||
|
import 'package:game_tracker/data/dto/player.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
class Match {
|
||||||
|
final String id;
|
||||||
|
final DateTime createdAt;
|
||||||
|
final String name;
|
||||||
|
final List<Player>? players;
|
||||||
|
final Group? group;
|
||||||
|
final Player? winner;
|
||||||
|
|
||||||
|
Match({
|
||||||
|
String? id,
|
||||||
|
DateTime? createdAt,
|
||||||
|
required this.name,
|
||||||
|
this.players,
|
||||||
|
this.group,
|
||||||
|
this.winner,
|
||||||
|
}) : id = id ?? const Uuid().v4(),
|
||||||
|
createdAt = createdAt ?? clock.now();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'Match{\n\tid: $id,\n\tname: $name,\n\tplayers: $players,\n\tgroup: $group,\n\twinner: $winner\n}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a Match instance from a JSON object.
|
||||||
|
Match.fromJson(Map<String, dynamic> json)
|
||||||
|
: id = json['id'],
|
||||||
|
name = json['name'],
|
||||||
|
createdAt = DateTime.parse(json['createdAt']),
|
||||||
|
players = json['players'] != null
|
||||||
|
? (json['players'] as List)
|
||||||
|
.map((playerJson) => Player.fromJson(playerJson))
|
||||||
|
.toList()
|
||||||
|
: null,
|
||||||
|
group = json['group'] != null ? Group.fromJson(json['group']) : null,
|
||||||
|
winner = json['winner'] != null ? Player.fromJson(json['winner']) : null;
|
||||||
|
|
||||||
|
/// Converts the Match instance to a JSON object.
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'id': id,
|
||||||
|
'createdAt': createdAt.toIso8601String(),
|
||||||
|
'name': name,
|
||||||
|
'players': players?.map((player) => player.toJson()).toList(),
|
||||||
|
'group': group?.toJson(),
|
||||||
|
'winner': winner?.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
30
lib/data/dto/player.dart
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import 'package:clock/clock.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
class Player {
|
||||||
|
final String id;
|
||||||
|
final DateTime createdAt;
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
Player({String? id, DateTime? createdAt, required this.name})
|
||||||
|
: id = id ?? const Uuid().v4(),
|
||||||
|
createdAt = createdAt ?? clock.now();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'Player{id: $id,name: $name}';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a Player instance from a JSON object.
|
||||||
|
Player.fromJson(Map<String, dynamic> json)
|
||||||
|
: id = json['id'],
|
||||||
|
createdAt = DateTime.parse(json['createdAt']),
|
||||||
|
name = json['name'];
|
||||||
|
|
||||||
|
/// Converts the Player instance to a JSON object.
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'id': id,
|
||||||
|
'createdAt': createdAt.toIso8601String(),
|
||||||
|
'name': name,
|
||||||
|
};
|
||||||
|
}
|
||||||
83
lib/l10n/arb/app_de.arb
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"@@locale": "de",
|
||||||
|
"all_players": "Alle Spieler:innen",
|
||||||
|
"all_players_selected": "Alle Spieler:innen ausgewählt",
|
||||||
|
"amount_of_matches": "Anzahl der Spiele",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"choose_game": "Spielvorlage wählen",
|
||||||
|
"choose_group": "Gruppe wählen",
|
||||||
|
"choose_ruleset": "Regelwerk wählen",
|
||||||
|
"could_not_add_player": "Spieler:in {playerName} konnte nicht hinzugefügt werden",
|
||||||
|
"create_group": "Gruppe erstellen",
|
||||||
|
"create_match": "Spiel erstellen",
|
||||||
|
"create_new_group": "Neue Gruppe erstellen",
|
||||||
|
"create_new_match": "Neues Spiel erstellen",
|
||||||
|
"data_successfully_deleted": "Daten erfolgreich gelöscht",
|
||||||
|
"data_successfully_exported": "Daten erfolgreich exportiert",
|
||||||
|
"data_successfully_imported": "Daten erfolgreich importiert",
|
||||||
|
"days_ago": "vor {count} Tagen",
|
||||||
|
"delete": "Löschen",
|
||||||
|
"delete_all_data": "Alle Daten löschen?",
|
||||||
|
"error_creating_group": "Fehler beim Erstellen der Gruppe, bitte erneut versuchen",
|
||||||
|
"error_reading_file": "Fehler beim Lesen der Datei",
|
||||||
|
"export_canceled": "Export abgebrochen",
|
||||||
|
"export_data": "Daten exportieren",
|
||||||
|
"format_exception": "Formatfehler (siehe Konsole)",
|
||||||
|
"game": "Spielvorlage",
|
||||||
|
"game_name": "Spielvorlagenname",
|
||||||
|
"group": "Gruppe",
|
||||||
|
"group_name": "Gruppenname",
|
||||||
|
"groups": "Gruppen",
|
||||||
|
"home": "Startseite",
|
||||||
|
"import_canceled": "Import abgebrochen",
|
||||||
|
"import_data": "Daten importieren",
|
||||||
|
"info": "Info",
|
||||||
|
"invalid_schema": "Ungültiges Schema",
|
||||||
|
"least_points": "Niedrigste Punkte",
|
||||||
|
"match_in_progress": "Spiel läuft...",
|
||||||
|
"match_name": "Spieltitel",
|
||||||
|
"matches": "Spiele",
|
||||||
|
"menu": "Menü",
|
||||||
|
"most_points": "Höchste Punkte",
|
||||||
|
"no_data_available": "Keine Daten verfügbar",
|
||||||
|
"no_groups_created_yet": "Noch keine Gruppen erstellt",
|
||||||
|
"no_matches_created_yet": "Noch keine Spiele erstellt",
|
||||||
|
"no_players_created_yet": "Noch keine Spieler:in erstellt",
|
||||||
|
"no_players_found_with_that_name": "Keine Spieler:in mit diesem Namen gefunden",
|
||||||
|
"no_players_selected": "Keine Spieler:innen ausgewählt",
|
||||||
|
"no_recent_matches_available": "Keine letzten Spiele verfügbar",
|
||||||
|
"no_second_match_available": "Kein zweites Spiel verfügbar",
|
||||||
|
"no_statistics_available": "Keine Statistiken verfügbar",
|
||||||
|
"none": "Kein",
|
||||||
|
"none_group": "Keine",
|
||||||
|
"not_available": "Nicht verfügbar",
|
||||||
|
"player_name": "Spieler:innenname",
|
||||||
|
"players": "Spieler:innen",
|
||||||
|
"players_count": "{count} Spieler",
|
||||||
|
"quick_create": "Schnellzugriff",
|
||||||
|
"recent_matches": "Letzte Spiele",
|
||||||
|
"ruleset": "Regelwerk",
|
||||||
|
"ruleset_least_points": "Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.",
|
||||||
|
"ruleset_most_points": "Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.",
|
||||||
|
"ruleset_single_loser": "Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.",
|
||||||
|
"ruleset_single_winner": "Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.",
|
||||||
|
"search_for_groups": "Nach Gruppen suchen",
|
||||||
|
"search_for_players": "Nach Spieler:innen suchen",
|
||||||
|
"select_winner": "Gewinner:in wählen:",
|
||||||
|
"selected_players": "Ausgewählte Spieler:innen",
|
||||||
|
"settings": "Einstellungen",
|
||||||
|
"single_loser": "Ein:e Verlierer:in",
|
||||||
|
"single_winner": "Ein:e Gewinner:in",
|
||||||
|
"statistics": "Statistiken",
|
||||||
|
"stats": "Statistiken",
|
||||||
|
"successfully_added_player": "Spieler:in {playerName} erfolgreich hinzugefügt",
|
||||||
|
"there_is_no_group_matching_your_search": "Es gibt keine Gruppe, die deiner Suche entspricht",
|
||||||
|
"this_cannot_be_undone": "Dies kann nicht rückgängig gemacht werden",
|
||||||
|
"today_at": "Heute um",
|
||||||
|
"undo": "Rückgängig",
|
||||||
|
"unknown_exception": "Unbekannter Fehler (siehe Konsole)",
|
||||||
|
"winner": "Gewinner:in",
|
||||||
|
"winrate": "Siegquote",
|
||||||
|
"wins": "Siege",
|
||||||
|
"yesterday_at": "Gestern um"
|
||||||
|
}
|
||||||
343
lib/l10n/arb/app_en.arb
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
{
|
||||||
|
"@@locale": "en",
|
||||||
|
"@all_players": {
|
||||||
|
"description": "Label for all players list"
|
||||||
|
},
|
||||||
|
"@all_players_selected": {
|
||||||
|
"description": "Message when all players are added to selection"
|
||||||
|
},
|
||||||
|
"@amount_of_matches": {
|
||||||
|
"description": "Label for amount of matches statistic"
|
||||||
|
},
|
||||||
|
"@app_name": {
|
||||||
|
"description": "The name of the App"
|
||||||
|
},
|
||||||
|
"@cancel": {
|
||||||
|
"description": "Cancel button text"
|
||||||
|
},
|
||||||
|
"@choose_game": {
|
||||||
|
"description": "Label for choosing a game"
|
||||||
|
},
|
||||||
|
"@choose_group": {
|
||||||
|
"description": "Label for choosing a group"
|
||||||
|
},
|
||||||
|
"@choose_ruleset": {
|
||||||
|
"description": "Label for choosing a ruleset"
|
||||||
|
},
|
||||||
|
"@could_not_add_player": {
|
||||||
|
"description": "Error message when adding a player fails"
|
||||||
|
},
|
||||||
|
"@create_group": {
|
||||||
|
"description": "Button text to create a group"
|
||||||
|
},
|
||||||
|
"@create_match": {
|
||||||
|
"description": "Button text to create a match"
|
||||||
|
},
|
||||||
|
"@create_new_group": {
|
||||||
|
"description": "Button text to create a new group"
|
||||||
|
},
|
||||||
|
"@create_new_match": {
|
||||||
|
"description": "Button text to create a new match"
|
||||||
|
},
|
||||||
|
"@data_successfully_deleted": {
|
||||||
|
"description": "Success message after deleting data"
|
||||||
|
},
|
||||||
|
"@data_successfully_exported": {
|
||||||
|
"description": "Success message after exporting data"
|
||||||
|
},
|
||||||
|
"@data_successfully_imported": {
|
||||||
|
"description": "Success message after importing data"
|
||||||
|
},
|
||||||
|
"@days_ago": {
|
||||||
|
"description": "Date format for days ago",
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@delete": {
|
||||||
|
"description": "Delete button text"
|
||||||
|
},
|
||||||
|
"@delete_all_data": {
|
||||||
|
"description": "Confirmation dialog for deleting all data"
|
||||||
|
},
|
||||||
|
"@error_creating_group": {
|
||||||
|
"description": "Error message when group creation fails"
|
||||||
|
},
|
||||||
|
"@error_reading_file": {
|
||||||
|
"description": "Error message when file cannot be read"
|
||||||
|
},
|
||||||
|
"@export_canceled": {
|
||||||
|
"description": "Message when export is canceled"
|
||||||
|
},
|
||||||
|
"@export_data": {
|
||||||
|
"description": "Export data menu item"
|
||||||
|
},
|
||||||
|
"@format_exception": {
|
||||||
|
"description": "Error message for format exceptions"
|
||||||
|
},
|
||||||
|
"@game": {
|
||||||
|
"description": "Game label"
|
||||||
|
},
|
||||||
|
"@game_name": {
|
||||||
|
"description": "Placeholder for game name search"
|
||||||
|
},
|
||||||
|
"@group": {
|
||||||
|
"description": "Group label"
|
||||||
|
},
|
||||||
|
"@group_name": {
|
||||||
|
"description": "Placeholder for group name input"
|
||||||
|
},
|
||||||
|
"@groups": {
|
||||||
|
"description": "Label for groups"
|
||||||
|
},
|
||||||
|
"@home": {
|
||||||
|
"description": "Home tab label"
|
||||||
|
},
|
||||||
|
"@import_canceled": {
|
||||||
|
"description": "Message when import is canceled"
|
||||||
|
},
|
||||||
|
"@import_data": {
|
||||||
|
"description": "Import data menu item"
|
||||||
|
},
|
||||||
|
"@info": {
|
||||||
|
"description": "Info label"
|
||||||
|
},
|
||||||
|
"@invalid_schema": {
|
||||||
|
"description": "Error message for invalid schema"
|
||||||
|
},
|
||||||
|
"@least_points": {
|
||||||
|
"description": "Title for least points ruleset"
|
||||||
|
},
|
||||||
|
"@match_in_progress": {
|
||||||
|
"description": "Message when match is in progress"
|
||||||
|
},
|
||||||
|
"@match_name": {
|
||||||
|
"description": "Placeholder for match name input"
|
||||||
|
},
|
||||||
|
"@matches": {
|
||||||
|
"description": "Label for matches"
|
||||||
|
},
|
||||||
|
"@menu": {
|
||||||
|
"description": "Menu label"
|
||||||
|
},
|
||||||
|
"@most_points": {
|
||||||
|
"description": "Title for most points ruleset"
|
||||||
|
},
|
||||||
|
"@no_data_available": {
|
||||||
|
"description": "Message when no data in the statistic tiles is given"
|
||||||
|
},
|
||||||
|
"@no_groups_created_yet": {
|
||||||
|
"description": "Message when no groups exist"
|
||||||
|
},
|
||||||
|
"@no_matches_created_yet": {
|
||||||
|
"description": "Message when no matches exist"
|
||||||
|
},
|
||||||
|
"@no_players_created_yet": {
|
||||||
|
"description": "Message when no players exist"
|
||||||
|
},
|
||||||
|
"@no_players_found_with_that_name": {
|
||||||
|
"description": "Message when search returns no results"
|
||||||
|
},
|
||||||
|
"@no_players_selected": {
|
||||||
|
"description": "Message when no players are selected"
|
||||||
|
},
|
||||||
|
"@no_recent_matches_available": {
|
||||||
|
"description": "Message when no recent matches exist"
|
||||||
|
},
|
||||||
|
"@no_second_match_available": {
|
||||||
|
"description": "Message when no second match exists"
|
||||||
|
},
|
||||||
|
"@no_statistics_available": {
|
||||||
|
"description": "Message when no statistics are available, because no matches were played yet"
|
||||||
|
},
|
||||||
|
"@none": {
|
||||||
|
"description": "None option label"
|
||||||
|
},
|
||||||
|
"@none_group": {
|
||||||
|
"description": "None group option label"
|
||||||
|
},
|
||||||
|
"@not_available": {
|
||||||
|
"description": "Abbreviation for not available"
|
||||||
|
},
|
||||||
|
"@player_name": {
|
||||||
|
"description": "Placeholder for player name input"
|
||||||
|
},
|
||||||
|
"@players": {
|
||||||
|
"description": "Players label"
|
||||||
|
},
|
||||||
|
"@players_count": {
|
||||||
|
"description": "Shows the number of players",
|
||||||
|
"placeholders": {
|
||||||
|
"count": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@quick_create": {
|
||||||
|
"description": "Title for quick create section"
|
||||||
|
},
|
||||||
|
"@recent_matches": {
|
||||||
|
"description": "Title for recent matches section"
|
||||||
|
},
|
||||||
|
"@ruleset": {
|
||||||
|
"description": "Ruleset label"
|
||||||
|
},
|
||||||
|
"@ruleset_least_points": {
|
||||||
|
"description": "Description for least points ruleset"
|
||||||
|
},
|
||||||
|
"@ruleset_most_points": {
|
||||||
|
"description": "Description for most points ruleset"
|
||||||
|
},
|
||||||
|
"@ruleset_single_loser": {
|
||||||
|
"description": "Description for single loser ruleset"
|
||||||
|
},
|
||||||
|
"@ruleset_single_winner": {
|
||||||
|
"description": "Description for single winner ruleset"
|
||||||
|
},
|
||||||
|
"@search_for_groups": {
|
||||||
|
"description": "Hint text for group search input field"
|
||||||
|
},
|
||||||
|
"@search_for_players": {
|
||||||
|
"description": "Hint text for player search input field"
|
||||||
|
},
|
||||||
|
"@select_winner": {
|
||||||
|
"description": "Label to select the winner"
|
||||||
|
},
|
||||||
|
"@selected_players": {
|
||||||
|
"description": "Shows the number of selected players"
|
||||||
|
},
|
||||||
|
"@settings": {
|
||||||
|
"description": "Settings label"
|
||||||
|
},
|
||||||
|
"@single_loser": {
|
||||||
|
"description": "Title for single loser ruleset"
|
||||||
|
},
|
||||||
|
"@single_winner": {
|
||||||
|
"description": "Title for single winner ruleset"
|
||||||
|
},
|
||||||
|
"@statistics": {
|
||||||
|
"description": "Statistics tab label"
|
||||||
|
},
|
||||||
|
"@stats": {
|
||||||
|
"description": "Stats tab label (short)"
|
||||||
|
},
|
||||||
|
"@successfully_added_player": {
|
||||||
|
"description": "Success message when adding a player",
|
||||||
|
"placeholders": {
|
||||||
|
"playerName": {
|
||||||
|
"type": "String",
|
||||||
|
"example": "John"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@there_is_no_group_matching_your_search": {
|
||||||
|
"description": "Message when search returns no groups"
|
||||||
|
},
|
||||||
|
"@this_cannot_be_undone": {
|
||||||
|
"description": "Warning message for irreversible actions"
|
||||||
|
},
|
||||||
|
"@today_at": {
|
||||||
|
"description": "Date format for today"
|
||||||
|
},
|
||||||
|
"@undo": {
|
||||||
|
"description": "Undo button text"
|
||||||
|
},
|
||||||
|
"@unknown_exception": {
|
||||||
|
"description": "Error message for unknown exceptions"
|
||||||
|
},
|
||||||
|
"@winner": {
|
||||||
|
"description": "Winner label"
|
||||||
|
},
|
||||||
|
"@winrate": {
|
||||||
|
"description": "Label for winrate statistic"
|
||||||
|
},
|
||||||
|
"@wins": {
|
||||||
|
"description": "Label for wins statistic"
|
||||||
|
},
|
||||||
|
"@yesterday_at": {
|
||||||
|
"description": "Date format for yesterday"
|
||||||
|
},
|
||||||
|
"all_players": "All players",
|
||||||
|
"all_players_selected": "All players selected",
|
||||||
|
"amount_of_matches": "Amount of Matches",
|
||||||
|
"app_name": "Game Tracker",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"choose_game": "Choose Game",
|
||||||
|
"choose_group": "Choose Group",
|
||||||
|
"choose_ruleset": "Choose Ruleset",
|
||||||
|
"could_not_add_player": "Could not add player",
|
||||||
|
"create_group": "Create Group",
|
||||||
|
"create_match": "Create match",
|
||||||
|
"create_new_group": "Create new group",
|
||||||
|
"create_new_match": "Create new match",
|
||||||
|
"data_successfully_deleted": "Data successfully deleted",
|
||||||
|
"data_successfully_exported": "Data successfully exported",
|
||||||
|
"data_successfully_imported": "Data successfully imported",
|
||||||
|
"days_ago": "{count} days ago",
|
||||||
|
"delete": "Delete",
|
||||||
|
"delete_all_data": "Delete all data?",
|
||||||
|
"error_creating_group": "Error while creating group, please try again",
|
||||||
|
"error_reading_file": "Error reading file",
|
||||||
|
"export_canceled": "Export canceled",
|
||||||
|
"export_data": "Export data",
|
||||||
|
"format_exception": "Format Exception (see console)",
|
||||||
|
"game": "Game",
|
||||||
|
"game_name": "Game Name",
|
||||||
|
"group": "Group",
|
||||||
|
"group_name": "Group name",
|
||||||
|
"groups": "Groups",
|
||||||
|
"home": "Home",
|
||||||
|
"import_canceled": "Import canceled",
|
||||||
|
"import_data": "Import data",
|
||||||
|
"info": "Info",
|
||||||
|
"invalid_schema": "Invalid Schema",
|
||||||
|
"least_points": "Least Points",
|
||||||
|
"match_in_progress": "Match in progress...",
|
||||||
|
"match_name": "Match name",
|
||||||
|
"matches": "Matches",
|
||||||
|
"menu": "Menu",
|
||||||
|
"most_points": "Most Points",
|
||||||
|
"no_data_available": "No data available",
|
||||||
|
"no_groups_created_yet": "No groups created yet",
|
||||||
|
"no_matches_created_yet": "No matches created yet",
|
||||||
|
"no_players_created_yet": "No players created yet",
|
||||||
|
"no_players_found_with_that_name": "No players found with that name",
|
||||||
|
"no_players_selected": "No players selected",
|
||||||
|
"no_recent_matches_available": "No recent matches available",
|
||||||
|
"no_second_match_available": "No second match available",
|
||||||
|
"no_statistics_available": "No statistics available",
|
||||||
|
"none": "None",
|
||||||
|
"none_group": "None",
|
||||||
|
"not_available": "Not available",
|
||||||
|
"player_name": "Player name",
|
||||||
|
"players": "Players",
|
||||||
|
"players_count": "{count} Players",
|
||||||
|
"quick_create": "Quick Create",
|
||||||
|
"recent_matches": "Recent Matches",
|
||||||
|
"ruleset": "Ruleset",
|
||||||
|
"ruleset_least_points": "Inverse scoring: the player with the fewest points wins.",
|
||||||
|
"ruleset_most_points": "Traditional ruleset: the player with the most points wins.",
|
||||||
|
"ruleset_single_loser": "Exactly one loser is determined; last place receives the penalty or consequence.",
|
||||||
|
"ruleset_single_winner": "Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.",
|
||||||
|
"search_for_groups": "Search for groups",
|
||||||
|
"search_for_players": "Search for players",
|
||||||
|
"select_winner": "Select Winner:",
|
||||||
|
"selected_players": "Selected players",
|
||||||
|
"settings": "Settings",
|
||||||
|
"single_loser": "Single Loser",
|
||||||
|
"single_winner": "Single Winner",
|
||||||
|
"statistics": "Statistics",
|
||||||
|
"stats": "Stats",
|
||||||
|
"successfully_added_player": "Successfully added player {playerName}",
|
||||||
|
"there_is_no_group_matching_your_search": "There is no group matching your search",
|
||||||
|
"this_cannot_be_undone": "This can't be undone",
|
||||||
|
"today_at": "Today at",
|
||||||
|
"undo": "Undo",
|
||||||
|
"unknown_exception": "Unknown Exception (see console)",
|
||||||
|
"winner": "Winner",
|
||||||
|
"winrate": "Winrate",
|
||||||
|
"wins": "Wins",
|
||||||
|
"yesterday_at": "Yesterday at"
|
||||||
|
}
|
||||||
620
lib/l10n/generated/app_localizations.dart
Normal file
@@ -0,0 +1,620 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:intl/intl.dart' as intl;
|
||||||
|
|
||||||
|
import 'app_localizations_de.dart';
|
||||||
|
import 'app_localizations_en.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
|
||||||
|
/// Callers can lookup localized strings with an instance of AppLocalizations
|
||||||
|
/// returned by `AppLocalizations.of(context)`.
|
||||||
|
///
|
||||||
|
/// Applications need to include `AppLocalizations.delegate()` in their app's
|
||||||
|
/// `localizationDelegates` list, and the locales they support in the app's
|
||||||
|
/// `supportedLocales` list. For example:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// import 'generated/app_localizations.dart';
|
||||||
|
///
|
||||||
|
/// return MaterialApp(
|
||||||
|
/// localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
/// supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
/// home: MyApplicationHome(),
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Update pubspec.yaml
|
||||||
|
///
|
||||||
|
/// Please make sure to update your pubspec.yaml to include the following
|
||||||
|
/// packages:
|
||||||
|
///
|
||||||
|
/// ```yaml
|
||||||
|
/// dependencies:
|
||||||
|
/// # Internationalization support.
|
||||||
|
/// flutter_localizations:
|
||||||
|
/// sdk: flutter
|
||||||
|
/// intl: any # Use the pinned version from flutter_localizations
|
||||||
|
///
|
||||||
|
/// # Rest of dependencies
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## iOS Applications
|
||||||
|
///
|
||||||
|
/// iOS applications define key application metadata, including supported
|
||||||
|
/// locales, in an Info.plist file that is built into the application bundle.
|
||||||
|
/// To configure the locales supported by your app, you’ll need to edit this
|
||||||
|
/// file.
|
||||||
|
///
|
||||||
|
/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file.
|
||||||
|
/// Then, in the Project Navigator, open the Info.plist file under the Runner
|
||||||
|
/// project’s Runner folder.
|
||||||
|
///
|
||||||
|
/// Next, select the Information Property List item, select Add Item from the
|
||||||
|
/// Editor menu, then select Localizations from the pop-up menu.
|
||||||
|
///
|
||||||
|
/// Select and expand the newly-created Localizations item then, for each
|
||||||
|
/// locale your application supports, add a new item and select the locale
|
||||||
|
/// you wish to add from the pop-up menu in the Value field. This list should
|
||||||
|
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
|
||||||
|
/// property.
|
||||||
|
abstract class AppLocalizations {
|
||||||
|
AppLocalizations(String locale)
|
||||||
|
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
|
||||||
|
|
||||||
|
final String localeName;
|
||||||
|
|
||||||
|
static AppLocalizations of(BuildContext context) {
|
||||||
|
return Localizations.of<AppLocalizations>(context, AppLocalizations)!;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const LocalizationsDelegate<AppLocalizations> delegate =
|
||||||
|
_AppLocalizationsDelegate();
|
||||||
|
|
||||||
|
/// A list of this localizations delegate along with the default localizations
|
||||||
|
/// delegates.
|
||||||
|
///
|
||||||
|
/// Returns a list of localizations delegates containing this delegate along with
|
||||||
|
/// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
|
||||||
|
/// and GlobalWidgetsLocalizations.delegate.
|
||||||
|
///
|
||||||
|
/// Additional delegates can be added by appending to this list in
|
||||||
|
/// MaterialApp. This list does not have to be used at all if a custom list
|
||||||
|
/// of delegates is preferred or required.
|
||||||
|
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
|
||||||
|
<LocalizationsDelegate<dynamic>>[
|
||||||
|
delegate,
|
||||||
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
GlobalCupertinoLocalizations.delegate,
|
||||||
|
GlobalWidgetsLocalizations.delegate,
|
||||||
|
];
|
||||||
|
|
||||||
|
/// A list of this localizations delegate's supported locales.
|
||||||
|
static const List<Locale> supportedLocales = <Locale>[
|
||||||
|
Locale('de'),
|
||||||
|
Locale('en'),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Label for all players list
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'All players'**
|
||||||
|
String get all_players;
|
||||||
|
|
||||||
|
/// Message when all players are added to selection
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'All players selected'**
|
||||||
|
String get all_players_selected;
|
||||||
|
|
||||||
|
/// Label for amount of matches statistic
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Amount of Matches'**
|
||||||
|
String get amount_of_matches;
|
||||||
|
|
||||||
|
/// The name of the App
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Game Tracker'**
|
||||||
|
String get app_name;
|
||||||
|
|
||||||
|
/// Cancel button text
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Cancel'**
|
||||||
|
String get cancel;
|
||||||
|
|
||||||
|
/// Label for choosing a game
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Choose Game'**
|
||||||
|
String get choose_game;
|
||||||
|
|
||||||
|
/// Label for choosing a group
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Choose Group'**
|
||||||
|
String get choose_group;
|
||||||
|
|
||||||
|
/// Label for choosing a ruleset
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Choose Ruleset'**
|
||||||
|
String get choose_ruleset;
|
||||||
|
|
||||||
|
/// Error message when adding a player fails
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Could not add player'**
|
||||||
|
String could_not_add_player(Object playerName);
|
||||||
|
|
||||||
|
/// Button text to create a group
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Create Group'**
|
||||||
|
String get create_group;
|
||||||
|
|
||||||
|
/// Button text to create a match
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Create match'**
|
||||||
|
String get create_match;
|
||||||
|
|
||||||
|
/// Button text to create a new group
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Create new group'**
|
||||||
|
String get create_new_group;
|
||||||
|
|
||||||
|
/// Button text to create a new match
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Create new match'**
|
||||||
|
String get create_new_match;
|
||||||
|
|
||||||
|
/// Success message after deleting data
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Data successfully deleted'**
|
||||||
|
String get data_successfully_deleted;
|
||||||
|
|
||||||
|
/// Success message after exporting data
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Data successfully exported'**
|
||||||
|
String get data_successfully_exported;
|
||||||
|
|
||||||
|
/// Success message after importing data
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Data successfully imported'**
|
||||||
|
String get data_successfully_imported;
|
||||||
|
|
||||||
|
/// Date format for days ago
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'{count} days ago'**
|
||||||
|
String days_ago(int count);
|
||||||
|
|
||||||
|
/// Delete button text
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Delete'**
|
||||||
|
String get delete;
|
||||||
|
|
||||||
|
/// Confirmation dialog for deleting all data
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Delete all data?'**
|
||||||
|
String get delete_all_data;
|
||||||
|
|
||||||
|
/// Error message when group creation fails
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Error while creating group, please try again'**
|
||||||
|
String get error_creating_group;
|
||||||
|
|
||||||
|
/// Error message when file cannot be read
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Error reading file'**
|
||||||
|
String get error_reading_file;
|
||||||
|
|
||||||
|
/// Message when export is canceled
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Export canceled'**
|
||||||
|
String get export_canceled;
|
||||||
|
|
||||||
|
/// Export data menu item
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Export data'**
|
||||||
|
String get export_data;
|
||||||
|
|
||||||
|
/// Error message for format exceptions
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Format Exception (see console)'**
|
||||||
|
String get format_exception;
|
||||||
|
|
||||||
|
/// Game label
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Game'**
|
||||||
|
String get game;
|
||||||
|
|
||||||
|
/// Placeholder for game name search
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Game Name'**
|
||||||
|
String get game_name;
|
||||||
|
|
||||||
|
/// Group label
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Group'**
|
||||||
|
String get group;
|
||||||
|
|
||||||
|
/// Placeholder for group name input
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Group name'**
|
||||||
|
String get group_name;
|
||||||
|
|
||||||
|
/// Label for groups
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Groups'**
|
||||||
|
String get groups;
|
||||||
|
|
||||||
|
/// Home tab label
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Home'**
|
||||||
|
String get home;
|
||||||
|
|
||||||
|
/// Message when import is canceled
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Import canceled'**
|
||||||
|
String get import_canceled;
|
||||||
|
|
||||||
|
/// Import data menu item
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Import data'**
|
||||||
|
String get import_data;
|
||||||
|
|
||||||
|
/// Info label
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Info'**
|
||||||
|
String get info;
|
||||||
|
|
||||||
|
/// Error message for invalid schema
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Invalid Schema'**
|
||||||
|
String get invalid_schema;
|
||||||
|
|
||||||
|
/// Title for least points ruleset
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Least Points'**
|
||||||
|
String get least_points;
|
||||||
|
|
||||||
|
/// Message when match is in progress
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Match in progress...'**
|
||||||
|
String get match_in_progress;
|
||||||
|
|
||||||
|
/// Placeholder for match name input
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Match name'**
|
||||||
|
String get match_name;
|
||||||
|
|
||||||
|
/// Label for matches
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Matches'**
|
||||||
|
String get matches;
|
||||||
|
|
||||||
|
/// Menu label
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Menu'**
|
||||||
|
String get menu;
|
||||||
|
|
||||||
|
/// Title for most points ruleset
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Most Points'**
|
||||||
|
String get most_points;
|
||||||
|
|
||||||
|
/// Message when no data in the statistic tiles is given
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No data available'**
|
||||||
|
String get no_data_available;
|
||||||
|
|
||||||
|
/// Message when no groups exist
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No groups created yet'**
|
||||||
|
String get no_groups_created_yet;
|
||||||
|
|
||||||
|
/// Message when no matches exist
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No matches created yet'**
|
||||||
|
String get no_matches_created_yet;
|
||||||
|
|
||||||
|
/// Message when no players exist
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No players created yet'**
|
||||||
|
String get no_players_created_yet;
|
||||||
|
|
||||||
|
/// Message when search returns no results
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No players found with that name'**
|
||||||
|
String get no_players_found_with_that_name;
|
||||||
|
|
||||||
|
/// Message when no players are selected
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No players selected'**
|
||||||
|
String get no_players_selected;
|
||||||
|
|
||||||
|
/// Message when no recent matches exist
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No recent matches available'**
|
||||||
|
String get no_recent_matches_available;
|
||||||
|
|
||||||
|
/// Message when no second match exists
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No second match available'**
|
||||||
|
String get no_second_match_available;
|
||||||
|
|
||||||
|
/// Message when no statistics are available, because no matches were played yet
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'No statistics available'**
|
||||||
|
String get no_statistics_available;
|
||||||
|
|
||||||
|
/// None option label
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'None'**
|
||||||
|
String get none;
|
||||||
|
|
||||||
|
/// None group option label
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'None'**
|
||||||
|
String get none_group;
|
||||||
|
|
||||||
|
/// Abbreviation for not available
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Not available'**
|
||||||
|
String get not_available;
|
||||||
|
|
||||||
|
/// Placeholder for player name input
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Player name'**
|
||||||
|
String get player_name;
|
||||||
|
|
||||||
|
/// Players label
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Players'**
|
||||||
|
String get players;
|
||||||
|
|
||||||
|
/// Shows the number of players
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'{count} Players'**
|
||||||
|
String players_count(int count);
|
||||||
|
|
||||||
|
/// Title for quick create section
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Quick Create'**
|
||||||
|
String get quick_create;
|
||||||
|
|
||||||
|
/// Title for recent matches section
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Recent Matches'**
|
||||||
|
String get recent_matches;
|
||||||
|
|
||||||
|
/// Ruleset label
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Ruleset'**
|
||||||
|
String get ruleset;
|
||||||
|
|
||||||
|
/// Description for least points ruleset
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Inverse scoring: the player with the fewest points wins.'**
|
||||||
|
String get ruleset_least_points;
|
||||||
|
|
||||||
|
/// Description for most points ruleset
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Traditional ruleset: the player with the most points wins.'**
|
||||||
|
String get ruleset_most_points;
|
||||||
|
|
||||||
|
/// Description for single loser ruleset
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Exactly one loser is determined; last place receives the penalty or consequence.'**
|
||||||
|
String get ruleset_single_loser;
|
||||||
|
|
||||||
|
/// Description for single winner ruleset
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.'**
|
||||||
|
String get ruleset_single_winner;
|
||||||
|
|
||||||
|
/// Hint text for group search input field
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Search for groups'**
|
||||||
|
String get search_for_groups;
|
||||||
|
|
||||||
|
/// Hint text for player search input field
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Search for players'**
|
||||||
|
String get search_for_players;
|
||||||
|
|
||||||
|
/// Label to select the winner
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Select Winner:'**
|
||||||
|
String get select_winner;
|
||||||
|
|
||||||
|
/// Shows the number of selected players
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Selected players'**
|
||||||
|
String get selected_players;
|
||||||
|
|
||||||
|
/// Settings label
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Settings'**
|
||||||
|
String get settings;
|
||||||
|
|
||||||
|
/// Title for single loser ruleset
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Single Loser'**
|
||||||
|
String get single_loser;
|
||||||
|
|
||||||
|
/// Title for single winner ruleset
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Single Winner'**
|
||||||
|
String get single_winner;
|
||||||
|
|
||||||
|
/// Statistics tab label
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Statistics'**
|
||||||
|
String get statistics;
|
||||||
|
|
||||||
|
/// Stats tab label (short)
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Stats'**
|
||||||
|
String get stats;
|
||||||
|
|
||||||
|
/// Success message when adding a player
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Successfully added player {playerName}'**
|
||||||
|
String successfully_added_player(String playerName);
|
||||||
|
|
||||||
|
/// Message when search returns no groups
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'There is no group matching your search'**
|
||||||
|
String get there_is_no_group_matching_your_search;
|
||||||
|
|
||||||
|
/// Warning message for irreversible actions
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'This can\'t be undone'**
|
||||||
|
String get this_cannot_be_undone;
|
||||||
|
|
||||||
|
/// Date format for today
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Today at'**
|
||||||
|
String get today_at;
|
||||||
|
|
||||||
|
/// Undo button text
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Undo'**
|
||||||
|
String get undo;
|
||||||
|
|
||||||
|
/// Error message for unknown exceptions
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Unknown Exception (see console)'**
|
||||||
|
String get unknown_exception;
|
||||||
|
|
||||||
|
/// Winner label
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Winner'**
|
||||||
|
String get winner;
|
||||||
|
|
||||||
|
/// Label for winrate statistic
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Winrate'**
|
||||||
|
String get winrate;
|
||||||
|
|
||||||
|
/// Label for wins statistic
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Wins'**
|
||||||
|
String get wins;
|
||||||
|
|
||||||
|
/// Date format for yesterday
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'Yesterday at'**
|
||||||
|
String get yesterday_at;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AppLocalizationsDelegate
|
||||||
|
extends LocalizationsDelegate<AppLocalizations> {
|
||||||
|
const _AppLocalizationsDelegate();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<AppLocalizations> load(Locale locale) {
|
||||||
|
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isSupported(Locale locale) =>
|
||||||
|
<String>['de', 'en'].contains(locale.languageCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldReload(_AppLocalizationsDelegate old) => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppLocalizations lookupAppLocalizations(Locale locale) {
|
||||||
|
// Lookup logic when only language code is specified.
|
||||||
|
switch (locale.languageCode) {
|
||||||
|
case 'de':
|
||||||
|
return AppLocalizationsDe();
|
||||||
|
case 'en':
|
||||||
|
return AppLocalizationsEn();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw FlutterError(
|
||||||
|
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
|
||||||
|
'an issue with the localizations generation tool. Please file an issue '
|
||||||
|
'on GitHub with a reproducible sample app and the gen-l10n configuration '
|
||||||
|
'that was used.',
|
||||||
|
);
|
||||||
|
}
|
||||||
269
lib/l10n/generated/app_localizations_de.dart
Normal file
@@ -0,0 +1,269 @@
|
|||||||
|
// ignore: unused_import
|
||||||
|
import 'package:intl/intl.dart' as intl;
|
||||||
|
import 'app_localizations.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
|
||||||
|
/// The translations for German (`de`).
|
||||||
|
class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
AppLocalizationsDe([String locale = 'de']) : super(locale);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get all_players => 'Alle Spieler:innen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get all_players_selected => 'Alle Spieler:innen ausgewählt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get amount_of_matches => 'Anzahl der Spiele';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_name => 'Game Tracker';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cancel => 'Abbrechen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get choose_game => 'Spielvorlage wählen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get choose_group => 'Gruppe wählen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get choose_ruleset => 'Regelwerk wählen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String could_not_add_player(Object playerName) {
|
||||||
|
return 'Spieler:in $playerName konnte nicht hinzugefügt werden';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get create_group => 'Gruppe erstellen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get create_match => 'Spiel erstellen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get create_new_group => 'Neue Gruppe erstellen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get create_new_match => 'Neues Spiel erstellen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get data_successfully_deleted => 'Daten erfolgreich gelöscht';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get data_successfully_exported => 'Daten erfolgreich exportiert';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get data_successfully_imported => 'Daten erfolgreich importiert';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String days_ago(int count) {
|
||||||
|
return 'vor $count Tagen';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get delete => 'Löschen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get delete_all_data => 'Alle Daten löschen?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get error_creating_group =>
|
||||||
|
'Fehler beim Erstellen der Gruppe, bitte erneut versuchen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get error_reading_file => 'Fehler beim Lesen der Datei';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get export_canceled => 'Export abgebrochen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get export_data => 'Daten exportieren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get format_exception => 'Formatfehler (siehe Konsole)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get game => 'Spielvorlage';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get game_name => 'Spielvorlagenname';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get group => 'Gruppe';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get group_name => 'Gruppenname';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get groups => 'Gruppen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get home => 'Startseite';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get import_canceled => 'Import abgebrochen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get import_data => 'Daten importieren';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get info => 'Info';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get invalid_schema => 'Ungültiges Schema';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get least_points => 'Niedrigste Punkte';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get match_in_progress => 'Spiel läuft...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get match_name => 'Spieltitel';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get matches => 'Spiele';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menu => 'Menü';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get most_points => 'Höchste Punkte';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_data_available => 'Keine Daten verfügbar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_groups_created_yet => 'Noch keine Gruppen erstellt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_matches_created_yet => 'Noch keine Spiele erstellt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_players_created_yet => 'Noch keine Spieler:in erstellt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_players_found_with_that_name =>
|
||||||
|
'Keine Spieler:in mit diesem Namen gefunden';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_players_selected => 'Keine Spieler:innen ausgewählt';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_recent_matches_available => 'Keine letzten Spiele verfügbar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_second_match_available => 'Kein zweites Spiel verfügbar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_statistics_available => 'Keine Statistiken verfügbar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get none => 'Kein';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get none_group => 'Keine';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get not_available => 'Nicht verfügbar';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get player_name => 'Spieler:innenname';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get players => 'Spieler:innen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String players_count(int count) {
|
||||||
|
return '$count Spieler';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get quick_create => 'Schnellzugriff';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get recent_matches => 'Letzte Spiele';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ruleset => 'Regelwerk';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ruleset_least_points =>
|
||||||
|
'Umgekehrte Wertung: Der/die Spieler:in mit den wenigsten Punkten gewinnt.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ruleset_most_points =>
|
||||||
|
'Traditionelles Regelwerk: Der/die Spieler:in mit den meisten Punkten gewinnt.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ruleset_single_loser =>
|
||||||
|
'Genau ein:e Verlierer:in wird bestimmt; der letzte Platz erhält die Strafe oder Konsequenz.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ruleset_single_winner =>
|
||||||
|
'Genau ein:e Gewinner:in wird gewählt; Unentschieden werden durch einen vordefinierten Tie-Breaker aufgelöst.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get search_for_groups => 'Nach Gruppen suchen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get search_for_players => 'Nach Spieler:innen suchen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get select_winner => 'Gewinner:in wählen:';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selected_players => 'Ausgewählte Spieler:innen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings => 'Einstellungen';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get single_loser => 'Ein:e Verlierer:in';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get single_winner => 'Ein:e Gewinner:in';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get statistics => 'Statistiken';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get stats => 'Statistiken';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String successfully_added_player(String playerName) {
|
||||||
|
return 'Spieler:in $playerName erfolgreich hinzugefügt';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get there_is_no_group_matching_your_search =>
|
||||||
|
'Es gibt keine Gruppe, die deiner Suche entspricht';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get this_cannot_be_undone =>
|
||||||
|
'Dies kann nicht rückgängig gemacht werden';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get today_at => 'Heute um';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get undo => 'Rückgängig';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unknown_exception => 'Unbekannter Fehler (siehe Konsole)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get winner => 'Gewinner:in';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get winrate => 'Siegquote';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get wins => 'Siege';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get yesterday_at => 'Gestern um';
|
||||||
|
}
|
||||||
268
lib/l10n/generated/app_localizations_en.dart
Normal file
@@ -0,0 +1,268 @@
|
|||||||
|
// ignore: unused_import
|
||||||
|
import 'package:intl/intl.dart' as intl;
|
||||||
|
import 'app_localizations.dart';
|
||||||
|
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
|
||||||
|
/// The translations for English (`en`).
|
||||||
|
class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
AppLocalizationsEn([String locale = 'en']) : super(locale);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get all_players => 'All players';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get all_players_selected => 'All players selected';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get amount_of_matches => 'Amount of Matches';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get app_name => 'Game Tracker';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get cancel => 'Cancel';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get choose_game => 'Choose Game';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get choose_group => 'Choose Group';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get choose_ruleset => 'Choose Ruleset';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String could_not_add_player(Object playerName) {
|
||||||
|
return 'Could not add player';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get create_group => 'Create Group';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get create_match => 'Create match';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get create_new_group => 'Create new group';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get create_new_match => 'Create new match';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get data_successfully_deleted => 'Data successfully deleted';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get data_successfully_exported => 'Data successfully exported';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get data_successfully_imported => 'Data successfully imported';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String days_ago(int count) {
|
||||||
|
return '$count days ago';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get delete => 'Delete';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get delete_all_data => 'Delete all data?';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get error_creating_group =>
|
||||||
|
'Error while creating group, please try again';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get error_reading_file => 'Error reading file';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get export_canceled => 'Export canceled';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get export_data => 'Export data';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get format_exception => 'Format Exception (see console)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get game => 'Game';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get game_name => 'Game Name';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get group => 'Group';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get group_name => 'Group name';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get groups => 'Groups';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get home => 'Home';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get import_canceled => 'Import canceled';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get import_data => 'Import data';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get info => 'Info';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get invalid_schema => 'Invalid Schema';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get least_points => 'Least Points';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get match_in_progress => 'Match in progress...';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get match_name => 'Match name';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get matches => 'Matches';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get menu => 'Menu';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get most_points => 'Most Points';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_data_available => 'No data available';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_groups_created_yet => 'No groups created yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_matches_created_yet => 'No matches created yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_players_created_yet => 'No players created yet';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_players_found_with_that_name =>
|
||||||
|
'No players found with that name';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_players_selected => 'No players selected';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_recent_matches_available => 'No recent matches available';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_second_match_available => 'No second match available';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get no_statistics_available => 'No statistics available';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get none => 'None';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get none_group => 'None';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get not_available => 'Not available';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get player_name => 'Player name';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get players => 'Players';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String players_count(int count) {
|
||||||
|
return '$count Players';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get quick_create => 'Quick Create';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get recent_matches => 'Recent Matches';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ruleset => 'Ruleset';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ruleset_least_points =>
|
||||||
|
'Inverse scoring: the player with the fewest points wins.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ruleset_most_points =>
|
||||||
|
'Traditional ruleset: the player with the most points wins.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ruleset_single_loser =>
|
||||||
|
'Exactly one loser is determined; last place receives the penalty or consequence.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get ruleset_single_winner =>
|
||||||
|
'Exactly one winner is chosen; ties are resolved by a predefined tiebreaker.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get search_for_groups => 'Search for groups';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get search_for_players => 'Search for players';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get select_winner => 'Select Winner:';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get selected_players => 'Selected players';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get settings => 'Settings';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get single_loser => 'Single Loser';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get single_winner => 'Single Winner';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get statistics => 'Statistics';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get stats => 'Stats';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String successfully_added_player(String playerName) {
|
||||||
|
return 'Successfully added player $playerName';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get there_is_no_group_matching_your_search =>
|
||||||
|
'There is no group matching your search';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get this_cannot_be_undone => 'This can\'t be undone';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get today_at => 'Today at';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get undo => 'Undo';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get unknown_exception => 'Unknown Exception (see console)';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get winner => 'Winner';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get winrate => 'Winrate';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get wins => 'Wins';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get yesterday_at => 'Yesterday at';
|
||||||
|
}
|
||||||
@@ -1,21 +1,51 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:game_tracker/presentation/views/home_view.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';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
runApp(const MyApp());
|
runApp(
|
||||||
|
Provider<AppDatabase>(
|
||||||
|
create: (context) => AppDatabase(),
|
||||||
|
child: const GameTracker(),
|
||||||
|
dispose: (context, db) => db.close(),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class GameTracker extends StatelessWidget {
|
||||||
const MyApp({super.key});
|
const GameTracker({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'Game Tracker',
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
localeResolutionCallback: (locale, supportedLocales) {
|
||||||
|
for (final supportedLocale in supportedLocales) {
|
||||||
|
if (supportedLocale.languageCode == locale?.languageCode) {
|
||||||
|
return supportedLocale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return supportedLocales.firstWhere(
|
||||||
|
(locale) => locale.languageCode == 'en',
|
||||||
|
);
|
||||||
|
},
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
onGenerateTitle: (context) => AppLocalizations.of(context).app_name,
|
||||||
|
themeMode: ThemeMode.dark, // forces dark mode
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
|
primaryColor: CustomTheme.primaryColor,
|
||||||
|
scaffoldBackgroundColor: CustomTheme.backgroundColor,
|
||||||
|
appBarTheme: CustomTheme.appBarTheme,
|
||||||
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
seedColor: CustomTheme.primaryColor,
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
).copyWith(surface: CustomTheme.backgroundColor),
|
||||||
),
|
),
|
||||||
home: const HomeView(),
|
home: const CustomNavigationBar(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class HomeView extends StatelessWidget {
|
|
||||||
const HomeView({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return const Placeholder();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
148
lib/presentation/views/main_menu/custom_navigation_bar.dart
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
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/group_view/groups_view.dart';
|
||||||
|
import 'package:game_tracker/presentation/views/main_menu/home_view.dart';
|
||||||
|
import 'package:game_tracker/presentation/views/main_menu/match_view/match_view.dart';
|
||||||
|
import 'package:game_tracker/presentation/views/main_menu/settings_view.dart';
|
||||||
|
import 'package:game_tracker/presentation/views/main_menu/statistics_view.dart';
|
||||||
|
import 'package:game_tracker/presentation/widgets/navbar_item.dart';
|
||||||
|
|
||||||
|
class CustomNavigationBar extends StatefulWidget {
|
||||||
|
const CustomNavigationBar({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CustomNavigationBar> createState() => _CustomNavigationBarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CustomNavigationBarState extends State<CustomNavigationBar>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
|
/// Currently selected tab index
|
||||||
|
int currentIndex = 0;
|
||||||
|
|
||||||
|
/// Key count to force rebuild of tab views
|
||||||
|
int tabKeyCount = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
// Pretty ugly but works
|
||||||
|
final List<Widget> tabs = [
|
||||||
|
KeyedSubtree(key: ValueKey('home_$tabKeyCount'), child: const HomeView()),
|
||||||
|
KeyedSubtree(
|
||||||
|
key: ValueKey('matches_$tabKeyCount'),
|
||||||
|
child: const MatchView(),
|
||||||
|
),
|
||||||
|
KeyedSubtree(
|
||||||
|
key: ValueKey('groups_$tabKeyCount'),
|
||||||
|
child: const GroupsView(),
|
||||||
|
),
|
||||||
|
KeyedSubtree(
|
||||||
|
key: ValueKey('stats_$tabKeyCount'),
|
||||||
|
child: const StatisticsView(),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
centerTitle: true,
|
||||||
|
title: Text(
|
||||||
|
_currentTabTitle(context),
|
||||||
|
style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
backgroundColor: CustomTheme.backgroundColor,
|
||||||
|
scrolledUnderElevation: 0,
|
||||||
|
actions: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (_) => const SettingsView()),
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
tabKeyCount++;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.settings),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
elevation: 0,
|
||||||
|
),
|
||||||
|
backgroundColor: CustomTheme.backgroundColor,
|
||||||
|
body: tabs[currentIndex],
|
||||||
|
extendBody: true,
|
||||||
|
bottomNavigationBar: SafeArea(
|
||||||
|
minimum: const EdgeInsets.only(bottom: 30),
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 10),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
color: CustomTheme.primaryColor,
|
||||||
|
),
|
||||||
|
child: ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(24),
|
||||||
|
child: SizedBox(
|
||||||
|
height: 60,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: <Widget>[
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handles tab tap events. Updates the current [index] state.
|
||||||
|
void onTabTapped(int index) {
|
||||||
|
setState(() {
|
||||||
|
currentIndex = index;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the title of the current tab based on [currentIndex].
|
||||||
|
String _currentTabTitle(context) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
switch (currentIndex) {
|
||||||
|
case 0:
|
||||||
|
return loc.home;
|
||||||
|
case 1:
|
||||||
|
return loc.matches;
|
||||||
|
case 2:
|
||||||
|
return loc.groups;
|
||||||
|
case 3:
|
||||||
|
return loc.statistics;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
class CreateGroupView extends StatefulWidget {
|
||||||
|
const CreateGroupView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CreateGroupView> createState() => _CreateGroupViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CreateGroupViewState extends State<CreateGroupView> {
|
||||||
|
late final AppDatabase db;
|
||||||
|
|
||||||
|
/// Controller for the group name input field
|
||||||
|
final _groupNameController = TextEditingController();
|
||||||
|
|
||||||
|
/// List of currently selected players
|
||||||
|
List<Player> selectedPlayers = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
db = Provider.of<AppDatabase>(context, listen: false);
|
||||||
|
_groupNameController.addListener(() {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_groupNameController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: CustomTheme.backgroundColor,
|
||||||
|
appBar: AppBar(title: Text(loc.create_new_group)),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
margin: CustomTheme.standardMargin,
|
||||||
|
child: TextInputField(
|
||||||
|
controller: _groupNameController,
|
||||||
|
hintText: loc.group_name,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: PlayerSelection(
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
selectedPlayers = [...value];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CustomWidthButton(
|
||||||
|
text: loc.create_group,
|
||||||
|
sizeRelativeToWidth: 0.95,
|
||||||
|
buttonType: ButtonType.primary,
|
||||||
|
onPressed:
|
||||||
|
(_groupNameController.text.isEmpty ||
|
||||||
|
(selectedPlayers.length < 2))
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
bool success = await db.groupDao.addGroup(
|
||||||
|
group: Group(
|
||||||
|
name: _groupNameController.text.trim(),
|
||||||
|
members: selectedPlayers,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
if (success) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
} else {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
backgroundColor: CustomTheme.boxColor,
|
||||||
|
content: Center(
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
).error_creating_group,
|
||||||
|
style: const TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
122
lib/presentation/views/main_menu/group_view/groups_view.dart
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
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/group.dart';
|
||||||
|
import 'package:game_tracker/data/dto/player.dart';
|
||||||
|
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||||
|
import 'package:game_tracker/presentation/views/main_menu/group_view/create_group_view.dart';
|
||||||
|
import 'package:game_tracker/presentation/widgets/app_skeleton.dart';
|
||||||
|
import 'package:game_tracker/presentation/widgets/buttons/custom_width_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';
|
||||||
|
|
||||||
|
class GroupsView extends StatefulWidget {
|
||||||
|
const GroupsView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<GroupsView> createState() => _GroupsViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GroupsViewState extends State<GroupsView> {
|
||||||
|
late final AppDatabase db;
|
||||||
|
|
||||||
|
/// Loaded groups from the database
|
||||||
|
late List<Group> loadedGroups;
|
||||||
|
|
||||||
|
/// Loading state
|
||||||
|
bool isLoading = true;
|
||||||
|
|
||||||
|
List<Group> groups = List.filled(
|
||||||
|
7,
|
||||||
|
Group(
|
||||||
|
name: 'Skeleton Group',
|
||||||
|
members: List.filled(6, Player(name: 'Skeleton Player')),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
db = Provider.of<AppDatabase>(context, listen: false);
|
||||||
|
loadGroups();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: CustomTheme.backgroundColor,
|
||||||
|
body: Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
AppSkeleton(
|
||||||
|
enabled: isLoading,
|
||||||
|
child: Visibility(
|
||||||
|
visible: groups.isNotEmpty,
|
||||||
|
replacement: Center(
|
||||||
|
child: TopCenteredMessage(
|
||||||
|
icon: Icons.info,
|
||||||
|
title: loc.info,
|
||||||
|
message: loc.no_groups_created_yet,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: const EdgeInsets.only(bottom: 85),
|
||||||
|
itemCount: groups.length + 1,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
if (index == groups.length) {
|
||||||
|
return SizedBox(
|
||||||
|
height: MediaQuery.paddingOf(context).bottom - 20,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return GroupTile(group: groups[index]);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: MediaQuery.paddingOf(context).bottom,
|
||||||
|
child: CustomWidthButton(
|
||||||
|
text: loc.create_group,
|
||||||
|
sizeRelativeToWidth: 0.90,
|
||||||
|
onPressed: () async {
|
||||||
|
await Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return const CreateGroupView();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
loadGroups();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loadGroups() {
|
||||||
|
Future.wait([
|
||||||
|
db.groupDao.getAllGroups(),
|
||||||
|
Future.delayed(Constants.minimumSkeletonDuration),
|
||||||
|
]).then((results) {
|
||||||
|
loadedGroups = results[0] as List<Group>;
|
||||||
|
setState(() {
|
||||||
|
groups = loadedGroups
|
||||||
|
..sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||||
|
});
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
261
lib/presentation/views/main_menu/home_view.dart
Normal file
@@ -0,0 +1,261 @@
|
|||||||
|
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/group.dart';
|
||||||
|
import 'package:game_tracker/data/dto/match.dart';
|
||||||
|
import 'package:game_tracker/data/dto/player.dart';
|
||||||
|
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||||
|
import 'package:game_tracker/presentation/widgets/app_skeleton.dart';
|
||||||
|
import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart';
|
||||||
|
import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart';
|
||||||
|
import 'package:game_tracker/presentation/widgets/tiles/match_summary_tile.dart';
|
||||||
|
import 'package:game_tracker/presentation/widgets/tiles/quick_info_tile.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class HomeView extends StatefulWidget {
|
||||||
|
const HomeView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HomeView> createState() => _HomeViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomeViewState extends State<HomeView> {
|
||||||
|
bool isLoading = true;
|
||||||
|
|
||||||
|
/// Amount of matches in the database
|
||||||
|
int matchCount = 0;
|
||||||
|
|
||||||
|
/// Amount of groups in the database
|
||||||
|
int groupCount = 0;
|
||||||
|
|
||||||
|
/// Loaded recent matches from the database
|
||||||
|
List<Match> loadedRecentMatches = [];
|
||||||
|
|
||||||
|
/// Recent matches to display, initially filled with skeleton matches
|
||||||
|
List<Match> recentMatches = List.filled(
|
||||||
|
2,
|
||||||
|
Match(
|
||||||
|
name: 'Skeleton Match',
|
||||||
|
group: Group(
|
||||||
|
name: 'Skeleton Group',
|
||||||
|
members: [
|
||||||
|
Player(name: 'Skeleton Player 1'),
|
||||||
|
Player(name: 'Skeleton Player 2'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
winner: Player(name: 'Skeleton Player 1'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
loadHomeViewData();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
|
return AppSkeleton(
|
||||||
|
fixLayoutBuilder: true,
|
||||||
|
enabled: isLoading,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
QuickInfoTile(
|
||||||
|
width: constraints.maxWidth * 0.45,
|
||||||
|
height: constraints.maxHeight * 0.15,
|
||||||
|
title: loc.matches,
|
||||||
|
icon: Icons.groups_rounded,
|
||||||
|
value: matchCount,
|
||||||
|
),
|
||||||
|
SizedBox(width: constraints.maxWidth * 0.05),
|
||||||
|
QuickInfoTile(
|
||||||
|
width: constraints.maxWidth * 0.45,
|
||||||
|
height: constraints.maxHeight * 0.15,
|
||||||
|
title: loc.groups,
|
||||||
|
icon: Icons.groups_rounded,
|
||||||
|
value: groupCount,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 16.0),
|
||||||
|
child: InfoTile(
|
||||||
|
width: constraints.maxWidth * 0.95,
|
||||||
|
title: loc.recent_matches,
|
||||||
|
icon: Icons.timer,
|
||||||
|
content: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 40.0),
|
||||||
|
child: Visibility(
|
||||||
|
visible: !isLoading && loadedRecentMatches.isNotEmpty,
|
||||||
|
replacement: Center(
|
||||||
|
heightFactor: 12,
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
).no_recent_matches_available,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
MatchSummaryTile(
|
||||||
|
matchTitle: recentMatches[0].name,
|
||||||
|
game: 'Winner',
|
||||||
|
ruleset: 'Ruleset',
|
||||||
|
players: _getPlayerText(
|
||||||
|
recentMatches[0],
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
winner: recentMatches[0].winner == null
|
||||||
|
? AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
).match_in_progress
|
||||||
|
: recentMatches[0].winner!.name,
|
||||||
|
),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
child: Divider(),
|
||||||
|
),
|
||||||
|
if (loadedRecentMatches.length > 1) ...[
|
||||||
|
MatchSummaryTile(
|
||||||
|
matchTitle: recentMatches[1].name,
|
||||||
|
game: 'Winner',
|
||||||
|
ruleset: 'Ruleset',
|
||||||
|
players: _getPlayerText(
|
||||||
|
recentMatches[1],
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
winner: recentMatches[1].winner == null
|
||||||
|
? AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
).match_in_progress
|
||||||
|
: recentMatches[1].winner!.name,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
] else ...[
|
||||||
|
Center(
|
||||||
|
heightFactor: 5.35,
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
).no_second_match_available,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
InfoTile(
|
||||||
|
width: constraints.maxWidth * 0.95,
|
||||||
|
title: loc.quick_create,
|
||||||
|
icon: Icons.add_box_rounded,
|
||||||
|
content: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
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: () {},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
QuickCreateButton(
|
||||||
|
text: 'Category 5',
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
QuickCreateButton(
|
||||||
|
text: 'Category 6',
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Loads the data for the HomeView from the database.
|
||||||
|
/// This includes the match count, group count, and recent matches.
|
||||||
|
void loadHomeViewData() {
|
||||||
|
final db = Provider.of<AppDatabase>(context, listen: false);
|
||||||
|
Future.wait([
|
||||||
|
db.matchDao.getMatchCount(),
|
||||||
|
db.groupDao.getGroupCount(),
|
||||||
|
db.matchDao.getAllMatches(),
|
||||||
|
Future.delayed(Constants.minimumSkeletonDuration),
|
||||||
|
]).then((results) {
|
||||||
|
matchCount = results[0] as int;
|
||||||
|
groupCount = results[1] as int;
|
||||||
|
loadedRecentMatches = results[2] as List<Match>;
|
||||||
|
recentMatches =
|
||||||
|
(loadedRecentMatches
|
||||||
|
..sort((a, b) => b.createdAt.compareTo(a.createdAt)))
|
||||||
|
.take(2)
|
||||||
|
.toList();
|
||||||
|
if (loadedRecentMatches.length < 2) {
|
||||||
|
recentMatches.add(
|
||||||
|
Match(name: 'Dummy Match', winner: null, group: null, players: null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates a text representation of the players in the match.
|
||||||
|
/// If the match has a group, it returns the group name and the number of additional players.
|
||||||
|
/// If there is no group, it returns the count of players.
|
||||||
|
String _getPlayerText(Match game, context) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
if (game.group == null) {
|
||||||
|
final playerCount = game.players?.length ?? 0;
|
||||||
|
return loc.players_count(playerCount);
|
||||||
|
}
|
||||||
|
if (game.players == null || game.players!.isEmpty) {
|
||||||
|
return game.group!.name;
|
||||||
|
}
|
||||||
|
return '${game.group!.name} + ${game.players!.length}';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
class ChooseGameView extends StatefulWidget {
|
||||||
|
final List<(String, String, Ruleset)> games;
|
||||||
|
final int initialGameIndex;
|
||||||
|
|
||||||
|
const ChooseGameView({
|
||||||
|
super.key,
|
||||||
|
required this.games,
|
||||||
|
required this.initialGameIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChooseGameView> createState() => _ChooseGameViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChooseGameViewState extends State<ChooseGameView> {
|
||||||
|
/// Controller for the search bar
|
||||||
|
final TextEditingController searchBarController = TextEditingController();
|
||||||
|
|
||||||
|
/// Currently selected game index
|
||||||
|
late int selectedGameIndex;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
selectedGameIndex = widget.initialGameIndex;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: CustomTheme.backgroundColor,
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back_ios),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(selectedGameIndex);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
title: Text(loc.choose_game),
|
||||||
|
),
|
||||||
|
body: PopScope(
|
||||||
|
// This fixes that the Android Back Gesture didn't return the
|
||||||
|
// selectedGameIndex and therefore the selected Game wasn't saved
|
||||||
|
canPop: false,
|
||||||
|
onPopInvokedWithResult: (bool didPop, Object? result) {
|
||||||
|
if (didPop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop(selectedGameIndex);
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
child: CustomSearchBar(
|
||||||
|
controller: searchBarController,
|
||||||
|
hintText: loc.game_name,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: widget.games.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return TitleDescriptionListTile(
|
||||||
|
title: widget.games[index].$1,
|
||||||
|
description: widget.games[index].$2,
|
||||||
|
badgeText: translateRulesetToString(
|
||||||
|
widget.games[index].$3,
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
isHighlighted: selectedGameIndex == index,
|
||||||
|
onPressed: () async {
|
||||||
|
setState(() {
|
||||||
|
if (selectedGameIndex == index) {
|
||||||
|
selectedGameIndex = -1;
|
||||||
|
} else {
|
||||||
|
selectedGameIndex = index;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,149 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
class ChooseGroupView extends StatefulWidget {
|
||||||
|
final List<Group> groups;
|
||||||
|
final String initialGroupId;
|
||||||
|
|
||||||
|
const ChooseGroupView({
|
||||||
|
super.key,
|
||||||
|
required this.groups,
|
||||||
|
required this.initialGroupId,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChooseGroupView> createState() => _ChooseGroupViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChooseGroupViewState extends State<ChooseGroupView> {
|
||||||
|
late String selectedGroupId;
|
||||||
|
final TextEditingController controller = TextEditingController();
|
||||||
|
late final List<Group> filteredGroups;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
selectedGroupId = widget.initialGroupId;
|
||||||
|
filteredGroups = [...widget.groups];
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: CustomTheme.backgroundColor,
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back_ios),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(
|
||||||
|
selectedGroupId == ''
|
||||||
|
? null
|
||||||
|
: widget.groups.firstWhere(
|
||||||
|
(group) => group.id == selectedGroupId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
title: Text(loc.choose_group),
|
||||||
|
),
|
||||||
|
body: PopScope(
|
||||||
|
// This fixes that the Android Back Gesture didn't return the
|
||||||
|
// selectedGroupId and therefore the selected Group wasn't saved
|
||||||
|
canPop: false,
|
||||||
|
onPopInvokedWithResult: (bool didPop, Object? result) {
|
||||||
|
if (didPop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop(
|
||||||
|
selectedGroupId == ''
|
||||||
|
? null
|
||||||
|
: widget.groups.firstWhere(
|
||||||
|
(group) => group.id == selectedGroupId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
|
child: CustomSearchBar(
|
||||||
|
controller: controller,
|
||||||
|
hintText: loc.search_for_groups,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
filterGroups(value);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Visibility(
|
||||||
|
visible: filteredGroups.isNotEmpty,
|
||||||
|
replacement: Visibility(
|
||||||
|
visible: widget.groups.isNotEmpty,
|
||||||
|
replacement: TopCenteredMessage(
|
||||||
|
icon: Icons.info,
|
||||||
|
title: loc.info,
|
||||||
|
message: loc.no_groups_created_yet,
|
||||||
|
),
|
||||||
|
child: TopCenteredMessage(
|
||||||
|
icon: Icons.info,
|
||||||
|
title: loc.info,
|
||||||
|
message: AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
).there_is_no_group_matching_your_search,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: const EdgeInsets.only(bottom: 85),
|
||||||
|
itemCount: filteredGroups.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
if (selectedGroupId != filteredGroups[index].id) {
|
||||||
|
selectedGroupId = filteredGroups[index].id;
|
||||||
|
} else {
|
||||||
|
selectedGroupId = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: GroupTile(
|
||||||
|
group: filteredGroups[index],
|
||||||
|
isHighlighted:
|
||||||
|
selectedGroupId == filteredGroups[index].id,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Filters the groups based on the search [query].
|
||||||
|
void filterGroups(String query) {
|
||||||
|
setState(() {
|
||||||
|
if (query.isEmpty) {
|
||||||
|
filteredGroups.clear();
|
||||||
|
filteredGroups.addAll(widget.groups);
|
||||||
|
} else {
|
||||||
|
filteredGroups.clear();
|
||||||
|
filteredGroups.addAll(
|
||||||
|
widget.groups.where(
|
||||||
|
(group) => group.name.toLowerCase().contains(query.toLowerCase()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:game_tracker/core/custom_theme.dart';
|
||||||
|
import 'package:game_tracker/core/enums.dart';
|
||||||
|
import 'package:game_tracker/l10n/generated/app_localizations.dart';
|
||||||
|
import 'package:game_tracker/presentation/widgets/tiles/title_description_list_tile.dart';
|
||||||
|
|
||||||
|
class ChooseRulesetView extends StatefulWidget {
|
||||||
|
final List<(Ruleset, String)> rulesets;
|
||||||
|
final int initialRulesetIndex;
|
||||||
|
|
||||||
|
const ChooseRulesetView({
|
||||||
|
super.key,
|
||||||
|
required this.rulesets,
|
||||||
|
required this.initialRulesetIndex,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChooseRulesetView> createState() => _ChooseRulesetViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChooseRulesetViewState extends State<ChooseRulesetView> {
|
||||||
|
/// Currently selected ruleset index
|
||||||
|
late int selectedRulesetIndex;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
selectedRulesetIndex = widget.initialRulesetIndex;
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
return DefaultTabController(
|
||||||
|
length: 2,
|
||||||
|
initialIndex: 0,
|
||||||
|
child: Scaffold(
|
||||||
|
backgroundColor: CustomTheme.backgroundColor,
|
||||||
|
appBar: AppBar(
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back_ios),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(
|
||||||
|
selectedRulesetIndex == -1
|
||||||
|
? null
|
||||||
|
: widget.rulesets[selectedRulesetIndex].$1,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
title: Text(loc.choose_ruleset),
|
||||||
|
),
|
||||||
|
body: PopScope(
|
||||||
|
// This fixes that the Android Back Gesture didn't return the
|
||||||
|
// selectedRulesetIndex and therefore the selected Ruleset wasn't saved
|
||||||
|
canPop: false,
|
||||||
|
onPopInvokedWithResult: (bool didPop, Object? result) {
|
||||||
|
if (didPop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Navigator.of(context).pop(
|
||||||
|
selectedRulesetIndex == -1
|
||||||
|
? null
|
||||||
|
: widget.rulesets[selectedRulesetIndex].$1,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: ListView.builder(
|
||||||
|
padding: const EdgeInsets.only(bottom: 85),
|
||||||
|
itemCount: widget.rulesets.length,
|
||||||
|
itemBuilder: (BuildContext context, int index) {
|
||||||
|
return TitleDescriptionListTile(
|
||||||
|
onPressed: () async {
|
||||||
|
setState(() {
|
||||||
|
if (selectedRulesetIndex == index) {
|
||||||
|
selectedRulesetIndex = -1;
|
||||||
|
} else {
|
||||||
|
selectedRulesetIndex = index;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
title: translateRulesetToString(
|
||||||
|
widget.rulesets[index].$1,
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
description: widget.rulesets[index].$2,
|
||||||
|
isHighlighted: selectedRulesetIndex == index,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
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/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/create_match/choose_ruleset_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';
|
||||||
|
|
||||||
|
class CreateMatchView extends StatefulWidget {
|
||||||
|
final VoidCallback? onWinnerChanged;
|
||||||
|
const CreateMatchView({super.key, this.onWinnerChanged});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CreateMatchView> createState() => _CreateMatchViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CreateMatchViewState extends State<CreateMatchView> {
|
||||||
|
late final AppDatabase db;
|
||||||
|
|
||||||
|
/// Controller for the match name input field
|
||||||
|
final TextEditingController _matchNameController = TextEditingController();
|
||||||
|
|
||||||
|
/// Hint text for the match name input field
|
||||||
|
String? hintText;
|
||||||
|
|
||||||
|
/// List of all groups from the database
|
||||||
|
List<Group> groupsList = [];
|
||||||
|
|
||||||
|
/// List of all players from the database
|
||||||
|
List<Player> playerList = [];
|
||||||
|
|
||||||
|
/// List of players filtered based on the selected group
|
||||||
|
/// If a group is selected, this list contains all players from [playerList]
|
||||||
|
/// who are not members of the selected group. If no group is selected,
|
||||||
|
/// this list is identical to [playerList].
|
||||||
|
List<Player> filteredPlayerList = [];
|
||||||
|
|
||||||
|
/// The currently selected group
|
||||||
|
Group? selectedGroup;
|
||||||
|
|
||||||
|
/// The index of the currently selected group in [groupsList] to mark it in
|
||||||
|
/// the [ChooseGroupView]
|
||||||
|
String selectedGroupId = '';
|
||||||
|
|
||||||
|
/// The currently selected ruleset
|
||||||
|
Ruleset? selectedRuleset;
|
||||||
|
|
||||||
|
/// The index of the currently selected ruleset in [rulesets] to mark it in
|
||||||
|
/// the [ChooseRulesetView]
|
||||||
|
int selectedRulesetIndex = -1;
|
||||||
|
|
||||||
|
/// The index of the currently selected game in [games] to mark it in
|
||||||
|
/// the [ChooseGameView]
|
||||||
|
int selectedGameIndex = -1;
|
||||||
|
|
||||||
|
/// The currently selected players
|
||||||
|
List<Player>? selectedPlayers;
|
||||||
|
|
||||||
|
/// List of available rulesets with their localized string representations
|
||||||
|
late final List<(Ruleset, String)> _rulesets;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_matchNameController.addListener(() {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
|
||||||
|
db = Provider.of<AppDatabase>(context, listen: false);
|
||||||
|
|
||||||
|
Future.wait([
|
||||||
|
db.groupDao.getAllGroups(),
|
||||||
|
db.playerDao.getAllPlayers(),
|
||||||
|
]).then((result) async {
|
||||||
|
groupsList = result[0] as List<Group>;
|
||||||
|
playerList = result[1] as List<Player>;
|
||||||
|
setState(() {
|
||||||
|
filteredPlayerList = List.from(playerList);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_matchNameController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
hintText ??= loc.match_name;
|
||||||
|
_rulesets = [
|
||||||
|
(Ruleset.singleWinner, loc.ruleset_single_winner),
|
||||||
|
(Ruleset.singleLoser, loc.ruleset_single_loser),
|
||||||
|
(Ruleset.mostPoints, loc.ruleset_most_points),
|
||||||
|
(Ruleset.leastPoints, loc.ruleset_least_points),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Replace when games are implemented
|
||||||
|
List<(String, String, Ruleset)> games = [
|
||||||
|
('Example Game 1', 'This is a description', Ruleset.leastPoints),
|
||||||
|
('Example Game 2', '', Ruleset.singleWinner),
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context);
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: CustomTheme.backgroundColor,
|
||||||
|
appBar: AppBar(title: Text(loc.create_new_match)),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
margin: CustomTheme.tileMargin,
|
||||||
|
child: TextInputField(
|
||||||
|
controller: _matchNameController,
|
||||||
|
hintText: hintText ?? '',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ChooseTile(
|
||||||
|
title: loc.game,
|
||||||
|
trailingText: selectedGameIndex == -1
|
||||||
|
? loc.none
|
||||||
|
: games[selectedGameIndex].$1,
|
||||||
|
onPressed: () async {
|
||||||
|
selectedGameIndex = await Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => ChooseGameView(
|
||||||
|
games: games,
|
||||||
|
initialGameIndex: selectedGameIndex,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
setState(() {
|
||||||
|
if (selectedGameIndex != -1) {
|
||||||
|
hintText = games[selectedGameIndex].$1;
|
||||||
|
selectedRuleset = games[selectedGameIndex].$3;
|
||||||
|
selectedRulesetIndex = _rulesets.indexWhere(
|
||||||
|
(r) => r.$1 == selectedRuleset,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
hintText = loc.match_name;
|
||||||
|
selectedRuleset = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ChooseTile(
|
||||||
|
title: loc.ruleset,
|
||||||
|
trailingText: selectedRuleset == null
|
||||||
|
? loc.none
|
||||||
|
: translateRulesetToString(selectedRuleset!, context),
|
||||||
|
onPressed: () async {
|
||||||
|
selectedRuleset = await Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => ChooseRulesetView(
|
||||||
|
rulesets: _rulesets,
|
||||||
|
initialRulesetIndex: selectedRulesetIndex,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
selectedRulesetIndex = _rulesets.indexWhere(
|
||||||
|
(r) => r.$1 == selectedRuleset,
|
||||||
|
);
|
||||||
|
selectedGameIndex = -1;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ChooseTile(
|
||||||
|
title: loc.group,
|
||||||
|
trailingText: selectedGroup == null
|
||||||
|
? loc.none_group
|
||||||
|
: selectedGroup!.name,
|
||||||
|
onPressed: () async {
|
||||||
|
selectedGroup = await Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => ChooseGroupView(
|
||||||
|
groups: groupsList,
|
||||||
|
initialGroupId: selectedGroupId,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
selectedGroupId = selectedGroup?.id ?? '';
|
||||||
|
if (selectedGroup != null) {
|
||||||
|
filteredPlayerList = playerList
|
||||||
|
.where(
|
||||||
|
(p) => !selectedGroup!.members.any((m) => m.id == p.id),
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
} else {
|
||||||
|
filteredPlayerList = List.from(playerList);
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: PlayerSelection(
|
||||||
|
key: ValueKey(selectedGroup?.id ?? 'no_group'),
|
||||||
|
initialSelectedPlayers: selectedPlayers ?? [],
|
||||||
|
availablePlayers: filteredPlayerList,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
selectedPlayers = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
CustomWidthButton(
|
||||||
|
text: loc.create_match,
|
||||||
|
sizeRelativeToWidth: 0.95,
|
||||||
|
buttonType: ButtonType.primary,
|
||||||
|
onPressed: _enableCreateGameButton()
|
||||||
|
? () async {
|
||||||
|
Match match = Match(
|
||||||
|
name: _matchNameController.text.isEmpty
|
||||||
|
? (hintText ?? '')
|
||||||
|
: _matchNameController.text.trim(),
|
||||||
|
createdAt: DateTime.now(),
|
||||||
|
group: selectedGroup,
|
||||||
|
players: selectedPlayers,
|
||||||
|
);
|
||||||
|
await db.matchDao.addMatch(match: match);
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.pushReplacement(
|
||||||
|
context,
|
||||||
|
CupertinoPageRoute(
|
||||||
|
fullscreenDialog: true,
|
||||||
|
builder: (context) => MatchResultView(
|
||||||
|
match: match,
|
||||||
|
onWinnerChanged: widget.onWinnerChanged,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines whether the "Create Match" button should be enabled.
|
||||||
|
///
|
||||||
|
/// Returns `true` if:
|
||||||
|
/// - A ruleset is selected AND
|
||||||
|
/// - Either a group is selected OR at least 2 players are selected
|
||||||
|
bool _enableCreateGameButton() {
|
||||||
|
return (selectedGroup != null ||
|
||||||
|
(selectedPlayers != null && selectedPlayers!.length > 1)) &&
|
||||||
|
selectedRuleset != null;
|
||||||
|
}
|
||||||
|
}
|
||||||