Author SHA1 Message Date
linfeng 15700f918b 重构记分数据模型,替换弓型和靶纸类型为 ID 及名称,更新相关页面以支持新配置。 2026-08-09 19:09:21 +08:00
linfeng e3d6fac6e1 更换靶面美术资源 2026-08-09 04:26:19 +08:00
linfengandCursor 6901b08a94 设置 App 图标与启动页,并修复记分详情回显与环值分布溢出。
同步恢复试用次数校验,免费额度调整为 2 次,便于验证 IAP 流程。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-09 03:50:03 +08:00
81 changed files with 1765 additions and 754 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

@@ -1,12 +1,6 @@
<?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:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

@@ -1,12 +1,6 @@
<?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" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
<item>
<bitmap android:gravity="fill" android:src="@drawable/background"/>
</item>
</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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

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
the Flutter engine draws its first frame -->
<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>
<!-- Theme applied to the Android Window as soon as the process has started.
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
the Flutter engine draws its first frame -->
<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>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Binary file not shown.

After

Width:  |  Height:  |  Size: 214 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

+91
View File
@@ -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"
}
]
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

+58
View File
@@ -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

+2 -2
View File
@@ -547,7 +547,7 @@
isa = XCBuildConfiguration;
buildSettings = {
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_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@@ -604,7 +604,7 @@
isa = XCBuildConfiguration;
buildSettings = {
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_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
@@ -52,7 +52,7 @@
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
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" : "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"
}
}
{"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"}}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 B

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 450 B

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 282 B

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 B

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 406 B

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 586 B

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 862 B

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 762 B

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

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
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

@@ -1,23 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
"author" : "xcode",
"version" : 1
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 69 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 69 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 B

After

Width:  |  Height:  |  Size: 69 B

+11 -4
View File
@@ -16,13 +16,19 @@
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<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>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"></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"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="3T2-ad-Qdv"/>
<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>
</view>
</viewController>
@@ -33,5 +39,6 @@
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
<image name="LaunchBackground" width="1" height="1"/>
</resources>
</document>
+68 -66
View File
@@ -1,74 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>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>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<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>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</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>
<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>
+1 -1
View File
@@ -3,7 +3,7 @@ class AppDefaults {
static const int singletonId = 1;
static const int defaultFreeTrialLimit = 10;
static const int defaultFreeTrialLimit = 2;
static const int nicknameRandomMin = 1000;
static const int nicknameRandomMax = 9999;
+26 -31
View File
@@ -7,25 +7,6 @@ enum PointRecordStatus {
completed,
}
enum BowType {
recurve,
compound,
barebow,
traditional,
longbow,
other,
}
enum TargetFaceType {
cm40,
cm60,
cm80,
cm122,
vegas3Spot,
vertical3Spot,
custom,
}
@embedded
class HitPoint {
HitPoint({
@@ -67,11 +48,17 @@ class PointRecord {
@enumerated
PointRecordStatus status = PointRecordStatus.draft;
@enumerated
BowType bowType = BowType.recurve;
/// Matches [BowOption.id] from point book config.
@Index(type: IndexType.value)
int bowOptionId = 1;
@enumerated
TargetFaceType targetFaceType = TargetFaceType.cm40;
late String bowName;
/// Matches [TargetOption.id] from point book config.
@Index(type: IndexType.value)
int targetOptionId = 1;
late String targetName;
late String title;
int distanceMeters = 18;
@@ -88,8 +75,10 @@ class PointRecord {
scores.isEmpty ? null : totalScore / scores.length;
factory PointRecord.createDraft({
required BowType bowType,
required TargetFaceType targetFaceType,
required int bowOptionId,
required String bowName,
required int targetOptionId,
required String targetName,
required int distanceMeters,
required int endCount,
required int arrowsPerEnd,
@@ -101,8 +90,10 @@ class PointRecord {
return PointRecord()
..title = title ?? '未完成记分'
..status = PointRecordStatus.draft
..bowType = bowType
..targetFaceType = targetFaceType
..bowOptionId = bowOptionId
..bowName = bowName
..targetOptionId = targetOptionId
..targetName = targetName
..distanceMeters = distanceMeters
..endCount = endCount
..arrowsPerEnd = arrowsPerEnd
@@ -117,8 +108,10 @@ class PointRecord {
..id = id
..title = title
..status = PointRecordStatus.completed
..bowType = bowType
..targetFaceType = targetFaceType
..bowOptionId = bowOptionId
..bowName = bowName
..targetOptionId = targetOptionId
..targetName = targetName
..distanceMeters = distanceMeters
..endCount = endCount
..arrowsPerEnd = arrowsPerEnd
@@ -136,8 +129,10 @@ class PointRecord {
..id = id
..title = title
..status = status
..bowType = bowType
..targetFaceType = targetFaceType
..bowOptionId = bowOptionId
..bowName = bowName
..targetOptionId = targetOptionId
..targetName = targetName
..distanceMeters = distanceMeters
..endCount = endCount
..arrowsPerEnd = arrowsPerEnd
File diff suppressed because it is too large Load Diff
@@ -74,13 +74,13 @@ class PointBookHomeScreen extends ConsumerWidget {
if (config == null) return;
// IAP / trial gate.
// if (!config.canStartScoring) {
// await showDialog<void>(
// context: context,
// builder: (_) => const IapUnlockDialog(),
// );
// return;
// }
if (!config.canStartScoring) {
await showDialog<void>(
context: context,
builder: (_) => const IapUnlockDialog(),
);
return;
}
// Draft check.
final notifier = ref.read(pointRecordsProvider.notifier);
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:arcx/app/app_theme.dart';
import 'package:arcx/features/scoring/presentation/widgets/hit_point_painter.dart';
@@ -30,8 +31,8 @@ class HeatmapCard extends StatelessWidget {
fit: StackFit.expand,
alignment: Alignment.center,
children: [
Image.asset(
'assets/images/target_face.png',
SvgPicture.asset(
'assets/images/target_face.svg',
fit: BoxFit.fill,
),
CustomPaint(
@@ -79,40 +79,40 @@ class _Bar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final maxH = constraints.maxHeight - 40; // reserve space for count + label
final h = (maxH * ratio).clamp(4.0, maxH);
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
'$count',
style: TextStyle(
fontSize: 11,
color: AppTheme.textSecondary,
return Column(
children: [
Text(
'$count',
style: TextStyle(
fontSize: 11,
color: AppTheme.textSecondary,
),
),
const SizedBox(height: 4),
Expanded(
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,
height: h,
decoration: BoxDecoration(
color: AppTheme.primary,
borderRadius: BorderRadius.circular(4),
),
),
const SizedBox(height: 4),
Text(
label,
style: const TextStyle(
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/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/domain/point_book_config.dart';
import 'package:arcx/features/scoring/presentation/point_book_edit_page.dart';
class PointBookListPage extends ConsumerStatefulWidget {
@@ -14,22 +16,31 @@ class PointBookListPage extends ConsumerStatefulWidget {
}
class _PointBookListPageState extends ConsumerState<PointBookListPage> {
BowType? _bowFilter;
TargetFaceType? _targetFilter;
int? _bowFilter;
int? _targetFilter;
@override
Widget build(BuildContext context) {
final recordsAsync = ref.watch(pointRecordsProvider);
final configAsync = ref.watch(pointBookConfigProvider);
return Scaffold(
appBar: AppBar(title: const Text('计分记录')),
body: Column(
children: [
_FilterBar(
bowFilter: _bowFilter,
targetFilter: _targetFilter,
onBowChanged: (v) => setState(() => _bowFilter = v),
onTargetChanged: (v) => setState(() => _targetFilter = v),
configAsync.when(
loading: () => const SizedBox(height: 64),
error: (e, _) => Padding(
padding: const EdgeInsets.all(16),
child: Text('配置加载失败:$e'),
),
data: (config) => _FilterBar(
config: config,
bowFilter: _bowFilter,
targetFilter: _targetFilter,
onBowChanged: (v) => setState(() => _bowFilter = v),
onTargetChanged: (v) => setState(() => _targetFilter = v),
),
),
Expanded(
child: recordsAsync.when(
@@ -64,10 +75,10 @@ class _PointBookListPageState extends ConsumerState<PointBookListPage> {
List<PointRecord> _applyFilters(List<PointRecord> records) {
var result = records;
if (_bowFilter != null) {
result = result.where((r) => r.bowType == _bowFilter).toList();
result = result.where((r) => r.bowOptionId == _bowFilter).toList();
}
if (_targetFilter != null) {
result = result.where((r) => r.targetFaceType == _targetFilter).toList();
result = result.where((r) => r.targetOptionId == _targetFilter).toList();
}
return result;
}
@@ -107,16 +118,18 @@ class _PointBookListPageState extends ConsumerState<PointBookListPage> {
class _FilterBar extends StatelessWidget {
const _FilterBar({
required this.config,
required this.bowFilter,
required this.targetFilter,
required this.onBowChanged,
required this.onTargetChanged,
});
final BowType? bowFilter;
final TargetFaceType? targetFilter;
final ValueChanged<BowType?> onBowChanged;
final ValueChanged<TargetFaceType?> onTargetChanged;
final PointBookConfig config;
final int? bowFilter;
final int? targetFilter;
final ValueChanged<int?> onBowChanged;
final ValueChanged<int?> onTargetChanged;
@override
Widget build(BuildContext context) {
@@ -125,21 +138,23 @@ class _FilterBar extends StatelessWidget {
child: Row(
children: [
Expanded(
child: _Dropdown<BowType>(
child: _Dropdown<int>(
label: '弓型',
value: bowFilter,
items: BowType.values,
labelOf: _bowLabel,
items: config.bowOption.map((e) => e.id).toList(),
labelOf: (id) =>
config.bowById(id)?.name ?? id.toString(),
onChanged: onBowChanged,
),
),
const SizedBox(width: 12),
Expanded(
child: _Dropdown<TargetFaceType>(
child: _Dropdown<int>(
label: '靶纸',
value: targetFilter,
items: TargetFaceType.values,
labelOf: _targetLabel,
items: config.targetOption.map((e) => e.id).toList(),
labelOf: (id) =>
config.targetById(id)?.name ?? id.toString(),
onChanged: onTargetChanged,
),
),
@@ -177,10 +192,12 @@ class _Dropdown<T> extends StatelessWidget {
),
items: [
DropdownMenuItem<T>(value: null, child: Text('全部')),
...items.map((e) => DropdownMenuItem<T>(
value: e,
child: Text(labelOf(e)),
)),
...items.map(
(e) => DropdownMenuItem<T>(
value: e,
child: Text(labelOf(e), overflow: TextOverflow.ellipsis),
),
),
],
onChanged: onChanged,
);
@@ -232,7 +249,7 @@ class _RecordTile extends StatelessWidget {
],
),
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)}' : ''}',
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) {
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')}';
@@ -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).
/// Watches [pointRecordsProvider] so upsert/complete invalidates the cache.
final recordByIdProvider =
FutureProvider.family<PointRecord?, int>((ref, id) async {
ref.watch(pointRecordsProvider);
final isar = await ref.watch(isarProvider.future);
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.
Future<PointRecord> createDraft({
required BowType bowType,
required TargetFaceType targetFaceType,
required int bowOptionId,
required String bowName,
required int targetOptionId,
required String targetName,
required int distanceMeters,
required int endCount,
required int arrowsPerEnd,
@@ -47,8 +51,10 @@ class PointRecordsNotifier extends AsyncNotifier<List<PointRecord>> {
}) async {
final isar = await _isar();
final draft = PointRecord.createDraft(
bowType: bowType,
targetFaceType: targetFaceType,
bowOptionId: bowOptionId,
bowName: bowName,
targetOptionId: targetOptionId,
targetName: targetName,
distanceMeters: distanceMeters,
endCount: endCount,
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/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/domain/point_book_config.dart';
class PointBookCreatePage extends ConsumerStatefulWidget {
const PointBookCreatePage({super.key});
@@ -14,95 +16,187 @@ class PointBookCreatePage extends ConsumerStatefulWidget {
}
class _PointBookCreatePageState extends ConsumerState<PointBookCreatePage> {
BowType _bowType = BowType.recurve;
TargetFaceType _targetFace = TargetFaceType.cm40;
static const _distances = [10, 18, 25, 30, 40, 50, 60, 70, 90];
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 _endCount = 6;
int _arrowsPerEnd = 6;
bool _creating = false;
static const _distances = [10, 18, 25, 30, 40, 50, 60, 70, 90];
static const _endOptions = [3, 4, 5, 6, 8, 10, 12];
static const _arrowOptions = [3, 4, 5, 6, 12];
final _distanceCustom = TextEditingController();
final _endCustom = TextEditingController();
final _arrowCustom = TextEditingController();
@override
void dispose() {
_distanceCustom.dispose();
_endCustom.dispose();
_arrowCustom.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('新建记分')),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
_SectionTitle('弓型'),
_ChipGroup(
options: BowType.values,
value: _bowType,
labelOf: _bowLabel,
onChanged: (v) => setState(() => _bowType = v),
),
const SizedBox(height: 16),
_SectionTitle('距离'),
_ChipGroup<int>(
options: _distances,
value: _distance,
labelOf: (v) => '${v}m',
onChanged: (v) => setState(() => _distance = v),
),
const SizedBox(height: 16),
_SectionTitle('靶纸类'),
_ChipGroup(
options: TargetFaceType.values,
value: _targetFace,
labelOf: _targetLabel,
onChanged: (v) => setState(() => _targetFace = v),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _NumberPicker(
label: '组数',
value: _endCount,
options: _endOptions,
onChanged: (v) => setState(() => _endCount = v),
),
final configAsync = ref.watch(pointBookConfigProvider);
return GestureDetector(
onTap: _unfocus,
behavior: HitTestBehavior.translucent,
child: Scaffold(
appBar: AppBar(title: const Text('选择参数')),
body: configAsync.when(
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('配置加载失败:$e')),
data: (config) {
_ensureDefaults(config);
final bow = config.bowById(_bowOptionId!);
final target = config.targetById(_targetOptionId!);
if (bow == null || target == null) {
return const Center(child: Text('配置无效'));
}
return ListView(
padding: const EdgeInsets.all(16),
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
children: [
const _SectionTitle(''),
_ChipGroup<int>(
options: config.bowOption.map((e) => e.id).toList(),
value: _bowOptionId!,
labelOf: (id) => config.bowById(id)?.name ?? '$id',
onChanged: (v) {
_unfocus();
setState(() => _bowOptionId = v);
},
),
const SizedBox(width: 12),
Expanded(
child: _NumberPicker(
label: '每组箭数',
value: _arrowsPerEnd,
options: _arrowOptions,
onChanged: (v) => setState(() => _arrowsPerEnd = v),
),
const SizedBox(height: 16),
const _SectionTitle('距离'),
_PresetWithCustom(
options: _distances,
value: _distance,
labelOf: (v) => '${v}m',
customController: _distanceCustom,
customHint: '自定义(m)',
min: 1,
max: 300,
onPreset: (v) {
_unfocus();
setState(() {
_distance = v;
_distanceCustom.clear();
});
},
onCustom: (v) => setState(() => _distance = v),
onInvalid: () => _toast('请输入 1300 的距离'),
),
],
),
const SizedBox(height: 32),
_Summary(
bowType: _bowType,
distance: _distance,
targetFace: _targetFace,
endCount: _endCount,
arrowsPerEnd: _arrowsPerEnd,
),
const SizedBox(height: 16),
FilledButton.icon(
onPressed: _creating ? null : _start,
icon: const Icon(Icons.play_arrow),
label: const Text('开始记分'),
),
],
const SizedBox(height: 16),
const _SectionTitle('靶纸类型'),
_ChipGroup<int>(
options: config.targetOption.map((e) => e.id).toList(),
value: _targetOptionId!,
labelOf: (id) => config.targetById(id)?.name ?? '$id',
onChanged: (v) {
_unfocus();
setState(() => _targetOptionId = v);
},
),
const SizedBox(height: 16),
const _SectionTitle('组数'),
_PresetWithCustom(
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('请输入 136 的组数'),
),
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('请输入 136 的箭数'),
),
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);
final draft = await ref
.read(pointRecordsProvider.notifier)
.createDraft(
bowType: _bowType,
targetFaceType: _targetFace,
final draft = await ref.read(pointRecordsProvider.notifier).createDraft(
bowOptionId: bow.id,
bowName: bow.name,
targetOptionId: target.id,
targetName: target.name,
distanceMeters: _distance,
endCount: _endCount,
arrowsPerEnd: _arrowsPerEnd,
@@ -164,84 +258,129 @@ class _ChipGroup<T> extends StatelessWidget {
}
}
class _NumberPicker extends StatelessWidget {
const _NumberPicker({
required this.label,
required this.value,
/// Preset chips plus a trailing custom number field.
class _PresetWithCustom extends StatelessWidget {
const _PresetWithCustom({
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 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
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.black12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(child: Text(label, style: const TextStyle(fontSize: 13))),
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.remove_circle_outline),
iconSize: 20,
visualDensity: VisualDensity.compact,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
onPressed: () {
final i = options.indexOf(value);
if (i > 0) onChanged(options[i - 1]);
},
return Wrap(
spacing: 8,
runSpacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
...options.map((o) {
final selected = _isPreset && o == value;
return ChoiceChip(
label: Text(labelOf(o)),
selected: selected,
onSelected: (_) => onPreset(o),
selectedColor: AppTheme.primary,
labelStyle: TextStyle(
color: selected ? Colors.white : Colors.black87,
),
);
}),
SizedBox(
width: 96,
height: 40,
child: TextField(
controller: customController,
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(
'$value',
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: BorderSide(
color: !_isPreset ? AppTheme.primary : Colors.black26,
width: !_isPreset ? 1.5 : 1,
),
),
IconButton(
icon: const Icon(Icons.add_circle_outline),
iconSize: 20,
visualDensity: VisualDensity.compact,
padding: EdgeInsets.zero,
constraints: const BoxConstraints(),
onPressed: () {
final i = options.indexOf(value);
if (i < options.length - 1) onChanged(options[i + 1]);
},
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: const BorderSide(
color: AppTheme.primary,
width: 1.5,
),
),
],
),
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 {
const _Summary({
required this.bowType,
required this.bowName,
required this.distance,
required this.targetFace,
required this.targetName,
required this.endCount,
required this.arrowsPerEnd,
});
final BowType bowType;
final String bowName;
final int distance;
final TargetFaceType targetFace;
final String targetName;
final int endCount;
final int arrowsPerEnd;
@@ -258,7 +397,7 @@ class _Summary extends StatelessWidget {
children: [
Expanded(
child: Text(
'${_bowLabel(bowType)} · ${distance}m · ${_targetLabel(targetFace)}',
'$bowName · ${distance}m · $targetName',
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_riverpod/flutter_riverpod.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:arcx/app/app_theme.dart';
import 'package:arcx/features/config/application/app_config_controller.dart';
@@ -46,6 +47,7 @@ class _PointBookEditPageState extends ConsumerState<PointBookEditPage> {
],
),
body: recordAsync.when(
skipLoadingOnReload: true,
loading: () => const Center(child: CircularProgressIndicator()),
error: (e, _) => Center(child: Text('加载失败:$e')),
data: (record) {
@@ -57,6 +59,11 @@ class _PointBookEditPageState extends ConsumerState<PointBookEditPage> {
_scores = List<int>.from(record.scores);
_hitPoints = List<HitPoint>.from(record.hitPoints);
_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);
},
@@ -81,61 +88,63 @@ class _PointBookEditPageState extends ConsumerState<PointBookEditPage> {
),
const SizedBox(height: 12),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Center(
child: AspectRatio(
aspectRatio: 1.0,
child: LayoutBuilder(
builder: (context, constraints) {
final size = constraints.maxWidth;
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTapUp: widget.readOnly || isComplete || _saving
? null
: (details) => _onTap(details, size, record),
child: Stack(
alignment: Alignment.center,
children: [
Image.asset(
'assets/images/target_face.png',
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Center(
child: AspectRatio(
aspectRatio: 1.0,
child: LayoutBuilder(
builder: (context, constraints) {
final size = constraints.maxWidth;
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTapUp: widget.readOnly || isComplete || _saving
? null
: (details) => _onTap(details, size, record),
child: Stack(
alignment: Alignment.center,
children: [
SvgPicture.asset(
'assets/images/target_face.svg',
width: size,
height: size,
fit: BoxFit.fill,
),
CustomPaint(
size: Size(size, size),
painter: HitPointPainter(
hitPoints: _hitPoints,
highlightIndex: _hitPoints.isEmpty
? null
: _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),
CustomPaint(
size: Size(size, size),
painter: HitPointPainter(
hitPoints: _hitPoints,
highlightIndex: _hitPoints.isEmpty
? null
: _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),
),
),
],
),
);
},
),
),
),
),
@@ -262,42 +271,44 @@ class _ProgressHeader extends StatelessWidget {
width: double.infinity,
color: AppTheme.primary.withValues(alpha: 0.06),
padding: const EdgeInsets.fromLTRB(16, 12, 16, 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
record.title,
style: const TextStyle(fontWeight: FontWeight.w600),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
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(
'$placed / $total',
style: const TextStyle(
fontWeight: FontWeight.w700,
color: AppTheme.primaryDark,
),
'${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),
),
],
),
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),
),
),
],
],
),
),
);
}
+136
View File
@@ -17,6 +17,22 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@@ -121,6 +137,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@@ -177,6 +201,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.7"
csslib:
dependency: transitive
description:
name: csslib
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
cupertino_icons:
dependency: "direct main"
description:
@@ -270,6 +302,14 @@ packages:
description: flutter
source: sdk
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:
dependency: "direct dev"
description:
@@ -278,6 +318,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@@ -294,6 +342,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct dev"
description: flutter
@@ -336,6 +392,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.2"
html:
dependency: transitive
description:
name: html
sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
url: "https://pub.dev"
source: hosted
version: "0.15.6"
http:
dependency: transitive
description:
@@ -360,6 +424,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.1.2"
image:
dependency: transitive
description:
name: image
sha256: "6300175e00616bbc832e2fc91bfa4d776af5402c81c7151bee6905bb08473c52"
url: "https://pub.dev"
source: hosted
version: "4.9.1"
image_picker:
dependency: "direct main"
description:
@@ -624,6 +696,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description:
@@ -672,6 +752,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675"
url: "https://pub.dev"
source: hosted
version: "7.0.2"
platform:
dependency: transitive
description:
@@ -696,6 +784,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.5.2"
posix:
dependency: transitive
description:
name: posix
sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e
url: "https://pub.dev"
source: hosted
version: "6.5.2"
pub_semver:
dependency: transitive
description:
@@ -845,6 +941,38 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@@ -901,6 +1029,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: "67f0aff7be013d107995e9b75bf4e7f2c3ef2dfdb2c8e68024bba0a7fd5756a4"
url: "https://pub.dev"
source: hosted
version: "7.0.1"
xxh3:
dependency: transitive
description:
+21
View File
@@ -17,6 +17,7 @@ dependencies:
isar_flutter_libs: 3.1.0+1
path: ^1.9.1
path_provider: ^2.1.5
flutter_svg: ^2.3.0
dev_dependencies:
flutter_test:
@@ -24,8 +25,28 @@ dev_dependencies:
flutter_lints: ^6.0.0
build_runner: ^2.4.7
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:
uses-material-design: true
assets:
- assets/images/
- assets/branding/
- assets/config/