Merge remote-tracking branch 'origin/development' into feature/2-gamehistoryview-anpassen
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"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<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 -->
|
||||
<!-- <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">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:windowBackground">@color/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
||||
179
assets/schema.json
Normal file
@@ -0,0 +1,179 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"games": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"players": {
|
||||
"type": [
|
||||
"array",
|
||||
"null"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"createdAt",
|
||||
"name"
|
||||
]
|
||||
}
|
||||
},
|
||||
"group": {
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"members": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"createdAt",
|
||||
"name"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"createdAt",
|
||||
"name",
|
||||
"members"
|
||||
]
|
||||
},
|
||||
"winner": {
|
||||
"type": ["object","null"]
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"createdAt",
|
||||
"name"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"groups": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"members": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"createdAt",
|
||||
"name"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"createdAt",
|
||||
"name",
|
||||
"members"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"players": {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": [
|
||||
"object",
|
||||
"null"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"createdAt",
|
||||
"name"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,122 +1,14 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-20x20@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-29x29@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-40x40@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-App-60x60@3x.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "20x20",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-20x20@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "29x29",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-29x29@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "40x40",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-40x40@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@1x.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-76x76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "83.5x83.5",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Icon-App-1024x1024@1x.png",
|
||||
"scale" : "1x"
|
||||
"filename" : "icon_x1024.png",
|
||||
"idiom" : "universal",
|
||||
"platform" : "ios",
|
||||
"size" : "1024x1024"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 295 B |
|
Before Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 450 B |
|
Before Width: | Height: | Size: 282 B |
|
Before Width: | Height: | Size: 462 B |
|
Before Width: | Height: | Size: 704 B |
|
Before Width: | Height: | Size: 406 B |
|
Before Width: | Height: | Size: 586 B |
|
Before Width: | Height: | Size: 862 B |
|
Before Width: | Height: | Size: 862 B |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 762 B |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
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
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B |
|
Before Width: | Height: | Size: 68 B |
@@ -1,5 +0,0 @@
|
||||
# Launch Screen Assets
|
||||
|
||||
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||
|
||||
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "icon.png",
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "LaunchImage@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
BIN
ios/Runner/Assets.xcassets/LauncherIcon.imageset/icon.png
vendored
Normal file
|
After Width: | Height: | Size: 9.6 KiB |
@@ -1,8 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||
<?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="12089"/>
|
||||
<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-->
|
||||
@@ -14,24 +17,27 @@
|
||||
<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 opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
||||
<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" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||
</constraints>
|
||||
<color key="backgroundColor" name="LauncherBackgroundColor"/>
|
||||
</view>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="53" y="375"/>
|
||||
<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="LaunchImage" width="168" height="185"/>
|
||||
<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>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||
<?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="10085"/>
|
||||
<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-->
|
||||
@@ -14,13 +16,14 @@
|
||||
<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="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
<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>
|
||||
|
||||
@@ -1,2 +1,24 @@
|
||||
/// Button types used for styling the [CustomWidthButton]
|
||||
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 }
|
||||
|
||||
@@ -15,11 +15,24 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
|
||||
Future<List<Game>> getAllGames() async {
|
||||
final query = select(gameTable);
|
||||
final result = await query.get();
|
||||
return result
|
||||
.map(
|
||||
(row) => Game(id: row.id, name: row.name, createdAt: row.createdAt),
|
||||
)
|
||||
.toList();
|
||||
|
||||
return Future.wait(
|
||||
result.map((row) async {
|
||||
final group = await db.groupGameDao.getGroupOfGame(gameId: row.id);
|
||||
final players = await db.playerGameDao.getPlayersOfGame(gameId: row.id);
|
||||
final winner = row.winnerId != null
|
||||
? await db.playerDao.getPlayerById(playerId: row.winnerId!)
|
||||
: null;
|
||||
return Game(
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
group: group,
|
||||
players: players,
|
||||
createdAt: row.createdAt,
|
||||
winner: winner,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/// Retrieves a [Game] by its [gameId].
|
||||
@@ -29,11 +42,15 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
|
||||
|
||||
List<Player>? players;
|
||||
if (await db.playerGameDao.gameHasPlayers(gameId: gameId)) {
|
||||
players = await db.playerGameDao.getPlayersByGameId(gameId: gameId);
|
||||
players = await db.playerGameDao.getPlayersOfGame(gameId: gameId);
|
||||
}
|
||||
Group? group;
|
||||
if (await db.groupGameDao.hasGameGroup(gameId: gameId)) {
|
||||
group = await db.groupGameDao.getGroupByGameId(gameId: gameId);
|
||||
if (await db.groupGameDao.gameHasGroup(gameId: gameId)) {
|
||||
group = await db.groupGameDao.getGroupOfGame(gameId: gameId);
|
||||
}
|
||||
Player? winner;
|
||||
if (result.winnerId != null) {
|
||||
winner = await db.playerDao.getPlayerById(playerId: result.winnerId!);
|
||||
}
|
||||
|
||||
return Game(
|
||||
@@ -41,7 +58,7 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
|
||||
name: result.name,
|
||||
players: players,
|
||||
group: group,
|
||||
winner: result.winnerId,
|
||||
winner: winner,
|
||||
createdAt: result.createdAt,
|
||||
);
|
||||
}
|
||||
@@ -50,23 +67,157 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
|
||||
/// Also adds associated players and group if they exist.
|
||||
Future<void> addGame({required Game game}) async {
|
||||
await db.transaction(() async {
|
||||
for (final p in game.players ?? []) {
|
||||
await db.playerDao.addPlayer(player: p);
|
||||
await db.playerGameDao.addPlayerToGame(gameId: game.id, playerId: p.id);
|
||||
}
|
||||
if (game.group != null) {
|
||||
await db.groupDao.addGroup(group: game.group!);
|
||||
await db.groupGameDao.addGroupToGame(game.id, game.group!.id);
|
||||
}
|
||||
await into(gameTable).insert(
|
||||
GameTableCompanion.insert(
|
||||
id: game.id,
|
||||
name: game.name,
|
||||
winnerId: game.winner,
|
||||
winnerId: Value(game.winner?.id),
|
||||
createdAt: game.createdAt,
|
||||
),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
|
||||
if (game.players != null) {
|
||||
await db.playerDao.addPlayers(players: game.players!);
|
||||
for (final p in game.players ?? []) {
|
||||
await db.playerGameDao.addPlayerToGame(
|
||||
gameId: game.id,
|
||||
playerId: p.id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (game.group != null) {
|
||||
await db.groupDao.addGroup(group: game.group!);
|
||||
await db.groupGameDao.addGroupToGame(game.id, game.group!.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> addGames({required List<Game> games}) async {
|
||||
if (games.isEmpty) return;
|
||||
await db.transaction(() async {
|
||||
// Add all games in batch
|
||||
await db.batch(
|
||||
(b) => b.insertAll(
|
||||
gameTable,
|
||||
games
|
||||
.map(
|
||||
(game) => GameTableCompanion.insert(
|
||||
id: game.id,
|
||||
name: game.name,
|
||||
createdAt: game.createdAt,
|
||||
winnerId: Value(game.winner?.id),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
),
|
||||
);
|
||||
|
||||
// Add all groups of the games in batch
|
||||
await db.batch(
|
||||
(b) => b.insertAll(
|
||||
db.groupTable,
|
||||
games
|
||||
.where((game) => game.group != null)
|
||||
.map(
|
||||
(game) => GroupTableCompanion.insert(
|
||||
id: game.group!.id,
|
||||
name: game.group!.name,
|
||||
createdAt: game.group!.createdAt,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
),
|
||||
);
|
||||
|
||||
// Add all players of the games in batch (unique)
|
||||
final uniquePlayers = <String, Player>{};
|
||||
for (final game in games) {
|
||||
if (game.players != null) {
|
||||
for (final p in game.players!) {
|
||||
uniquePlayers[p.id] = p;
|
||||
}
|
||||
}
|
||||
// Also include members of groups
|
||||
if (game.group != null) {
|
||||
for (final m in game.group!.members) {
|
||||
uniquePlayers[m.id] = m;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (uniquePlayers.isNotEmpty) {
|
||||
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.insertOrReplace,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Add all player-game associations in batch
|
||||
await db.batch((b) {
|
||||
for (final game in games) {
|
||||
if (game.players != null) {
|
||||
for (final p in game.players ?? []) {
|
||||
b.insert(
|
||||
db.playerGameTable,
|
||||
PlayerGameTableCompanion.insert(
|
||||
gameId: game.id,
|
||||
playerId: p.id,
|
||||
),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add all player-group associations in batch
|
||||
await db.batch((b) {
|
||||
for (final game in games) {
|
||||
if (game.group != null) {
|
||||
for (final m in game.group!.members) {
|
||||
b.insert(
|
||||
db.playerGroupTable,
|
||||
PlayerGroupTableCompanion.insert(
|
||||
playerId: m.id,
|
||||
groupId: game.group!.id,
|
||||
),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add all group-game associations in batch
|
||||
await db.batch((b) {
|
||||
for (final game in games) {
|
||||
if (game.group != null) {
|
||||
b.insert(
|
||||
db.groupGameTable,
|
||||
GroupGameTableCompanion.insert(
|
||||
gameId: game.id,
|
||||
groupId: game.group!.id,
|
||||
),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -94,4 +245,12 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
|
||||
final result = await query.getSingleOrNull();
|
||||
return result != null;
|
||||
}
|
||||
|
||||
/// Deletes all games from the database.
|
||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||
Future<bool> deleteAllGames() async {
|
||||
final query = delete(gameTable);
|
||||
final rowsAffected = await query.go();
|
||||
return rowsAffected > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,5 @@ part of 'game_dao.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$GameDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
||||
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
final result = await query.get();
|
||||
return Future.wait(
|
||||
result.map((groupData) async {
|
||||
final members = await db.playerGroupDao.getPlayersOfGroupById(
|
||||
final members = await db.playerGroupDao.getPlayersOfGroup(
|
||||
groupId: groupData.id,
|
||||
);
|
||||
return Group(
|
||||
@@ -34,7 +34,7 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
final query = select(groupTable)..where((g) => g.id.equals(groupId));
|
||||
final result = await query.getSingle();
|
||||
|
||||
List<Player> members = await db.playerGroupDao.getPlayersOfGroupById(
|
||||
List<Player> members = await db.playerGroupDao.getPlayersOfGroup(
|
||||
groupId: groupId,
|
||||
);
|
||||
|
||||
@@ -57,6 +57,10 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
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(
|
||||
@@ -69,17 +73,94 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
),
|
||||
);
|
||||
await Future.wait(
|
||||
group.members.map((player) => db.playerDao.addPlayer(player: player)),
|
||||
);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Adds multiple groups to the database.
|
||||
/// Also adds the group's members to the [PlayerGroupTable].
|
||||
Future<void> addGroups({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
|
||||
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.insertOrReplace,
|
||||
),
|
||||
);
|
||||
|
||||
// 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) {
|
||||
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.insertOrReplace,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -117,4 +198,12 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,33 @@ class GroupGameDao extends DatabaseAccessor<AppDatabase>
|
||||
with _$GroupGameDaoMixin {
|
||||
GroupGameDao(super.db);
|
||||
|
||||
/// Associates a group with a game by inserting a record into the
|
||||
/// [GroupGameTable].
|
||||
Future<void> addGroupToGame(String gameId, String groupId) async {
|
||||
await into(groupGameTable).insert(
|
||||
GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
}
|
||||
|
||||
/// Retrieves the [Group] associated with the given [gameId].
|
||||
/// Returns `null` if no group is found.
|
||||
Future<Group?> getGroupOfGame({required String gameId}) async {
|
||||
final result = await (select(
|
||||
groupGameTable,
|
||||
)..where((g) => g.gameId.equals(gameId))).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 [gameId].
|
||||
/// Returns `true` if there is a group, otherwise `false`.
|
||||
Future<bool> hasGameGroup({required String gameId}) async {
|
||||
Future<bool> gameHasGroup({required String gameId}) async {
|
||||
final count =
|
||||
await (selectOnly(groupGameTable)
|
||||
..where(groupGameTable.gameId.equals(gameId))
|
||||
@@ -22,21 +46,34 @@ class GroupGameDao extends DatabaseAccessor<AppDatabase>
|
||||
return (count ?? 0) > 0;
|
||||
}
|
||||
|
||||
/// Retrieves the [Group] associated with the given [gameId].
|
||||
Future<Group> getGroupByGameId({required String gameId}) async {
|
||||
final result = await (select(
|
||||
groupGameTable,
|
||||
)..where((g) => g.gameId.equals(gameId))).getSingle();
|
||||
|
||||
final group = await db.groupDao.getGroupById(groupId: result.groupId);
|
||||
return group;
|
||||
/// Checks if a specific group is associated with a specific game.
|
||||
/// Returns `true` if the group is in the game, otherwise `false`.
|
||||
Future<bool> isGroupInGame({
|
||||
required String gameId,
|
||||
required String groupId,
|
||||
}) async {
|
||||
final count =
|
||||
await (selectOnly(groupGameTable)
|
||||
..where(
|
||||
groupGameTable.gameId.equals(gameId) &
|
||||
groupGameTable.groupId.equals(groupId),
|
||||
)
|
||||
..addColumns([groupGameTable.groupId.count()]))
|
||||
.map((row) => row.read(groupGameTable.groupId.count()))
|
||||
.getSingle();
|
||||
return (count ?? 0) > 0;
|
||||
}
|
||||
|
||||
/// Associates a group with a game by inserting a record into the
|
||||
/// [GroupGameTable].
|
||||
Future<void> addGroupToGame(String gameId, String groupId) async {
|
||||
await into(
|
||||
groupGameTable,
|
||||
).insert(GroupGameTableCompanion.insert(groupId: groupId, gameId: gameId));
|
||||
/// Removes the association of a group from a game based on [groupId] and
|
||||
/// [gameId].
|
||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||
Future<bool> removeGroupFromGame({
|
||||
required String gameId,
|
||||
required String groupId,
|
||||
}) async {
|
||||
final query = delete(groupGameTable)
|
||||
..where((g) => g.gameId.equals(gameId) & g.groupId.equals(groupId));
|
||||
final rowsAffected = await query.go();
|
||||
return rowsAffected > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ part of 'group_game_dao.dart';
|
||||
// ignore_for_file: type=lint
|
||||
mixin _$GroupGameDaoMixin on DatabaseAccessor<AppDatabase> {
|
||||
$GroupTableTable get groupTable => attachedDatabase.groupTable;
|
||||
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
|
||||
$GameTableTable get gameTable => attachedDatabase.gameTable;
|
||||
$GroupGameTableTable get groupGameTable => attachedDatabase.groupGameTable;
|
||||
}
|
||||
|
||||
@@ -42,12 +42,36 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
||||
name: player.name,
|
||||
createdAt: player.createdAt,
|
||||
),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Adds multiple [players] to the database in a batch operation.
|
||||
Future<bool> addPlayers({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.insertOrReplace,
|
||||
),
|
||||
);
|
||||
|
||||
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 {
|
||||
@@ -82,4 +106,12 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,34 @@ class PlayerGameDao extends DatabaseAccessor<AppDatabase>
|
||||
with _$PlayerGameDaoMixin {
|
||||
PlayerGameDao(super.db);
|
||||
|
||||
/// Associates a player with a game by inserting a record into the
|
||||
/// [PlayerGameTable].
|
||||
Future<void> addPlayerToGame({
|
||||
required String gameId,
|
||||
required String playerId,
|
||||
}) async {
|
||||
await into(playerGameTable).insert(
|
||||
PlayerGameTableCompanion.insert(playerId: playerId, gameId: gameId),
|
||||
mode: InsertMode.insertOrReplace,
|
||||
);
|
||||
}
|
||||
|
||||
/// Retrieves a list of [Player]s associated with the given [gameId].
|
||||
/// Returns null if no players are found.
|
||||
Future<List<Player>?> getPlayersOfGame({required String gameId}) async {
|
||||
final result = await (select(
|
||||
playerGameTable,
|
||||
)..where((p) => p.gameId.equals(gameId))).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 [gameId].
|
||||
/// Returns `true` if there are players, otherwise `false`.
|
||||
Future<bool> gameHasPlayers({required String gameId}) async {
|
||||
@@ -22,30 +50,33 @@ class PlayerGameDao extends DatabaseAccessor<AppDatabase>
|
||||
return (count ?? 0) > 0;
|
||||
}
|
||||
|
||||
/// Retrieves a list of [Player]s associated with the given [gameId].
|
||||
/// Returns an empty list if no players are found.
|
||||
Future<List<Player>> getPlayersByGameId({required String gameId}) async {
|
||||
final result = await (select(
|
||||
playerGameTable,
|
||||
)..where((p) => p.gameId.equals(gameId))).get();
|
||||
|
||||
if (result.isEmpty) return <Player>[];
|
||||
|
||||
final futures = result.map(
|
||||
(row) => db.playerDao.getPlayerById(playerId: row.playerId),
|
||||
);
|
||||
final players = await Future.wait(futures);
|
||||
return players.whereType<Player>().toList();
|
||||
}
|
||||
|
||||
/// Associates a player with a game by inserting a record into the
|
||||
/// [PlayerGameTable].
|
||||
Future<void> addPlayerToGame({
|
||||
/// Checks if a specific player is associated with a specific game.
|
||||
/// Returns `true` if the player is in the game, otherwise `false`.
|
||||
Future<bool> isPlayerInGame({
|
||||
required String gameId,
|
||||
required String playerId,
|
||||
}) async {
|
||||
await into(playerGameTable).insert(
|
||||
PlayerGameTableCompanion.insert(playerId: playerId, gameId: gameId),
|
||||
);
|
||||
final count =
|
||||
await (selectOnly(playerGameTable)
|
||||
..where(playerGameTable.gameId.equals(gameId))
|
||||
..where(playerGameTable.playerId.equals(playerId))
|
||||
..addColumns([playerGameTable.playerId.count()]))
|
||||
.map((row) => row.read(playerGameTable.playerId.count()))
|
||||
.getSingle();
|
||||
return (count ?? 0) > 0;
|
||||
}
|
||||
|
||||
/// Removes the association of a player with a game by deleting the record
|
||||
/// from the [PlayerGameTable].
|
||||
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
|
||||
Future<bool> removePlayerFromGame({
|
||||
required String gameId,
|
||||
required String playerId,
|
||||
}) async {
|
||||
final query = delete(playerGameTable)
|
||||
..where((pg) => pg.gameId.equals(gameId))
|
||||
..where((pg) => pg.playerId.equals(playerId));
|
||||
final rowsAffected = await query.go();
|
||||
return rowsAffected > 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,34 @@ 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>> getPlayersOfGroupById({required String groupId}) async {
|
||||
Future<List<Player>> getPlayersOfGroup({required String groupId}) async {
|
||||
final query = select(playerGroupTable)
|
||||
..where((pG) => pG.groupId.equals(groupId));
|
||||
final result = await query.get();
|
||||
@@ -38,29 +64,6 @@ class PlayerGroupDao extends DatabaseAccessor<AppDatabase>
|
||||
return rowsAffected > 0;
|
||||
}
|
||||
|
||||
/// 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) == false) {
|
||||
db.playerDao.addPlayer(player: player);
|
||||
}
|
||||
|
||||
await into(playerGroupTable).insert(
|
||||
PlayerGroupTableCompanion.insert(playerId: player.id, groupId: groupId),
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// 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({
|
||||
|
||||
@@ -40,6 +40,15 @@ class AppDatabase extends _$AppDatabase {
|
||||
@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',
|
||||
|
||||
@@ -552,12 +552,9 @@ class $GameTableTable extends GameTable
|
||||
late final GeneratedColumn<String> winnerId = GeneratedColumn<String>(
|
||||
'winner_id',
|
||||
aliasedName,
|
||||
false,
|
||||
true,
|
||||
type: DriftSqlType.string,
|
||||
requiredDuringInsert: true,
|
||||
defaultConstraints: GeneratedColumn.constraintIsAlways(
|
||||
'REFERENCES player_table (id) ON DELETE CASCADE',
|
||||
),
|
||||
requiredDuringInsert: false,
|
||||
);
|
||||
static const VerificationMeta _createdAtMeta = const VerificationMeta(
|
||||
'createdAt',
|
||||
@@ -602,8 +599,6 @@ class $GameTableTable extends GameTable
|
||||
_winnerIdMeta,
|
||||
winnerId.isAcceptableOrUnknown(data['winner_id']!, _winnerIdMeta),
|
||||
);
|
||||
} else if (isInserting) {
|
||||
context.missing(_winnerIdMeta);
|
||||
}
|
||||
if (data.containsKey('created_at')) {
|
||||
context.handle(
|
||||
@@ -633,7 +628,7 @@ class $GameTableTable extends GameTable
|
||||
winnerId: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.string,
|
||||
data['${effectivePrefix}winner_id'],
|
||||
)!,
|
||||
),
|
||||
createdAt: attachedDatabase.typeMapping.read(
|
||||
DriftSqlType.dateTime,
|
||||
data['${effectivePrefix}created_at'],
|
||||
@@ -650,12 +645,12 @@ class $GameTableTable extends GameTable
|
||||
class GameTableData extends DataClass implements Insertable<GameTableData> {
|
||||
final String id;
|
||||
final String name;
|
||||
final String winnerId;
|
||||
final String? winnerId;
|
||||
final DateTime createdAt;
|
||||
const GameTableData({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.winnerId,
|
||||
this.winnerId,
|
||||
required this.createdAt,
|
||||
});
|
||||
@override
|
||||
@@ -663,7 +658,9 @@ class GameTableData extends DataClass implements Insertable<GameTableData> {
|
||||
final map = <String, Expression>{};
|
||||
map['id'] = Variable<String>(id);
|
||||
map['name'] = Variable<String>(name);
|
||||
if (!nullToAbsent || winnerId != null) {
|
||||
map['winner_id'] = Variable<String>(winnerId);
|
||||
}
|
||||
map['created_at'] = Variable<DateTime>(createdAt);
|
||||
return map;
|
||||
}
|
||||
@@ -672,7 +669,9 @@ class GameTableData extends DataClass implements Insertable<GameTableData> {
|
||||
return GameTableCompanion(
|
||||
id: Value(id),
|
||||
name: Value(name),
|
||||
winnerId: Value(winnerId),
|
||||
winnerId: winnerId == null && nullToAbsent
|
||||
? const Value.absent()
|
||||
: Value(winnerId),
|
||||
createdAt: Value(createdAt),
|
||||
);
|
||||
}
|
||||
@@ -685,7 +684,7 @@ class GameTableData extends DataClass implements Insertable<GameTableData> {
|
||||
return GameTableData(
|
||||
id: serializer.fromJson<String>(json['id']),
|
||||
name: serializer.fromJson<String>(json['name']),
|
||||
winnerId: serializer.fromJson<String>(json['winnerId']),
|
||||
winnerId: serializer.fromJson<String?>(json['winnerId']),
|
||||
createdAt: serializer.fromJson<DateTime>(json['createdAt']),
|
||||
);
|
||||
}
|
||||
@@ -695,7 +694,7 @@ class GameTableData extends DataClass implements Insertable<GameTableData> {
|
||||
return <String, dynamic>{
|
||||
'id': serializer.toJson<String>(id),
|
||||
'name': serializer.toJson<String>(name),
|
||||
'winnerId': serializer.toJson<String>(winnerId),
|
||||
'winnerId': serializer.toJson<String?>(winnerId),
|
||||
'createdAt': serializer.toJson<DateTime>(createdAt),
|
||||
};
|
||||
}
|
||||
@@ -703,12 +702,12 @@ class GameTableData extends DataClass implements Insertable<GameTableData> {
|
||||
GameTableData copyWith({
|
||||
String? id,
|
||||
String? name,
|
||||
String? winnerId,
|
||||
Value<String?> winnerId = const Value.absent(),
|
||||
DateTime? createdAt,
|
||||
}) => GameTableData(
|
||||
id: id ?? this.id,
|
||||
name: name ?? this.name,
|
||||
winnerId: winnerId ?? this.winnerId,
|
||||
winnerId: winnerId.present ? winnerId.value : this.winnerId,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
);
|
||||
GameTableData copyWithCompanion(GameTableCompanion data) {
|
||||
@@ -746,7 +745,7 @@ class GameTableData extends DataClass implements Insertable<GameTableData> {
|
||||
class GameTableCompanion extends UpdateCompanion<GameTableData> {
|
||||
final Value<String> id;
|
||||
final Value<String> name;
|
||||
final Value<String> winnerId;
|
||||
final Value<String?> winnerId;
|
||||
final Value<DateTime> createdAt;
|
||||
final Value<int> rowid;
|
||||
const GameTableCompanion({
|
||||
@@ -759,12 +758,11 @@ class GameTableCompanion extends UpdateCompanion<GameTableData> {
|
||||
GameTableCompanion.insert({
|
||||
required String id,
|
||||
required String name,
|
||||
required String winnerId,
|
||||
this.winnerId = const Value.absent(),
|
||||
required DateTime createdAt,
|
||||
this.rowid = const Value.absent(),
|
||||
}) : id = Value(id),
|
||||
name = Value(name),
|
||||
winnerId = Value(winnerId),
|
||||
createdAt = Value(createdAt);
|
||||
static Insertable<GameTableData> custom({
|
||||
Expression<String>? id,
|
||||
@@ -785,7 +783,7 @@ class GameTableCompanion extends UpdateCompanion<GameTableData> {
|
||||
GameTableCompanion copyWith({
|
||||
Value<String>? id,
|
||||
Value<String>? name,
|
||||
Value<String>? winnerId,
|
||||
Value<String?>? winnerId,
|
||||
Value<DateTime>? createdAt,
|
||||
Value<int>? rowid,
|
||||
}) {
|
||||
@@ -1538,13 +1536,6 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
||||
];
|
||||
@override
|
||||
StreamQueryUpdateRules get streamUpdateRules => const StreamQueryUpdateRules([
|
||||
WritePropagation(
|
||||
on: TableUpdateQuery.onTableName(
|
||||
'player_table',
|
||||
limitUpdateKind: UpdateKind.delete,
|
||||
),
|
||||
result: [TableUpdate('game_table', kind: UpdateKind.delete)],
|
||||
),
|
||||
WritePropagation(
|
||||
on: TableUpdateQuery.onTableName(
|
||||
'player_table',
|
||||
@@ -1609,24 +1600,6 @@ final class $$PlayerTableTableReferences
|
||||
extends BaseReferences<_$AppDatabase, $PlayerTableTable, PlayerTableData> {
|
||||
$$PlayerTableTableReferences(super.$_db, super.$_table, super.$_typedResult);
|
||||
|
||||
static MultiTypedResultKey<$GameTableTable, List<GameTableData>>
|
||||
_gameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable(
|
||||
db.gameTable,
|
||||
aliasName: $_aliasNameGenerator(db.playerTable.id, db.gameTable.winnerId),
|
||||
);
|
||||
|
||||
$$GameTableTableProcessedTableManager get gameTableRefs {
|
||||
final manager = $$GameTableTableTableManager(
|
||||
$_db,
|
||||
$_db.gameTable,
|
||||
).filter((f) => f.winnerId.id.sqlEquals($_itemColumn<String>('id')!));
|
||||
|
||||
final cache = $_typedResult.readTableOrNull(_gameTableRefsTable($_db));
|
||||
return ProcessedTableManager(
|
||||
manager.$state.copyWith(prefetchedData: cache),
|
||||
);
|
||||
}
|
||||
|
||||
static MultiTypedResultKey<$PlayerGroupTableTable, List<PlayerGroupTableData>>
|
||||
_playerGroupTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable(
|
||||
db.playerGroupTable,
|
||||
@@ -1698,31 +1671,6 @@ class $$PlayerTableTableFilterComposer
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
Expression<bool> gameTableRefs(
|
||||
Expression<bool> Function($$GameTableTableFilterComposer f) f,
|
||||
) {
|
||||
final $$GameTableTableFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.id,
|
||||
referencedTable: $db.gameTable,
|
||||
getReferencedColumn: (t) => t.winnerId,
|
||||
builder:
|
||||
(
|
||||
joinBuilder, {
|
||||
$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
}) => $$GameTableTableFilterComposer(
|
||||
$db: $db,
|
||||
$table: $db.gameTable,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
),
|
||||
);
|
||||
return f(composer);
|
||||
}
|
||||
|
||||
Expression<bool> playerGroupTableRefs(
|
||||
Expression<bool> Function($$PlayerGroupTableTableFilterComposer f) f,
|
||||
) {
|
||||
@@ -1817,31 +1765,6 @@ class $$PlayerTableTableAnnotationComposer
|
||||
GeneratedColumn<DateTime> get createdAt =>
|
||||
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
||||
|
||||
Expression<T> gameTableRefs<T extends Object>(
|
||||
Expression<T> Function($$GameTableTableAnnotationComposer a) f,
|
||||
) {
|
||||
final $$GameTableTableAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.id,
|
||||
referencedTable: $db.gameTable,
|
||||
getReferencedColumn: (t) => t.winnerId,
|
||||
builder:
|
||||
(
|
||||
joinBuilder, {
|
||||
$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
}) => $$GameTableTableAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: $db.gameTable,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
),
|
||||
);
|
||||
return f(composer);
|
||||
}
|
||||
|
||||
Expression<T> playerGroupTableRefs<T extends Object>(
|
||||
Expression<T> Function($$PlayerGroupTableTableAnnotationComposer a) f,
|
||||
) {
|
||||
@@ -1907,7 +1830,6 @@ class $$PlayerTableTableTableManager
|
||||
(PlayerTableData, $$PlayerTableTableReferences),
|
||||
PlayerTableData,
|
||||
PrefetchHooks Function({
|
||||
bool gameTableRefs,
|
||||
bool playerGroupTableRefs,
|
||||
bool playerGameTableRefs,
|
||||
})
|
||||
@@ -1956,42 +1878,16 @@ class $$PlayerTableTableTableManager
|
||||
)
|
||||
.toList(),
|
||||
prefetchHooksCallback:
|
||||
({
|
||||
gameTableRefs = false,
|
||||
playerGroupTableRefs = false,
|
||||
playerGameTableRefs = false,
|
||||
}) {
|
||||
({playerGroupTableRefs = false, playerGameTableRefs = false}) {
|
||||
return PrefetchHooks(
|
||||
db: db,
|
||||
explicitlyWatchedTables: [
|
||||
if (gameTableRefs) db.gameTable,
|
||||
if (playerGroupTableRefs) db.playerGroupTable,
|
||||
if (playerGameTableRefs) db.playerGameTable,
|
||||
],
|
||||
addJoins: null,
|
||||
getPrefetchedDataCallback: (items) async {
|
||||
return [
|
||||
if (gameTableRefs)
|
||||
await $_getPrefetchedData<
|
||||
PlayerTableData,
|
||||
$PlayerTableTable,
|
||||
GameTableData
|
||||
>(
|
||||
currentTable: table,
|
||||
referencedTable: $$PlayerTableTableReferences
|
||||
._gameTableRefsTable(db),
|
||||
managerFromTypedResult: (p0) =>
|
||||
$$PlayerTableTableReferences(
|
||||
db,
|
||||
table,
|
||||
p0,
|
||||
).gameTableRefs,
|
||||
referencedItemsForCurrentItem:
|
||||
(item, referencedItems) => referencedItems.where(
|
||||
(e) => e.winnerId == item.id,
|
||||
),
|
||||
typedResults: items,
|
||||
),
|
||||
if (playerGroupTableRefs)
|
||||
await $_getPrefetchedData<
|
||||
PlayerTableData,
|
||||
@@ -2055,7 +1951,6 @@ typedef $$PlayerTableTableProcessedTableManager =
|
||||
(PlayerTableData, $$PlayerTableTableReferences),
|
||||
PlayerTableData,
|
||||
PrefetchHooks Function({
|
||||
bool gameTableRefs,
|
||||
bool playerGroupTableRefs,
|
||||
bool playerGameTableRefs,
|
||||
})
|
||||
@@ -2436,7 +2331,7 @@ typedef $$GameTableTableCreateCompanionBuilder =
|
||||
GameTableCompanion Function({
|
||||
required String id,
|
||||
required String name,
|
||||
required String winnerId,
|
||||
Value<String?> winnerId,
|
||||
required DateTime createdAt,
|
||||
Value<int> rowid,
|
||||
});
|
||||
@@ -2444,7 +2339,7 @@ typedef $$GameTableTableUpdateCompanionBuilder =
|
||||
GameTableCompanion Function({
|
||||
Value<String> id,
|
||||
Value<String> name,
|
||||
Value<String> winnerId,
|
||||
Value<String?> winnerId,
|
||||
Value<DateTime> createdAt,
|
||||
Value<int> rowid,
|
||||
});
|
||||
@@ -2453,25 +2348,6 @@ final class $$GameTableTableReferences
|
||||
extends BaseReferences<_$AppDatabase, $GameTableTable, GameTableData> {
|
||||
$$GameTableTableReferences(super.$_db, super.$_table, super.$_typedResult);
|
||||
|
||||
static $PlayerTableTable _winnerIdTable(_$AppDatabase db) =>
|
||||
db.playerTable.createAlias(
|
||||
$_aliasNameGenerator(db.gameTable.winnerId, db.playerTable.id),
|
||||
);
|
||||
|
||||
$$PlayerTableTableProcessedTableManager get winnerId {
|
||||
final $_column = $_itemColumn<String>('winner_id')!;
|
||||
|
||||
final manager = $$PlayerTableTableTableManager(
|
||||
$_db,
|
||||
$_db.playerTable,
|
||||
).filter((f) => f.id.sqlEquals($_column));
|
||||
final item = $_typedResult.readTableOrNull(_winnerIdTable($_db));
|
||||
if (item == null) return manager;
|
||||
return ProcessedTableManager(
|
||||
manager.$state.copyWith(prefetchedData: [item]),
|
||||
);
|
||||
}
|
||||
|
||||
static MultiTypedResultKey<$PlayerGameTableTable, List<PlayerGameTableData>>
|
||||
_playerGameTableRefsTable(_$AppDatabase db) => MultiTypedResultKey.fromTable(
|
||||
db.playerGameTable,
|
||||
@@ -2530,34 +2406,16 @@ class $$GameTableTableFilterComposer
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<String> get winnerId => $composableBuilder(
|
||||
column: $table.winnerId,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
ColumnFilters<DateTime> get createdAt => $composableBuilder(
|
||||
column: $table.createdAt,
|
||||
builder: (column) => ColumnFilters(column),
|
||||
);
|
||||
|
||||
$$PlayerTableTableFilterComposer get winnerId {
|
||||
final $$PlayerTableTableFilterComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.winnerId,
|
||||
referencedTable: $db.playerTable,
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder:
|
||||
(
|
||||
joinBuilder, {
|
||||
$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
}) => $$PlayerTableTableFilterComposer(
|
||||
$db: $db,
|
||||
$table: $db.playerTable,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
),
|
||||
);
|
||||
return composer;
|
||||
}
|
||||
|
||||
Expression<bool> playerGameTableRefs(
|
||||
Expression<bool> Function($$PlayerGameTableTableFilterComposer f) f,
|
||||
) {
|
||||
@@ -2628,33 +2486,15 @@ class $$GameTableTableOrderingComposer
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<String> get winnerId => $composableBuilder(
|
||||
column: $table.winnerId,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
ColumnOrderings<DateTime> get createdAt => $composableBuilder(
|
||||
column: $table.createdAt,
|
||||
builder: (column) => ColumnOrderings(column),
|
||||
);
|
||||
|
||||
$$PlayerTableTableOrderingComposer get winnerId {
|
||||
final $$PlayerTableTableOrderingComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.winnerId,
|
||||
referencedTable: $db.playerTable,
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder:
|
||||
(
|
||||
joinBuilder, {
|
||||
$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
}) => $$PlayerTableTableOrderingComposer(
|
||||
$db: $db,
|
||||
$table: $db.playerTable,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
),
|
||||
);
|
||||
return composer;
|
||||
}
|
||||
}
|
||||
|
||||
class $$GameTableTableAnnotationComposer
|
||||
@@ -2672,32 +2512,12 @@ class $$GameTableTableAnnotationComposer
|
||||
GeneratedColumn<String> get name =>
|
||||
$composableBuilder(column: $table.name, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<String> get winnerId =>
|
||||
$composableBuilder(column: $table.winnerId, builder: (column) => column);
|
||||
|
||||
GeneratedColumn<DateTime> get createdAt =>
|
||||
$composableBuilder(column: $table.createdAt, builder: (column) => column);
|
||||
|
||||
$$PlayerTableTableAnnotationComposer get winnerId {
|
||||
final $$PlayerTableTableAnnotationComposer composer = $composerBuilder(
|
||||
composer: this,
|
||||
getCurrentColumn: (t) => t.winnerId,
|
||||
referencedTable: $db.playerTable,
|
||||
getReferencedColumn: (t) => t.id,
|
||||
builder:
|
||||
(
|
||||
joinBuilder, {
|
||||
$addJoinBuilderToRootComposer,
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
}) => $$PlayerTableTableAnnotationComposer(
|
||||
$db: $db,
|
||||
$table: $db.playerTable,
|
||||
$addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer,
|
||||
joinBuilder: joinBuilder,
|
||||
$removeJoinBuilderFromRootComposer:
|
||||
$removeJoinBuilderFromRootComposer,
|
||||
),
|
||||
);
|
||||
return composer;
|
||||
}
|
||||
|
||||
Expression<T> playerGameTableRefs<T extends Object>(
|
||||
Expression<T> Function($$PlayerGameTableTableAnnotationComposer a) f,
|
||||
) {
|
||||
@@ -2763,7 +2583,6 @@ class $$GameTableTableTableManager
|
||||
(GameTableData, $$GameTableTableReferences),
|
||||
GameTableData,
|
||||
PrefetchHooks Function({
|
||||
bool winnerId,
|
||||
bool playerGameTableRefs,
|
||||
bool groupGameTableRefs,
|
||||
})
|
||||
@@ -2783,7 +2602,7 @@ class $$GameTableTableTableManager
|
||||
({
|
||||
Value<String> id = const Value.absent(),
|
||||
Value<String> name = const Value.absent(),
|
||||
Value<String> winnerId = const Value.absent(),
|
||||
Value<String?> winnerId = const Value.absent(),
|
||||
Value<DateTime> createdAt = const Value.absent(),
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) => GameTableCompanion(
|
||||
@@ -2797,7 +2616,7 @@ class $$GameTableTableTableManager
|
||||
({
|
||||
required String id,
|
||||
required String name,
|
||||
required String winnerId,
|
||||
Value<String?> winnerId = const Value.absent(),
|
||||
required DateTime createdAt,
|
||||
Value<int> rowid = const Value.absent(),
|
||||
}) => GameTableCompanion.insert(
|
||||
@@ -2816,49 +2635,14 @@ class $$GameTableTableTableManager
|
||||
)
|
||||
.toList(),
|
||||
prefetchHooksCallback:
|
||||
({
|
||||
winnerId = false,
|
||||
playerGameTableRefs = false,
|
||||
groupGameTableRefs = false,
|
||||
}) {
|
||||
({playerGameTableRefs = false, groupGameTableRefs = false}) {
|
||||
return PrefetchHooks(
|
||||
db: db,
|
||||
explicitlyWatchedTables: [
|
||||
if (playerGameTableRefs) db.playerGameTable,
|
||||
if (groupGameTableRefs) db.groupGameTable,
|
||||
],
|
||||
addJoins:
|
||||
<
|
||||
T extends TableManagerState<
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic,
|
||||
dynamic
|
||||
>
|
||||
>(state) {
|
||||
if (winnerId) {
|
||||
state =
|
||||
state.withJoin(
|
||||
currentTable: table,
|
||||
currentColumn: table.winnerId,
|
||||
referencedTable: $$GameTableTableReferences
|
||||
._winnerIdTable(db),
|
||||
referencedColumn: $$GameTableTableReferences
|
||||
._winnerIdTable(db)
|
||||
.id,
|
||||
)
|
||||
as T;
|
||||
}
|
||||
|
||||
return state;
|
||||
},
|
||||
addJoins: null,
|
||||
getPrefetchedDataCallback: (items) async {
|
||||
return [
|
||||
if (playerGameTableRefs)
|
||||
@@ -2924,7 +2708,6 @@ typedef $$GameTableTableProcessedTableManager =
|
||||
(GameTableData, $$GameTableTableReferences),
|
||||
GameTableData,
|
||||
PrefetchHooks Function({
|
||||
bool winnerId,
|
||||
bool playerGameTableRefs,
|
||||
bool groupGameTableRefs,
|
||||
})
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:game_tracker/data/db/tables/player_table.dart';
|
||||
|
||||
class GameTable extends Table {
|
||||
TextColumn get id => text()();
|
||||
TextColumn get name => text()();
|
||||
TextColumn get winnerId =>
|
||||
text().references(PlayerTable, #id, onDelete: KeyAction.cascade)();
|
||||
late final winnerId = text().nullable()();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
|
||||
@override
|
||||
|
||||
@@ -5,11 +5,11 @@ import 'package:uuid/uuid.dart';
|
||||
|
||||
class Game {
|
||||
final String id;
|
||||
final DateTime createdAt;
|
||||
final String name;
|
||||
final List<Player>? players;
|
||||
final Group? group;
|
||||
final String winner;
|
||||
final DateTime createdAt;
|
||||
final Player? winner;
|
||||
|
||||
Game({
|
||||
String? id,
|
||||
@@ -17,7 +17,7 @@ class Game {
|
||||
required this.name,
|
||||
this.players,
|
||||
this.group,
|
||||
this.winner = '',
|
||||
this.winner,
|
||||
}) : id = id ?? const Uuid().v4(),
|
||||
createdAt = createdAt ?? clock.now();
|
||||
|
||||
@@ -25,4 +25,27 @@ class Game {
|
||||
String toString() {
|
||||
return 'Game{\n\tid: $id,\n\tname: $name,\n\tplayers: $players,\n\tgroup: $group,\n\twinner: $winner\n}';
|
||||
}
|
||||
|
||||
/// Creates a Game instance from a JSON object.
|
||||
Game.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 Game 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(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import 'package:uuid/uuid.dart';
|
||||
|
||||
class Group {
|
||||
final String id;
|
||||
final DateTime createdAt;
|
||||
final String name;
|
||||
final List<Player> members;
|
||||
final DateTime createdAt;
|
||||
|
||||
Group({
|
||||
String? id,
|
||||
@@ -20,4 +20,21 @@ class Group {
|
||||
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(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ import 'package:uuid/uuid.dart';
|
||||
|
||||
class Player {
|
||||
final String id;
|
||||
final String name;
|
||||
final DateTime createdAt;
|
||||
final String name;
|
||||
|
||||
Player({String? id, DateTime? createdAt, required this.name})
|
||||
: id = id ?? const Uuid().v4(),
|
||||
@@ -14,4 +14,17 @@ class Player {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -49,13 +49,16 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
@override
|
||||
void dispose() {
|
||||
_groupNameController.dispose();
|
||||
_searchBarController
|
||||
.dispose(); // Listener entfernen und Controller aufräumen
|
||||
_searchBarController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void loadPlayerList() {
|
||||
_allPlayersFuture = db.playerDao.getAllPlayers();
|
||||
_allPlayersFuture = Future.delayed(
|
||||
const Duration(milliseconds: 250),
|
||||
() => db.playerDao.getAllPlayers(),
|
||||
);
|
||||
suggestedPlayers = skeletonData;
|
||||
_allPlayersFuture.then((loadedPlayers) {
|
||||
setState(() {
|
||||
loadedPlayers.sort((a, b) => a.name.compareTo(b.name));
|
||||
@@ -67,8 +70,7 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SafeArea(
|
||||
child: Scaffold(
|
||||
return Scaffold(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
appBar: AppBar(
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
@@ -79,7 +81,8 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: Column(
|
||||
body: SafeArea(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
@@ -123,12 +126,7 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
.trim()
|
||||
.isNotEmpty,
|
||||
onTrailingButtonPressed: () async {
|
||||
addNewPlayerFromSearch(
|
||||
context: context,
|
||||
searchBarController: _searchBarController,
|
||||
db: db,
|
||||
loadPlayerList: loadPlayerList,
|
||||
);
|
||||
addNewPlayerFromSearch(context: context);
|
||||
},
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
@@ -339,25 +337,24 @@ class _CreateGroupViewState extends State<CreateGroupView> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a new player to the database from the search bar input.
|
||||
/// Shows a snackbar indicating success or failure.
|
||||
/// [context] - BuildContext to show the snackbar.
|
||||
/// [searchBarController] - TextEditingController of the search bar.
|
||||
/// [db] - AppDatabase instance to interact with the database.
|
||||
/// [loadPlayerList] - Function to reload the player list after adding.
|
||||
void addNewPlayerFromSearch({
|
||||
required BuildContext context,
|
||||
required TextEditingController searchBarController,
|
||||
required AppDatabase db,
|
||||
required Function loadPlayerList,
|
||||
}) async {
|
||||
String playerName = searchBarController.text.trim();
|
||||
bool success = await db.playerDao.addPlayer(player: Player(name: playerName));
|
||||
/// Adds a new player to the database from the search bar input.
|
||||
/// Shows a snackbar indicating success or failure.
|
||||
/// [context] - BuildContext to show the snackbar.
|
||||
void addNewPlayerFromSearch({required BuildContext context}) async {
|
||||
String playerName = _searchBarController.text.trim();
|
||||
Player createdPlayer = Player(name: playerName);
|
||||
bool success = await db.playerDao.addPlayer(player: createdPlayer);
|
||||
if (!context.mounted) return;
|
||||
if (success) {
|
||||
loadPlayerList();
|
||||
selectedPlayers.add(createdPlayer);
|
||||
allPlayers.add(createdPlayer);
|
||||
setState(() {
|
||||
_searchBarController.clear();
|
||||
suggestedPlayers = allPlayers.where((player) {
|
||||
return !selectedPlayers.contains(player);
|
||||
}).toList();
|
||||
});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: CustomTheme.boxColor,
|
||||
@@ -369,7 +366,6 @@ void addNewPlayerFromSearch({
|
||||
),
|
||||
),
|
||||
);
|
||||
searchBarController.clear();
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
@@ -383,4 +379,5 @@ void addNewPlayerFromSearch({
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,12 +17,7 @@ class CustomNavigationBar extends StatefulWidget {
|
||||
class _CustomNavigationBarState extends State<CustomNavigationBar>
|
||||
with SingleTickerProviderStateMixin {
|
||||
int currentIndex = 0;
|
||||
final List<Widget> tabs = [
|
||||
const HomeView(),
|
||||
const GameHistoryView(),
|
||||
const GroupsView(),
|
||||
const StatisticsView(),
|
||||
];
|
||||
int tabKeyCount = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -31,6 +26,22 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Pretty ugly but works
|
||||
final List<Widget> tabs = [
|
||||
KeyedSubtree(key: ValueKey('home_$tabKeyCount'), child: const HomeView()),
|
||||
KeyedSubtree(
|
||||
key: ValueKey('games_$tabKeyCount'),
|
||||
child: const GameHistoryView(),
|
||||
),
|
||||
KeyedSubtree(
|
||||
key: ValueKey('groups_$tabKeyCount'),
|
||||
child: const GroupsView(),
|
||||
),
|
||||
KeyedSubtree(
|
||||
key: ValueKey('stats_$tabKeyCount'),
|
||||
child: const StatisticsView(),
|
||||
),
|
||||
];
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
centerTitle: true,
|
||||
@@ -42,10 +53,15 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
|
||||
scrolledUnderElevation: 0,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => Navigator.push(
|
||||
onPressed: () async {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (_) => const SettingsView()),
|
||||
),
|
||||
);
|
||||
setState(() {
|
||||
tabKeyCount++;
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.settings),
|
||||
),
|
||||
],
|
||||
@@ -54,12 +70,14 @@ class _CustomNavigationBarState extends State<CustomNavigationBar>
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
body: tabs[currentIndex],
|
||||
extendBody: true,
|
||||
bottomNavigationBar: Padding(
|
||||
padding: const EdgeInsets.only(left: 12.0, right: 12.0, bottom: 8.0),
|
||||
child: Material(
|
||||
elevation: 10,
|
||||
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(
|
||||
|
||||
@@ -34,7 +34,10 @@ class _GroupsViewState extends State<GroupsView> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
db = Provider.of<AppDatabase>(context, listen: false);
|
||||
_allGroupsFuture = db.groupDao.getAllGroups();
|
||||
_allGroupsFuture = Future.delayed(
|
||||
const Duration(milliseconds: 250),
|
||||
() => db.groupDao.getAllGroups(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -69,9 +72,9 @@ class _GroupsViewState extends State<GroupsView> {
|
||||
}
|
||||
final bool isLoading =
|
||||
snapshot.connectionState == ConnectionState.waiting;
|
||||
final List<Group> groups = isLoading
|
||||
? skeletonData
|
||||
: (snapshot.data ?? []);
|
||||
final List<Group> groups =
|
||||
isLoading ? skeletonData : (snapshot.data ?? [])
|
||||
..sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||
return Skeletonizer(
|
||||
effect: PulseEffect(
|
||||
from: Colors.grey[800]!,
|
||||
@@ -93,7 +96,9 @@ class _GroupsViewState extends State<GroupsView> {
|
||||
itemCount: groups.length + 1,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
if (index == groups.length) {
|
||||
return const SizedBox(height: 60);
|
||||
return SizedBox(
|
||||
height: MediaQuery.paddingOf(context).bottom - 20,
|
||||
);
|
||||
}
|
||||
return GroupTile(group: groups[index]);
|
||||
},
|
||||
@@ -103,7 +108,7 @@ class _GroupsViewState extends State<GroupsView> {
|
||||
),
|
||||
|
||||
Positioned(
|
||||
bottom: 80,
|
||||
bottom: MediaQuery.paddingOf(context).bottom,
|
||||
child: CustomWidthButton(
|
||||
text: 'Create Group',
|
||||
sizeRelativeToWidth: 0.90,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/dto/game.dart';
|
||||
import 'package:game_tracker/data/dto/group.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:game_tracker/presentation/widgets/buttons/quick_create_button.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/game_tile.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart';
|
||||
@@ -17,23 +20,42 @@ class HomeView extends StatefulWidget {
|
||||
class _HomeViewState extends State<HomeView> {
|
||||
late Future<int> _gameCountFuture;
|
||||
late Future<int> _groupCountFuture;
|
||||
late Future<List<Game>> _recentGamesFuture;
|
||||
bool isLoading = true;
|
||||
|
||||
late final List<Game> skeletonData = List.filled(
|
||||
2,
|
||||
Game(
|
||||
name: 'Skeleton Game',
|
||||
group: Group(
|
||||
name: 'Skeleton Group',
|
||||
members: [
|
||||
Player(name: 'Skeleton Player 1'),
|
||||
Player(name: 'Skeleton Player 2'),
|
||||
],
|
||||
),
|
||||
winner: Player(name: 'Skeleton Player 1'),
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
final db = Provider.of<AppDatabase>(context, listen: false);
|
||||
_gameCountFuture = db.gameDao.getGameCount();
|
||||
_groupCountFuture = db.groupDao.getGroupCount();
|
||||
_recentGamesFuture = db.gameDao.getAllGames();
|
||||
|
||||
Future.wait([_gameCountFuture, _groupCountFuture]).then((_) async {
|
||||
await Future.delayed(const Duration(milliseconds: 50));
|
||||
Future.wait([_gameCountFuture, _groupCountFuture, _recentGamesFuture]).then(
|
||||
(_) async {
|
||||
await Future.delayed(const Duration(milliseconds: 250));
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -48,12 +70,21 @@ class _HomeViewState extends State<HomeView> {
|
||||
),
|
||||
enabled: isLoading,
|
||||
enableSwitchAnimation: true,
|
||||
switchAnimationConfig: const SwitchAnimationConfig(
|
||||
duration: Duration(milliseconds: 200),
|
||||
switchAnimationConfig: SwitchAnimationConfig(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
switchInCurve: Curves.linear,
|
||||
switchOutCurve: Curves.linear,
|
||||
transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder,
|
||||
layoutBuilder: AnimatedSwitcher.defaultLayoutBuilder,
|
||||
layoutBuilder:
|
||||
(Widget? currentChild, List<Widget> previousChildren) {
|
||||
return Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
...previousChildren,
|
||||
if (currentChild != null) currentChild,
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
@@ -103,32 +134,91 @@ class _HomeViewState extends State<HomeView> {
|
||||
width: constraints.maxWidth * 0.95,
|
||||
title: 'Recent Games',
|
||||
icon: Icons.timer,
|
||||
content: const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 40.0),
|
||||
child: Column(
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 40.0),
|
||||
child: FutureBuilder(
|
||||
future: _recentGamesFuture,
|
||||
builder:
|
||||
(
|
||||
BuildContext context,
|
||||
AsyncSnapshot<List<Game>> snapshot,
|
||||
) {
|
||||
if (snapshot.hasError) {
|
||||
return const Center(
|
||||
heightFactor: 4,
|
||||
child: Text(
|
||||
'Error while loading recent games.',
|
||||
),
|
||||
);
|
||||
}
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done &&
|
||||
(!snapshot.hasData ||
|
||||
snapshot.data!.isEmpty)) {
|
||||
return const Center(
|
||||
heightFactor: 4,
|
||||
child: Text('No recent games available.'),
|
||||
);
|
||||
}
|
||||
final List<Game> games =
|
||||
(isLoading
|
||||
? skeletonData
|
||||
: (snapshot.data ?? [])
|
||||
..sort(
|
||||
(a, b) => b.createdAt.compareTo(
|
||||
a.createdAt,
|
||||
),
|
||||
))
|
||||
.take(2)
|
||||
.toList();
|
||||
if (games.isNotEmpty) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
GameTile(
|
||||
gameTitle: 'Gamenight',
|
||||
gameType: 'Cabo',
|
||||
ruleset: 'Lowest Points',
|
||||
players: '5 Players',
|
||||
winner: 'Leonard',
|
||||
gameTitle: games[0].name,
|
||||
gameType: 'Winner',
|
||||
ruleset: 'Ruleset',
|
||||
players: _getPlayerText(games[0]),
|
||||
winner: games[0].winner == null
|
||||
? 'Game in progress...'
|
||||
: games[0].winner!.name,
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: Divider(),
|
||||
),
|
||||
if (games.length > 1) ...[
|
||||
GameTile(
|
||||
gameTitle: 'Schoolbreak',
|
||||
gameType: 'Uno',
|
||||
ruleset: 'Highest Points',
|
||||
players: 'The Gang',
|
||||
winner: 'Lina',
|
||||
gameTitle: games[1].name,
|
||||
gameType: 'Winner',
|
||||
ruleset: 'Ruleset',
|
||||
players: _getPlayerText(games[1]),
|
||||
winner: games[1].winner == null
|
||||
? 'Game in progress...'
|
||||
: games[1].winner!.name,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
] else ...[
|
||||
const Center(
|
||||
heightFactor: 4,
|
||||
child: Text(
|
||||
'No second game available.',
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
],
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const Center(
|
||||
heightFactor: 4,
|
||||
child: Text('No recent games available.'),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -189,4 +279,15 @@ class _HomeViewState extends State<HomeView> {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String _getPlayerText(Game game) {
|
||||
if (game.group == null) {
|
||||
final playerCount = game.players?.length ?? 0;
|
||||
return '$playerCount Players';
|
||||
}
|
||||
if (game.players == null || game.players!.isEmpty) {
|
||||
return game.group!.name;
|
||||
}
|
||||
return '${game.group!.name} + ${game.players!.length}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,191 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
import 'package:game_tracker/core/enums.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/settings_list_tile.dart';
|
||||
import 'package:game_tracker/services/data_transfer_service.dart';
|
||||
|
||||
class SettingsView extends StatelessWidget {
|
||||
class SettingsView extends StatefulWidget {
|
||||
const SettingsView({super.key});
|
||||
|
||||
@override
|
||||
State<SettingsView> createState() => _SettingsViewState();
|
||||
}
|
||||
|
||||
class _SettingsViewState extends State<SettingsView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Einstellungen')),
|
||||
body: const Center(child: Text('Settings View')),
|
||||
appBar: AppBar(backgroundColor: CustomTheme.backgroundColor),
|
||||
backgroundColor: CustomTheme.backgroundColor,
|
||||
body: LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) =>
|
||||
SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 0, 24, 10),
|
||||
child: Text(
|
||||
textAlign: TextAlign.start,
|
||||
'Menu',
|
||||
style: TextStyle(
|
||||
fontSize: 28,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 10),
|
||||
child: Text(
|
||||
textAlign: TextAlign.start,
|
||||
'Settings',
|
||||
style: TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
SettingsListTile(
|
||||
title: 'Export data',
|
||||
icon: Icons.upload_outlined,
|
||||
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
|
||||
onPressed: () async {
|
||||
final String json =
|
||||
await DataTransferService.getAppDataAsJson(context);
|
||||
final result = await DataTransferService.exportData(
|
||||
json,
|
||||
'game_tracker-data',
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
showExportSnackBar(context: context, result: result);
|
||||
},
|
||||
),
|
||||
SettingsListTile(
|
||||
title: 'Import data',
|
||||
icon: Icons.download_outlined,
|
||||
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
|
||||
onPressed: () async {
|
||||
final result = await DataTransferService.importData(
|
||||
context,
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
showImportSnackBar(context: context, result: result);
|
||||
},
|
||||
),
|
||||
SettingsListTile(
|
||||
title: 'Delete all data',
|
||||
icon: Icons.download_outlined,
|
||||
suffixWidget: const Icon(Icons.arrow_forward_ios, size: 16),
|
||||
onPressed: () {
|
||||
showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Delete all data?'),
|
||||
content: const Text('This can\'t be undone'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: const Text('Abbrechen'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(true),
|
||||
child: const Text('Löschen'),
|
||||
),
|
||||
],
|
||||
),
|
||||
).then((confirmed) {
|
||||
if (confirmed == true && context.mounted) {
|
||||
DataTransferService.deleteAllData(context);
|
||||
showSnackbar(
|
||||
context: context,
|
||||
message: 'Daten erfolgreich gelöscht',
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Displays a snackbar based on the import result.
|
||||
///
|
||||
/// [context] The BuildContext to show the snackbar in.
|
||||
/// [result] The result of the import operation.
|
||||
void showImportSnackBar({
|
||||
required BuildContext context,
|
||||
required ImportResult result,
|
||||
}) {
|
||||
switch (result) {
|
||||
case ImportResult.success:
|
||||
showSnackbar(context: context, message: 'Data successfully imported');
|
||||
case ImportResult.invalidSchema:
|
||||
showSnackbar(context: context, message: 'Invalid Schema');
|
||||
case ImportResult.fileReadError:
|
||||
showSnackbar(context: context, message: 'Error reading file');
|
||||
case ImportResult.canceled:
|
||||
showSnackbar(context: context, message: 'Import canceled');
|
||||
case ImportResult.formatException:
|
||||
showSnackbar(
|
||||
context: context,
|
||||
message: 'Format Exception (see console)',
|
||||
);
|
||||
case ImportResult.unknownException:
|
||||
showSnackbar(
|
||||
context: context,
|
||||
message: 'Unknown Exception (see console)',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays a snackbar based on the export result.
|
||||
///
|
||||
/// [context] The BuildContext to show the snackbar in.
|
||||
/// [result] The result of the export operation.
|
||||
void showExportSnackBar({
|
||||
required BuildContext context,
|
||||
required ExportResult result,
|
||||
}) {
|
||||
switch (result) {
|
||||
case ExportResult.success:
|
||||
showSnackbar(context: context, message: 'Data successfully exported');
|
||||
case ExportResult.canceled:
|
||||
showSnackbar(context: context, message: 'Export canceled');
|
||||
case ExportResult.unknownException:
|
||||
showSnackbar(
|
||||
context: context,
|
||||
message: 'Unknown Exception (see console)',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Displays a snackbar with the given message and optional action.
|
||||
///
|
||||
/// [context] The BuildContext to show the snackbar in.
|
||||
/// [message] The message to display in the snackbar.
|
||||
/// [duration] The duration for which the snackbar is displayed.
|
||||
/// [action] An optional callback function to execute when the action button is pressed.
|
||||
void showSnackbar({
|
||||
required BuildContext context,
|
||||
required String message,
|
||||
Duration duration = const Duration(seconds: 3),
|
||||
VoidCallback? action,
|
||||
}) {
|
||||
final messenger = ScaffoldMessenger.of(context);
|
||||
messenger.hideCurrentSnackBar();
|
||||
messenger.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message, style: const TextStyle(color: Colors.white)),
|
||||
backgroundColor: CustomTheme.onBoxColor,
|
||||
duration: duration,
|
||||
action: action != null
|
||||
? SnackBarAction(label: 'Rückgängig', onPressed: action)
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,262 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/dto/game.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/statistics_tile.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:skeletonizer/skeletonizer.dart';
|
||||
|
||||
class StatisticsView extends StatelessWidget {
|
||||
class StatisticsView extends StatefulWidget {
|
||||
const StatisticsView({super.key});
|
||||
|
||||
@override
|
||||
State<StatisticsView> createState() => _StatisticsViewState();
|
||||
}
|
||||
|
||||
class _StatisticsViewState extends State<StatisticsView> {
|
||||
late Future<List<Game>> _gamesFuture;
|
||||
late Future<List<Player>> _playersFuture;
|
||||
List<(String, int)> winCounts = List.filled(6, ('Skeleton Player', 1));
|
||||
List<(String, int)> gameCounts = List.filled(6, ('Skeleton Player', 1));
|
||||
List<(String, double)> winRates = List.filled(6, ('Skeleton Player', 1));
|
||||
bool isLoading = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final db = Provider.of<AppDatabase>(context, listen: false);
|
||||
_gamesFuture = db.gameDao.getAllGames();
|
||||
_playersFuture = db.playerDao.getAllPlayers();
|
||||
|
||||
Future.wait([_gamesFuture, _playersFuture]).then((results) async {
|
||||
await Future.delayed(const Duration(milliseconds: 250));
|
||||
final games = results[0] as List<Game>;
|
||||
final players = results[1] as List<Player>;
|
||||
winCounts = _calculateWinsForAllPlayers(games, players);
|
||||
gameCounts = _calculateGameAmountsForAllPlayers(games, players);
|
||||
winRates = computeWinRatePercent(wins: winCounts, games: gameCounts);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Center(child: Text('Statistics View'));
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: Skeletonizer(
|
||||
effect: PulseEffect(
|
||||
from: Colors.grey[800]!,
|
||||
to: Colors.grey[600]!,
|
||||
duration: const Duration(milliseconds: 800),
|
||||
),
|
||||
enabled: isLoading,
|
||||
enableSwitchAnimation: true,
|
||||
switchAnimationConfig: SwitchAnimationConfig(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
switchInCurve: Curves.linear,
|
||||
switchOutCurve: Curves.linear,
|
||||
transitionBuilder: AnimatedSwitcher.defaultTransitionBuilder,
|
||||
layoutBuilder:
|
||||
(Widget? currentChild, List<Widget> previousChildren) {
|
||||
return Stack(
|
||||
alignment: Alignment.topCenter,
|
||||
children: [
|
||||
...previousChildren,
|
||||
if (currentChild != null) currentChild,
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minWidth: constraints.maxWidth),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(height: constraints.maxHeight * 0.01),
|
||||
StatisticsTile(
|
||||
icon: Icons.sports_score,
|
||||
title: 'Wins per Player',
|
||||
width: constraints.maxWidth * 0.95,
|
||||
values: winCounts,
|
||||
itemCount: 3,
|
||||
barColor: Colors.blue,
|
||||
),
|
||||
SizedBox(height: constraints.maxHeight * 0.02),
|
||||
StatisticsTile(
|
||||
icon: Icons.percent,
|
||||
title: 'Winrate per Player',
|
||||
width: constraints.maxWidth * 0.95,
|
||||
values: winRates,
|
||||
itemCount: 5,
|
||||
barColor: Colors.orange[700]!,
|
||||
),
|
||||
SizedBox(height: constraints.maxHeight * 0.02),
|
||||
StatisticsTile(
|
||||
icon: Icons.casino,
|
||||
title: 'Games per Player',
|
||||
width: constraints.maxWidth * 0.95,
|
||||
values: gameCounts,
|
||||
itemCount: 10,
|
||||
barColor: Colors.green,
|
||||
),
|
||||
SizedBox(height: MediaQuery.paddingOf(context).bottom),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Calculates the number of wins for each player
|
||||
/// and returns a sorted list of tuples (playerName, winCount)
|
||||
List<(String, int)> _calculateWinsForAllPlayers(
|
||||
List<Game> games,
|
||||
List<Player> players,
|
||||
) {
|
||||
List<(String, int)> winCounts = [];
|
||||
|
||||
// Getting the winners
|
||||
for (var game in games) {
|
||||
final winner = game.winner;
|
||||
if (winner != null) {
|
||||
final index = winCounts.indexWhere((entry) => entry.$1 == winner.id);
|
||||
// -1 means winner not found in winCounts
|
||||
if (index != -1) {
|
||||
final current = winCounts[index].$2;
|
||||
winCounts[index] = (winner.id, current + 1);
|
||||
} else {
|
||||
winCounts.add((winner.id, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adding all players with zero wins
|
||||
for (var player in players) {
|
||||
final index = winCounts.indexWhere((entry) => entry.$1 == player.id);
|
||||
// -1 means player not found in winCounts
|
||||
if (index == -1) {
|
||||
winCounts.add((player.id, 0));
|
||||
}
|
||||
}
|
||||
|
||||
// Replace player IDs with names
|
||||
for (int i = 0; i < winCounts.length; i++) {
|
||||
final playerId = winCounts[i].$1;
|
||||
final player = players.firstWhere(
|
||||
(p) => p.id == playerId,
|
||||
orElse: () => Player(id: playerId, name: 'N.a.'),
|
||||
);
|
||||
winCounts[i] = (player.name, winCounts[i].$2);
|
||||
}
|
||||
|
||||
winCounts.sort((a, b) => b.$2.compareTo(a.$2));
|
||||
|
||||
return winCounts;
|
||||
}
|
||||
|
||||
/// Calculates the number of games played for each player
|
||||
/// and returns a sorted list of tuples (playerName, gameCount)
|
||||
List<(String, int)> _calculateGameAmountsForAllPlayers(
|
||||
List<Game> games,
|
||||
List<Player> players,
|
||||
) {
|
||||
List<(String, int)> gameCounts = [];
|
||||
|
||||
// Counting games for each player
|
||||
for (var game in games) {
|
||||
if (game.group != null) {
|
||||
final members = game.group!.members.map((p) => p.id).toList();
|
||||
for (var playerId in members) {
|
||||
final index = gameCounts.indexWhere((entry) => entry.$1 == playerId);
|
||||
// -1 means player not found in gameCounts
|
||||
if (index != -1) {
|
||||
final current = gameCounts[index].$2;
|
||||
gameCounts[index] = (playerId, current + 1);
|
||||
} else {
|
||||
gameCounts.add((playerId, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (game.players != null) {
|
||||
final members = game.players!.map((p) => p.id).toList();
|
||||
for (var playerId in members) {
|
||||
final index = gameCounts.indexWhere((entry) => entry.$1 == playerId);
|
||||
// -1 means player not found in gameCounts
|
||||
if (index != -1) {
|
||||
final current = gameCounts[index].$2;
|
||||
gameCounts[index] = (playerId, current + 1);
|
||||
} else {
|
||||
gameCounts.add((playerId, 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adding all players with zero games
|
||||
for (var player in players) {
|
||||
final index = gameCounts.indexWhere((entry) => entry.$1 == player.id);
|
||||
// -1 means player not found in gameCounts
|
||||
if (index == -1) {
|
||||
gameCounts.add((player.id, 0));
|
||||
}
|
||||
}
|
||||
|
||||
// Replace player IDs with names
|
||||
for (int i = 0; i < gameCounts.length; i++) {
|
||||
final playerId = gameCounts[i].$1;
|
||||
final player = players.firstWhere(
|
||||
(p) => p.id == playerId,
|
||||
orElse: () => Player(id: playerId, name: 'N.a.'),
|
||||
);
|
||||
gameCounts[i] = (player.name, gameCounts[i].$2);
|
||||
}
|
||||
|
||||
gameCounts.sort((a, b) => b.$2.compareTo(a.$2));
|
||||
|
||||
return gameCounts;
|
||||
}
|
||||
|
||||
// dart
|
||||
List<(String, double)> computeWinRatePercent({
|
||||
required List<(String, int)> wins,
|
||||
required List<(String, int)> games,
|
||||
}) {
|
||||
final Map<String, int> winsMap = {for (var e in wins) e.$1: e.$2};
|
||||
final Map<String, int> gamesMap = {for (var e in games) e.$1: e.$2};
|
||||
|
||||
// Get all unique player names
|
||||
final names = {...winsMap.keys, ...gamesMap.keys};
|
||||
|
||||
// Calculate win rates
|
||||
final result = names.map((name) {
|
||||
final int w = winsMap[name] ?? 0;
|
||||
final int g = gamesMap[name] ?? 0;
|
||||
// Calculate percentage and round to 2 decimal places
|
||||
// Avoid division by zero
|
||||
final double percent = (g > 0)
|
||||
? double.parse(((w / g)).toStringAsFixed(2))
|
||||
: 0;
|
||||
return (name, percent);
|
||||
}).toList();
|
||||
|
||||
// Sort the result: first by winrate descending,
|
||||
// then by wins descending in case of a tie
|
||||
result.sort((a, b) {
|
||||
final cmp = b.$2.compareTo(a.$2);
|
||||
if (cmp != 0) return cmp;
|
||||
final wa = winsMap[a.$1] ?? 0;
|
||||
final wb = winsMap[b.$1] ?? 0;
|
||||
return wb.compareTo(wa);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
64
lib/presentation/widgets/tiles/settings_list_tile.dart
Normal file
@@ -0,0 +1,64 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/core/custom_theme.dart';
|
||||
|
||||
class SettingsListTile extends StatelessWidget {
|
||||
final VoidCallback? onPressed;
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final Widget? suffixWidget;
|
||||
const SettingsListTile({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.icon,
|
||||
this.suffixWidget,
|
||||
this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.95,
|
||||
child: GestureDetector(
|
||||
onTap: onPressed ?? () {},
|
||||
child: Container(
|
||||
margin: EdgeInsets.zero,
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14),
|
||||
decoration: BoxDecoration(
|
||||
color: CustomTheme.boxColor,
|
||||
border: Border.all(color: CustomTheme.boxBorder),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: CustomTheme.primaryColor,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(icon, size: 24),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(title, style: const TextStyle(fontSize: 18)),
|
||||
],
|
||||
),
|
||||
if (suffixWidget != null)
|
||||
suffixWidget!
|
||||
else
|
||||
const SizedBox.shrink(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
102
lib/presentation/widgets/tiles/statistics_tile.dart
Normal file
@@ -0,0 +1,102 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:game_tracker/presentation/widgets/tiles/info_tile.dart';
|
||||
|
||||
class StatisticsTile extends StatelessWidget {
|
||||
const StatisticsTile({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.width,
|
||||
required this.values,
|
||||
required this.itemCount,
|
||||
required this.barColor,
|
||||
});
|
||||
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final double width;
|
||||
final List<(String, num)> values;
|
||||
final int itemCount;
|
||||
final Color barColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final maxBarWidth = MediaQuery.of(context).size.width * 0.65;
|
||||
|
||||
return InfoTile(
|
||||
width: width,
|
||||
title: title,
|
||||
icon: icon,
|
||||
content: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: Visibility(
|
||||
visible: values.isNotEmpty,
|
||||
replacement: const Center(
|
||||
heightFactor: 4,
|
||||
child: Text('No data available.'),
|
||||
),
|
||||
child: Column(
|
||||
children: List.generate(min(values.length, itemCount), (index) {
|
||||
/// The maximum wins among all players
|
||||
final maxGames = values.isNotEmpty ? values[0].$2 : 0;
|
||||
|
||||
/// Fraction of wins
|
||||
final double fraction = (maxGames > 0)
|
||||
? (values[index].$2 / maxGames)
|
||||
: 0.0;
|
||||
|
||||
/// Calculated width for current the bar
|
||||
final double barWidth = maxBarWidth * fraction;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
Container(
|
||||
height: 24,
|
||||
width: barWidth,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: barColor,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 4.0),
|
||||
child: Text(
|
||||
values[index].$1,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const Spacer(),
|
||||
Center(
|
||||
child: Text(
|
||||
values[index].$2 <= 1 && values[index].$2 is double
|
||||
? values[index].$2.toStringAsFixed(2)
|
||||
: values[index].$2.toString(),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
161
lib/services/data_transfer_service.dart
Normal file
@@ -0,0 +1,161 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:game_tracker/core/enums.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/dto/game.dart';
|
||||
import 'package:game_tracker/data/dto/group.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
import 'package:json_schema/json_schema.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class DataTransferService {
|
||||
/// Deletes all data from the database.
|
||||
static Future<void> deleteAllData(BuildContext context) async {
|
||||
final db = Provider.of<AppDatabase>(context, listen: false);
|
||||
await db.gameDao.deleteAllGames();
|
||||
await db.groupDao.deleteAllGroups();
|
||||
await db.playerDao.deleteAllPlayers();
|
||||
}
|
||||
|
||||
/// Retrieves all application data and converts it to a JSON string.
|
||||
/// Returns the JSON string representation of the data.
|
||||
static Future<String> getAppDataAsJson(BuildContext context) async {
|
||||
final db = Provider.of<AppDatabase>(context, listen: false);
|
||||
final games = await db.gameDao.getAllGames();
|
||||
final groups = await db.groupDao.getAllGroups();
|
||||
final players = await db.playerDao.getAllPlayers();
|
||||
|
||||
// Construct a JSON representation of the data
|
||||
final Map<String, dynamic> jsonMap = {
|
||||
'games': games.map((game) => game.toJson()).toList(),
|
||||
'groups': groups.map((group) => group.toJson()).toList(),
|
||||
'players': players.map((player) => player.toJson()).toList(),
|
||||
};
|
||||
|
||||
return json.encode(jsonMap);
|
||||
}
|
||||
|
||||
/// Exports the given JSON string to a file with the specified name.
|
||||
/// Returns an [ExportResult] indicating the outcome.
|
||||
///
|
||||
/// [jsonString] The JSON string to be exported.
|
||||
/// [fileName] The desired name for the exported file (without extension).
|
||||
static Future<ExportResult> exportData(
|
||||
String jsonString,
|
||||
String fileName,
|
||||
) async {
|
||||
try {
|
||||
final bytes = Uint8List.fromList(utf8.encode(jsonString));
|
||||
final path = await FilePicker.platform.saveFile(
|
||||
fileName: '$fileName.json',
|
||||
bytes: bytes,
|
||||
);
|
||||
if (path == null) {
|
||||
return ExportResult.canceled;
|
||||
} else {
|
||||
return ExportResult.success;
|
||||
}
|
||||
} catch (e, stack) {
|
||||
print('[exportData] $e');
|
||||
print(stack);
|
||||
return ExportResult.unknownException;
|
||||
}
|
||||
}
|
||||
|
||||
/// Imports data from a selected JSON file into the database.
|
||||
static Future<ImportResult> importData(BuildContext context) async {
|
||||
final db = Provider.of<AppDatabase>(context, listen: false);
|
||||
|
||||
final path = await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ['json'],
|
||||
);
|
||||
|
||||
if (path == null) {
|
||||
return ImportResult.canceled;
|
||||
}
|
||||
|
||||
try {
|
||||
final jsonString = await _readFileContent(path.files.single);
|
||||
if (jsonString == null) {
|
||||
return ImportResult.fileReadError;
|
||||
}
|
||||
|
||||
if (await _validateJsonSchema(jsonString)) {
|
||||
final Map<String, dynamic> jsonData =
|
||||
json.decode(jsonString) as Map<String, dynamic>;
|
||||
|
||||
final List<dynamic>? gamesJson = jsonData['games'] as List<dynamic>?;
|
||||
final List<dynamic>? groupsJson = jsonData['groups'] as List<dynamic>?;
|
||||
final List<dynamic>? playersJson =
|
||||
jsonData['players'] as List<dynamic>?;
|
||||
|
||||
final List<Game> importedGames =
|
||||
gamesJson
|
||||
?.map((g) => Game.fromJson(g as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
[];
|
||||
final List<Group> importedGroups =
|
||||
groupsJson
|
||||
?.map((g) => Group.fromJson(g as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
[];
|
||||
final List<Player> importedPlayers =
|
||||
playersJson
|
||||
?.map((p) => Player.fromJson(p as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
[];
|
||||
|
||||
await db.playerDao.addPlayers(players: importedPlayers);
|
||||
await db.groupDao.addGroups(groups: importedGroups);
|
||||
await db.gameDao.addGames(games: importedGames);
|
||||
} else {
|
||||
return ImportResult.invalidSchema;
|
||||
}
|
||||
return ImportResult.success;
|
||||
} on FormatException catch (e, stack) {
|
||||
print('[importData] FormatException');
|
||||
print('[importData] $e');
|
||||
print(stack);
|
||||
return ImportResult.formatException;
|
||||
} on Exception catch (e, stack) {
|
||||
print('[importData] Exception');
|
||||
print('[importData] $e');
|
||||
print(stack);
|
||||
return ImportResult.unknownException;
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper method to read file content from either bytes or path
|
||||
static Future<String?> _readFileContent(PlatformFile file) async {
|
||||
if (file.bytes != null) return utf8.decode(file.bytes!);
|
||||
if (file.path != null) return await File(file.path!).readAsString();
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Validates the given JSON string against the predefined schema.
|
||||
static Future<bool> _validateJsonSchema(String jsonString) async {
|
||||
final String schemaString;
|
||||
|
||||
schemaString = await rootBundle.loadString('assets/schema.json');
|
||||
|
||||
try {
|
||||
final schema = JsonSchema.create(json.decode(schemaString));
|
||||
final jsonData = json.decode(jsonString);
|
||||
final result = schema.validate(jsonData);
|
||||
|
||||
if (result.isValid) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (e, stack) {
|
||||
print('[validateJsonSchema] $e');
|
||||
print(stack);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,9 @@ dependencies:
|
||||
provider: ^6.1.5
|
||||
skeletonizer: ^2.1.0+1
|
||||
uuid: ^4.5.2
|
||||
file_picker: ^10.3.6
|
||||
json_schema: ^5.2.2
|
||||
file_saver: ^0.3.1
|
||||
clock: ^1.1.2
|
||||
|
||||
dev_dependencies:
|
||||
@@ -31,3 +34,5 @@ dev_dependencies:
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- assets/schema.json
|
||||
|
||||
@@ -9,13 +9,17 @@ import 'package:game_tracker/data/dto/player.dart';
|
||||
|
||||
void main() {
|
||||
late AppDatabase database;
|
||||
late Player player1;
|
||||
late Player player2;
|
||||
late Player player3;
|
||||
late Player player4;
|
||||
late Player player5;
|
||||
late Group testgroup;
|
||||
late Game testgame;
|
||||
late Player testPlayer1;
|
||||
late Player testPlayer2;
|
||||
late Player testPlayer3;
|
||||
late Player testPlayer4;
|
||||
late Player testPlayer5;
|
||||
late Group testGroup1;
|
||||
late Group testGroup2;
|
||||
late Game testGame1;
|
||||
late Game testGame2;
|
||||
late Game testGameOnlyPlayers;
|
||||
late Game testGameOnlyGroup;
|
||||
final fixedDate = DateTime(2025, 19, 11, 00, 11, 23);
|
||||
final fakeClock = Clock(() => fixedDate);
|
||||
|
||||
@@ -29,88 +33,206 @@ void main() {
|
||||
);
|
||||
|
||||
withClock(fakeClock, () {
|
||||
player1 = Player(name: 'Alice');
|
||||
player2 = Player(name: 'Bob');
|
||||
player3 = Player(name: 'Charlie');
|
||||
player4 = Player(name: 'Diana');
|
||||
player5 = Player(name: 'Eve');
|
||||
testgroup = Group(
|
||||
name: 'Test Group',
|
||||
members: [player1, player2, player3],
|
||||
testPlayer1 = Player(name: 'Alice');
|
||||
testPlayer2 = Player(name: 'Bob');
|
||||
testPlayer3 = Player(name: 'Charlie');
|
||||
testPlayer4 = Player(name: 'Diana');
|
||||
testPlayer5 = Player(name: 'Eve');
|
||||
testGroup1 = Group(
|
||||
name: 'Test Group 2',
|
||||
members: [testPlayer1, testPlayer2, testPlayer3],
|
||||
);
|
||||
testgame = Game(
|
||||
name: 'Test Game',
|
||||
group: testgroup,
|
||||
players: [player4, player5],
|
||||
testGroup2 = Group(
|
||||
name: 'Test Group 2',
|
||||
members: [testPlayer4, testPlayer5],
|
||||
);
|
||||
testGame1 = Game(
|
||||
name: 'First Test Game',
|
||||
group: testGroup1,
|
||||
players: [testPlayer4, testPlayer5],
|
||||
winner: testPlayer4,
|
||||
);
|
||||
testGame2 = Game(
|
||||
name: 'Second Test Game',
|
||||
group: testGroup2,
|
||||
players: [testPlayer1, testPlayer2, testPlayer3],
|
||||
winner: testPlayer2,
|
||||
);
|
||||
testGameOnlyPlayers = Game(
|
||||
name: 'Test Game with Players',
|
||||
players: [testPlayer1, testPlayer2, testPlayer3],
|
||||
winner: testPlayer3,
|
||||
);
|
||||
testGameOnlyGroup = Game(name: 'Test Game with Group', group: testGroup2);
|
||||
});
|
||||
});
|
||||
tearDown(() async {
|
||||
await database.close();
|
||||
});
|
||||
|
||||
group('game tests', () {
|
||||
test('game is added correctly', () async {
|
||||
await database.gameDao.addGame(game: testgame);
|
||||
group('Game Tests', () {
|
||||
test('Adding and fetching single game works correctly', () async {
|
||||
await database.gameDao.addGame(game: testGame1);
|
||||
|
||||
final result = await database.gameDao.getGameById(gameId: testgame.id);
|
||||
final result = await database.gameDao.getGameById(gameId: testGame1.id);
|
||||
|
||||
expect(result.id, testgame.id);
|
||||
expect(result.name, testgame.name);
|
||||
expect(result.winner, testgame.winner);
|
||||
expect(result.createdAt, testgame.createdAt);
|
||||
expect(result.id, testGame1.id);
|
||||
expect(result.name, testGame1.name);
|
||||
expect(result.createdAt, testGame1.createdAt);
|
||||
|
||||
if (result.winner != null && testGame1.winner != null) {
|
||||
expect(result.winner!.id, testGame1.winner!.id);
|
||||
expect(result.winner!.name, testGame1.winner!.name);
|
||||
expect(result.winner!.createdAt, testGame1.winner!.createdAt);
|
||||
} else {
|
||||
expect(result.winner, testGame1.winner);
|
||||
}
|
||||
|
||||
if (result.group != null) {
|
||||
expect(result.group!.members.length, testgroup.members.length);
|
||||
expect(result.group!.members.length, testGroup1.members.length);
|
||||
|
||||
for (int i = 0; i < testgroup.members.length; i++) {
|
||||
expect(result.group!.members[i].id, testgroup.members[i].id);
|
||||
expect(result.group!.members[i].name, testgroup.members[i].name);
|
||||
for (int i = 0; i < testGroup1.members.length; i++) {
|
||||
expect(result.group!.members[i].id, testGroup1.members[i].id);
|
||||
expect(result.group!.members[i].name, testGroup1.members[i].name);
|
||||
}
|
||||
} else {
|
||||
fail('Group is null');
|
||||
}
|
||||
if (result.players != null) {
|
||||
expect(result.players!.length, testgame.players!.length);
|
||||
expect(result.players!.length, testGame1.players!.length);
|
||||
|
||||
for (int i = 0; i < testgame.players!.length; i++) {
|
||||
expect(result.players![i].id, testgame.players![i].id);
|
||||
expect(result.players![i].name, testgame.players![i].name);
|
||||
expect(result.players![i].createdAt, testgame.players![i].createdAt);
|
||||
for (int i = 0; i < testGame1.players!.length; i++) {
|
||||
expect(result.players![i].id, testGame1.players![i].id);
|
||||
expect(result.players![i].name, testGame1.players![i].name);
|
||||
expect(result.players![i].createdAt, testGame1.players![i].createdAt);
|
||||
}
|
||||
} else {
|
||||
fail('Players is null');
|
||||
}
|
||||
});
|
||||
|
||||
test('game is deleted correctly', () async {
|
||||
await database.gameDao.addGame(game: testgame);
|
||||
test('Adding and fetching multiple games works correctly', () async {
|
||||
await database.gameDao.addGames(
|
||||
games: [testGame1, testGame2, testGameOnlyGroup, testGameOnlyPlayers],
|
||||
);
|
||||
|
||||
final allGames = await database.gameDao.getAllGames();
|
||||
expect(allGames.length, 4);
|
||||
|
||||
final testGames = {
|
||||
testGame1.id: testGame1,
|
||||
testGame2.id: testGame2,
|
||||
testGameOnlyGroup.id: testGameOnlyGroup,
|
||||
testGameOnlyPlayers.id: testGameOnlyPlayers,
|
||||
};
|
||||
|
||||
for (final game in allGames) {
|
||||
final testGame = testGames[game.id]!;
|
||||
|
||||
// Game-Checks
|
||||
expect(game.id, testGame.id);
|
||||
expect(game.name, testGame.name);
|
||||
expect(game.createdAt, testGame.createdAt);
|
||||
if (game.winner != null && testGame.winner != null) {
|
||||
expect(game.winner!.id, testGame.winner!.id);
|
||||
expect(game.winner!.name, testGame.winner!.name);
|
||||
expect(game.winner!.createdAt, testGame.winner!.createdAt);
|
||||
} else {
|
||||
expect(game.winner, testGame.winner);
|
||||
}
|
||||
|
||||
// Group-Checks
|
||||
if (testGame.group != null) {
|
||||
expect(game.group!.id, testGame.group!.id);
|
||||
expect(game.group!.name, testGame.group!.name);
|
||||
expect(game.group!.createdAt, testGame.group!.createdAt);
|
||||
|
||||
// Group Members-Checks
|
||||
expect(game.group!.members.length, testGame.group!.members.length);
|
||||
for (int i = 0; i < testGame.group!.members.length; i++) {
|
||||
expect(game.group!.members[i].id, testGame.group!.members[i].id);
|
||||
expect(
|
||||
game.group!.members[i].name,
|
||||
testGame.group!.members[i].name,
|
||||
);
|
||||
expect(
|
||||
game.group!.members[i].createdAt,
|
||||
testGame.group!.members[i].createdAt,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
expect(game.group, null);
|
||||
}
|
||||
|
||||
// Players-Checks
|
||||
if (testGame.players != null) {
|
||||
expect(game.players!.length, testGame.players!.length);
|
||||
for (int i = 0; i < testGame.players!.length; i++) {
|
||||
expect(game.players![i].id, testGame.players![i].id);
|
||||
expect(game.players![i].name, testGame.players![i].name);
|
||||
expect(game.players![i].createdAt, testGame.players![i].createdAt);
|
||||
}
|
||||
} else {
|
||||
expect(game.players, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('Adding the same game twice does not create duplicates', () async {
|
||||
await database.gameDao.addGame(game: testGame1);
|
||||
await database.gameDao.addGame(game: testGame1);
|
||||
|
||||
final gameCount = await database.gameDao.getGameCount();
|
||||
expect(gameCount, 1);
|
||||
});
|
||||
|
||||
test('Game existence check works correctly', () async {
|
||||
var gameExists = await database.gameDao.gameExists(gameId: testGame1.id);
|
||||
expect(gameExists, false);
|
||||
|
||||
await database.gameDao.addGame(game: testGame1);
|
||||
|
||||
gameExists = await database.gameDao.gameExists(gameId: testGame1.id);
|
||||
expect(gameExists, true);
|
||||
});
|
||||
|
||||
test('Deleting a game works correctly', () async {
|
||||
await database.gameDao.addGame(game: testGame1);
|
||||
|
||||
final gameDeleted = await database.gameDao.deleteGame(
|
||||
gameId: testgame.id,
|
||||
gameId: testGame1.id,
|
||||
);
|
||||
expect(gameDeleted, true);
|
||||
|
||||
final gameExists = await database.gameDao.gameExists(gameId: testgame.id);
|
||||
final gameExists = await database.gameDao.gameExists(
|
||||
gameId: testGame1.id,
|
||||
);
|
||||
expect(gameExists, false);
|
||||
});
|
||||
|
||||
test('get game count works correctly', () async {
|
||||
final initialCount = await database.gameDao.getGameCount();
|
||||
expect(initialCount, 0);
|
||||
test('Getting the game count works correctly', () async {
|
||||
var gameCount = await database.gameDao.getGameCount();
|
||||
expect(gameCount, 0);
|
||||
|
||||
await database.gameDao.addGame(game: testgame);
|
||||
await database.gameDao.addGame(game: testGame1);
|
||||
|
||||
final gameAdded = await database.gameDao.getGameCount();
|
||||
expect(gameAdded, 1);
|
||||
gameCount = await database.gameDao.getGameCount();
|
||||
expect(gameCount, 1);
|
||||
|
||||
final gameRemoved = await database.gameDao.deleteGame(
|
||||
gameId: testgame.id,
|
||||
);
|
||||
expect(gameRemoved, true);
|
||||
await database.gameDao.addGame(game: testGame2);
|
||||
|
||||
final finalCount = await database.gameDao.getGameCount();
|
||||
expect(finalCount, 0);
|
||||
gameCount = await database.gameDao.getGameCount();
|
||||
expect(gameCount, 2);
|
||||
|
||||
await database.gameDao.deleteGame(gameId: testGame1.id);
|
||||
|
||||
gameCount = await database.gameDao.getGameCount();
|
||||
expect(gameCount, 1);
|
||||
|
||||
await database.gameDao.deleteGame(gameId: testGame2.id);
|
||||
|
||||
gameCount = await database.gameDao.getGameCount();
|
||||
expect(gameCount, 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
134
test/db_tests/group_game_test.dart
Normal file
@@ -0,0 +1,134 @@
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/dto/game.dart';
|
||||
import 'package:game_tracker/data/dto/group.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
|
||||
void main() {
|
||||
late AppDatabase database;
|
||||
late Player testPlayer1;
|
||||
late Player testPlayer2;
|
||||
late Player testPlayer3;
|
||||
late Player testPlayer4;
|
||||
late Player testPlayer5;
|
||||
late Group testgroup;
|
||||
late Game testgameWithGroup;
|
||||
late Game testgameWithPlayers;
|
||||
final fixedDate = DateTime(2025, 19, 11, 00, 11, 23);
|
||||
final fakeClock = Clock(() => fixedDate);
|
||||
|
||||
setUp(() {
|
||||
database = AppDatabase(
|
||||
DatabaseConnection(
|
||||
NativeDatabase.memory(),
|
||||
// Recommended for widget tests to avoid test errors.
|
||||
closeStreamsSynchronously: true,
|
||||
),
|
||||
);
|
||||
|
||||
withClock(fakeClock, () {
|
||||
testPlayer1 = Player(name: 'Alice');
|
||||
testPlayer2 = Player(name: 'Bob');
|
||||
testPlayer3 = Player(name: 'Charlie');
|
||||
testPlayer4 = Player(name: 'Diana');
|
||||
testPlayer5 = Player(name: 'Eve');
|
||||
testgroup = Group(
|
||||
name: 'Test Group',
|
||||
members: [testPlayer1, testPlayer2, testPlayer3],
|
||||
);
|
||||
testgameWithPlayers = Game(
|
||||
name: 'Test Game with Players',
|
||||
players: [testPlayer4, testPlayer5],
|
||||
);
|
||||
testgameWithGroup = Game(name: 'Test Game with Group', group: testgroup);
|
||||
});
|
||||
});
|
||||
tearDown(() async {
|
||||
await database.close();
|
||||
});
|
||||
group('Group-Game Tests', () {
|
||||
test('Game has group works correctly', () async {
|
||||
await database.gameDao.addGame(game: testgameWithPlayers);
|
||||
await database.groupDao.addGroup(group: testgroup);
|
||||
|
||||
var gameHasGroup = await database.groupGameDao.gameHasGroup(
|
||||
gameId: testgameWithPlayers.id,
|
||||
);
|
||||
|
||||
expect(gameHasGroup, false);
|
||||
|
||||
await database.groupGameDao.addGroupToGame(
|
||||
testgameWithPlayers.id,
|
||||
testgroup.id,
|
||||
);
|
||||
|
||||
gameHasGroup = await database.groupGameDao.gameHasGroup(
|
||||
gameId: testgameWithPlayers.id,
|
||||
);
|
||||
|
||||
expect(gameHasGroup, true);
|
||||
});
|
||||
|
||||
test('Adding a group to a game works correctly', () async {
|
||||
await database.gameDao.addGame(game: testgameWithPlayers);
|
||||
await database.groupDao.addGroup(group: testgroup);
|
||||
await database.groupGameDao.addGroupToGame(
|
||||
testgameWithPlayers.id,
|
||||
testgroup.id,
|
||||
);
|
||||
|
||||
var groupAdded = await database.groupGameDao.isGroupInGame(
|
||||
gameId: testgameWithPlayers.id,
|
||||
groupId: testgroup.id,
|
||||
);
|
||||
expect(groupAdded, true);
|
||||
|
||||
groupAdded = await database.groupGameDao.isGroupInGame(
|
||||
gameId: testgameWithPlayers.id,
|
||||
groupId: '',
|
||||
);
|
||||
expect(groupAdded, false);
|
||||
});
|
||||
|
||||
test('Removing group from game works correctly', () async {
|
||||
await database.gameDao.addGame(game: testgameWithGroup);
|
||||
|
||||
final groupToRemove = testgameWithGroup.group!;
|
||||
|
||||
final removed = await database.groupGameDao.removeGroupFromGame(
|
||||
groupId: groupToRemove.id,
|
||||
gameId: testgameWithGroup.id,
|
||||
);
|
||||
expect(removed, true);
|
||||
|
||||
final result = await database.gameDao.getGameById(
|
||||
gameId: testgameWithGroup.id,
|
||||
);
|
||||
expect(result.group, null);
|
||||
});
|
||||
|
||||
test('Retrieving group of a game works correctly', () async {
|
||||
await database.gameDao.addGame(game: testgameWithGroup);
|
||||
final group = await database.groupGameDao.getGroupOfGame(
|
||||
gameId: testgameWithGroup.id,
|
||||
);
|
||||
|
||||
if (group == null) {
|
||||
fail('Group should not be null');
|
||||
}
|
||||
|
||||
expect(group.id, testgroup.id);
|
||||
expect(group.name, testgroup.name);
|
||||
expect(group.createdAt, testgroup.createdAt);
|
||||
expect(group.members.length, testgroup.members.length);
|
||||
for (int i = 0; i < group.members.length; i++) {
|
||||
expect(group.members[i].id, testgroup.members[i].id);
|
||||
expect(group.members[i].name, testgroup.members[i].name);
|
||||
expect(group.members[i].createdAt, testgroup.members[i].createdAt);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -8,12 +8,14 @@ import 'package:game_tracker/data/dto/player.dart';
|
||||
|
||||
void main() {
|
||||
late AppDatabase database;
|
||||
late Player player1;
|
||||
late Player player2;
|
||||
late Player player3;
|
||||
late Player player4;
|
||||
late Group testgroup;
|
||||
late Group testgroup2;
|
||||
late Player testPlayer1;
|
||||
late Player testPlayer2;
|
||||
late Player testPlayer3;
|
||||
late Player testPlayer4;
|
||||
late Group testGroup1;
|
||||
late Group testGroup2;
|
||||
late Group testGroup3;
|
||||
late Group testGroup4;
|
||||
final fixedDate = DateTime(2025, 19, 11, 00, 11, 23);
|
||||
final fakeClock = Clock(() => fixedDate);
|
||||
|
||||
@@ -27,157 +29,144 @@ void main() {
|
||||
);
|
||||
|
||||
withClock(fakeClock, () {
|
||||
player1 = Player(name: 'Alice');
|
||||
player2 = Player(name: 'Bob');
|
||||
player3 = Player(name: 'Charlie');
|
||||
player4 = Player(name: 'Diana');
|
||||
testgroup = Group(
|
||||
testPlayer1 = Player(name: 'Alice');
|
||||
testPlayer2 = Player(name: 'Bob');
|
||||
testPlayer3 = Player(name: 'Charlie');
|
||||
testPlayer4 = Player(name: 'Diana');
|
||||
testGroup1 = Group(
|
||||
name: 'Test Group',
|
||||
members: [player1, player2, player3],
|
||||
members: [testPlayer1, testPlayer2, testPlayer3],
|
||||
);
|
||||
testgroup2 = Group(
|
||||
testGroup2 = Group(
|
||||
id: 'gr2',
|
||||
name: 'Second Group',
|
||||
members: [player2, player3, player4],
|
||||
members: [testPlayer2, testPlayer3, testPlayer4],
|
||||
);
|
||||
testGroup3 = Group(
|
||||
id: 'gr2',
|
||||
name: 'Second Group',
|
||||
members: [testPlayer2, testPlayer4],
|
||||
);
|
||||
testGroup4 = Group(
|
||||
id: 'gr2',
|
||||
name: 'Second Group',
|
||||
members: [testPlayer1, testPlayer2, testPlayer3, testPlayer4],
|
||||
);
|
||||
});
|
||||
});
|
||||
tearDown(() async {
|
||||
await database.close();
|
||||
});
|
||||
group('group tests', () {
|
||||
test('all groups get fetched correctly', () async {
|
||||
await database.groupDao.addGroup(group: testgroup);
|
||||
await database.groupDao.addGroup(group: testgroup2);
|
||||
group('Group Tests', () {
|
||||
test('Adding and fetching a single group works correctly', () async {
|
||||
await database.groupDao.addGroup(group: testGroup1);
|
||||
|
||||
final fetchedGroup = await database.groupDao.getGroupById(
|
||||
groupId: testGroup1.id,
|
||||
);
|
||||
|
||||
expect(fetchedGroup.id, testGroup1.id);
|
||||
expect(fetchedGroup.name, testGroup1.name);
|
||||
expect(fetchedGroup.createdAt, testGroup1.createdAt);
|
||||
|
||||
expect(fetchedGroup.members.length, testGroup1.members.length);
|
||||
for (int i = 0; i < testGroup1.members.length; i++) {
|
||||
expect(fetchedGroup.members[i].id, testGroup1.members[i].id);
|
||||
expect(fetchedGroup.members[i].name, testGroup1.members[i].name);
|
||||
expect(
|
||||
fetchedGroup.members[i].createdAt,
|
||||
testGroup1.members[i].createdAt,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('Adding and fetching multiple groups works correctly', () async {
|
||||
await database.groupDao.addGroups(
|
||||
groups: [testGroup1, testGroup2, testGroup3, testGroup4],
|
||||
);
|
||||
|
||||
final allGroups = await database.groupDao.getAllGroups();
|
||||
expect(allGroups.length, 2);
|
||||
|
||||
final fetchedGroup1 = allGroups.firstWhere((g) => g.id == testgroup.id);
|
||||
expect(fetchedGroup1.name, testgroup.name);
|
||||
expect(fetchedGroup1.members.length, testgroup.members.length);
|
||||
expect(fetchedGroup1.members.elementAt(0).id, player1.id);
|
||||
expect(fetchedGroup1.members.elementAt(0).createdAt, player1.createdAt);
|
||||
final testGroups = {testGroup1.id: testGroup1, testGroup2.id: testGroup2};
|
||||
|
||||
final fetchedGroup2 = allGroups.firstWhere((g) => g.id == testgroup2.id);
|
||||
expect(fetchedGroup2.name, testgroup2.name);
|
||||
expect(fetchedGroup2.members.length, testgroup2.members.length);
|
||||
expect(fetchedGroup2.members.elementAt(0).id, player2.id);
|
||||
expect(fetchedGroup2.members.elementAt(0).createdAt, player2.createdAt);
|
||||
});
|
||||
for (final group in allGroups) {
|
||||
final testGroup = testGroups[group.id]!;
|
||||
|
||||
test('group and group members gets added correctly', () async {
|
||||
await database.groupDao.addGroup(group: testgroup);
|
||||
expect(group.id, testGroup.id);
|
||||
expect(group.name, testGroup.name);
|
||||
expect(group.createdAt, testGroup.createdAt);
|
||||
|
||||
final result = await database.groupDao.getGroupById(
|
||||
groupId: testgroup.id,
|
||||
);
|
||||
|
||||
expect(result.id, testgroup.id);
|
||||
expect(result.name, testgroup.name);
|
||||
expect(result.createdAt, testgroup.createdAt);
|
||||
|
||||
expect(result.members.length, testgroup.members.length);
|
||||
for (int i = 0; i < testgroup.members.length; i++) {
|
||||
expect(result.members[i].id, testgroup.members[i].id);
|
||||
expect(result.members[i].name, testgroup.members[i].name);
|
||||
expect(result.members[i].createdAt, testgroup.members[i].createdAt);
|
||||
expect(group.members.length, testGroup.members.length);
|
||||
for (int i = 0; i < testGroup.members.length; i++) {
|
||||
expect(group.members[i].id, testGroup.members[i].id);
|
||||
expect(group.members[i].name, testGroup.members[i].name);
|
||||
expect(group.members[i].createdAt, testGroup.members[i].createdAt);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('group gets deleted correctly', () async {
|
||||
await database.groupDao.addGroup(group: testgroup);
|
||||
test('Adding the same group twice does not create duplicates', () async {
|
||||
await database.groupDao.addGroup(group: testGroup1);
|
||||
await database.groupDao.addGroup(group: testGroup1);
|
||||
|
||||
final allGroups = await database.groupDao.getAllGroups();
|
||||
expect(allGroups.length, 1);
|
||||
});
|
||||
|
||||
test('Group existence check works correctly', () async {
|
||||
var groupExists = await database.groupDao.groupExists(
|
||||
groupId: testGroup1.id,
|
||||
);
|
||||
expect(groupExists, false);
|
||||
|
||||
await database.groupDao.addGroup(group: testGroup1);
|
||||
|
||||
groupExists = await database.groupDao.groupExists(groupId: testGroup1.id);
|
||||
expect(groupExists, true);
|
||||
});
|
||||
|
||||
test('Deleting a group works correctly', () async {
|
||||
await database.groupDao.addGroup(group: testGroup1);
|
||||
|
||||
final groupDeleted = await database.groupDao.deleteGroup(
|
||||
groupId: testgroup.id,
|
||||
groupId: testGroup1.id,
|
||||
);
|
||||
expect(groupDeleted, true);
|
||||
|
||||
final groupExists = await database.groupDao.groupExists(
|
||||
groupId: testgroup.id,
|
||||
groupId: testGroup1.id,
|
||||
);
|
||||
expect(groupExists, false);
|
||||
});
|
||||
|
||||
test('group name gets updated correcly ', () async {
|
||||
await database.groupDao.addGroup(group: testgroup);
|
||||
test('Updating a group name works correcly', () async {
|
||||
await database.groupDao.addGroup(group: testGroup1);
|
||||
|
||||
const newGroupName = 'new group name';
|
||||
|
||||
await database.groupDao.updateGroupname(
|
||||
groupId: testgroup.id,
|
||||
groupId: testGroup1.id,
|
||||
newName: newGroupName,
|
||||
);
|
||||
|
||||
final result = await database.groupDao.getGroupById(
|
||||
groupId: testgroup.id,
|
||||
groupId: testGroup1.id,
|
||||
);
|
||||
expect(result.name, newGroupName);
|
||||
});
|
||||
|
||||
test('Adding player to group works correctly', () async {
|
||||
await database.groupDao.addGroup(group: testgroup);
|
||||
|
||||
await database.playerGroupDao.addPlayerToGroup(
|
||||
player: player4,
|
||||
groupId: testgroup.id,
|
||||
);
|
||||
|
||||
final playerAdded = await database.playerGroupDao.isPlayerInGroup(
|
||||
playerId: player4.id,
|
||||
groupId: testgroup.id,
|
||||
);
|
||||
|
||||
expect(playerAdded, true);
|
||||
|
||||
final playerNotAdded = !await database.playerGroupDao.isPlayerInGroup(
|
||||
playerId: '',
|
||||
groupId: testgroup.id,
|
||||
);
|
||||
|
||||
expect(playerNotAdded, true);
|
||||
|
||||
final result = await database.groupDao.getGroupById(
|
||||
groupId: testgroup.id,
|
||||
);
|
||||
expect(result.members.length, testgroup.members.length + 1);
|
||||
|
||||
final addedPlayer = result.members.firstWhere((p) => p.id == player4.id);
|
||||
expect(addedPlayer.name, player4.name);
|
||||
expect(addedPlayer.createdAt, player4.createdAt);
|
||||
});
|
||||
|
||||
test('Removing player from group works correctly', () async {
|
||||
await database.groupDao.addGroup(group: testgroup);
|
||||
|
||||
final playerToRemove = testgroup.members[0];
|
||||
|
||||
final removed = await database.playerGroupDao.removePlayerFromGroup(
|
||||
playerId: playerToRemove.id,
|
||||
groupId: testgroup.id,
|
||||
);
|
||||
expect(removed, true);
|
||||
|
||||
final result = await database.groupDao.getGroupById(
|
||||
groupId: testgroup.id,
|
||||
);
|
||||
expect(result.members.length, testgroup.members.length - 1);
|
||||
|
||||
final playerExists = result.members.any((p) => p.id == playerToRemove.id);
|
||||
expect(playerExists, false);
|
||||
});
|
||||
|
||||
test('get group count works correctly', () async {
|
||||
test('Getting the group count works correctly', () async {
|
||||
final initialCount = await database.groupDao.getGroupCount();
|
||||
expect(initialCount, 0);
|
||||
|
||||
await database.groupDao.addGroup(group: testgroup);
|
||||
await database.groupDao.addGroup(group: testGroup1);
|
||||
|
||||
final groupAdded = await database.groupDao.getGroupCount();
|
||||
expect(groupAdded, 1);
|
||||
|
||||
final groupRemoved = await database.groupDao.deleteGroup(
|
||||
groupId: testgroup.id,
|
||||
groupId: testGroup1.id,
|
||||
);
|
||||
expect(groupRemoved, true);
|
||||
|
||||
|
||||
140
test/db_tests/player_game_test.dart
Normal file
@@ -0,0 +1,140 @@
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/dto/game.dart';
|
||||
import 'package:game_tracker/data/dto/group.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
|
||||
void main() {
|
||||
late AppDatabase database;
|
||||
late Player testPlayer1;
|
||||
late Player testPlayer2;
|
||||
late Player testPlayer3;
|
||||
late Player testPlayer4;
|
||||
late Player testPlayer5;
|
||||
late Player testPlayer6;
|
||||
late Group testgroup;
|
||||
late Game testGameOnlyGroup;
|
||||
late Game testGameOnlyPlayers;
|
||||
final fixedDate = DateTime(2025, 19, 11, 00, 11, 23);
|
||||
final fakeClock = Clock(() => fixedDate);
|
||||
|
||||
setUp(() {
|
||||
database = AppDatabase(
|
||||
DatabaseConnection(
|
||||
NativeDatabase.memory(),
|
||||
// Recommended for widget tests to avoid test errors.
|
||||
closeStreamsSynchronously: true,
|
||||
),
|
||||
);
|
||||
|
||||
withClock(fakeClock, () {
|
||||
testPlayer1 = Player(name: 'Alice');
|
||||
testPlayer2 = Player(name: 'Bob');
|
||||
testPlayer3 = Player(name: 'Charlie');
|
||||
testPlayer4 = Player(name: 'Diana');
|
||||
testPlayer5 = Player(name: 'Eve');
|
||||
testPlayer6 = Player(name: 'Frank');
|
||||
testgroup = Group(
|
||||
name: 'Test Group',
|
||||
members: [testPlayer1, testPlayer2, testPlayer3],
|
||||
);
|
||||
testGameOnlyGroup = Game(name: 'Test Game with Group', group: testgroup);
|
||||
testGameOnlyPlayers = Game(
|
||||
name: 'Test Game with Players',
|
||||
players: [testPlayer4, testPlayer5, testPlayer6],
|
||||
);
|
||||
});
|
||||
});
|
||||
tearDown(() async {
|
||||
await database.close();
|
||||
});
|
||||
|
||||
group('Player-Game Tests', () {
|
||||
test('Game has player works correctly', () async {
|
||||
await database.gameDao.addGame(game: testGameOnlyGroup);
|
||||
await database.playerDao.addPlayer(player: testPlayer1);
|
||||
|
||||
var gameHasPlayers = await database.playerGameDao.gameHasPlayers(
|
||||
gameId: testGameOnlyGroup.id,
|
||||
);
|
||||
|
||||
expect(gameHasPlayers, false);
|
||||
|
||||
await database.playerGameDao.addPlayerToGame(
|
||||
gameId: testGameOnlyGroup.id,
|
||||
playerId: testPlayer1.id,
|
||||
);
|
||||
|
||||
gameHasPlayers = await database.playerGameDao.gameHasPlayers(
|
||||
gameId: testGameOnlyGroup.id,
|
||||
);
|
||||
|
||||
expect(gameHasPlayers, true);
|
||||
});
|
||||
|
||||
test('Adding a player to a game works correctly', () async {
|
||||
await database.gameDao.addGame(game: testGameOnlyGroup);
|
||||
await database.playerDao.addPlayer(player: testPlayer5);
|
||||
await database.playerGameDao.addPlayerToGame(
|
||||
gameId: testGameOnlyGroup.id,
|
||||
playerId: testPlayer5.id,
|
||||
);
|
||||
|
||||
var playerAdded = await database.playerGameDao.isPlayerInGame(
|
||||
gameId: testGameOnlyGroup.id,
|
||||
playerId: testPlayer5.id,
|
||||
);
|
||||
|
||||
expect(playerAdded, true);
|
||||
|
||||
playerAdded = await database.playerGameDao.isPlayerInGame(
|
||||
gameId: testGameOnlyGroup.id,
|
||||
playerId: '',
|
||||
);
|
||||
|
||||
expect(playerAdded, false);
|
||||
});
|
||||
|
||||
test('Removing player from game works correctly', () async {
|
||||
await database.gameDao.addGame(game: testGameOnlyPlayers);
|
||||
|
||||
final playerToRemove = testGameOnlyPlayers.players![0];
|
||||
|
||||
final removed = await database.playerGameDao.removePlayerFromGame(
|
||||
playerId: playerToRemove.id,
|
||||
gameId: testGameOnlyPlayers.id,
|
||||
);
|
||||
expect(removed, true);
|
||||
|
||||
final result = await database.gameDao.getGameById(
|
||||
gameId: testGameOnlyPlayers.id,
|
||||
);
|
||||
expect(result.players!.length, testGameOnlyPlayers.players!.length - 1);
|
||||
|
||||
final playerExists = result.players!.any(
|
||||
(p) => p.id == playerToRemove.id,
|
||||
);
|
||||
expect(playerExists, false);
|
||||
});
|
||||
|
||||
test('Retrieving players of a game works correctly', () async {
|
||||
await database.gameDao.addGame(game: testGameOnlyPlayers);
|
||||
final players = await database.playerGameDao.getPlayersOfGame(
|
||||
gameId: testGameOnlyPlayers.id,
|
||||
);
|
||||
|
||||
if (players == null) {
|
||||
fail('Players should not be null');
|
||||
}
|
||||
|
||||
for (int i = 0; i < players.length; i++) {
|
||||
expect(players[i].id, testGameOnlyPlayers.players![i].id);
|
||||
expect(players[i].name, testGameOnlyPlayers.players![i].name);
|
||||
expect(players[i].createdAt, testGameOnlyPlayers.players![i].createdAt);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
103
test/db_tests/player_group_test.dart
Normal file
@@ -0,0 +1,103 @@
|
||||
import 'package:clock/clock.dart';
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:game_tracker/data/db/database.dart';
|
||||
import 'package:game_tracker/data/dto/group.dart';
|
||||
import 'package:game_tracker/data/dto/player.dart';
|
||||
|
||||
void main() {
|
||||
late AppDatabase database;
|
||||
late Player testPlayer1;
|
||||
late Player testPlayer2;
|
||||
late Player testPlayer3;
|
||||
late Player testPlayer4;
|
||||
late Group testgroup;
|
||||
final fixedDate = DateTime(2025, 19, 11, 00, 11, 23);
|
||||
final fakeClock = Clock(() => fixedDate);
|
||||
|
||||
setUp(() {
|
||||
database = AppDatabase(
|
||||
DatabaseConnection(
|
||||
NativeDatabase.memory(),
|
||||
// Recommended for widget tests to avoid test errors.
|
||||
closeStreamsSynchronously: true,
|
||||
),
|
||||
);
|
||||
|
||||
withClock(fakeClock, () {
|
||||
testPlayer1 = Player(name: 'Alice');
|
||||
testPlayer2 = Player(name: 'Bob');
|
||||
testPlayer3 = Player(name: 'Charlie');
|
||||
testPlayer4 = Player(name: 'Diana');
|
||||
testgroup = Group(
|
||||
name: 'Test Group',
|
||||
members: [testPlayer1, testPlayer2, testPlayer3],
|
||||
);
|
||||
});
|
||||
});
|
||||
tearDown(() async {
|
||||
await database.close();
|
||||
});
|
||||
|
||||
group('Player-Group Tests', () {
|
||||
/// No need to test if group has players since the members attribute is
|
||||
/// not nullable
|
||||
|
||||
test('Adding a player to a group works correctly', () async {
|
||||
await database.groupDao.addGroup(group: testgroup);
|
||||
await database.playerDao.addPlayer(player: testPlayer4);
|
||||
await database.playerGroupDao.addPlayerToGroup(
|
||||
groupId: testgroup.id,
|
||||
player: testPlayer4,
|
||||
);
|
||||
|
||||
var playerAdded = await database.playerGroupDao.isPlayerInGroup(
|
||||
groupId: testgroup.id,
|
||||
playerId: testPlayer4.id,
|
||||
);
|
||||
|
||||
expect(playerAdded, true);
|
||||
|
||||
playerAdded = await database.playerGroupDao.isPlayerInGroup(
|
||||
groupId: testgroup.id,
|
||||
playerId: '',
|
||||
);
|
||||
|
||||
expect(playerAdded, false);
|
||||
});
|
||||
|
||||
test('Removing player from group works correctly', () async {
|
||||
await database.groupDao.addGroup(group: testgroup);
|
||||
|
||||
final playerToRemove = testgroup.members[0];
|
||||
|
||||
final removed = await database.playerGroupDao.removePlayerFromGroup(
|
||||
playerId: playerToRemove.id,
|
||||
groupId: testgroup.id,
|
||||
);
|
||||
expect(removed, true);
|
||||
|
||||
final result = await database.groupDao.getGroupById(
|
||||
groupId: testgroup.id,
|
||||
);
|
||||
expect(result.members.length, testgroup.members.length - 1);
|
||||
|
||||
final playerExists = result.members.any((p) => p.id == playerToRemove.id);
|
||||
expect(playerExists, false);
|
||||
});
|
||||
|
||||
test('Retrieving players of a group works correctly', () async {
|
||||
await database.groupDao.addGroup(group: testgroup);
|
||||
final players = await database.playerGroupDao.getPlayersOfGroup(
|
||||
groupId: testgroup.id,
|
||||
);
|
||||
|
||||
for (int i = 0; i < players.length; i++) {
|
||||
expect(players[i].id, testgroup.members[i].id);
|
||||
expect(players[i].name, testgroup.members[i].name);
|
||||
expect(players[i].createdAt, testgroup.members[i].createdAt);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -7,8 +7,10 @@ import 'package:game_tracker/data/dto/player.dart';
|
||||
|
||||
void main() {
|
||||
late AppDatabase database;
|
||||
late Player testPlayer;
|
||||
late Player testPlayer1;
|
||||
late Player testPlayer2;
|
||||
late Player testPlayer3;
|
||||
late Player testPlayer4;
|
||||
final fixedDate = DateTime(2025, 19, 11, 00, 11, 23);
|
||||
final fakeClock = Clock(() => fixedDate);
|
||||
|
||||
@@ -22,27 +24,29 @@ void main() {
|
||||
);
|
||||
|
||||
withClock(fakeClock, () {
|
||||
testPlayer = Player(name: 'Test Player');
|
||||
testPlayer2 = Player(name: 'Second Group');
|
||||
testPlayer1 = Player(name: 'Test Player');
|
||||
testPlayer2 = Player(name: 'Second Player');
|
||||
testPlayer3 = Player(name: 'Charlie');
|
||||
testPlayer4 = Player(name: 'Diana');
|
||||
});
|
||||
});
|
||||
tearDown(() async {
|
||||
await database.close();
|
||||
});
|
||||
|
||||
group('player tests', () {
|
||||
test('all players get fetched correctly', () async {
|
||||
await database.playerDao.addPlayer(player: testPlayer);
|
||||
group('Player Tests', () {
|
||||
test('Adding and fetching single player works correctly', () async {
|
||||
await database.playerDao.addPlayer(player: testPlayer1);
|
||||
await database.playerDao.addPlayer(player: testPlayer2);
|
||||
|
||||
final allPlayers = await database.playerDao.getAllPlayers();
|
||||
expect(allPlayers.length, 2);
|
||||
|
||||
final fetchedPlayer1 = allPlayers.firstWhere(
|
||||
(g) => g.id == testPlayer.id,
|
||||
(g) => g.id == testPlayer1.id,
|
||||
);
|
||||
expect(fetchedPlayer1.name, testPlayer.name);
|
||||
expect(fetchedPlayer1.createdAt, testPlayer.createdAt);
|
||||
expect(fetchedPlayer1.name, testPlayer1.name);
|
||||
expect(fetchedPlayer1.createdAt, testPlayer1.createdAt);
|
||||
|
||||
final fetchedPlayer2 = allPlayers.firstWhere(
|
||||
(g) => g.id == testPlayer2.id,
|
||||
@@ -51,62 +55,105 @@ void main() {
|
||||
expect(fetchedPlayer2.createdAt, testPlayer2.createdAt);
|
||||
});
|
||||
|
||||
test('players get inserted correcly ', () async {
|
||||
await database.playerDao.addPlayer(player: testPlayer);
|
||||
final result = await database.playerDao.getPlayerById(
|
||||
playerId: testPlayer.id,
|
||||
test('Adding and fetching multiple players works correctly', () async {
|
||||
await database.playerDao.addPlayers(
|
||||
players: [testPlayer1, testPlayer2, testPlayer3, testPlayer4],
|
||||
);
|
||||
|
||||
expect(result.id, testPlayer.id);
|
||||
expect(result.name, testPlayer.name);
|
||||
expect(result.createdAt, testPlayer.createdAt);
|
||||
final allPlayers = await database.playerDao.getAllPlayers();
|
||||
expect(allPlayers.length, 4);
|
||||
|
||||
// Map for connencting fetched players with expected players
|
||||
final testPlayers = {
|
||||
testPlayer1.id: testPlayer1,
|
||||
testPlayer2.id: testPlayer2,
|
||||
testPlayer3.id: testPlayer3,
|
||||
testPlayer4.id: testPlayer4,
|
||||
};
|
||||
|
||||
for (final player in allPlayers) {
|
||||
final testPlayer = testPlayers[player.id]!;
|
||||
|
||||
expect(player.id, testPlayer.id);
|
||||
expect(player.name, testPlayer.name);
|
||||
expect(player.createdAt, testPlayer.createdAt);
|
||||
}
|
||||
});
|
||||
|
||||
test('players get deleted correcly ', () async {
|
||||
await database.playerDao.addPlayer(player: testPlayer);
|
||||
test('Adding the same player twice does not create duplicates', () async {
|
||||
await database.playerDao.addPlayer(player: testPlayer1);
|
||||
await database.playerDao.addPlayer(player: testPlayer1);
|
||||
|
||||
final allPlayers = await database.playerDao.getAllPlayers();
|
||||
expect(allPlayers.length, 1);
|
||||
});
|
||||
|
||||
test('Player existence check works correctly', () async {
|
||||
var playerExists = await database.playerDao.playerExists(
|
||||
playerId: testPlayer1.id,
|
||||
);
|
||||
expect(playerExists, false);
|
||||
|
||||
await database.playerDao.addPlayer(player: testPlayer1);
|
||||
|
||||
playerExists = await database.playerDao.playerExists(
|
||||
playerId: testPlayer1.id,
|
||||
);
|
||||
expect(playerExists, true);
|
||||
});
|
||||
|
||||
test('Deleting a player works correctly', () async {
|
||||
await database.playerDao.addPlayer(player: testPlayer1);
|
||||
final playerDeleted = await database.playerDao.deletePlayer(
|
||||
playerId: testPlayer.id,
|
||||
playerId: testPlayer1.id,
|
||||
);
|
||||
expect(playerDeleted, true);
|
||||
|
||||
final playerExists = await database.playerDao.playerExists(
|
||||
playerId: testPlayer.id,
|
||||
playerId: testPlayer1.id,
|
||||
);
|
||||
expect(playerExists, false);
|
||||
});
|
||||
|
||||
test('player name gets updated correcly ', () async {
|
||||
await database.playerDao.addPlayer(player: testPlayer);
|
||||
test('Updating a player name works correcly', () async {
|
||||
await database.playerDao.addPlayer(player: testPlayer1);
|
||||
|
||||
const newPlayerName = 'new player name';
|
||||
|
||||
await database.playerDao.updatePlayername(
|
||||
playerId: testPlayer.id,
|
||||
playerId: testPlayer1.id,
|
||||
newName: newPlayerName,
|
||||
);
|
||||
|
||||
final result = await database.playerDao.getPlayerById(
|
||||
playerId: testPlayer.id,
|
||||
playerId: testPlayer1.id,
|
||||
);
|
||||
expect(result.name, newPlayerName);
|
||||
});
|
||||
|
||||
test('get player count works correctly', () async {
|
||||
final initialCount = await database.playerDao.getPlayerCount();
|
||||
expect(initialCount, 0);
|
||||
test('Getting the player count works correctly', () async {
|
||||
var playerCount = await database.playerDao.getPlayerCount();
|
||||
expect(playerCount, 0);
|
||||
|
||||
await database.playerDao.addPlayer(player: testPlayer);
|
||||
await database.playerDao.addPlayer(player: testPlayer1);
|
||||
|
||||
final playerAdded = await database.playerDao.getPlayerCount();
|
||||
expect(playerAdded, 1);
|
||||
playerCount = await database.playerDao.getPlayerCount();
|
||||
expect(playerCount, 1);
|
||||
|
||||
final playerRemoved = await database.playerDao.deletePlayer(
|
||||
playerId: testPlayer.id,
|
||||
);
|
||||
expect(playerRemoved, true);
|
||||
await database.playerDao.addPlayer(player: testPlayer2);
|
||||
|
||||
final finalCount = await database.playerDao.getPlayerCount();
|
||||
expect(finalCount, 0);
|
||||
playerCount = await database.playerDao.getPlayerCount();
|
||||
expect(playerCount, 2);
|
||||
|
||||
await database.playerDao.deletePlayer(playerId: testPlayer1.id);
|
||||
|
||||
playerCount = await database.playerDao.getPlayerCount();
|
||||
expect(playerCount, 1);
|
||||
|
||||
await database.playerDao.deletePlayer(playerId: testPlayer2.id);
|
||||
|
||||
playerCount = await database.playerDao.getPlayerCount();
|
||||
expect(playerCount, 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||