Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15700f918b | ||
|
|
e3d6fac6e1 | ||
|
|
6901b08a94 |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 23 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 71 KiB |
@@ -1,12 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:drawable="?android:colorBackground" />
|
<item>
|
||||||
|
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||||
<!-- You can insert your own image assets here -->
|
</item>
|
||||||
<!-- <item>
|
|
||||||
<bitmap
|
|
||||||
android:gravity="center"
|
|
||||||
android:src="@mipmap/launch_image" />
|
|
||||||
</item> -->
|
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
|||||||
|
After Width: | Height: | Size: 74 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 191 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 71 KiB |
@@ -1,12 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?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">
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<item android:drawable="@android:color/white" />
|
<item>
|
||||||
|
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||||
<!-- You can insert your own image assets here -->
|
</item>
|
||||||
<!-- <item>
|
|
||||||
<bitmap
|
|
||||||
android:gravity="center"
|
|
||||||
android:src="@mipmap/launch_image" />
|
|
||||||
</item> -->
|
|
||||||
</layer-list>
|
</layer-list>
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?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>
|
||||||
|
<inset
|
||||||
|
android:drawable="@drawable/ic_launcher_foreground"
|
||||||
|
android:inset="16%" />
|
||||||
|
</foreground>
|
||||||
|
</adaptive-icon>
|
||||||
|
Before Width: | Height: | Size: 544 B After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 442 B After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 721 B After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
|
<item name="android:windowSplashScreenBackground">#FFFFFF</item>
|
||||||
|
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</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
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -5,6 +5,10 @@
|
|||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
the Flutter engine draws its first frame -->
|
the Flutter engine draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
This theme determines the color of the Android Window while your
|
This theme determines the color of the Android Window while your
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
|
<item name="android:windowSplashScreenBackground">#FFFFFF</item>
|
||||||
|
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</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
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#F5C400</color>
|
||||||
|
</resources>
|
||||||
@@ -5,6 +5,10 @@
|
|||||||
<!-- Show a splash screen on the activity. Automatically removed when
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
the Flutter engine draws its first frame -->
|
the Flutter engine draws its first frame -->
|
||||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
<item name="android:forceDarkAllowed">false</item>
|
||||||
|
<item name="android:windowFullscreen">false</item>
|
||||||
|
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||||
|
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||||
</style>
|
</style>
|
||||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
This theme determines the color of the Android Window while your
|
This theme determines the color of the Android Window while your
|
||||||
|
|||||||
|
After Width: | Height: | Size: 214 KiB |
|
After Width: | Height: | Size: 71 KiB |
@@ -0,0 +1,91 @@
|
|||||||
|
{
|
||||||
|
"bowOption": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "反曲弓",
|
||||||
|
"icon": "https://static.shelingxingqiu.com/attachment/2025-08-04/dbt8n02c0mwlgpcihn.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "复合弓",
|
||||||
|
"icon": "https://static.shelingxingqiu.com/attachment/2025-08-04/dbt8n02d17qt8548j4.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "美洲猎弓",
|
||||||
|
"icon": "https://static.shelingxingqiu.com/attachment/2025-08-04/dbt8n02cv1b8ucjp4y.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "传统弓",
|
||||||
|
"icon": "https://static.shelingxingqiu.com/attachment/2025-08-04/dbt8n02cohc2omofsb.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "光弓",
|
||||||
|
"icon": "https://static.shelingxingqiu.com/attachment/2025-08-04/dbt8n01ru48k0bwwz8.png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"targetOption": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"name": "40 全环靶",
|
||||||
|
"icon": "https://static.shelingxingqiu.com/target20251126/%E5%85%A8%E7%8E%AF%E9%9D%B6@3x.svg",
|
||||||
|
"iconPng": "https://static.shelingxingqiu.com/shootTarget/shoot@2x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"name": "80 全环靶",
|
||||||
|
"icon": "https://static.shelingxingqiu.com/target20251126/%E5%85%A8%E7%8E%AF%E9%9D%B6@3x.svg",
|
||||||
|
"iconPng": "https://static.shelingxingqiu.com/shootTarget/shoot@2x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"name": "122 全环靶",
|
||||||
|
"icon": "https://static.shelingxingqiu.com/target20251126/%E5%85%A8%E7%8E%AF%E9%9D%B6@3x.svg",
|
||||||
|
"iconPng": "https://static.shelingxingqiu.com/shootTarget/shoot@2x.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"name": "40 半环靶",
|
||||||
|
"icon": "https://static.shelingxingqiu.com/target20251126/%E5%8D%8A%E7%8E%AF%E9%9D%B6.svg",
|
||||||
|
"iconPng": "https://static.shelingxingqiu.com/shootTarget/shoot@2x(1).png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"name": "60 半环靶",
|
||||||
|
"icon": "https://static.shelingxingqiu.com/target20251126/%E5%8D%8A%E7%8E%AF%E9%9D%B6.svg",
|
||||||
|
"iconPng": "https://static.shelingxingqiu.com/shootTarget/shoot@2x(1).png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"name": "80 半环靶",
|
||||||
|
"icon": "https://static.shelingxingqiu.com/target20251126/%E5%8D%8A%E7%8E%AF%E9%9D%B6.svg",
|
||||||
|
"iconPng": "https://static.shelingxingqiu.com/shootTarget/shoot@2x(1).png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"name": "三连靶",
|
||||||
|
"icon": "https://static.shelingxingqiu.com/20260310target/%E4%B8%89%E8%BF%9E.svg",
|
||||||
|
"iconPng": "https://static.shelingxingqiu.com/shootTarget/shoot@2x(2).png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"name": "品字靶",
|
||||||
|
"icon": "https://static.shelingxingqiu.com/target20251126/%E5%93%81%E5%AD%97%E9%9D%B6.svg",
|
||||||
|
"iconPng": "https://static.shelingxingqiu.com/shootTarget/shoot@2x(3).png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"name": "复合 三连靶",
|
||||||
|
"icon": "https://static.shelingxingqiu.com/20260310target/%E5%A4%8D%E5%90%88%E4%B8%89%E8%BF%9E.svg",
|
||||||
|
"iconPng": "https://static.shelingxingqiu.com/shootTarget/shoot@2x(5).png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"name": "复合 品字靶",
|
||||||
|
"icon": "https://static.shelingxingqiu.com/target20251126/%E5%A4%8D%E5%90%88%E5%93%81%E5%AD%97%E9%9D%B6.svg",
|
||||||
|
"iconPng": "https://static.shelingxingqiu.com/shootTarget/shoot@2x(4).png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 200 KiB |
@@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="345px" height="345px" viewBox="0 0 345 345" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>40/80/122 全环靶@3x</title>
|
||||||
|
<g id="靶子与靶点-" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="靶" transform="translate(-15.000000, -51.000000)">
|
||||||
|
<g id="shoot" transform="translate(15.000000, 51.000000)">
|
||||||
|
<g id="编组">
|
||||||
|
<circle id="椭圆形" stroke="#666666" stroke-width="0.5" fill="#FFFFFF" cx="172.5" cy="172.5" r="172.25"></circle>
|
||||||
|
<circle id="椭圆形" stroke="#666666" stroke-width="0.5" cx="172.5" cy="172.5" r="155.25"></circle>
|
||||||
|
<circle id="椭圆形" fill="#5E5E5E" cx="172.5" cy="172.5" r="138"></circle>
|
||||||
|
<circle id="椭圆形" stroke="#FFFFFF" stroke-width="0.5" opacity="0.6" cx="172.5" cy="172.5" r="120.75"></circle>
|
||||||
|
<circle id="椭圆形" fill="#38BFF2" cx="172.5" cy="172.5" r="103.5"></circle>
|
||||||
|
<circle id="椭圆形" stroke="#333333" stroke-width="0.5" opacity="0.600000024" cx="172.5" cy="172.5" r="86.25"></circle>
|
||||||
|
<circle id="椭圆形" fill="#FF5665" cx="172.5" cy="172.5" r="69"></circle>
|
||||||
|
<circle id="椭圆形" stroke="#333333" stroke-width="0.5" opacity="0.600000024" cx="172.5" cy="172.5" r="51.75"></circle>
|
||||||
|
<circle id="椭圆形备份" fill="#FDDC61" cx="172.5" cy="172.5" r="34.5"></circle>
|
||||||
|
<circle id="椭圆形" stroke="#333333" stroke-width="0.5" opacity="0.600000024" cx="172.5" cy="172.5" r="17.25"></circle>
|
||||||
|
<circle id="椭圆形" stroke="#333333" stroke-width="0.5" opacity="0.600000024" cx="172.5" cy="172.5" r="8.625"></circle>
|
||||||
|
</g>
|
||||||
|
<g id="编组-5" transform="translate(161.000000, 164.500000)" font-family="PingFangSC-Regular, PingFang SC" font-weight="normal">
|
||||||
|
<text id="1" font-size="10" fill="#333333">
|
||||||
|
<tspan x="172.608636" y="11">1</tspan>
|
||||||
|
</text>
|
||||||
|
<text id="2" font-size="10" fill="#333333">
|
||||||
|
<tspan x="154.136364" y="11">2</tspan>
|
||||||
|
</text>
|
||||||
|
<text id="3" font-size="10" fill="#FFFFFF">
|
||||||
|
<tspan x="137.136364" y="11">3</tspan>
|
||||||
|
</text>
|
||||||
|
<text id="4" font-size="10" fill="#FFFFFF">
|
||||||
|
<tspan x="120" y="11">4</tspan>
|
||||||
|
</text>
|
||||||
|
<text id="5" font-size="10" fill="#333333">
|
||||||
|
<tspan x="103" y="11">5</tspan>
|
||||||
|
</text>
|
||||||
|
<text id="6" font-size="10" fill="#333333">
|
||||||
|
<tspan x="86" y="11">6</tspan>
|
||||||
|
</text>
|
||||||
|
<text id="7" font-size="10" fill="#333333">
|
||||||
|
<tspan x="69.265" y="11">7</tspan>
|
||||||
|
</text>
|
||||||
|
<text id="8" font-size="10" fill="#333333">
|
||||||
|
<tspan x="52" y="11">8</tspan>
|
||||||
|
</text>
|
||||||
|
<text id="9" font-size="10" fill="#333333">
|
||||||
|
<tspan x="35" y="11">9</tspan>
|
||||||
|
</text>
|
||||||
|
<text id="10" font-size="7" fill="#333333">
|
||||||
|
<tspan x="20.9965" y="10">10</tspan>
|
||||||
|
</text>
|
||||||
|
<text id="X" font-size="7" fill="#333333">
|
||||||
|
<tspan x="9.2705" y="10.5">X</tspan>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.7 KiB |
@@ -547,7 +547,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
@@ -604,7 +604,7 @@
|
|||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ALWAYS_SEARCH_USER_PATHS = NO;
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = AppIcon;
|
||||||
CLANG_ANALYZER_NONNULL = YES;
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
CLANG_CXX_LIBRARY = "libc++";
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
|||||||
@@ -52,7 +52,7 @@
|
|||||||
</Testables>
|
</Testables>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Release"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||||
|
|||||||
@@ -1,122 +1 @@
|
|||||||
{
|
{"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":"57x57","idiom":"iphone","filename":"Icon-App-57x57@1x.png","scale":"1x"},{"size":"57x57","idiom":"iphone","filename":"Icon-App-57x57@2x.png","scale":"2x"},{"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":"50x50","idiom":"ipad","filename":"Icon-App-50x50@1x.png","scale":"1x"},{"size":"50x50","idiom":"ipad","filename":"Icon-App-50x50@2x.png","scale":"2x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@1x.png","scale":"1x"},{"size":"72x72","idiom":"ipad","filename":"Icon-App-72x72@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"}],"info":{"version":1,"author":"xcode"}}
|
||||||
"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"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"info" : {
|
|
||||||
"version" : 1,
|
|
||||||
"author" : "xcode"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 191 KiB |
|
Before Width: | Height: | Size: 295 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 450 B After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 282 B After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 406 B After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
Before Width: | Height: | Size: 862 B After Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 762 B After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"filename" : "background.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 71 KiB |
@@ -1,23 +1,23 @@
|
|||||||
{
|
{
|
||||||
"images" : [
|
"images" : [
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "LaunchImage.png",
|
"filename" : "LaunchImage.png",
|
||||||
|
"idiom" : "universal",
|
||||||
"scale" : "1x"
|
"scale" : "1x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "LaunchImage@2x.png",
|
"filename" : "LaunchImage@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
"scale" : "2x"
|
"scale" : "2x"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"idiom" : "universal",
|
|
||||||
"filename" : "LaunchImage@3x.png",
|
"filename" : "LaunchImage@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
"scale" : "3x"
|
"scale" : "3x"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"info" : {
|
"info" : {
|
||||||
"version" : 1,
|
"author" : "xcode",
|
||||||
"author" : "xcode"
|
"version" : 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 69 B |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 69 B |
|
Before Width: | Height: | Size: 68 B After Width: | Height: | Size: 69 B |
@@ -16,13 +16,19 @@
|
|||||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
<subviews>
|
<subviews>
|
||||||
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
|
||||||
</imageView>
|
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"></imageView>
|
||||||
</subviews>
|
</subviews>
|
||||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
<constraints>
|
<constraints>
|
||||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="3T2-ad-Qdv"/>
|
||||||
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
<constraint firstItem="tWc-Dq-wcI" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="RPx-PI-7Xg"/>
|
||||||
|
<constraint firstItem="tWc-Dq-wcI" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="SdS-ul-q2q"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="tWc-Dq-wcI" secondAttribute="trailing" id="Swv-Gf-Rwn"/>
|
||||||
|
<constraint firstAttribute="trailing" secondItem="YRO-k0-Ey4" secondAttribute="trailing" id="TQA-XW-tRk"/>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="duK-uY-Gun"/>
|
||||||
|
<constraint firstItem="tWc-Dq-wcI" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="kV7-tw-vXt"/>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="xPn-NY-SIU"/>
|
||||||
</constraints>
|
</constraints>
|
||||||
</view>
|
</view>
|
||||||
</viewController>
|
</viewController>
|
||||||
@@ -33,5 +39,6 @@
|
|||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="LaunchImage" width="168" height="185"/>
|
<image name="LaunchImage" width="168" height="185"/>
|
||||||
|
<image name="LaunchBackground" width="1" height="1"/>
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@@ -1,74 +1,76 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
|
||||||
<true/>
|
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
|
||||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
|
||||||
<key>CFBundleDisplayName</key>
|
|
||||||
<string>Arcx</string>
|
|
||||||
<key>CFBundleExecutable</key>
|
|
||||||
<string>$(EXECUTABLE_NAME)</string>
|
|
||||||
<key>CFBundleIdentifier</key>
|
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
|
||||||
<key>CFBundleInfoDictionaryVersion</key>
|
|
||||||
<string>6.0</string>
|
|
||||||
<key>CFBundleName</key>
|
|
||||||
<string>arcx</string>
|
|
||||||
<key>CFBundlePackageType</key>
|
|
||||||
<string>APPL</string>
|
|
||||||
<key>CFBundleShortVersionString</key>
|
|
||||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
|
||||||
<key>CFBundleSignature</key>
|
|
||||||
<string>????</string>
|
|
||||||
<key>CFBundleVersion</key>
|
|
||||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
|
||||||
<key>LSRequiresIPhoneOS</key>
|
|
||||||
<true/>
|
|
||||||
<key>UIApplicationSceneManifest</key>
|
|
||||||
<dict>
|
<dict>
|
||||||
<key>UIApplicationSupportsMultipleScenes</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<false/>
|
<true/>
|
||||||
<key>UISceneConfigurations</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Arcx</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>$(EXECUTABLE_NAME)</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>arcx</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIApplicationSceneManifest</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>UIWindowSceneSessionRoleApplication</key>
|
<key>UIApplicationSupportsMultipleScenes</key>
|
||||||
<array>
|
<false/>
|
||||||
<dict>
|
<key>UISceneConfigurations</key>
|
||||||
<key>UISceneClassName</key>
|
<dict>
|
||||||
<string>UIWindowScene</string>
|
<key>UIWindowSceneSessionRoleApplication</key>
|
||||||
<key>UISceneConfigurationName</key>
|
<array>
|
||||||
<string>flutter</string>
|
<dict>
|
||||||
<key>UISceneDelegateClassName</key>
|
<key>UISceneClassName</key>
|
||||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
<string>UIWindowScene</string>
|
||||||
<key>UISceneStoryboardFile</key>
|
<key>UISceneConfigurationName</key>
|
||||||
<string>Main</string>
|
<string>flutter</string>
|
||||||
</dict>
|
<key>UISceneDelegateClassName</key>
|
||||||
</array>
|
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||||
|
<key>UISceneStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
</dict>
|
||||||
|
</array>
|
||||||
|
</dict>
|
||||||
</dict>
|
</dict>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>用于拍摄并设置个人头像</string>
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>用于从相册选择并设置个人头像</string>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||||
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
|
</array>
|
||||||
|
<key>UIStatusBarHidden</key>
|
||||||
|
<false/>
|
||||||
</dict>
|
</dict>
|
||||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
|
||||||
<true/>
|
|
||||||
<key>NSCameraUsageDescription</key>
|
|
||||||
<string>用于拍摄并设置个人头像</string>
|
|
||||||
<key>NSPhotoLibraryUsageDescription</key>
|
|
||||||
<string>用于从相册选择并设置个人头像</string>
|
|
||||||
<key>UILaunchStoryboardName</key>
|
|
||||||
<string>LaunchScreen</string>
|
|
||||||
<key>UIMainStoryboardFile</key>
|
|
||||||
<string>Main</string>
|
|
||||||
<key>UISupportedInterfaceOrientations</key>
|
|
||||||
<array>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
|
||||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
|
||||||
<array>
|
|
||||||
<string>UIInterfaceOrientationPortrait</string>
|
|
||||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
|
||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ class AppDefaults {
|
|||||||
|
|
||||||
static const int singletonId = 1;
|
static const int singletonId = 1;
|
||||||
|
|
||||||
static const int defaultFreeTrialLimit = 10;
|
static const int defaultFreeTrialLimit = 2;
|
||||||
static const int nicknameRandomMin = 1000;
|
static const int nicknameRandomMin = 1000;
|
||||||
static const int nicknameRandomMax = 9999;
|
static const int nicknameRandomMax = 9999;
|
||||||
|
|
||||||
|
|||||||
@@ -7,25 +7,6 @@ enum PointRecordStatus {
|
|||||||
completed,
|
completed,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum BowType {
|
|
||||||
recurve,
|
|
||||||
compound,
|
|
||||||
barebow,
|
|
||||||
traditional,
|
|
||||||
longbow,
|
|
||||||
other,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum TargetFaceType {
|
|
||||||
cm40,
|
|
||||||
cm60,
|
|
||||||
cm80,
|
|
||||||
cm122,
|
|
||||||
vegas3Spot,
|
|
||||||
vertical3Spot,
|
|
||||||
custom,
|
|
||||||
}
|
|
||||||
|
|
||||||
@embedded
|
@embedded
|
||||||
class HitPoint {
|
class HitPoint {
|
||||||
HitPoint({
|
HitPoint({
|
||||||
@@ -67,11 +48,17 @@ class PointRecord {
|
|||||||
@enumerated
|
@enumerated
|
||||||
PointRecordStatus status = PointRecordStatus.draft;
|
PointRecordStatus status = PointRecordStatus.draft;
|
||||||
|
|
||||||
@enumerated
|
/// Matches [BowOption.id] from point book config.
|
||||||
BowType bowType = BowType.recurve;
|
@Index(type: IndexType.value)
|
||||||
|
int bowOptionId = 1;
|
||||||
|
|
||||||
@enumerated
|
late String bowName;
|
||||||
TargetFaceType targetFaceType = TargetFaceType.cm40;
|
|
||||||
|
/// Matches [TargetOption.id] from point book config.
|
||||||
|
@Index(type: IndexType.value)
|
||||||
|
int targetOptionId = 1;
|
||||||
|
|
||||||
|
late String targetName;
|
||||||
|
|
||||||
late String title;
|
late String title;
|
||||||
int distanceMeters = 18;
|
int distanceMeters = 18;
|
||||||
@@ -88,8 +75,10 @@ class PointRecord {
|
|||||||
scores.isEmpty ? null : totalScore / scores.length;
|
scores.isEmpty ? null : totalScore / scores.length;
|
||||||
|
|
||||||
factory PointRecord.createDraft({
|
factory PointRecord.createDraft({
|
||||||
required BowType bowType,
|
required int bowOptionId,
|
||||||
required TargetFaceType targetFaceType,
|
required String bowName,
|
||||||
|
required int targetOptionId,
|
||||||
|
required String targetName,
|
||||||
required int distanceMeters,
|
required int distanceMeters,
|
||||||
required int endCount,
|
required int endCount,
|
||||||
required int arrowsPerEnd,
|
required int arrowsPerEnd,
|
||||||
@@ -101,8 +90,10 @@ class PointRecord {
|
|||||||
return PointRecord()
|
return PointRecord()
|
||||||
..title = title ?? '未完成记分'
|
..title = title ?? '未完成记分'
|
||||||
..status = PointRecordStatus.draft
|
..status = PointRecordStatus.draft
|
||||||
..bowType = bowType
|
..bowOptionId = bowOptionId
|
||||||
..targetFaceType = targetFaceType
|
..bowName = bowName
|
||||||
|
..targetOptionId = targetOptionId
|
||||||
|
..targetName = targetName
|
||||||
..distanceMeters = distanceMeters
|
..distanceMeters = distanceMeters
|
||||||
..endCount = endCount
|
..endCount = endCount
|
||||||
..arrowsPerEnd = arrowsPerEnd
|
..arrowsPerEnd = arrowsPerEnd
|
||||||
@@ -117,8 +108,10 @@ class PointRecord {
|
|||||||
..id = id
|
..id = id
|
||||||
..title = title
|
..title = title
|
||||||
..status = PointRecordStatus.completed
|
..status = PointRecordStatus.completed
|
||||||
..bowType = bowType
|
..bowOptionId = bowOptionId
|
||||||
..targetFaceType = targetFaceType
|
..bowName = bowName
|
||||||
|
..targetOptionId = targetOptionId
|
||||||
|
..targetName = targetName
|
||||||
..distanceMeters = distanceMeters
|
..distanceMeters = distanceMeters
|
||||||
..endCount = endCount
|
..endCount = endCount
|
||||||
..arrowsPerEnd = arrowsPerEnd
|
..arrowsPerEnd = arrowsPerEnd
|
||||||
@@ -136,8 +129,10 @@ class PointRecord {
|
|||||||
..id = id
|
..id = id
|
||||||
..title = title
|
..title = title
|
||||||
..status = status
|
..status = status
|
||||||
..bowType = bowType
|
..bowOptionId = bowOptionId
|
||||||
..targetFaceType = targetFaceType
|
..bowName = bowName
|
||||||
|
..targetOptionId = targetOptionId
|
||||||
|
..targetName = targetName
|
||||||
..distanceMeters = distanceMeters
|
..distanceMeters = distanceMeters
|
||||||
..endCount = endCount
|
..endCount = endCount
|
||||||
..arrowsPerEnd = arrowsPerEnd
|
..arrowsPerEnd = arrowsPerEnd
|
||||||
|
|||||||
@@ -74,13 +74,13 @@ class PointBookHomeScreen extends ConsumerWidget {
|
|||||||
if (config == null) return;
|
if (config == null) return;
|
||||||
|
|
||||||
// IAP / trial gate.
|
// IAP / trial gate.
|
||||||
// if (!config.canStartScoring) {
|
if (!config.canStartScoring) {
|
||||||
// await showDialog<void>(
|
await showDialog<void>(
|
||||||
// context: context,
|
context: context,
|
||||||
// builder: (_) => const IapUnlockDialog(),
|
builder: (_) => const IapUnlockDialog(),
|
||||||
// );
|
);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Draft check.
|
// Draft check.
|
||||||
final notifier = ref.read(pointRecordsProvider.notifier);
|
final notifier = ref.read(pointRecordsProvider.notifier);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
import 'package:arcx/app/app_theme.dart';
|
import 'package:arcx/app/app_theme.dart';
|
||||||
import 'package:arcx/features/scoring/presentation/widgets/hit_point_painter.dart';
|
import 'package:arcx/features/scoring/presentation/widgets/hit_point_painter.dart';
|
||||||
@@ -30,8 +31,8 @@ class HeatmapCard extends StatelessWidget {
|
|||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
Image.asset(
|
SvgPicture.asset(
|
||||||
'assets/images/target_face.png',
|
'assets/images/target_face.svg',
|
||||||
fit: BoxFit.fill,
|
fit: BoxFit.fill,
|
||||||
),
|
),
|
||||||
CustomPaint(
|
CustomPaint(
|
||||||
|
|||||||
@@ -79,40 +79,40 @@ class _Bar extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LayoutBuilder(
|
return Column(
|
||||||
builder: (context, constraints) {
|
children: [
|
||||||
final maxH = constraints.maxHeight - 40; // reserve space for count + label
|
Text(
|
||||||
final h = (maxH * ratio).clamp(4.0, maxH);
|
'$count',
|
||||||
return Column(
|
style: TextStyle(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
fontSize: 11,
|
||||||
children: [
|
color: AppTheme.textSecondary,
|
||||||
Text(
|
),
|
||||||
'$count',
|
),
|
||||||
style: TextStyle(
|
const SizedBox(height: 4),
|
||||||
fontSize: 11,
|
Expanded(
|
||||||
color: AppTheme.textSecondary,
|
child: Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: FractionallySizedBox(
|
||||||
|
heightFactor: ratio.clamp(0.04, 1.0),
|
||||||
|
widthFactor: 1,
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppTheme.primary,
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
),
|
||||||
Container(
|
),
|
||||||
width: double.infinity,
|
const SizedBox(height: 4),
|
||||||
height: h,
|
Text(
|
||||||
decoration: BoxDecoration(
|
label,
|
||||||
color: AppTheme.primary,
|
style: const TextStyle(
|
||||||
borderRadius: BorderRadius.circular(4),
|
fontSize: 12,
|
||||||
),
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
),
|
||||||
Text(
|
],
|
||||||
label,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
|
|
||||||
import 'package:arcx/app/app_theme.dart';
|
import 'package:arcx/app/app_theme.dart';
|
||||||
import 'package:arcx/data/local/models/models.dart';
|
import 'package:arcx/data/local/models/models.dart';
|
||||||
|
import 'package:arcx/features/scoring/application/point_book_config_provider.dart';
|
||||||
import 'package:arcx/features/scoring/application/scoring_service.dart';
|
import 'package:arcx/features/scoring/application/scoring_service.dart';
|
||||||
|
import 'package:arcx/features/scoring/domain/point_book_config.dart';
|
||||||
import 'package:arcx/features/scoring/presentation/point_book_edit_page.dart';
|
import 'package:arcx/features/scoring/presentation/point_book_edit_page.dart';
|
||||||
|
|
||||||
class PointBookListPage extends ConsumerStatefulWidget {
|
class PointBookListPage extends ConsumerStatefulWidget {
|
||||||
@@ -14,22 +16,31 @@ class PointBookListPage extends ConsumerStatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _PointBookListPageState extends ConsumerState<PointBookListPage> {
|
class _PointBookListPageState extends ConsumerState<PointBookListPage> {
|
||||||
BowType? _bowFilter;
|
int? _bowFilter;
|
||||||
TargetFaceType? _targetFilter;
|
int? _targetFilter;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final recordsAsync = ref.watch(pointRecordsProvider);
|
final recordsAsync = ref.watch(pointRecordsProvider);
|
||||||
|
final configAsync = ref.watch(pointBookConfigProvider);
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: const Text('计分记录')),
|
appBar: AppBar(title: const Text('计分记录')),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
_FilterBar(
|
configAsync.when(
|
||||||
bowFilter: _bowFilter,
|
loading: () => const SizedBox(height: 64),
|
||||||
targetFilter: _targetFilter,
|
error: (e, _) => Padding(
|
||||||
onBowChanged: (v) => setState(() => _bowFilter = v),
|
padding: const EdgeInsets.all(16),
|
||||||
onTargetChanged: (v) => setState(() => _targetFilter = v),
|
child: Text('配置加载失败:$e'),
|
||||||
|
),
|
||||||
|
data: (config) => _FilterBar(
|
||||||
|
config: config,
|
||||||
|
bowFilter: _bowFilter,
|
||||||
|
targetFilter: _targetFilter,
|
||||||
|
onBowChanged: (v) => setState(() => _bowFilter = v),
|
||||||
|
onTargetChanged: (v) => setState(() => _targetFilter = v),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: recordsAsync.when(
|
child: recordsAsync.when(
|
||||||
@@ -64,10 +75,10 @@ class _PointBookListPageState extends ConsumerState<PointBookListPage> {
|
|||||||
List<PointRecord> _applyFilters(List<PointRecord> records) {
|
List<PointRecord> _applyFilters(List<PointRecord> records) {
|
||||||
var result = records;
|
var result = records;
|
||||||
if (_bowFilter != null) {
|
if (_bowFilter != null) {
|
||||||
result = result.where((r) => r.bowType == _bowFilter).toList();
|
result = result.where((r) => r.bowOptionId == _bowFilter).toList();
|
||||||
}
|
}
|
||||||
if (_targetFilter != null) {
|
if (_targetFilter != null) {
|
||||||
result = result.where((r) => r.targetFaceType == _targetFilter).toList();
|
result = result.where((r) => r.targetOptionId == _targetFilter).toList();
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -107,16 +118,18 @@ class _PointBookListPageState extends ConsumerState<PointBookListPage> {
|
|||||||
|
|
||||||
class _FilterBar extends StatelessWidget {
|
class _FilterBar extends StatelessWidget {
|
||||||
const _FilterBar({
|
const _FilterBar({
|
||||||
|
required this.config,
|
||||||
required this.bowFilter,
|
required this.bowFilter,
|
||||||
required this.targetFilter,
|
required this.targetFilter,
|
||||||
required this.onBowChanged,
|
required this.onBowChanged,
|
||||||
required this.onTargetChanged,
|
required this.onTargetChanged,
|
||||||
});
|
});
|
||||||
|
|
||||||
final BowType? bowFilter;
|
final PointBookConfig config;
|
||||||
final TargetFaceType? targetFilter;
|
final int? bowFilter;
|
||||||
final ValueChanged<BowType?> onBowChanged;
|
final int? targetFilter;
|
||||||
final ValueChanged<TargetFaceType?> onTargetChanged;
|
final ValueChanged<int?> onBowChanged;
|
||||||
|
final ValueChanged<int?> onTargetChanged;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -125,21 +138,23 @@ class _FilterBar extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _Dropdown<BowType>(
|
child: _Dropdown<int>(
|
||||||
label: '弓型',
|
label: '弓型',
|
||||||
value: bowFilter,
|
value: bowFilter,
|
||||||
items: BowType.values,
|
items: config.bowOption.map((e) => e.id).toList(),
|
||||||
labelOf: _bowLabel,
|
labelOf: (id) =>
|
||||||
|
config.bowById(id)?.name ?? id.toString(),
|
||||||
onChanged: onBowChanged,
|
onChanged: onBowChanged,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: _Dropdown<TargetFaceType>(
|
child: _Dropdown<int>(
|
||||||
label: '靶纸',
|
label: '靶纸',
|
||||||
value: targetFilter,
|
value: targetFilter,
|
||||||
items: TargetFaceType.values,
|
items: config.targetOption.map((e) => e.id).toList(),
|
||||||
labelOf: _targetLabel,
|
labelOf: (id) =>
|
||||||
|
config.targetById(id)?.name ?? id.toString(),
|
||||||
onChanged: onTargetChanged,
|
onChanged: onTargetChanged,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -177,10 +192,12 @@ class _Dropdown<T> extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
items: [
|
items: [
|
||||||
DropdownMenuItem<T>(value: null, child: Text('全部')),
|
DropdownMenuItem<T>(value: null, child: Text('全部')),
|
||||||
...items.map((e) => DropdownMenuItem<T>(
|
...items.map(
|
||||||
value: e,
|
(e) => DropdownMenuItem<T>(
|
||||||
child: Text(labelOf(e)),
|
value: e,
|
||||||
)),
|
child: Text(labelOf(e), overflow: TextOverflow.ellipsis),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
);
|
);
|
||||||
@@ -232,7 +249,7 @@ class _RecordTile extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
'${_bowLabel(record.bowType)} · ${record.distanceMeters}m · ${_targetLabel(record.targetFaceType)}\n'
|
'${record.bowName} · ${record.distanceMeters}m · ${record.targetName}\n'
|
||||||
'${_formatDate(record.createdAt)} ${record.totalArrows}支 ${record.averageScore != null ? '均${record.averageScore!.toStringAsFixed(1)}环' : ''}',
|
'${_formatDate(record.createdAt)} ${record.totalArrows}支 ${record.averageScore != null ? '均${record.averageScore!.toStringAsFixed(1)}环' : ''}',
|
||||||
style: TextStyle(fontSize: 12, color: AppTheme.textSecondary),
|
style: TextStyle(fontSize: 12, color: AppTheme.textSecondary),
|
||||||
),
|
),
|
||||||
@@ -272,42 +289,6 @@ class _EmptyState extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _bowLabel(BowType t) {
|
|
||||||
switch (t) {
|
|
||||||
case BowType.recurve:
|
|
||||||
return '反曲弓';
|
|
||||||
case BowType.compound:
|
|
||||||
return '复合弓';
|
|
||||||
case BowType.barebow:
|
|
||||||
return '光弓';
|
|
||||||
case BowType.traditional:
|
|
||||||
return '传统弓';
|
|
||||||
case BowType.longbow:
|
|
||||||
return '长弓';
|
|
||||||
case BowType.other:
|
|
||||||
return '其他';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _targetLabel(TargetFaceType t) {
|
|
||||||
switch (t) {
|
|
||||||
case TargetFaceType.cm40:
|
|
||||||
return '40cm';
|
|
||||||
case TargetFaceType.cm60:
|
|
||||||
return '60cm';
|
|
||||||
case TargetFaceType.cm80:
|
|
||||||
return '80cm';
|
|
||||||
case TargetFaceType.cm122:
|
|
||||||
return '122cm';
|
|
||||||
case TargetFaceType.vegas3Spot:
|
|
||||||
return '维加斯三联';
|
|
||||||
case TargetFaceType.vertical3Spot:
|
|
||||||
return '垂直三联';
|
|
||||||
case TargetFaceType.custom:
|
|
||||||
return '自定义';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _formatDate(DateTime dt) {
|
String _formatDate(DateTime dt) {
|
||||||
return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} '
|
return '${dt.year}-${dt.month.toString().padLeft(2, '0')}-${dt.day.toString().padLeft(2, '0')} '
|
||||||
'${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}';
|
'${dt.hour.toString().padLeft(2, '0')}:${dt.minute.toString().padLeft(2, '0')}';
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
|
import '../domain/point_book_config.dart';
|
||||||
|
|
||||||
|
final pointBookConfigProvider = FutureProvider<PointBookConfig>((ref) {
|
||||||
|
return PointBookConfig.fromAsset();
|
||||||
|
});
|
||||||
@@ -11,8 +11,10 @@ final pointRecordsProvider =
|
|||||||
);
|
);
|
||||||
|
|
||||||
/// Loads a single record by id (used by the edit/view page).
|
/// Loads a single record by id (used by the edit/view page).
|
||||||
|
/// Watches [pointRecordsProvider] so upsert/complete invalidates the cache.
|
||||||
final recordByIdProvider =
|
final recordByIdProvider =
|
||||||
FutureProvider.family<PointRecord?, int>((ref, id) async {
|
FutureProvider.family<PointRecord?, int>((ref, id) async {
|
||||||
|
ref.watch(pointRecordsProvider);
|
||||||
final isar = await ref.watch(isarProvider.future);
|
final isar = await ref.watch(isarProvider.future);
|
||||||
return isar.pointRecords.get(id);
|
return isar.pointRecords.get(id);
|
||||||
});
|
});
|
||||||
@@ -38,8 +40,10 @@ class PointRecordsNotifier extends AsyncNotifier<List<PointRecord>> {
|
|||||||
|
|
||||||
/// Creates a new draft from scoring parameters and returns it.
|
/// Creates a new draft from scoring parameters and returns it.
|
||||||
Future<PointRecord> createDraft({
|
Future<PointRecord> createDraft({
|
||||||
required BowType bowType,
|
required int bowOptionId,
|
||||||
required TargetFaceType targetFaceType,
|
required String bowName,
|
||||||
|
required int targetOptionId,
|
||||||
|
required String targetName,
|
||||||
required int distanceMeters,
|
required int distanceMeters,
|
||||||
required int endCount,
|
required int endCount,
|
||||||
required int arrowsPerEnd,
|
required int arrowsPerEnd,
|
||||||
@@ -47,8 +51,10 @@ class PointRecordsNotifier extends AsyncNotifier<List<PointRecord>> {
|
|||||||
}) async {
|
}) async {
|
||||||
final isar = await _isar();
|
final isar = await _isar();
|
||||||
final draft = PointRecord.createDraft(
|
final draft = PointRecord.createDraft(
|
||||||
bowType: bowType,
|
bowOptionId: bowOptionId,
|
||||||
targetFaceType: targetFaceType,
|
bowName: bowName,
|
||||||
|
targetOptionId: targetOptionId,
|
||||||
|
targetName: targetName,
|
||||||
distanceMeters: distanceMeters,
|
distanceMeters: distanceMeters,
|
||||||
endCount: endCount,
|
endCount: endCount,
|
||||||
arrowsPerEnd: arrowsPerEnd,
|
arrowsPerEnd: arrowsPerEnd,
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class BowOption {
|
||||||
|
const BowOption({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.icon,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
final String name;
|
||||||
|
final String icon;
|
||||||
|
|
||||||
|
factory BowOption.fromJson(Map<String, dynamic> json) {
|
||||||
|
return BowOption(
|
||||||
|
id: json['id'] as int,
|
||||||
|
name: json['name'] as String,
|
||||||
|
icon: json['icon'] as String? ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TargetOption {
|
||||||
|
const TargetOption({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.icon,
|
||||||
|
required this.iconPng,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
final String name;
|
||||||
|
final String icon;
|
||||||
|
final String iconPng;
|
||||||
|
|
||||||
|
factory TargetOption.fromJson(Map<String, dynamic> json) {
|
||||||
|
return TargetOption(
|
||||||
|
id: json['id'] as int,
|
||||||
|
name: json['name'] as String,
|
||||||
|
icon: json['icon'] as String? ?? '',
|
||||||
|
iconPng: json['iconPng'] as String? ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PointBookConfig {
|
||||||
|
const PointBookConfig({
|
||||||
|
required this.bowOption,
|
||||||
|
required this.targetOption,
|
||||||
|
});
|
||||||
|
|
||||||
|
static const assetPath = 'assets/config/point_book_config.json';
|
||||||
|
|
||||||
|
final List<BowOption> bowOption;
|
||||||
|
final List<TargetOption> targetOption;
|
||||||
|
|
||||||
|
factory PointBookConfig.fromJson(Map<String, dynamic> json) {
|
||||||
|
final bows = (json['bowOption'] as List<dynamic>? ?? const [])
|
||||||
|
.cast<Map<String, dynamic>>()
|
||||||
|
.map(BowOption.fromJson)
|
||||||
|
.toList();
|
||||||
|
final targets = (json['targetOption'] as List<dynamic>? ?? const [])
|
||||||
|
.cast<Map<String, dynamic>>()
|
||||||
|
.map(TargetOption.fromJson)
|
||||||
|
.toList();
|
||||||
|
return PointBookConfig(bowOption: bows, targetOption: targets);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<PointBookConfig> fromAsset({
|
||||||
|
String path = assetPath,
|
||||||
|
}) async {
|
||||||
|
final raw = await rootBundle.loadString(path);
|
||||||
|
return PointBookConfig.fromJson(
|
||||||
|
jsonDecode(raw) as Map<String, dynamic>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BowOption? bowById(int id) {
|
||||||
|
for (final o in bowOption) {
|
||||||
|
if (o.id == id) return o;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
TargetOption? targetById(int id) {
|
||||||
|
for (final o in targetOption) {
|
||||||
|
if (o.id == id) return o;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:arcx/app/app_theme.dart';
|
import 'package:arcx/app/app_theme.dart';
|
||||||
import 'package:arcx/data/local/models/models.dart';
|
import 'package:arcx/features/scoring/application/point_book_config_provider.dart';
|
||||||
import 'package:arcx/features/scoring/application/scoring_service.dart';
|
import 'package:arcx/features/scoring/application/scoring_service.dart';
|
||||||
|
import 'package:arcx/features/scoring/domain/point_book_config.dart';
|
||||||
|
|
||||||
class PointBookCreatePage extends ConsumerStatefulWidget {
|
class PointBookCreatePage extends ConsumerStatefulWidget {
|
||||||
const PointBookCreatePage({super.key});
|
const PointBookCreatePage({super.key});
|
||||||
@@ -14,95 +16,187 @@ class PointBookCreatePage extends ConsumerStatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _PointBookCreatePageState extends ConsumerState<PointBookCreatePage> {
|
class _PointBookCreatePageState extends ConsumerState<PointBookCreatePage> {
|
||||||
BowType _bowType = BowType.recurve;
|
static const _distances = [10, 18, 25, 30, 40, 50, 60, 70, 90];
|
||||||
TargetFaceType _targetFace = TargetFaceType.cm40;
|
static const _endOptions = [3, 4, 5, 6, 8, 10, 12];
|
||||||
|
static const _arrowOptions = [3, 4, 5, 6, 12];
|
||||||
|
|
||||||
|
int? _bowOptionId;
|
||||||
|
int? _targetOptionId;
|
||||||
int _distance = 18;
|
int _distance = 18;
|
||||||
int _endCount = 6;
|
int _endCount = 6;
|
||||||
int _arrowsPerEnd = 6;
|
int _arrowsPerEnd = 6;
|
||||||
bool _creating = false;
|
bool _creating = false;
|
||||||
|
|
||||||
static const _distances = [10, 18, 25, 30, 40, 50, 60, 70, 90];
|
final _distanceCustom = TextEditingController();
|
||||||
static const _endOptions = [3, 4, 5, 6, 8, 10, 12];
|
final _endCustom = TextEditingController();
|
||||||
static const _arrowOptions = [3, 4, 5, 6, 12];
|
final _arrowCustom = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_distanceCustom.dispose();
|
||||||
|
_endCustom.dispose();
|
||||||
|
_arrowCustom.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
final configAsync = ref.watch(pointBookConfigProvider);
|
||||||
appBar: AppBar(title: const Text('新建记分')),
|
|
||||||
body: ListView(
|
return GestureDetector(
|
||||||
padding: const EdgeInsets.all(16),
|
onTap: _unfocus,
|
||||||
children: [
|
behavior: HitTestBehavior.translucent,
|
||||||
_SectionTitle('弓型'),
|
child: Scaffold(
|
||||||
_ChipGroup(
|
appBar: AppBar(title: const Text('选择参数')),
|
||||||
options: BowType.values,
|
body: configAsync.when(
|
||||||
value: _bowType,
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
labelOf: _bowLabel,
|
error: (e, _) => Center(child: Text('配置加载失败:$e')),
|
||||||
onChanged: (v) => setState(() => _bowType = v),
|
data: (config) {
|
||||||
),
|
_ensureDefaults(config);
|
||||||
const SizedBox(height: 16),
|
final bow = config.bowById(_bowOptionId!);
|
||||||
_SectionTitle('距离'),
|
final target = config.targetById(_targetOptionId!);
|
||||||
_ChipGroup<int>(
|
if (bow == null || target == null) {
|
||||||
options: _distances,
|
return const Center(child: Text('配置无效'));
|
||||||
value: _distance,
|
}
|
||||||
labelOf: (v) => '${v}m',
|
return ListView(
|
||||||
onChanged: (v) => setState(() => _distance = v),
|
padding: const EdgeInsets.all(16),
|
||||||
),
|
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
|
||||||
const SizedBox(height: 16),
|
children: [
|
||||||
_SectionTitle('靶纸类型'),
|
const _SectionTitle('弓型'),
|
||||||
_ChipGroup(
|
_ChipGroup<int>(
|
||||||
options: TargetFaceType.values,
|
options: config.bowOption.map((e) => e.id).toList(),
|
||||||
value: _targetFace,
|
value: _bowOptionId!,
|
||||||
labelOf: _targetLabel,
|
labelOf: (id) => config.bowById(id)?.name ?? '$id',
|
||||||
onChanged: (v) => setState(() => _targetFace = v),
|
onChanged: (v) {
|
||||||
),
|
_unfocus();
|
||||||
const SizedBox(height: 16),
|
setState(() => _bowOptionId = v);
|
||||||
Row(
|
},
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: _NumberPicker(
|
|
||||||
label: '组数',
|
|
||||||
value: _endCount,
|
|
||||||
options: _endOptions,
|
|
||||||
onChanged: (v) => setState(() => _endCount = v),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
const SizedBox(height: 16),
|
||||||
Expanded(
|
const _SectionTitle('距离'),
|
||||||
child: _NumberPicker(
|
_PresetWithCustom(
|
||||||
label: '每组箭数',
|
options: _distances,
|
||||||
value: _arrowsPerEnd,
|
value: _distance,
|
||||||
options: _arrowOptions,
|
labelOf: (v) => '${v}m',
|
||||||
onChanged: (v) => setState(() => _arrowsPerEnd = v),
|
customController: _distanceCustom,
|
||||||
),
|
customHint: '自定义(m)',
|
||||||
|
min: 1,
|
||||||
|
max: 300,
|
||||||
|
onPreset: (v) {
|
||||||
|
_unfocus();
|
||||||
|
setState(() {
|
||||||
|
_distance = v;
|
||||||
|
_distanceCustom.clear();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCustom: (v) => setState(() => _distance = v),
|
||||||
|
onInvalid: () => _toast('请输入 1–300 的距离'),
|
||||||
),
|
),
|
||||||
],
|
const SizedBox(height: 16),
|
||||||
),
|
const _SectionTitle('靶纸类型'),
|
||||||
const SizedBox(height: 32),
|
_ChipGroup<int>(
|
||||||
_Summary(
|
options: config.targetOption.map((e) => e.id).toList(),
|
||||||
bowType: _bowType,
|
value: _targetOptionId!,
|
||||||
distance: _distance,
|
labelOf: (id) => config.targetById(id)?.name ?? '$id',
|
||||||
targetFace: _targetFace,
|
onChanged: (v) {
|
||||||
endCount: _endCount,
|
_unfocus();
|
||||||
arrowsPerEnd: _arrowsPerEnd,
|
setState(() => _targetOptionId = v);
|
||||||
),
|
},
|
||||||
const SizedBox(height: 16),
|
),
|
||||||
FilledButton.icon(
|
const SizedBox(height: 16),
|
||||||
onPressed: _creating ? null : _start,
|
const _SectionTitle('组数'),
|
||||||
icon: const Icon(Icons.play_arrow),
|
_PresetWithCustom(
|
||||||
label: const Text('开始记分'),
|
options: _endOptions,
|
||||||
),
|
value: _endCount,
|
||||||
],
|
labelOf: (v) => '$v',
|
||||||
|
customController: _endCustom,
|
||||||
|
customHint: '自定义',
|
||||||
|
min: 1,
|
||||||
|
max: 36,
|
||||||
|
onPreset: (v) {
|
||||||
|
_unfocus();
|
||||||
|
setState(() {
|
||||||
|
_endCount = v;
|
||||||
|
_endCustom.clear();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCustom: (v) => setState(() => _endCount = v),
|
||||||
|
onInvalid: () => _toast('请输入 1–36 的组数'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
const _SectionTitle('每组箭数'),
|
||||||
|
_PresetWithCustom(
|
||||||
|
options: _arrowOptions,
|
||||||
|
value: _arrowsPerEnd,
|
||||||
|
labelOf: (v) => '$v',
|
||||||
|
customController: _arrowCustom,
|
||||||
|
customHint: '自定义',
|
||||||
|
min: 1,
|
||||||
|
max: 36,
|
||||||
|
onPreset: (v) {
|
||||||
|
_unfocus();
|
||||||
|
setState(() {
|
||||||
|
_arrowsPerEnd = v;
|
||||||
|
_arrowCustom.clear();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onCustom: (v) => setState(() => _arrowsPerEnd = v),
|
||||||
|
onInvalid: () => _toast('请输入 1–36 的箭数'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
_Summary(
|
||||||
|
bowName: bow.name,
|
||||||
|
distance: _distance,
|
||||||
|
targetName: target.name,
|
||||||
|
endCount: _endCount,
|
||||||
|
arrowsPerEnd: _arrowsPerEnd,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: _creating
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
_unfocus();
|
||||||
|
_start(bow: bow, target: target);
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.play_arrow),
|
||||||
|
label: const Text('下一步'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _start() async {
|
void _unfocus() {
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _ensureDefaults(PointBookConfig config) {
|
||||||
|
_bowOptionId ??= config.bowOption.isNotEmpty
|
||||||
|
? config.bowOption.first.id
|
||||||
|
: 1;
|
||||||
|
_targetOptionId ??= config.targetOption.isNotEmpty
|
||||||
|
? config.targetOption.first.id
|
||||||
|
: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _toast(String msg) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _start({
|
||||||
|
required BowOption bow,
|
||||||
|
required TargetOption target,
|
||||||
|
}) async {
|
||||||
setState(() => _creating = true);
|
setState(() => _creating = true);
|
||||||
final draft = await ref
|
final draft = await ref.read(pointRecordsProvider.notifier).createDraft(
|
||||||
.read(pointRecordsProvider.notifier)
|
bowOptionId: bow.id,
|
||||||
.createDraft(
|
bowName: bow.name,
|
||||||
bowType: _bowType,
|
targetOptionId: target.id,
|
||||||
targetFaceType: _targetFace,
|
targetName: target.name,
|
||||||
distanceMeters: _distance,
|
distanceMeters: _distance,
|
||||||
endCount: _endCount,
|
endCount: _endCount,
|
||||||
arrowsPerEnd: _arrowsPerEnd,
|
arrowsPerEnd: _arrowsPerEnd,
|
||||||
@@ -164,84 +258,129 @@ class _ChipGroup<T> extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NumberPicker extends StatelessWidget {
|
/// Preset chips plus a trailing custom number field.
|
||||||
const _NumberPicker({
|
class _PresetWithCustom extends StatelessWidget {
|
||||||
required this.label,
|
const _PresetWithCustom({
|
||||||
required this.value,
|
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.onChanged,
|
required this.value,
|
||||||
|
required this.labelOf,
|
||||||
|
required this.customController,
|
||||||
|
required this.customHint,
|
||||||
|
required this.min,
|
||||||
|
required this.max,
|
||||||
|
required this.onPreset,
|
||||||
|
required this.onCustom,
|
||||||
|
required this.onInvalid,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String label;
|
|
||||||
final int value;
|
|
||||||
final List<int> options;
|
final List<int> options;
|
||||||
final ValueChanged<int> onChanged;
|
final int value;
|
||||||
|
final String Function(int) labelOf;
|
||||||
|
final TextEditingController customController;
|
||||||
|
final String customHint;
|
||||||
|
final int min;
|
||||||
|
final int max;
|
||||||
|
final ValueChanged<int> onPreset;
|
||||||
|
final ValueChanged<int> onCustom;
|
||||||
|
final VoidCallback onInvalid;
|
||||||
|
|
||||||
|
bool get _isPreset => options.contains(value);
|
||||||
|
|
||||||
|
void _applyCustom(String raw) {
|
||||||
|
final n = int.tryParse(raw.trim());
|
||||||
|
if (n == null || n < min || n > max) {
|
||||||
|
onInvalid();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onCustom(n);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
return Wrap(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
spacing: 8,
|
||||||
decoration: BoxDecoration(
|
runSpacing: 8,
|
||||||
color: Colors.white,
|
crossAxisAlignment: WrapCrossAlignment.center,
|
||||||
borderRadius: BorderRadius.circular(12),
|
children: [
|
||||||
border: Border.all(color: Colors.black12),
|
...options.map((o) {
|
||||||
),
|
final selected = _isPreset && o == value;
|
||||||
child: Row(
|
return ChoiceChip(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
label: Text(labelOf(o)),
|
||||||
children: [
|
selected: selected,
|
||||||
Flexible(child: Text(label, style: const TextStyle(fontSize: 13))),
|
onSelected: (_) => onPreset(o),
|
||||||
Row(
|
selectedColor: AppTheme.primary,
|
||||||
mainAxisSize: MainAxisSize.min,
|
labelStyle: TextStyle(
|
||||||
children: [
|
color: selected ? Colors.white : Colors.black87,
|
||||||
IconButton(
|
),
|
||||||
icon: const Icon(Icons.remove_circle_outline),
|
);
|
||||||
iconSize: 20,
|
}),
|
||||||
visualDensity: VisualDensity.compact,
|
SizedBox(
|
||||||
padding: EdgeInsets.zero,
|
width: 96,
|
||||||
constraints: const BoxConstraints(),
|
height: 40,
|
||||||
onPressed: () {
|
child: TextField(
|
||||||
final i = options.indexOf(value);
|
controller: customController,
|
||||||
if (i > 0) onChanged(options[i - 1]);
|
keyboardType: TextInputType.number,
|
||||||
},
|
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: const TextStyle(fontSize: 14),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
hintText: customHint,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8,
|
||||||
|
vertical: 10,
|
||||||
),
|
),
|
||||||
Text(
|
border: OutlineInputBorder(
|
||||||
'$value',
|
borderRadius: BorderRadius.circular(20),
|
||||||
style: const TextStyle(
|
),
|
||||||
fontSize: 18,
|
enabledBorder: OutlineInputBorder(
|
||||||
fontWeight: FontWeight.w700,
|
borderRadius: BorderRadius.circular(20),
|
||||||
|
borderSide: BorderSide(
|
||||||
|
color: !_isPreset ? AppTheme.primary : Colors.black26,
|
||||||
|
width: !_isPreset ? 1.5 : 1,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
IconButton(
|
focusedBorder: OutlineInputBorder(
|
||||||
icon: const Icon(Icons.add_circle_outline),
|
borderRadius: BorderRadius.circular(20),
|
||||||
iconSize: 20,
|
borderSide: const BorderSide(
|
||||||
visualDensity: VisualDensity.compact,
|
color: AppTheme.primary,
|
||||||
padding: EdgeInsets.zero,
|
width: 1.5,
|
||||||
constraints: const BoxConstraints(),
|
),
|
||||||
onPressed: () {
|
|
||||||
final i = options.indexOf(value);
|
|
||||||
if (i < options.length - 1) onChanged(options[i + 1]);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
onTapOutside: (_) {
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
final raw = customController.text.trim();
|
||||||
|
if (raw.isNotEmpty) _applyCustom(raw);
|
||||||
|
},
|
||||||
|
onChanged: (raw) {
|
||||||
|
if (raw.trim().isEmpty) return;
|
||||||
|
final n = int.tryParse(raw.trim());
|
||||||
|
if (n != null && n >= min && n <= max) {
|
||||||
|
onCustom(n);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onSubmitted: _applyCustom,
|
||||||
|
onEditingComplete: () => _applyCustom(customController.text),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _Summary extends StatelessWidget {
|
class _Summary extends StatelessWidget {
|
||||||
const _Summary({
|
const _Summary({
|
||||||
required this.bowType,
|
required this.bowName,
|
||||||
required this.distance,
|
required this.distance,
|
||||||
required this.targetFace,
|
required this.targetName,
|
||||||
required this.endCount,
|
required this.endCount,
|
||||||
required this.arrowsPerEnd,
|
required this.arrowsPerEnd,
|
||||||
});
|
});
|
||||||
|
|
||||||
final BowType bowType;
|
final String bowName;
|
||||||
final int distance;
|
final int distance;
|
||||||
final TargetFaceType targetFace;
|
final String targetName;
|
||||||
final int endCount;
|
final int endCount;
|
||||||
final int arrowsPerEnd;
|
final int arrowsPerEnd;
|
||||||
|
|
||||||
@@ -258,7 +397,7 @@ class _Summary extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
'${_bowLabel(bowType)} · ${distance}m · ${_targetLabel(targetFace)}',
|
'$bowName · ${distance}m · $targetName',
|
||||||
style: const TextStyle(fontSize: 13),
|
style: const TextStyle(fontSize: 13),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -275,39 +414,3 @@ class _Summary extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _bowLabel(BowType t) {
|
|
||||||
switch (t) {
|
|
||||||
case BowType.recurve:
|
|
||||||
return '反曲弓';
|
|
||||||
case BowType.compound:
|
|
||||||
return '复合弓';
|
|
||||||
case BowType.barebow:
|
|
||||||
return '光弓';
|
|
||||||
case BowType.traditional:
|
|
||||||
return '传统弓';
|
|
||||||
case BowType.longbow:
|
|
||||||
return '长弓';
|
|
||||||
case BowType.other:
|
|
||||||
return '其他';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _targetLabel(TargetFaceType t) {
|
|
||||||
switch (t) {
|
|
||||||
case TargetFaceType.cm40:
|
|
||||||
return '40cm';
|
|
||||||
case TargetFaceType.cm60:
|
|
||||||
return '60cm';
|
|
||||||
case TargetFaceType.cm80:
|
|
||||||
return '80cm';
|
|
||||||
case TargetFaceType.cm122:
|
|
||||||
return '122cm';
|
|
||||||
case TargetFaceType.vegas3Spot:
|
|
||||||
return '维加斯三联';
|
|
||||||
case TargetFaceType.vertical3Spot:
|
|
||||||
return '垂直三联';
|
|
||||||
case TargetFaceType.custom:
|
|
||||||
return '自定义';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
|
||||||
import 'package:arcx/app/app_theme.dart';
|
import 'package:arcx/app/app_theme.dart';
|
||||||
import 'package:arcx/features/config/application/app_config_controller.dart';
|
import 'package:arcx/features/config/application/app_config_controller.dart';
|
||||||
@@ -46,6 +47,7 @@ class _PointBookEditPageState extends ConsumerState<PointBookEditPage> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: recordAsync.when(
|
body: recordAsync.when(
|
||||||
|
skipLoadingOnReload: true,
|
||||||
loading: () => const Center(child: CircularProgressIndicator()),
|
loading: () => const Center(child: CircularProgressIndicator()),
|
||||||
error: (e, _) => Center(child: Text('加载失败:$e')),
|
error: (e, _) => Center(child: Text('加载失败:$e')),
|
||||||
data: (record) {
|
data: (record) {
|
||||||
@@ -57,6 +59,11 @@ class _PointBookEditPageState extends ConsumerState<PointBookEditPage> {
|
|||||||
_scores = List<int>.from(record.scores);
|
_scores = List<int>.from(record.scores);
|
||||||
_hitPoints = List<HitPoint>.from(record.hitPoints);
|
_hitPoints = List<HitPoint>.from(record.hitPoints);
|
||||||
_initialized = true;
|
_initialized = true;
|
||||||
|
} else if (widget.readOnly) {
|
||||||
|
// Detail view: always mirror latest provider/Isar data.
|
||||||
|
_record = record;
|
||||||
|
_scores = List<int>.from(record.scores);
|
||||||
|
_hitPoints = List<HitPoint>.from(record.hitPoints);
|
||||||
}
|
}
|
||||||
return _buildBody(context, record);
|
return _buildBody(context, record);
|
||||||
},
|
},
|
||||||
@@ -81,61 +88,63 @@ class _PointBookEditPageState extends ConsumerState<PointBookEditPage> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
child: Padding(
|
||||||
child: Center(
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
child: AspectRatio(
|
child: Center(
|
||||||
aspectRatio: 1.0,
|
child: AspectRatio(
|
||||||
child: LayoutBuilder(
|
aspectRatio: 1.0,
|
||||||
builder: (context, constraints) {
|
child: LayoutBuilder(
|
||||||
final size = constraints.maxWidth;
|
builder: (context, constraints) {
|
||||||
return GestureDetector(
|
final size = constraints.maxWidth;
|
||||||
behavior: HitTestBehavior.opaque,
|
return GestureDetector(
|
||||||
onTapUp: widget.readOnly || isComplete || _saving
|
behavior: HitTestBehavior.opaque,
|
||||||
? null
|
onTapUp: widget.readOnly || isComplete || _saving
|
||||||
: (details) => _onTap(details, size, record),
|
? null
|
||||||
child: Stack(
|
: (details) => _onTap(details, size, record),
|
||||||
alignment: Alignment.center,
|
child: Stack(
|
||||||
children: [
|
alignment: Alignment.center,
|
||||||
Image.asset(
|
children: [
|
||||||
'assets/images/target_face.png',
|
SvgPicture.asset(
|
||||||
|
'assets/images/target_face.svg',
|
||||||
width: size,
|
width: size,
|
||||||
height: size,
|
height: size,
|
||||||
fit: BoxFit.fill,
|
fit: BoxFit.fill,
|
||||||
),
|
),
|
||||||
CustomPaint(
|
CustomPaint(
|
||||||
size: Size(size, size),
|
size: Size(size, size),
|
||||||
painter: HitPointPainter(
|
painter: HitPointPainter(
|
||||||
hitPoints: _hitPoints,
|
hitPoints: _hitPoints,
|
||||||
highlightIndex: _hitPoints.isEmpty
|
highlightIndex: _hitPoints.isEmpty
|
||||||
? null
|
? null
|
||||||
: _hitPoints.length - 1,
|
: _hitPoints.length - 1,
|
||||||
),
|
|
||||||
),
|
|
||||||
if (widget.readOnly && _hitPoints.isEmpty)
|
|
||||||
Text(
|
|
||||||
'无落点数据',
|
|
||||||
style: TextStyle(color: AppTheme.textSecondary),
|
|
||||||
),
|
|
||||||
if (!widget.readOnly && isComplete)
|
|
||||||
Container(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
vertical: 8,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.black54,
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
),
|
|
||||||
child: const Text(
|
|
||||||
'已射完,点击右上角保存',
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
if (widget.readOnly && _hitPoints.isEmpty)
|
||||||
),
|
Text(
|
||||||
);
|
'无落点数据',
|
||||||
},
|
style: TextStyle(color: AppTheme.textSecondary),
|
||||||
|
),
|
||||||
|
if (!widget.readOnly && isComplete)
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16,
|
||||||
|
vertical: 8,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.black54,
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: const Text(
|
||||||
|
'已射完,点击右上角保存',
|
||||||
|
style: TextStyle(color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -262,42 +271,44 @@ class _ProgressHeader extends StatelessWidget {
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
color: AppTheme.primary.withValues(alpha: 0.06),
|
color: AppTheme.primary.withValues(alpha: 0.06),
|
||||||
padding: const EdgeInsets.fromLTRB(16, 12, 16, 12),
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 12),
|
||||||
child: Column(
|
child: SingleChildScrollView(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Column(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Row(
|
children: [
|
||||||
children: [
|
Row(
|
||||||
Expanded(
|
children: [
|
||||||
child: Text(
|
Expanded(
|
||||||
record.title,
|
child: Text(
|
||||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
record.title,
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Text(
|
||||||
|
'$placed / $total 支',
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
color: AppTheme.primaryDark,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
if (placed < total)
|
||||||
Text(
|
Text(
|
||||||
'$placed / $total 支',
|
'第 ${currentEnd + 1}/${record.endCount} 组 · 本组第 ${currentArrow + 1}/${record.arrowsPerEnd} 支',
|
||||||
style: const TextStyle(
|
style: TextStyle(fontSize: 12, color: AppTheme.textSecondary),
|
||||||
fontWeight: FontWeight.w700,
|
),
|
||||||
color: AppTheme.primaryDark,
|
const SizedBox(height: 8),
|
||||||
),
|
ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: total == 0 ? 0 : placed / total,
|
||||||
|
minHeight: 6,
|
||||||
|
backgroundColor: AppTheme.primary.withValues(alpha: 0.15),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 6),
|
|
||||||
if (placed < total)
|
|
||||||
Text(
|
|
||||||
'第 ${currentEnd + 1}/${record.endCount} 组 · 本组第 ${currentArrow + 1}/${record.arrowsPerEnd} 支',
|
|
||||||
style: TextStyle(fontSize: 12, color: AppTheme.textSecondary),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
],
|
||||||
ClipRRect(
|
),
|
||||||
borderRadius: BorderRadius.circular(4),
|
|
||||||
child: LinearProgressIndicator(
|
|
||||||
value: total == 0 ? 0 : placed / total,
|
|
||||||
minHeight: 6,
|
|
||||||
backgroundColor: AppTheme.primary.withValues(alpha: 0.15),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.13.0"
|
version: "5.13.0"
|
||||||
|
ansicolor:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ansicolor
|
||||||
|
sha256: "50e982d500bc863e1d703448afdbf9e5a72eb48840a4f766fa361ffd6877055f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
|
archive:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: archive
|
||||||
|
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.9"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -121,6 +137,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.4"
|
version: "2.0.4"
|
||||||
|
cli_util:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_util
|
||||||
|
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.2"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -177,6 +201,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.7"
|
version: "3.0.7"
|
||||||
|
csslib:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: csslib
|
||||||
|
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
cupertino_icons:
|
cupertino_icons:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -270,6 +302,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_launcher_icons:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_launcher_icons
|
||||||
|
sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.14.4"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -278,6 +318,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.0.0"
|
||||||
|
flutter_native_splash:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_native_splash
|
||||||
|
sha256: "9db4b80b044e9af17cc4b1272137fc7ace0054d879ef8210a76adc34aaf4cdff"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.8"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -294,6 +342,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.6.1"
|
version: "2.6.1"
|
||||||
|
flutter_svg:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_svg
|
||||||
|
sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -336,6 +392,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.0.2"
|
||||||
|
html:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: html
|
||||||
|
sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.15.6"
|
||||||
http:
|
http:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -360,6 +424,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.2"
|
version: "4.1.2"
|
||||||
|
image:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image
|
||||||
|
sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.9.1"
|
||||||
image_picker:
|
image_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -624,6 +696,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.1"
|
version: "1.9.1"
|
||||||
|
path_parsing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_parsing
|
||||||
|
sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
path_provider:
|
path_provider:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -672,6 +752,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.0"
|
||||||
|
petitparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: petitparser
|
||||||
|
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.2"
|
||||||
platform:
|
platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -696,6 +784,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.2"
|
version: "1.5.2"
|
||||||
|
posix:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: posix
|
||||||
|
sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.5.2"
|
||||||
pub_semver:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -845,6 +941,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
universal_io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: universal_io
|
||||||
|
sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.1"
|
||||||
|
vector_graphics:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_graphics
|
||||||
|
sha256: "9d0e3b9cb16542ad660daee871e726a10d13a93b7b5391677c3160e8f5e83935"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.3"
|
||||||
|
vector_graphics_codec:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_graphics_codec
|
||||||
|
sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.13"
|
||||||
|
vector_graphics_compiler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_graphics_compiler
|
||||||
|
sha256: "4dca4feb77dc3ec7f6e27e49c53241eb8217f55e4f9b12599a27f8903bca5682"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -901,6 +1029,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.0"
|
version: "1.1.0"
|
||||||
|
xml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xml
|
||||||
|
sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
xxh3:
|
xxh3:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ dependencies:
|
|||||||
isar_flutter_libs: 3.1.0+1
|
isar_flutter_libs: 3.1.0+1
|
||||||
path: ^1.9.1
|
path: ^1.9.1
|
||||||
path_provider: ^2.1.5
|
path_provider: ^2.1.5
|
||||||
|
flutter_svg: ^2.3.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
@@ -24,8 +25,28 @@ dev_dependencies:
|
|||||||
flutter_lints: ^6.0.0
|
flutter_lints: ^6.0.0
|
||||||
build_runner: ^2.4.7
|
build_runner: ^2.4.7
|
||||||
isar_generator: 3.1.0+1
|
isar_generator: 3.1.0+1
|
||||||
|
flutter_launcher_icons: ^0.14.4
|
||||||
|
flutter_native_splash: ^2.4.4
|
||||||
|
|
||||||
|
flutter_launcher_icons:
|
||||||
|
android: true
|
||||||
|
ios: true
|
||||||
|
image_path: assets/branding/app_icon.png
|
||||||
|
remove_alpha_ios: true
|
||||||
|
adaptive_icon_background: "#F5C400"
|
||||||
|
adaptive_icon_foreground: assets/branding/app_icon.png
|
||||||
|
|
||||||
|
flutter_native_splash:
|
||||||
|
background_image: assets/branding/splash.png
|
||||||
|
android_12:
|
||||||
|
color: "#FFFFFF"
|
||||||
|
image: assets/branding/app_icon.png
|
||||||
|
ios: true
|
||||||
|
android: true
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
assets:
|
assets:
|
||||||
- assets/images/
|
- assets/images/
|
||||||
|
- assets/branding/
|
||||||
|
- assets/config/
|
||||||
|
|||||||