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"?> <?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>
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"?> <?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>
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 <!-- 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
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; 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"
}
}
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" : [ "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
} }
} }
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"> <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>
+2
View File
@@ -70,5 +70,7 @@
<string>UIInterfaceOrientationLandscapeLeft</string> <string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>UIStatusBarHidden</key>
<false/>
</dict> </dict>
</plist> </plist>
+1 -1
View File
@@ -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;
+26 -31
View File
@@ -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
File diff suppressed because it is too large Load Diff
@@ -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,12 +79,7 @@ class _Bar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { 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( return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
Text( Text(
'$count', '$count',
@@ -94,14 +89,21 @@ class _Bar extends StatelessWidget {
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Container( Expanded(
width: double.infinity, child: Align(
height: h, alignment: Alignment.bottomCenter,
child: FractionallySizedBox(
heightFactor: ratio.clamp(0.04, 1.0),
widthFactor: 1,
child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
color: AppTheme.primary, color: AppTheme.primary,
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
), ),
),
),
),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
label, label,
@@ -112,7 +114,5 @@ class _Bar extends StatelessWidget {
), ),
], ],
); );
},
);
} }
} }
@@ -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,23 +16,32 @@ 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(
loading: () => const SizedBox(height: 64),
error: (e, _) => Padding(
padding: const EdgeInsets.all(16),
child: Text('配置加载失败:$e'),
),
data: (config) => _FilterBar(
config: config,
bowFilter: _bowFilter, bowFilter: _bowFilter,
targetFilter: _targetFilter, targetFilter: _targetFilter,
onBowChanged: (v) => setState(() => _bowFilter = v), onBowChanged: (v) => setState(() => _bowFilter = v),
onTargetChanged: (v) => setState(() => _targetFilter = v), onTargetChanged: (v) => setState(() => _targetFilter = v),
), ),
),
Expanded( Expanded(
child: recordsAsync.when( child: recordsAsync.when(
loading: () => const Center(child: CircularProgressIndicator()), loading: () => const Center(child: CircularProgressIndicator()),
@@ -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(
(e) => DropdownMenuItem<T>(
value: e, value: e,
child: Text(labelOf(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(
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), padding: const EdgeInsets.all(16),
keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag,
children: [ children: [
_SectionTitle('弓型'), const _SectionTitle('弓型'),
_ChipGroup( _ChipGroup<int>(
options: BowType.values, options: config.bowOption.map((e) => e.id).toList(),
value: _bowType, value: _bowOptionId!,
labelOf: _bowLabel, labelOf: (id) => config.bowById(id)?.name ?? '$id',
onChanged: (v) => setState(() => _bowType = v), onChanged: (v) {
_unfocus();
setState(() => _bowOptionId = v);
},
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_SectionTitle('距离'), const _SectionTitle('距离'),
_ChipGroup<int>( _PresetWithCustom(
options: _distances, options: _distances,
value: _distance, value: _distance,
labelOf: (v) => '${v}m', labelOf: (v) => '${v}m',
onChanged: (v) => setState(() => _distance = v), 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: 16), const SizedBox(height: 16),
_SectionTitle('靶纸类型'), const _SectionTitle('靶纸类型'),
_ChipGroup( _ChipGroup<int>(
options: TargetFaceType.values, options: config.targetOption.map((e) => e.id).toList(),
value: _targetFace, value: _targetOptionId!,
labelOf: _targetLabel, labelOf: (id) => config.targetById(id)?.name ?? '$id',
onChanged: (v) => setState(() => _targetFace = v), onChanged: (v) {
_unfocus();
setState(() => _targetOptionId = v);
},
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Row( const _SectionTitle('组数'),
children: [ _PresetWithCustom(
Expanded(
child: _NumberPicker(
label: '组数',
value: _endCount,
options: _endOptions, options: _endOptions,
onChanged: (v) => setState(() => _endCount = v), 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 SizedBox(width: 12), const _SectionTitle('每组箭数'),
Expanded( _PresetWithCustom(
child: _NumberPicker(
label: '每组箭数',
value: _arrowsPerEnd,
options: _arrowOptions, options: _arrowOptions,
onChanged: (v) => setState(() => _arrowsPerEnd = v), 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), const SizedBox(height: 32),
_Summary( _Summary(
bowType: _bowType, bowName: bow.name,
distance: _distance, distance: _distance,
targetFace: _targetFace, targetName: target.name,
endCount: _endCount, endCount: _endCount,
arrowsPerEnd: _arrowsPerEnd, arrowsPerEnd: _arrowsPerEnd,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
FilledButton.icon( FilledButton.icon(
onPressed: _creating ? null : _start, onPressed: _creating
? null
: () {
_unfocus();
_start(bow: bow, target: target);
},
icon: const Icon(Icons.play_arrow), icon: const Icon(Icons.play_arrow),
label: const Text('开始记分'), 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),
border: Border.all(color: Colors.black12),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Flexible(child: Text(label, style: const TextStyle(fontSize: 13))), ...options.map((o) {
Row( final selected = _isPreset && o == value;
mainAxisSize: MainAxisSize.min, return ChoiceChip(
children: [ label: Text(labelOf(o)),
IconButton( selected: selected,
icon: const Icon(Icons.remove_circle_outline), onSelected: (_) => onPreset(o),
iconSize: 20, selectedColor: AppTheme.primary,
visualDensity: VisualDensity.compact, labelStyle: TextStyle(
padding: EdgeInsets.zero, color: selected ? Colors.white : Colors.black87,
constraints: const BoxConstraints(), ),
onPressed: () { );
final i = options.indexOf(value); }),
if (i > 0) onChanged(options[i - 1]); 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,
),
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,
),
),
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) {
Text( if (raw.trim().isEmpty) return;
'$value', final n = int.tryParse(raw.trim());
style: const TextStyle( if (n != null && n >= min && n <= max) {
fontSize: 18, onCustom(n);
fontWeight: FontWeight.w700, }
),
),
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]);
}, },
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,6 +88,7 @@ class _PointBookEditPageState extends ConsumerState<PointBookEditPage> {
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Expanded( Expanded(
child: SingleChildScrollView(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Center( child: Center(
@@ -97,8 +105,8 @@ class _PointBookEditPageState extends ConsumerState<PointBookEditPage> {
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
children: [ children: [
Image.asset( SvgPicture.asset(
'assets/images/target_face.png', 'assets/images/target_face.svg',
width: size, width: size,
height: size, height: size,
fit: BoxFit.fill, fit: BoxFit.fill,
@@ -141,6 +149,7 @@ class _PointBookEditPageState extends ConsumerState<PointBookEditPage> {
), ),
), ),
), ),
),
const SizedBox(height: 12), const SizedBox(height: 12),
_EndSummary(record: record, scores: _scores), _EndSummary(record: record, scores: _scores),
if (!widget.readOnly) if (!widget.readOnly)
@@ -262,6 +271,7 @@ 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: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -299,6 +309,7 @@ class _ProgressHeader extends StatelessWidget {
), ),
], ],
), ),
),
); );
} }
} }
+136
View File
@@ -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:
+21
View File
@@ -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/