Initial commit: Coin scan demo with blur detection and coin contour cropping
- YOLOv5 TFLite coin detection with NMS - Blur classifier for image quality gating - Circular contour refinement and cropping - Camera capture and gallery pick Co-authored-by: Cursor <cursoragent@cursor.com>
46
.gitignore
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Android Studio will place build artifacts here
|
||||||
|
/android/app/debug
|
||||||
|
/android/app/profile
|
||||||
|
/android/app/release
|
||||||
|
|
||||||
|
# Native phash host build (CLI / hashgen)
|
||||||
|
/native/phash/build/
|
||||||
33
.metadata
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: "5dcb86f68f239346676ceb1ed1ea385bd215fba1"
|
||||||
|
channel: "stable"
|
||||||
|
|
||||||
|
project_type: app
|
||||||
|
|
||||||
|
# Tracks metadata for the flutter migrate command
|
||||||
|
migration:
|
||||||
|
platforms:
|
||||||
|
- platform: root
|
||||||
|
create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||||
|
base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||||
|
- platform: android
|
||||||
|
create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||||
|
base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||||
|
- platform: ios
|
||||||
|
create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||||
|
base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1
|
||||||
|
|
||||||
|
# User provided section
|
||||||
|
|
||||||
|
# List of Local paths (relative to this file) that should be
|
||||||
|
# ignored by the migrate tool.
|
||||||
|
#
|
||||||
|
# Files that are not part of the templates will be ignored by default.
|
||||||
|
unmanaged_files:
|
||||||
|
- 'lib/main.dart'
|
||||||
|
- 'ios/Runner.xcodeproj/project.pbxproj'
|
||||||
23
README.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Coin Scan Demo
|
||||||
|
|
||||||
|
对照 CoinSnap 反编译:`detect.tflite`(YOLOv5 硬币检测)+ `blur.tflite`(模糊度二分类)。
|
||||||
|
|
||||||
|
流程对齐 card-dex-demo,但换成硬币圆框与双模型:
|
||||||
|
|
||||||
|
拍照 / 选图 → **blur 清晰度检测** → **detect 目标框** → **圆形轮廓精修** → 圆形裁剪预览。
|
||||||
|
|
||||||
|
模型来自 CoinSnap APK `assets/`:
|
||||||
|
|
||||||
|
| 模型 | 输入 | 输出 |
|
||||||
|
|------|------|------|
|
||||||
|
| `detect.tflite` | `[1,320,320,3]` NHWC, `/255`, letterbox | `[1,6300,6]` → cx,cy,w,h,obj,cls |
|
||||||
|
| `blur.tflite` | `[1,3,224,224]` NCHW, `/255`, 白底 letterbox | `[1,2]` → `[blur, clear]` |
|
||||||
|
|
||||||
|
## 运行
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export PATH="$HOME/flutter/bin:$PATH"
|
||||||
|
cd /Users/a1518/Desktop/project/coin-dex-demo
|
||||||
|
flutter pub get
|
||||||
|
flutter run
|
||||||
|
```
|
||||||
28
analysis_options.yaml
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# This file configures the analyzer, which statically analyzes Dart code to
|
||||||
|
# check for errors, warnings, and lints.
|
||||||
|
#
|
||||||
|
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
|
||||||
|
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
|
||||||
|
# invoked from the command line by running `flutter analyze`.
|
||||||
|
|
||||||
|
# The following line activates a set of recommended lints for Flutter apps,
|
||||||
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
|
||||||
|
linter:
|
||||||
|
# The lint rules applied to this project can be customized in the
|
||||||
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
# included above or to enable additional rules. A list of all available lints
|
||||||
|
# and their documentation is published at https://dart.dev/lints.
|
||||||
|
#
|
||||||
|
# Instead of disabling a lint rule for the entire project in the
|
||||||
|
# section below, it can also be suppressed for a single line of code
|
||||||
|
# or a specific dart file by using the `// ignore: name_of_lint` and
|
||||||
|
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
||||||
|
# producing the lint.
|
||||||
|
rules:
|
||||||
|
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||||
|
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||||
|
|
||||||
|
# Additional information about this file can be found at
|
||||||
|
# https://dart.dev/guides/language/analysis-options
|
||||||
13
android/.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||||
|
key.properties
|
||||||
|
**/*.keystore
|
||||||
|
**/*.jks
|
||||||
58
android/app/build.gradle
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
plugins {
|
||||||
|
id "com.android.application"
|
||||||
|
id "kotlin-android"
|
||||||
|
id "dev.flutter.flutter-gradle-plugin"
|
||||||
|
}
|
||||||
|
|
||||||
|
def localProperties = new Properties()
|
||||||
|
def localPropertiesFile = rootProject.file("local.properties")
|
||||||
|
if (localPropertiesFile.exists()) {
|
||||||
|
localPropertiesFile.withReader("UTF-8") { reader ->
|
||||||
|
localProperties.load(reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionCode = localProperties.getProperty("flutter.versionCode")
|
||||||
|
if (flutterVersionCode == null) {
|
||||||
|
flutterVersionCode = "1"
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionName = localProperties.getProperty("flutter.versionName")
|
||||||
|
if (flutterVersionName == null) {
|
||||||
|
flutterVersionName = "1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.coindex.coin_dex_demo"
|
||||||
|
compileSdk = 35
|
||||||
|
ndkVersion "27.0.12077973"
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = "17"
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.coindex.coin_dex_demo"
|
||||||
|
minSdk = 26
|
||||||
|
targetSdk = 35
|
||||||
|
versionCode = flutterVersionCode.toInteger()
|
||||||
|
versionName = flutterVersionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
signingConfig = signingConfigs.debug
|
||||||
|
minifyEnabled false
|
||||||
|
shrinkResources false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source = "../.."
|
||||||
|
}
|
||||||
7
android/app/src/debug/AndroidManifest.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
39
android/app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.CAMERA" />
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||||
|
<uses-feature android:name="android.hardware.camera" android:required="true" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:label="Coin Scan Demo"
|
||||||
|
android:name="${applicationName}"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:taskAffinity=""
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2" />
|
||||||
|
</application>
|
||||||
|
<queries>
|
||||||
|
<intent>
|
||||||
|
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||||
|
<data android:mimeType="text/plain"/>
|
||||||
|
</intent>
|
||||||
|
</queries>
|
||||||
|
</manifest>
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package com.coindex.coin_dex_demo
|
||||||
|
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
|
||||||
|
class MainActivity: FlutterActivity()
|
||||||
12
android/app/src/main/res/drawable-v21/launch_background.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?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> -->
|
||||||
|
</layer-list>
|
||||||
12
android/app/src/main/res/drawable/launch_background.xml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<?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> -->
|
||||||
|
</layer-list>
|
||||||
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 544 B |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 442 B |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 721 B |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
18
android/app/src/main/res/values-night/styles.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?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">
|
||||||
|
<!-- 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>
|
||||||
|
</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>
|
||||||
18
android/app/src/main/res/values/styles.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?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">
|
||||||
|
<!-- 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>
|
||||||
|
</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>
|
||||||
7
android/app/src/profile/AndroidManifest.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<!-- The INTERNET permission is required for development. Specifically,
|
||||||
|
the Flutter tool needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
||||||
18
android/build.gradle
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.buildDir = "../build"
|
||||||
|
subprojects {
|
||||||
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
|
}
|
||||||
|
subprojects {
|
||||||
|
project.evaluationDependsOn(":app")
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.register("clean", Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
||||||
3
android/gradle.properties
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
5
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||||
25
android/settings.gradle
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
pluginManagement {
|
||||||
|
def flutterSdkPath = {
|
||||||
|
def properties = new Properties()
|
||||||
|
file("local.properties").withInputStream { properties.load(it) }
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
return flutterSdkPath
|
||||||
|
}()
|
||||||
|
|
||||||
|
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||||
|
id "com.android.application" version "8.7.0" apply false
|
||||||
|
id "org.jetbrains.kotlin.android" version "2.0.21" apply false
|
||||||
|
}
|
||||||
|
|
||||||
|
include ":app"
|
||||||
BIN
assets/ml/blur.tflite
Normal file
BIN
assets/ml/detect.tflite
Normal file
34
ios/.gitignore
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
**/dgph
|
||||||
|
*.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
*.moved-aside
|
||||||
|
*.pbxuser
|
||||||
|
*.perspectivev3
|
||||||
|
**/*sync/
|
||||||
|
.sconsign.dblite
|
||||||
|
.tags*
|
||||||
|
**/.vagrant/
|
||||||
|
**/DerivedData/
|
||||||
|
Icon?
|
||||||
|
**/Pods/
|
||||||
|
**/.symlinks/
|
||||||
|
profile
|
||||||
|
xcuserdata
|
||||||
|
**/.generated/
|
||||||
|
Flutter/App.framework
|
||||||
|
Flutter/Flutter.framework
|
||||||
|
Flutter/Flutter.podspec
|
||||||
|
Flutter/Generated.xcconfig
|
||||||
|
Flutter/ephemeral/
|
||||||
|
Flutter/app.flx
|
||||||
|
Flutter/app.zip
|
||||||
|
Flutter/flutter_assets/
|
||||||
|
Flutter/flutter_export_environment.sh
|
||||||
|
ServiceDefinitions.json
|
||||||
|
Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!default.mode1v3
|
||||||
|
!default.mode2v3
|
||||||
|
!default.pbxuser
|
||||||
|
!default.perspectivev3
|
||||||
26
ios/Flutter/AppFrameworkInfo.plist
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>io.flutter.flutter.app</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>MinimumOSVersion</key>
|
||||||
|
<string>12.0</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
2
ios/Flutter/Debug.xcconfig
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
2
ios/Flutter/Release.xcconfig
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
||||||
42
ios/Podfile
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
platform :ios, '12.0'
|
||||||
|
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutter_root
|
||||||
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||||
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
|
raise "#{generated_xcode_build_settings_path} must exist. Run flutter pub get first"
|
||||||
|
end
|
||||||
|
|
||||||
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
|
return matches[1].strip if matches
|
||||||
|
end
|
||||||
|
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}"
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_ios_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
use_modular_headers!
|
||||||
|
|
||||||
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_ios_build_settings(target)
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
616
ios/Runner.xcodeproj/project.pbxproj
Normal file
@ -0,0 +1,616 @@
|
|||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 54;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXContainerItemProxy section */
|
||||||
|
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
|
||||||
|
isa = PBXContainerItemProxy;
|
||||||
|
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
proxyType = 1;
|
||||||
|
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
|
||||||
|
remoteInfo = Runner;
|
||||||
|
};
|
||||||
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Embed Frameworks";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
331C807B294A618700263BE5 /* RunnerTests.swift */,
|
||||||
|
);
|
||||||
|
path = RunnerTests;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146E51CF9000F007C117D = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
|
331C8082294A63A400263BE5 /* RunnerTests */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146EF1CF9000F007C117D /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
|
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */,
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
|
);
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
|
||||||
|
buildPhases = (
|
||||||
|
331C807D294A63A400263BE5 /* Sources */,
|
||||||
|
331C807F294A63A400263BE5 /* Resources */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */,
|
||||||
|
);
|
||||||
|
name = RunnerTests;
|
||||||
|
productName = RunnerTests;
|
||||||
|
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
|
||||||
|
productType = "com.apple.product-type.bundle.unit-test";
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
|
buildPhases = (
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Runner;
|
||||||
|
productName = Runner;
|
||||||
|
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
BuildIndependentTargetsInParallel = YES;
|
||||||
|
LastUpgradeCheck = 1510;
|
||||||
|
ORGANIZATIONNAME = "";
|
||||||
|
TargetAttributes = {
|
||||||
|
331C8080294A63A400263BE5 = {
|
||||||
|
CreatedOnToolsVersion = 14.0;
|
||||||
|
TestTargetID = 97C146ED1CF9000F007C117D;
|
||||||
|
};
|
||||||
|
97C146ED1CF9000F007C117D = {
|
||||||
|
CreatedOnToolsVersion = 7.3.1;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
|
331C8080294A63A400263BE5 /* RunnerTests */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
331C807F294A63A400263BE5 /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
|
||||||
|
);
|
||||||
|
name = "Thin Binary";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
|
};
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Run Script";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
331C807D294A63A400263BE5 /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXTargetDependency section */
|
||||||
|
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
|
||||||
|
isa = PBXTargetDependency;
|
||||||
|
target = 97C146ED1CF9000F007C117D /* Runner */;
|
||||||
|
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
|
||||||
|
};
|
||||||
|
/* End PBXTargetDependency section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C146FB1CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = Main.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C147001CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = LaunchScreen.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.coindex.coinDexDemo;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
331C8088294A63A400263BE5 /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.coindex.coinDexDemo.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
331C8089294A63A400263BE5 /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.coindex.coinDexDemo.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
331C808A294A63A400263BE5 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||||
|
CODE_SIGN_STYLE = Automatic;
|
||||||
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
|
MARKETING_VERSION = 1.0;
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.coindex.coinDexDemo.RunnerTests;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
97C147031CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147041CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_USER_SCRIPT_SANDBOXING = NO;
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
97C147061CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.coindex.coinDexDemo;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147071CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = com.coindex.coinDexDemo;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
331C8088294A63A400263BE5 /* Debug */,
|
||||||
|
331C8089294A63A400263BE5 /* Release */,
|
||||||
|
331C808A294A63A400263BE5 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147031CF9000F007C117D /* Debug */,
|
||||||
|
97C147041CF9000F007C117D /* Release */,
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147061CF9000F007C117D /* Debug */,
|
||||||
|
97C147071CF9000F007C117D /* Release */,
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
}
|
||||||
7
ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
98
ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1510"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<Testables>
|
||||||
|
<TestableReference
|
||||||
|
skipped = "NO"
|
||||||
|
parallelizable = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "331C8080294A63A400263BE5"
|
||||||
|
BuildableName = "RunnerTests.xctest"
|
||||||
|
BlueprintName = "RunnerTests"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</TestableReference>
|
||||||
|
</Testables>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
||||||
7
ios/Runner.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
13
ios/Runner/AppDelegate.swift
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
@UIApplicationMain
|
||||||
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
|
) -> Bool {
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
122
ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 295 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 450 B |
|
After Width: | Height: | Size: 282 B |
|
After Width: | Height: | Size: 462 B |
|
After Width: | Height: | Size: 704 B |
|
After Width: | Height: | Size: 406 B |
|
After Width: | Height: | Size: 586 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 862 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 762 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
23
ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
BIN
ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
vendored
Normal file
|
After Width: | Height: | Size: 68 B |
5
ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# Launch Screen Assets
|
||||||
|
|
||||||
|
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
|
||||||
|
|
||||||
|
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
|
||||||
37
ios/Runner/Base.lproj/LaunchScreen.storyboard
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--View Controller-->
|
||||||
|
<scene sceneID="EHf-IW-A2E">
|
||||||
|
<objects>
|
||||||
|
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||||
|
<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>
|
||||||
|
</subviews>
|
||||||
|
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||||
|
<constraints>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
|
||||||
|
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
|
||||||
|
</constraints>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
<point key="canvasLocation" x="53" y="375"/>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
<resources>
|
||||||
|
<image name="LaunchImage" width="168" height="185"/>
|
||||||
|
</resources>
|
||||||
|
</document>
|
||||||
26
ios/Runner/Base.lproj/Main.storyboard
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
|
||||||
|
<dependencies>
|
||||||
|
<deployment identifier="iOS"/>
|
||||||
|
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
|
||||||
|
</dependencies>
|
||||||
|
<scenes>
|
||||||
|
<!--Flutter View Controller-->
|
||||||
|
<scene sceneID="tne-QT-ifu">
|
||||||
|
<objects>
|
||||||
|
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
|
||||||
|
<layoutGuides>
|
||||||
|
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
|
||||||
|
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
|
||||||
|
</layoutGuides>
|
||||||
|
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
|
||||||
|
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||||
|
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||||
|
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||||
|
</view>
|
||||||
|
</viewController>
|
||||||
|
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
|
||||||
|
</objects>
|
||||||
|
</scene>
|
||||||
|
</scenes>
|
||||||
|
</document>
|
||||||
53
ios/Runner/Info.plist
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>Coin Scan Demo</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>coin_dex_demo</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||||
|
<key>LSRequiresIPhoneOS</key>
|
||||||
|
<true/>
|
||||||
|
<key>UILaunchStoryboardName</key>
|
||||||
|
<string>LaunchScreen</string>
|
||||||
|
<key>UIMainStoryboardFile</key>
|
||||||
|
<string>Main</string>
|
||||||
|
<key>UISupportedInterfaceOrientations</key>
|
||||||
|
<array>
|
||||||
|
<string>UIInterfaceOrientationPortrait</string>
|
||||||
|
<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>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>Camera access is required to scan and crop coins.</string>
|
||||||
|
<key>NSPhotoLibraryUsageDescription</key>
|
||||||
|
<string>Photo library access is required to pick a coin image.</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
1
ios/Runner/Runner-Bridging-Header.h
Normal file
@ -0,0 +1 @@
|
|||||||
|
#import "GeneratedPluginRegistrant.h"
|
||||||
12
ios/RunnerTests/RunnerTests.swift
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import Flutter
|
||||||
|
import UIKit
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
class RunnerTests: XCTestCase {
|
||||||
|
|
||||||
|
func testExample() {
|
||||||
|
// If you add code to the Runner application, consider adding tests here.
|
||||||
|
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
34
lib/main.dart
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
import 'screens/capture_screen.dart';
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await SystemChrome.setPreferredOrientations([
|
||||||
|
DeviceOrientation.portraitUp,
|
||||||
|
]);
|
||||||
|
runApp(const CoinScanDemoApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
class CoinScanDemoApp extends StatelessWidget {
|
||||||
|
const CoinScanDemoApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
title: 'Coin Scan Demo',
|
||||||
|
debugShowCheckedModeBanner: false,
|
||||||
|
theme: ThemeData(
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
colorScheme: ColorScheme.fromSeed(
|
||||||
|
seedColor: const Color(0xFFEAB308),
|
||||||
|
brightness: Brightness.dark,
|
||||||
|
),
|
||||||
|
useMaterial3: true,
|
||||||
|
scaffoldBackgroundColor: const Color(0xFF0B0C10),
|
||||||
|
),
|
||||||
|
home: const CaptureScreen(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
323
lib/screens/capture_screen.dart
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
import 'package:camera/camera.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
|
||||||
|
import '../services/coin_scan_gate.dart';
|
||||||
|
import '../widgets/guide_overlay.dart';
|
||||||
|
import 'preview_screen.dart';
|
||||||
|
|
||||||
|
/// Capture only — blur + detect run after shutter (CoinSnap flow).
|
||||||
|
class CaptureScreen extends StatefulWidget {
|
||||||
|
const CaptureScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<CaptureScreen> createState() => _CaptureScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CaptureScreenState extends State<CaptureScreen>
|
||||||
|
with WidgetsBindingObserver {
|
||||||
|
final _picker = ImagePicker();
|
||||||
|
|
||||||
|
CameraController? _camera;
|
||||||
|
List<CameraDescription> _cameras = const [];
|
||||||
|
bool _ready = false;
|
||||||
|
bool _busy = false;
|
||||||
|
String? _error;
|
||||||
|
Size? _previewSize;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addObserver(this);
|
||||||
|
_initCamera();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
_camera?.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||||
|
final cam = _camera;
|
||||||
|
if (cam == null || !cam.value.isInitialized) return;
|
||||||
|
if (state == AppLifecycleState.inactive) {
|
||||||
|
cam.dispose();
|
||||||
|
_camera = null;
|
||||||
|
_ready = false;
|
||||||
|
} else if (state == AppLifecycleState.resumed) {
|
||||||
|
_initCamera();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initCamera() async {
|
||||||
|
try {
|
||||||
|
_cameras = await availableCameras();
|
||||||
|
if (_cameras.isEmpty) {
|
||||||
|
setState(() => _error = '没有可用相机');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final back = _cameras.firstWhere(
|
||||||
|
(c) => c.lensDirection == CameraLensDirection.back,
|
||||||
|
orElse: () => _cameras.first,
|
||||||
|
);
|
||||||
|
final controller = CameraController(
|
||||||
|
back,
|
||||||
|
ResolutionPreset.high,
|
||||||
|
enableAudio: false,
|
||||||
|
);
|
||||||
|
await controller.initialize();
|
||||||
|
if (!mounted) {
|
||||||
|
await controller.dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_camera = controller;
|
||||||
|
setState(() {
|
||||||
|
_ready = true;
|
||||||
|
_error = null;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
setState(() => _error = '相机初始化失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _openPreviewIfOk(String path) async {
|
||||||
|
final result = await CoinScanGate.evaluate(
|
||||||
|
imagePath: path,
|
||||||
|
captureViewport: _previewSize,
|
||||||
|
);
|
||||||
|
if (!mounted) return;
|
||||||
|
if (!result.ok) {
|
||||||
|
final msg = switch (result.fail) {
|
||||||
|
CoinGateFail.blurry =>
|
||||||
|
'图像过模糊(清晰度 ${(result.blur!.clearScore * 100).toStringAsFixed(0)}%),请重拍',
|
||||||
|
CoinGateFail.noCoin => '未检测到硬币,请对准圆框后重拍',
|
||||||
|
CoinGateFail.none => '无法处理该图片',
|
||||||
|
};
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text(msg), duration: const Duration(seconds: 2)),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (_) => PreviewScreen(
|
||||||
|
imagePath: path,
|
||||||
|
captureViewport: _previewSize,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _capture() async {
|
||||||
|
if (_busy) return;
|
||||||
|
final cam = _camera;
|
||||||
|
if (cam == null || !cam.value.isInitialized) return;
|
||||||
|
|
||||||
|
setState(() => _busy = true);
|
||||||
|
try {
|
||||||
|
final shot = await cam.takePicture();
|
||||||
|
await _openPreviewIfOk(shot.path);
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('拍照失败: $e')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mounted) setState(() => _busy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _pickGallery() async {
|
||||||
|
if (_busy) return;
|
||||||
|
setState(() => _busy = true);
|
||||||
|
try {
|
||||||
|
final file = await _picker.pickImage(
|
||||||
|
source: ImageSource.gallery,
|
||||||
|
imageQuality: 100,
|
||||||
|
requestFullMetadata: true,
|
||||||
|
);
|
||||||
|
if (file == null) return;
|
||||||
|
await _openPreviewIfOk(file.path);
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text('选图失败: $e')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mounted) setState(() => _busy = false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _switchCamera() async {
|
||||||
|
if (_cameras.length < 2 || _busy) return;
|
||||||
|
final current = _camera?.description;
|
||||||
|
final next = _cameras.firstWhere(
|
||||||
|
(c) => c.lensDirection != current?.lensDirection,
|
||||||
|
orElse: () => _cameras.first,
|
||||||
|
);
|
||||||
|
setState(() => _ready = false);
|
||||||
|
await _camera?.dispose();
|
||||||
|
final controller = CameraController(
|
||||||
|
next,
|
||||||
|
ResolutionPreset.high,
|
||||||
|
enableAudio: false,
|
||||||
|
);
|
||||||
|
await controller.initialize();
|
||||||
|
if (!mounted) {
|
||||||
|
await controller.dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_camera = controller;
|
||||||
|
setState(() => _ready = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
_buildPreview(),
|
||||||
|
SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 16, 20, 0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'硬币扫描',
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall?.copyWith(
|
||||||
|
fontWeight: FontWeight.w700,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'将硬币放入圆框 → blur 清晰度检测 → detect 轮廓锁定',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Colors.white70,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (_busy)
|
||||||
|
const ColoredBox(
|
||||||
|
color: Color(0x66000000),
|
||||||
|
child: Center(child: CircularProgressIndicator()),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: _buildControls(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildPreview() {
|
||||||
|
if (_error != null) {
|
||||||
|
return Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(24),
|
||||||
|
child: Text(_error!, textAlign: TextAlign.center),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final cam = _camera;
|
||||||
|
if (!_ready || cam == null || !cam.value.isInitialized) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final previewSize = Size(constraints.maxWidth, constraints.maxHeight);
|
||||||
|
_previewSize = previewSize;
|
||||||
|
return Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
FittedBox(
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
child: SizedBox(
|
||||||
|
width: cam.value.previewSize?.height ?? previewSize.width,
|
||||||
|
height: cam.value.previewSize?.width ?? previewSize.height,
|
||||||
|
child: CameraPreview(cam),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GuideOverlay(size: previewSize),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildControls() {
|
||||||
|
return SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(28, 12, 28, 24),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
_RoundButton(
|
||||||
|
icon: Icons.photo_library_outlined,
|
||||||
|
onTap: _busy ? null : _pickGallery,
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: _busy ? null : _capture,
|
||||||
|
child: Container(
|
||||||
|
width: 76,
|
||||||
|
height: 76,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
border: Border.all(color: Colors.white, width: 4),
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(5),
|
||||||
|
child: Container(
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_RoundButton(
|
||||||
|
icon: Icons.flip_camera_ios_outlined,
|
||||||
|
onTap: _busy ? null : _switchCamera,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RoundButton extends StatelessWidget {
|
||||||
|
const _RoundButton({required this.icon, this.onTap});
|
||||||
|
|
||||||
|
final IconData icon;
|
||||||
|
final VoidCallback? onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Material(
|
||||||
|
color: Colors.white12,
|
||||||
|
shape: const CircleBorder(),
|
||||||
|
child: InkWell(
|
||||||
|
customBorder: const CircleBorder(),
|
||||||
|
onTap: onTap,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 52,
|
||||||
|
height: 52,
|
||||||
|
child: Icon(icon, color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
377
lib/screens/preview_screen.dart
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:image/image.dart' as img;
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
import '../services/blur_detector.dart';
|
||||||
|
import '../services/coin_cropper.dart';
|
||||||
|
import '../services/coin_detector.dart';
|
||||||
|
import '../widgets/guide_frame.dart';
|
||||||
|
|
||||||
|
/// Post-capture: blur check → detect → contour refine → circular crop.
|
||||||
|
class PreviewScreen extends StatefulWidget {
|
||||||
|
const PreviewScreen({
|
||||||
|
super.key,
|
||||||
|
required this.imagePath,
|
||||||
|
this.captureViewport,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String imagePath;
|
||||||
|
final Size? captureViewport;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<PreviewScreen> createState() => _PreviewScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PreviewScreenState extends State<PreviewScreen> {
|
||||||
|
final _detector = CoinDetector();
|
||||||
|
final _blur = BlurDetector();
|
||||||
|
|
||||||
|
bool _loading = true;
|
||||||
|
String? _error;
|
||||||
|
ui.Image? _sourceImage;
|
||||||
|
CoinBox? _locked;
|
||||||
|
CropResult? _crop;
|
||||||
|
BlurResult? _blurResult;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_sourceImage?.dispose();
|
||||||
|
_detector.dispose();
|
||||||
|
_blur.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _run() async {
|
||||||
|
try {
|
||||||
|
final raw = await File(widget.imagePath).readAsBytes();
|
||||||
|
final displayCodec = await ui.instantiateImageCodec(raw);
|
||||||
|
final displayFrame = await displayCodec.getNextFrame();
|
||||||
|
final fullImage = displayFrame.image;
|
||||||
|
|
||||||
|
final rgba = await fullImage.toByteData(format: ui.ImageByteFormat.rawRgba);
|
||||||
|
if (rgba == null) {
|
||||||
|
fullImage.dispose();
|
||||||
|
throw StateError('无法读取像素');
|
||||||
|
}
|
||||||
|
|
||||||
|
final fullRaster = img.Image.fromBytes(
|
||||||
|
width: fullImage.width,
|
||||||
|
height: fullImage.height,
|
||||||
|
bytes: rgba.buffer,
|
||||||
|
bytesOffset: rgba.offsetInBytes,
|
||||||
|
numChannels: 4,
|
||||||
|
order: img.ChannelOrder.rgba,
|
||||||
|
);
|
||||||
|
fullImage.dispose();
|
||||||
|
|
||||||
|
final imageSize = Size(
|
||||||
|
fullRaster.width.toDouble(),
|
||||||
|
fullRaster.height.toDouble(),
|
||||||
|
);
|
||||||
|
final guide = widget.captureViewport != null
|
||||||
|
? GuideFrame.inCoverImage(
|
||||||
|
viewport: widget.captureViewport!,
|
||||||
|
imageSize: imageSize,
|
||||||
|
)
|
||||||
|
: GuideFrame.inImage(imageSize);
|
||||||
|
|
||||||
|
final gx = guide.left.round().clamp(0, fullRaster.width - 1);
|
||||||
|
final gy = guide.top.round().clamp(0, fullRaster.height - 1);
|
||||||
|
final gw = guide.width.round().clamp(1, fullRaster.width - gx);
|
||||||
|
final gh = guide.height.round().clamp(1, fullRaster.height - gy);
|
||||||
|
final raster = img.copyCrop(
|
||||||
|
fullRaster,
|
||||||
|
x: gx,
|
||||||
|
y: gy,
|
||||||
|
width: gw,
|
||||||
|
height: gh,
|
||||||
|
);
|
||||||
|
|
||||||
|
final blurResult = await _blur.evaluate(raster);
|
||||||
|
if (blurResult.isBlurry) {
|
||||||
|
if (!mounted) return;
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(
|
||||||
|
'图像过模糊(清晰 ${(blurResult.clearScore * 100).toStringAsFixed(0)}%),请重拍',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final dir = await getTemporaryDirectory();
|
||||||
|
final guidePath =
|
||||||
|
'${dir.path}/coin_guide_${DateTime.now().millisecondsSinceEpoch}.jpg';
|
||||||
|
await File(guidePath).writeAsBytes(
|
||||||
|
img.encodeJpg(raster, quality: 100),
|
||||||
|
flush: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
final guideBytes = img.encodeJpg(raster, quality: 100);
|
||||||
|
final guideCodec = await ui.instantiateImageCodec(guideBytes);
|
||||||
|
final guideFrame = await guideCodec.getNextFrame();
|
||||||
|
final displayImage = guideFrame.image;
|
||||||
|
|
||||||
|
final boxes = await _detector.detectImage(raster);
|
||||||
|
final guideSize = Size(
|
||||||
|
displayImage.width.toDouble(),
|
||||||
|
displayImage.height.toDouble(),
|
||||||
|
);
|
||||||
|
var best = _detector.pickBest(boxes, imageSize: guideSize);
|
||||||
|
if (best == null) {
|
||||||
|
displayImage.dispose();
|
||||||
|
if (!mounted) return;
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(content: Text('未检测到硬币,请重拍')),
|
||||||
|
);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
best = _detector.refineContour(raster, best);
|
||||||
|
|
||||||
|
final crop = await CoinCropper.cropAndCorrect(
|
||||||
|
imagePath: guidePath,
|
||||||
|
box: best,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!mounted) {
|
||||||
|
displayImage.dispose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_sourceImage = displayImage;
|
||||||
|
_locked = best;
|
||||||
|
_crop = crop;
|
||||||
|
_blurResult = blurResult;
|
||||||
|
_loading = false;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (!mounted) return;
|
||||||
|
setState(() {
|
||||||
|
_error = '处理失败: $e';
|
||||||
|
_loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('硬币轮廓锁定'),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
),
|
||||||
|
body: SafeArea(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(20, 8, 20, 24),
|
||||||
|
child: _loading
|
||||||
|
? const Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
CircularProgressIndicator(),
|
||||||
|
SizedBox(height: 16),
|
||||||
|
Text('blur → detect → 轮廓精修…'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: _error != null
|
||||||
|
? Center(child: Text(_error!, textAlign: TextAlign.center))
|
||||||
|
: _buildContent(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildContent(BuildContext context) {
|
||||||
|
final blur = _blurResult;
|
||||||
|
final locked = _locked;
|
||||||
|
final meta = [
|
||||||
|
if (blur != null)
|
||||||
|
'清晰度 ${(blur.clearScore * 100).toStringAsFixed(0)}%',
|
||||||
|
if (locked != null)
|
||||||
|
'检测 ${(locked.confidence * 100).toStringAsFixed(0)}%',
|
||||||
|
if (locked?.radius != null)
|
||||||
|
'半径 ${locked!.radius!.round()}px',
|
||||||
|
].join(' · ');
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
meta,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Colors.white70,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
Expanded(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 260,
|
||||||
|
child: _LockedPhotoView(
|
||||||
|
image: _sourceImage!,
|
||||||
|
locked: _locked,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
Text('圆形裁剪', style: Theme.of(context).textTheme.titleSmall),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: Center(
|
||||||
|
child: ClipOval(
|
||||||
|
child: ColoredBox(
|
||||||
|
color: Colors.black26,
|
||||||
|
child: Image.memory(
|
||||||
|
_crop!.bytes,
|
||||||
|
width: 180,
|
||||||
|
height: 180,
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
filterQuality: FilterQuality.high,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
icon: const Icon(Icons.camera_alt_outlined),
|
||||||
|
label: const Text('重新拍摄'),
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
minimumSize: const Size.fromHeight(48),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LockedPhotoView extends StatelessWidget {
|
||||||
|
const _LockedPhotoView({required this.image, required this.locked});
|
||||||
|
|
||||||
|
final ui.Image image;
|
||||||
|
final CoinBox? locked;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
|
final view = Size(constraints.maxWidth, constraints.maxHeight);
|
||||||
|
final imgSize = Size(image.width.toDouble(), image.height.toDouble());
|
||||||
|
final fitted = _contain(imgSize, view);
|
||||||
|
|
||||||
|
return ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
child: ColoredBox(
|
||||||
|
color: Colors.black26,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fromRect(
|
||||||
|
rect: fitted,
|
||||||
|
child: RawImage(
|
||||||
|
image: image,
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
filterQuality: FilterQuality.high,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (locked != null)
|
||||||
|
Positioned.fill(
|
||||||
|
child: CustomPaint(
|
||||||
|
painter: _LockPainter(
|
||||||
|
box: locked!,
|
||||||
|
imageSize: imgSize,
|
||||||
|
fitted: fitted,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rect _contain(Size image, Size view) {
|
||||||
|
final scale = (view.width / image.width < view.height / image.height)
|
||||||
|
? view.width / image.width
|
||||||
|
: view.height / image.height;
|
||||||
|
final w = image.width * scale;
|
||||||
|
final h = image.height * scale;
|
||||||
|
return Rect.fromLTWH(
|
||||||
|
(view.width - w) / 2,
|
||||||
|
(view.height - h) / 2,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LockPainter extends CustomPainter {
|
||||||
|
_LockPainter({
|
||||||
|
required this.box,
|
||||||
|
required this.imageSize,
|
||||||
|
required this.fitted,
|
||||||
|
});
|
||||||
|
|
||||||
|
final CoinBox box;
|
||||||
|
final Size imageSize;
|
||||||
|
final Rect fitted;
|
||||||
|
|
||||||
|
Offset _map(Offset p) => Offset(
|
||||||
|
fitted.left + p.dx / imageSize.width * fitted.width,
|
||||||
|
fitted.top + p.dy / imageSize.height * fitted.height,
|
||||||
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final c = _map(box.effectiveCenter);
|
||||||
|
final r = box.effectiveRadius / imageSize.width * fitted.width;
|
||||||
|
|
||||||
|
final ring = Paint()
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 3
|
||||||
|
..color = const Color(0xFFEAB308);
|
||||||
|
canvas.drawCircle(c, r, ring);
|
||||||
|
|
||||||
|
final aabb = Paint()
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 1.2
|
||||||
|
..color = const Color(0x66EAB308);
|
||||||
|
final tl = _map(box.rect.topLeft);
|
||||||
|
final br = _map(box.rect.bottomRight);
|
||||||
|
canvas.drawRect(Rect.fromPoints(tl, br), aabb);
|
||||||
|
|
||||||
|
final cross = Paint()
|
||||||
|
..color = const Color(0xFFEAB308)
|
||||||
|
..strokeWidth = 2;
|
||||||
|
const len = 10.0;
|
||||||
|
canvas.drawLine(Offset(c.dx - len, c.dy), Offset(c.dx + len, c.dy), cross);
|
||||||
|
canvas.drawLine(Offset(c.dx, c.dy - len), Offset(c.dx, c.dy + len), cross);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant _LockPainter old) {
|
||||||
|
return old.box != box || old.fitted != fitted || old.imageSize != imageSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
117
lib/services/blur_detector.dart
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:image/image.dart' as img;
|
||||||
|
import 'package:tflite_flutter/tflite_flutter.dart';
|
||||||
|
|
||||||
|
/// Blur / sharpness result from CoinSnap `blur.tflite`.
|
||||||
|
class BlurResult {
|
||||||
|
const BlurResult({
|
||||||
|
required this.blurScore,
|
||||||
|
required this.clearScore,
|
||||||
|
required this.isBlurry,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Softmax-ish class 0 = blur, class 1 = clear (from model).
|
||||||
|
final double blurScore;
|
||||||
|
final double clearScore;
|
||||||
|
final bool isBlurry;
|
||||||
|
|
||||||
|
double get confidence => isBlurry ? blurScore : clearScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CoinSnap blur classifier (`assets/ml/blur.tflite`).
|
||||||
|
///
|
||||||
|
/// Input: `[1, 3, 224, 224]` NCHW float32, letterbox on white, pixels / 255.
|
||||||
|
/// Output: `[1, 2]` → `[blur, clear]`.
|
||||||
|
class BlurDetector {
|
||||||
|
BlurDetector({this.clearThreshold = 0.55});
|
||||||
|
|
||||||
|
static const int inputSize = 224;
|
||||||
|
static const String assetPath = 'assets/ml/blur.tflite';
|
||||||
|
|
||||||
|
/// Clear score must be ≥ this to pass (mirrors app-side threshold).
|
||||||
|
final double clearThreshold;
|
||||||
|
|
||||||
|
Interpreter? _interpreter;
|
||||||
|
bool _loading = false;
|
||||||
|
|
||||||
|
Future<void> ensureLoaded() async {
|
||||||
|
if (_interpreter != null || _loading) return;
|
||||||
|
_loading = true;
|
||||||
|
try {
|
||||||
|
_interpreter = await Interpreter.fromAsset(assetPath);
|
||||||
|
} finally {
|
||||||
|
_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<BlurResult> evaluate(img.Image image) async {
|
||||||
|
await ensureLoaded();
|
||||||
|
final interp = _interpreter;
|
||||||
|
if (interp == null) {
|
||||||
|
return const BlurResult(blurScore: 0, clearScore: 1, isBlurry: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
final canvas = _letterboxWhite(image, inputSize);
|
||||||
|
final flat = _toNchw(canvas);
|
||||||
|
// Nested list matches [1, 3, 224, 224] NCHW.
|
||||||
|
final inputNd = List.generate(
|
||||||
|
1,
|
||||||
|
(_) => List.generate(
|
||||||
|
3,
|
||||||
|
(c) => List.generate(
|
||||||
|
inputSize,
|
||||||
|
(y) => List.generate(inputSize, (x) {
|
||||||
|
final i = c * inputSize * inputSize + y * inputSize + x;
|
||||||
|
return flat[i];
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final output = List.generate(1, (_) => List<double>.filled(2, 0.0));
|
||||||
|
interp.run(inputNd, output);
|
||||||
|
|
||||||
|
final blur = output[0][0];
|
||||||
|
final clear = output[0][1];
|
||||||
|
final isBlurry = clear < clearThreshold;
|
||||||
|
return BlurResult(blurScore: blur, clearScore: clear, isBlurry: isBlurry);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> dispose() async {
|
||||||
|
_interpreter?.close();
|
||||||
|
_interpreter = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static img.Image _letterboxWhite(img.Image src, int size) {
|
||||||
|
final scale = size / (src.width > src.height ? src.width : src.height);
|
||||||
|
final nw = (src.width * scale).round().clamp(1, size);
|
||||||
|
final nh = (src.height * scale).round().clamp(1, size);
|
||||||
|
final resized = img.copyResize(
|
||||||
|
src,
|
||||||
|
width: nw,
|
||||||
|
height: nh,
|
||||||
|
interpolation: img.Interpolation.linear,
|
||||||
|
);
|
||||||
|
final canvas = img.Image(width: size, height: size);
|
||||||
|
img.fill(canvas, color: img.ColorRgb8(255, 255, 255));
|
||||||
|
final padX = ((size - nw) / 2).floor();
|
||||||
|
final padY = ((size - nh) / 2).floor();
|
||||||
|
img.compositeImage(canvas, resized, dstX: padX, dstY: padY);
|
||||||
|
return canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Float32List _toNchw(img.Image canvas) {
|
||||||
|
final out = Float32List(1 * 3 * inputSize * inputSize);
|
||||||
|
const plane = inputSize * inputSize;
|
||||||
|
for (var y = 0; y < inputSize; y++) {
|
||||||
|
for (var x = 0; x < inputSize; x++) {
|
||||||
|
final p = canvas.getPixel(x, y);
|
||||||
|
final i = y * inputSize + x;
|
||||||
|
out[i] = p.r / 255.0;
|
||||||
|
out[plane + i] = p.g / 255.0;
|
||||||
|
out[plane * 2 + i] = p.b / 255.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
}
|
||||||
96
lib/services/coin_contour_refiner.dart
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:image/image.dart' as img;
|
||||||
|
|
||||||
|
class RefinedCircle {
|
||||||
|
const RefinedCircle({required this.center, required this.radius});
|
||||||
|
final Offset center;
|
||||||
|
final double radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refine an AABB into a circular coin contour via radial edge search.
|
||||||
|
class CoinContourRefiner {
|
||||||
|
static RefinedCircle? refine(img.Image image, Rect aabb) {
|
||||||
|
if (aabb.width < 16 || aabb.height < 16) return null;
|
||||||
|
|
||||||
|
final cx = aabb.center.dx;
|
||||||
|
final cy = aabb.center.dy;
|
||||||
|
final maxR = math.max(aabb.width, aabb.height) * 0.62;
|
||||||
|
final minR = math.min(aabb.width, aabb.height) * 0.28;
|
||||||
|
if (maxR <= minR + 2) {
|
||||||
|
return RefinedCircle(
|
||||||
|
center: Offset(cx, cy),
|
||||||
|
radius: math.min(aabb.width, aabb.height) / 2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sample edge strength along rays; vote for radius with strongest ring.
|
||||||
|
const rays = 64;
|
||||||
|
final radiusVotes = <double>[];
|
||||||
|
for (var i = 0; i < rays; i++) {
|
||||||
|
final ang = (i / rays) * math.pi * 2;
|
||||||
|
final dx = math.cos(ang);
|
||||||
|
final dy = math.sin(ang);
|
||||||
|
var bestR = (minR + maxR) / 2;
|
||||||
|
var bestMag = -1.0;
|
||||||
|
for (var r = minR; r <= maxR; r += 1.5) {
|
||||||
|
final x1 = (cx + dx * r).round();
|
||||||
|
final y1 = (cy + dy * r).round();
|
||||||
|
final x0 = (cx + dx * (r - 2)).round();
|
||||||
|
final y0 = (cy + dy * (r - 2)).round();
|
||||||
|
if (!_in(image, x0, y0) || !_in(image, x1, y1)) continue;
|
||||||
|
final g0 = _gray(image, x0, y0);
|
||||||
|
final g1 = _gray(image, x1, y1);
|
||||||
|
final mag = (g1 - g0).abs();
|
||||||
|
if (mag > bestMag) {
|
||||||
|
bestMag = mag;
|
||||||
|
bestR = r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (bestMag > 8) radiusVotes.add(bestR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (radiusVotes.length < rays * 0.35) {
|
||||||
|
return RefinedCircle(
|
||||||
|
center: Offset(cx, cy),
|
||||||
|
radius: math.min(aabb.width, aabb.height) / 2 * 0.96,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
radiusVotes.sort();
|
||||||
|
final radius = radiusVotes[radiusVotes.length ~/ 2] * 1.04;
|
||||||
|
|
||||||
|
// Slight center nudge from opposite ray midpoints.
|
||||||
|
var nx = 0.0;
|
||||||
|
var ny = 0.0;
|
||||||
|
var n = 0;
|
||||||
|
for (var i = 0; i < rays; i += 2) {
|
||||||
|
final ang = (i / rays) * math.pi * 2;
|
||||||
|
final r = radiusVotes[math.min(i, radiusVotes.length - 1)];
|
||||||
|
nx += cx + math.cos(ang) * (r - radius);
|
||||||
|
ny += cy + math.sin(ang) * (r - radius);
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
final center = n == 0
|
||||||
|
? Offset(cx, cy)
|
||||||
|
: Offset(
|
||||||
|
((nx / n) + cx) / 2,
|
||||||
|
((ny / n) + cy) / 2,
|
||||||
|
);
|
||||||
|
|
||||||
|
final clamped = Offset(
|
||||||
|
center.dx.clamp(radius, image.width - radius),
|
||||||
|
center.dy.clamp(radius, image.height - radius),
|
||||||
|
);
|
||||||
|
return RefinedCircle(center: clamped, radius: radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool _in(img.Image image, int x, int y) =>
|
||||||
|
x >= 0 && y >= 0 && x < image.width && y < image.height;
|
||||||
|
|
||||||
|
static double _gray(img.Image image, int x, int y) {
|
||||||
|
final p = image.getPixel(x, y);
|
||||||
|
return 0.299 * p.r + 0.587 * p.g + 0.114 * p.b;
|
||||||
|
}
|
||||||
|
}
|
||||||
80
lib/services/coin_cropper.dart
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:image/image.dart' as img;
|
||||||
|
|
||||||
|
import 'coin_detector.dart';
|
||||||
|
|
||||||
|
class CropResult {
|
||||||
|
const CropResult({
|
||||||
|
required this.bytes,
|
||||||
|
required this.width,
|
||||||
|
required this.height,
|
||||||
|
required this.usedDetection,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Uint8List bytes;
|
||||||
|
final int width;
|
||||||
|
final int height;
|
||||||
|
final bool usedDetection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Circular crop of the detected coin (square output with transparent-ish bg).
|
||||||
|
class CoinCropper {
|
||||||
|
static Future<CropResult> cropAndCorrect({
|
||||||
|
required String imagePath,
|
||||||
|
CoinBox? box,
|
||||||
|
double padding = 0.12,
|
||||||
|
}) async {
|
||||||
|
final raw = await File(imagePath).readAsBytes();
|
||||||
|
var decoded = img.decodeImage(raw);
|
||||||
|
if (decoded == null) {
|
||||||
|
throw StateError('Failed to decode image: $imagePath');
|
||||||
|
}
|
||||||
|
decoded = img.bakeOrientation(decoded);
|
||||||
|
|
||||||
|
final usedDetection = box != null;
|
||||||
|
final center = box?.effectiveCenter ??
|
||||||
|
Offset(decoded.width / 2, decoded.height / 2);
|
||||||
|
var radius = box?.effectiveRadius ??
|
||||||
|
math.min(decoded.width, decoded.height) * 0.35;
|
||||||
|
radius *= (1 + padding);
|
||||||
|
|
||||||
|
final size = (radius * 2).round().clamp(32, 2048);
|
||||||
|
final out = img.Image(width: size, height: size, numChannels: 4);
|
||||||
|
img.fill(out, color: img.ColorRgba8(0, 0, 0, 0));
|
||||||
|
|
||||||
|
final r2 = radius * radius;
|
||||||
|
for (var y = 0; y < size; y++) {
|
||||||
|
for (var x = 0; x < size; x++) {
|
||||||
|
final dx = x + 0.5 - size / 2;
|
||||||
|
final dy = y + 0.5 - size / 2;
|
||||||
|
if (dx * dx + dy * dy > r2) continue;
|
||||||
|
final sx = (center.dx + dx).round();
|
||||||
|
final sy = (center.dy + dy).round();
|
||||||
|
if (sx < 0 || sy < 0 || sx >= decoded.width || sy >= decoded.height) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final p = decoded.getPixel(sx, sy);
|
||||||
|
out.setPixelRgba(x, y, p.r.toInt(), p.g.toInt(), p.b.toInt(), 255);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const outSize = 320;
|
||||||
|
final resized = img.copyResize(
|
||||||
|
out,
|
||||||
|
width: outSize,
|
||||||
|
height: outSize,
|
||||||
|
interpolation: img.Interpolation.cubic,
|
||||||
|
);
|
||||||
|
final bytes = Uint8List.fromList(img.encodePng(resized));
|
||||||
|
return CropResult(
|
||||||
|
bytes: bytes,
|
||||||
|
width: resized.width,
|
||||||
|
height: resized.height,
|
||||||
|
usedDetection: usedDetection,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
251
lib/services/coin_detector.dart
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:image/image.dart' as img;
|
||||||
|
import 'package:tflite_flutter/tflite_flutter.dart';
|
||||||
|
|
||||||
|
import 'coin_contour_refiner.dart';
|
||||||
|
|
||||||
|
/// Detected coin region in image pixel coordinates.
|
||||||
|
class CoinBox {
|
||||||
|
const CoinBox({
|
||||||
|
required this.rect,
|
||||||
|
required this.confidence,
|
||||||
|
this.center,
|
||||||
|
this.radius,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Axis-aligned outer bounds.
|
||||||
|
final Rect rect;
|
||||||
|
final double confidence;
|
||||||
|
|
||||||
|
/// Refined circle (image pixels). When null, use [rect] inscribed circle.
|
||||||
|
final Offset? center;
|
||||||
|
final double? radius;
|
||||||
|
|
||||||
|
Offset get effectiveCenter =>
|
||||||
|
center ?? Offset(rect.center.dx, rect.center.dy);
|
||||||
|
|
||||||
|
double get effectiveRadius =>
|
||||||
|
radius ?? (math.min(rect.width, rect.height) / 2);
|
||||||
|
|
||||||
|
CoinBox copyWith({
|
||||||
|
Rect? rect,
|
||||||
|
double? confidence,
|
||||||
|
Offset? center,
|
||||||
|
double? radius,
|
||||||
|
}) {
|
||||||
|
return CoinBox(
|
||||||
|
rect: rect ?? this.rect,
|
||||||
|
confidence: confidence ?? this.confidence,
|
||||||
|
center: center ?? this.center,
|
||||||
|
radius: radius ?? this.radius,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// CoinSnap-style YOLOv5 detector (`assets/ml/detect.tflite`).
|
||||||
|
///
|
||||||
|
/// Input: `[1, 320, 320, 3]` float32, pixels / 255, letterbox resize.
|
||||||
|
/// Output: `[1, 6300, 6]` → `cx, cy, w, h, obj, cls` (already 0–1).
|
||||||
|
class CoinDetector {
|
||||||
|
CoinDetector();
|
||||||
|
|
||||||
|
static const int inputSize = 320;
|
||||||
|
static const double confThreshold = 0.35;
|
||||||
|
static const double iouThreshold = 0.45;
|
||||||
|
static const String assetPath = 'assets/ml/detect.tflite';
|
||||||
|
|
||||||
|
Interpreter? _interpreter;
|
||||||
|
bool _loading = false;
|
||||||
|
|
||||||
|
Future<void> ensureLoaded() async {
|
||||||
|
if (_interpreter != null || _loading) return;
|
||||||
|
_loading = true;
|
||||||
|
try {
|
||||||
|
_interpreter = await Interpreter.fromAsset(assetPath);
|
||||||
|
} finally {
|
||||||
|
_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<CoinBox>> detectImage(img.Image image) async {
|
||||||
|
await ensureLoaded();
|
||||||
|
final interp = _interpreter;
|
||||||
|
if (interp == null) return const [];
|
||||||
|
|
||||||
|
final prepared = _letterbox(image, inputSize);
|
||||||
|
final input = _toInputTensor(prepared.canvas);
|
||||||
|
// Nested list matches [1, 6300, 6] expected by tflite_flutter.
|
||||||
|
final output = List.generate(
|
||||||
|
1,
|
||||||
|
(_) => List.generate(6300, (_) => List<double>.filled(6, 0.0)),
|
||||||
|
);
|
||||||
|
final inputNd = List.generate(
|
||||||
|
1,
|
||||||
|
(_) => List.generate(
|
||||||
|
inputSize,
|
||||||
|
(y) => List.generate(
|
||||||
|
inputSize,
|
||||||
|
(x) {
|
||||||
|
final i = (y * inputSize + x) * 3;
|
||||||
|
return [input[i], input[i + 1], input[i + 2]];
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
interp.run(inputNd, output);
|
||||||
|
|
||||||
|
final boxes = <CoinBox>[];
|
||||||
|
final rows = output[0];
|
||||||
|
for (final row in rows) {
|
||||||
|
final score = row[4] * row[5];
|
||||||
|
if (score < confThreshold) continue;
|
||||||
|
|
||||||
|
// Normalized cx,cy,w,h on the letterboxed 320 canvas.
|
||||||
|
final cx = row[0] * inputSize;
|
||||||
|
final cy = row[1] * inputSize;
|
||||||
|
final w = row[2] * inputSize;
|
||||||
|
final h = row[3] * inputSize;
|
||||||
|
|
||||||
|
// Map back from letterbox → original image pixels.
|
||||||
|
final left = (cx - w / 2 - prepared.padX) / prepared.scale;
|
||||||
|
final top = (cy - h / 2 - prepared.padY) / prepared.scale;
|
||||||
|
final right = (cx + w / 2 - prepared.padX) / prepared.scale;
|
||||||
|
final bottom = (cy + h / 2 - prepared.padY) / prepared.scale;
|
||||||
|
|
||||||
|
final rect = Rect.fromLTRB(left, top, right, bottom).intersect(
|
||||||
|
Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()),
|
||||||
|
);
|
||||||
|
if (rect.width < 8 || rect.height < 8) continue;
|
||||||
|
|
||||||
|
boxes.add(CoinBox(rect: rect, confidence: score));
|
||||||
|
}
|
||||||
|
|
||||||
|
return _nms(boxes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Prefer largest reasonably confident near-circular box.
|
||||||
|
CoinBox? pickBest(List<CoinBox> boxes, {Size? imageSize}) {
|
||||||
|
if (boxes.isEmpty) return null;
|
||||||
|
CoinBox? best;
|
||||||
|
var bestScore = -1.0;
|
||||||
|
for (final b in boxes) {
|
||||||
|
final area = b.rect.width * b.rect.height;
|
||||||
|
final imgArea = imageSize == null
|
||||||
|
? area
|
||||||
|
: (imageSize.width * imageSize.height).clamp(1.0, double.infinity);
|
||||||
|
final fill = area / imgArea;
|
||||||
|
if (fill < 0.02 || fill > 0.95) continue;
|
||||||
|
|
||||||
|
final aspect = b.rect.width / math.max(b.rect.height, 1.0);
|
||||||
|
final roundScore = 1.0 - (aspect - 1.0).abs().clamp(0.0, 1.0);
|
||||||
|
final score = b.confidence * 0.45 + fill * 0.25 + roundScore * 0.3;
|
||||||
|
if (score > bestScore) {
|
||||||
|
bestScore = score;
|
||||||
|
best = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return best ?? boxes.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Refine AABB into a circle using local edge intensity.
|
||||||
|
CoinBox refineContour(img.Image image, CoinBox box) {
|
||||||
|
final refined = CoinContourRefiner.refine(image, box.rect);
|
||||||
|
if (refined == null) {
|
||||||
|
final r = math.min(box.rect.width, box.rect.height) / 2;
|
||||||
|
return box.copyWith(center: box.rect.center, radius: r);
|
||||||
|
}
|
||||||
|
final side = refined.radius * 2;
|
||||||
|
final rect = Rect.fromCenter(
|
||||||
|
center: refined.center,
|
||||||
|
width: side,
|
||||||
|
height: side,
|
||||||
|
).intersect(
|
||||||
|
Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble()),
|
||||||
|
);
|
||||||
|
return box.copyWith(
|
||||||
|
rect: rect,
|
||||||
|
center: refined.center,
|
||||||
|
radius: refined.radius,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> dispose() async {
|
||||||
|
_interpreter?.close();
|
||||||
|
_interpreter = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Float32List _toInputTensor(img.Image canvas) {
|
||||||
|
final out = Float32List(1 * inputSize * inputSize * 3);
|
||||||
|
var i = 0;
|
||||||
|
for (var y = 0; y < inputSize; y++) {
|
||||||
|
for (var x = 0; x < inputSize; x++) {
|
||||||
|
final p = canvas.getPixel(x, y);
|
||||||
|
out[i++] = p.r / 255.0;
|
||||||
|
out[i++] = p.g / 255.0;
|
||||||
|
out[i++] = p.b / 255.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _Letterbox _letterbox(img.Image src, int size) {
|
||||||
|
final scale = math.min(size / src.width, size / src.height);
|
||||||
|
final nw = math.max(1, (src.width * scale).round());
|
||||||
|
final nh = math.max(1, (src.height * scale).round());
|
||||||
|
final resized = img.copyResize(
|
||||||
|
src,
|
||||||
|
width: nw,
|
||||||
|
height: nh,
|
||||||
|
interpolation: img.Interpolation.linear,
|
||||||
|
);
|
||||||
|
final canvas = img.Image(width: size, height: size);
|
||||||
|
img.fill(canvas, color: img.ColorRgb8(114, 114, 114));
|
||||||
|
final padX = ((size - nw) / 2).floor();
|
||||||
|
final padY = ((size - nh) / 2).floor();
|
||||||
|
img.compositeImage(canvas, resized, dstX: padX, dstY: padY);
|
||||||
|
return _Letterbox(canvas: canvas, scale: scale, padX: padX.toDouble(), padY: padY.toDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<CoinBox> _nms(List<CoinBox> boxes) {
|
||||||
|
final sorted = [...boxes]..sort((a, b) => b.confidence.compareTo(a.confidence));
|
||||||
|
final kept = <CoinBox>[];
|
||||||
|
final suppressed = List<bool>.filled(sorted.length, false);
|
||||||
|
for (var i = 0; i < sorted.length; i++) {
|
||||||
|
if (suppressed[i]) continue;
|
||||||
|
kept.add(sorted[i]);
|
||||||
|
for (var j = i + 1; j < sorted.length; j++) {
|
||||||
|
if (suppressed[j]) continue;
|
||||||
|
if (_iou(sorted[i].rect, sorted[j].rect) > iouThreshold) {
|
||||||
|
suppressed[j] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return kept;
|
||||||
|
}
|
||||||
|
|
||||||
|
static double _iou(Rect a, Rect b) {
|
||||||
|
final inter = a.intersect(b);
|
||||||
|
if (inter.isEmpty) return 0;
|
||||||
|
final interArea = inter.width * inter.height;
|
||||||
|
final union = a.width * a.height + b.width * b.height - interArea;
|
||||||
|
return union <= 0 ? 0 : interArea / union;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Letterbox {
|
||||||
|
const _Letterbox({
|
||||||
|
required this.canvas,
|
||||||
|
required this.scale,
|
||||||
|
required this.padX,
|
||||||
|
required this.padY,
|
||||||
|
});
|
||||||
|
|
||||||
|
final img.Image canvas;
|
||||||
|
final double scale;
|
||||||
|
final double padX;
|
||||||
|
final double padY;
|
||||||
|
}
|
||||||
122
lib/services/coin_scan_gate.dart
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:ui' as ui;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:image/image.dart' as img;
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
import '../widgets/guide_frame.dart';
|
||||||
|
import 'blur_detector.dart';
|
||||||
|
import 'coin_detector.dart';
|
||||||
|
|
||||||
|
enum CoinGateFail { none, blurry, noCoin }
|
||||||
|
|
||||||
|
class CoinGateResult {
|
||||||
|
const CoinGateResult({
|
||||||
|
required this.ok,
|
||||||
|
this.fail = CoinGateFail.none,
|
||||||
|
this.blur,
|
||||||
|
this.box,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool ok;
|
||||||
|
final CoinGateFail fail;
|
||||||
|
final BlurResult? blur;
|
||||||
|
final CoinBox? box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Post-capture gate: blur.tflite → detect.tflite (CoinSnap order).
|
||||||
|
class CoinScanGate {
|
||||||
|
static Future<CoinGateResult> evaluate({
|
||||||
|
required String imagePath,
|
||||||
|
Size? captureViewport,
|
||||||
|
CoinDetector? detector,
|
||||||
|
BlurDetector? blurDetector,
|
||||||
|
}) async {
|
||||||
|
final ownedDet = detector == null;
|
||||||
|
final ownedBlur = blurDetector == null;
|
||||||
|
final det = detector ?? CoinDetector();
|
||||||
|
final blur = blurDetector ?? BlurDetector();
|
||||||
|
|
||||||
|
try {
|
||||||
|
final raster = await _guideCrop(imagePath, captureViewport);
|
||||||
|
if (raster == null) {
|
||||||
|
return const CoinGateResult(ok: false, fail: CoinGateFail.noCoin);
|
||||||
|
}
|
||||||
|
|
||||||
|
final blurResult = await blur.evaluate(raster);
|
||||||
|
if (blurResult.isBlurry) {
|
||||||
|
return CoinGateResult(
|
||||||
|
ok: false,
|
||||||
|
fail: CoinGateFail.blurry,
|
||||||
|
blur: blurResult,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final boxes = await det.detectImage(raster);
|
||||||
|
final best = det.pickBest(
|
||||||
|
boxes,
|
||||||
|
imageSize: Size(raster.width.toDouble(), raster.height.toDouble()),
|
||||||
|
);
|
||||||
|
if (best == null) {
|
||||||
|
return CoinGateResult(
|
||||||
|
ok: false,
|
||||||
|
fail: CoinGateFail.noCoin,
|
||||||
|
blur: blurResult,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return CoinGateResult(ok: true, blur: blurResult, box: best);
|
||||||
|
} finally {
|
||||||
|
if (ownedDet) await det.dispose();
|
||||||
|
if (ownedBlur) await blur.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<img.Image?> _guideCrop(
|
||||||
|
String imagePath,
|
||||||
|
Size? captureViewport,
|
||||||
|
) async {
|
||||||
|
final raw = await File(imagePath).readAsBytes();
|
||||||
|
final codec = await ui.instantiateImageCodec(raw);
|
||||||
|
final frame = await codec.getNextFrame();
|
||||||
|
final fullImage = frame.image;
|
||||||
|
final rgba = await fullImage.toByteData(format: ui.ImageByteFormat.rawRgba);
|
||||||
|
if (rgba == null) {
|
||||||
|
fullImage.dispose();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final fullRaster = img.Image.fromBytes(
|
||||||
|
width: fullImage.width,
|
||||||
|
height: fullImage.height,
|
||||||
|
bytes: rgba.buffer,
|
||||||
|
bytesOffset: rgba.offsetInBytes,
|
||||||
|
numChannels: 4,
|
||||||
|
order: img.ChannelOrder.rgba,
|
||||||
|
);
|
||||||
|
fullImage.dispose();
|
||||||
|
|
||||||
|
final imageSize = Size(
|
||||||
|
fullRaster.width.toDouble(),
|
||||||
|
fullRaster.height.toDouble(),
|
||||||
|
);
|
||||||
|
final guide = captureViewport != null
|
||||||
|
? GuideFrame.inCoverImage(viewport: captureViewport, imageSize: imageSize)
|
||||||
|
: GuideFrame.inImage(imageSize);
|
||||||
|
|
||||||
|
final gx = guide.left.round().clamp(0, fullRaster.width - 1);
|
||||||
|
final gy = guide.top.round().clamp(0, fullRaster.height - 1);
|
||||||
|
final gw = guide.width.round().clamp(1, fullRaster.width - gx);
|
||||||
|
final gh = guide.height.round().clamp(1, fullRaster.height - gy);
|
||||||
|
return img.copyCrop(fullRaster, x: gx, y: gy, width: gw, height: gh);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Persist guide crop for preview reuse.
|
||||||
|
static Future<String> writeGuideJpeg(img.Image raster) async {
|
||||||
|
final dir = await getTemporaryDirectory();
|
||||||
|
final path =
|
||||||
|
'${dir.path}/coin_guide_${DateTime.now().millisecondsSinceEpoch}.jpg';
|
||||||
|
await File(path).writeAsBytes(img.encodeJpg(raster, quality: 95), flush: true);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
}
|
||||||
44
lib/widgets/guide_frame.dart
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
/// Circular capture guide (coin frame).
|
||||||
|
class GuideFrame {
|
||||||
|
static const double maxDiameterFraction = 0.72;
|
||||||
|
static const double centerYFraction = 0.46;
|
||||||
|
|
||||||
|
static Rect inViewport(Size size) {
|
||||||
|
final d = math.min(size.width, size.height) * maxDiameterFraction;
|
||||||
|
return Rect.fromCenter(
|
||||||
|
center: Offset(size.width / 2, size.height * centerYFraction),
|
||||||
|
width: d,
|
||||||
|
height: d,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rect inCoverImage({
|
||||||
|
required Size viewport,
|
||||||
|
required Size imageSize,
|
||||||
|
}) {
|
||||||
|
final guide = inViewport(viewport);
|
||||||
|
final scale = math.max(
|
||||||
|
viewport.width / imageSize.width,
|
||||||
|
viewport.height / imageSize.height,
|
||||||
|
);
|
||||||
|
final dispW = imageSize.width * scale;
|
||||||
|
final dispH = imageSize.height * scale;
|
||||||
|
final ox = (viewport.width - dispW) / 2;
|
||||||
|
final oy = (viewport.height - dispH) / 2;
|
||||||
|
|
||||||
|
final mapped = Rect.fromLTRB(
|
||||||
|
(guide.left - ox) / scale,
|
||||||
|
(guide.top - oy) / scale,
|
||||||
|
(guide.right - ox) / scale,
|
||||||
|
(guide.bottom - oy) / scale,
|
||||||
|
);
|
||||||
|
return mapped.intersect(
|
||||||
|
Rect.fromLTWH(0, 0, imageSize.width, imageSize.height),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Rect inImage(Size imageSize) => inViewport(imageSize);
|
||||||
|
}
|
||||||
50
lib/widgets/guide_overlay.dart
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'guide_frame.dart';
|
||||||
|
|
||||||
|
/// Circular shooting guide overlay.
|
||||||
|
class GuideOverlay extends StatelessWidget {
|
||||||
|
const GuideOverlay({super.key, required this.size});
|
||||||
|
|
||||||
|
final Size size;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return CustomPaint(
|
||||||
|
size: size,
|
||||||
|
painter: _GuidePainter(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GuidePainter extends CustomPainter {
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final rect = GuideFrame.inViewport(size);
|
||||||
|
final dim = Paint()..color = Colors.black.withOpacity(0.45);
|
||||||
|
final path = Path()
|
||||||
|
..addRect(Offset.zero & size)
|
||||||
|
..addOval(rect)
|
||||||
|
..fillType = PathFillType.evenOdd;
|
||||||
|
canvas.drawPath(path, dim);
|
||||||
|
|
||||||
|
final border = Paint()
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 2
|
||||||
|
..color = Colors.white70;
|
||||||
|
canvas.drawOval(rect, border);
|
||||||
|
|
||||||
|
// Crosshair ticks
|
||||||
|
final tick = Paint()
|
||||||
|
..style = PaintingStyle.stroke
|
||||||
|
..strokeWidth = 2
|
||||||
|
..color = const Color(0xFFEAB308);
|
||||||
|
final c = rect.center;
|
||||||
|
const len = 14.0;
|
||||||
|
canvas.drawLine(Offset(c.dx - len, c.dy), Offset(c.dx + len, c.dy), tick);
|
||||||
|
canvas.drawLine(Offset(c.dx, c.dy - len), Offset(c.dx, c.dy + len), tick);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||||
|
}
|
||||||
554
pubspec.lock
Normal file
@ -0,0 +1,554 @@
|
|||||||
|
# Generated by pub
|
||||||
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
|
packages:
|
||||||
|
archive:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: archive
|
||||||
|
sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.9"
|
||||||
|
async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: async
|
||||||
|
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.11.0"
|
||||||
|
boolean_selector:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: boolean_selector
|
||||||
|
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
camera:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: camera
|
||||||
|
sha256: dfa8fc5a1adaeb95e7a54d86a5bd56f4bb0e035515354c8ac6d262e35cec2ec8
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.6"
|
||||||
|
camera_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: camera_android
|
||||||
|
sha256: "32f04948a284b71d938fe275616faf4957d07f9b3aab8021bfc8c418301a289e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.9+11"
|
||||||
|
camera_avfoundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: camera_avfoundation
|
||||||
|
sha256: ca36181194f429eef3b09de3c96280f2400693f9735025f90d1f4a27465fdd72
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.19"
|
||||||
|
camera_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: camera_platform_interface
|
||||||
|
sha256: "2f757024a48696ff4814a789b0bd90f5660c0fb25f393ab4564fb483327930e2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.10.0"
|
||||||
|
camera_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: camera_web
|
||||||
|
sha256: "595f28c89d1fb62d77c73c633193755b781c6d2e0ebcd8dc25b763b514e6ba8f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.5"
|
||||||
|
characters:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: characters
|
||||||
|
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
clock:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: clock
|
||||||
|
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
collection:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: collection
|
||||||
|
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.18.0"
|
||||||
|
cross_file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cross_file
|
||||||
|
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.4+2"
|
||||||
|
cupertino_icons:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: cupertino_icons
|
||||||
|
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.8"
|
||||||
|
fake_async:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fake_async
|
||||||
|
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.1"
|
||||||
|
ffi:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: ffi
|
||||||
|
sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
file_selector_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_linux
|
||||||
|
sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.3+2"
|
||||||
|
file_selector_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_macos
|
||||||
|
sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.4+2"
|
||||||
|
file_selector_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_platform_interface
|
||||||
|
sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.6.2"
|
||||||
|
file_selector_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: file_selector_windows
|
||||||
|
sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.9.3+4"
|
||||||
|
flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_lints:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: flutter_lints
|
||||||
|
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.2"
|
||||||
|
flutter_plugin_android_lifecycle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_plugin_android_lifecycle
|
||||||
|
sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.22"
|
||||||
|
flutter_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
flutter_web_plugins:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
|
http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http
|
||||||
|
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.6.0"
|
||||||
|
http_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_parser
|
||||||
|
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.2"
|
||||||
|
image:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: image
|
||||||
|
sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.8.0"
|
||||||
|
image_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: image_picker
|
||||||
|
sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
|
image_picker_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_android
|
||||||
|
sha256: "8c5abf0dcc24fe6e8e0b4a5c0b51a5cf30cefdf6407a3213dae61edc75a70f56"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.12+12"
|
||||||
|
image_picker_for_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_for_web
|
||||||
|
sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.6"
|
||||||
|
image_picker_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_ios
|
||||||
|
sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.12+2"
|
||||||
|
image_picker_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_linux
|
||||||
|
sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.1+2"
|
||||||
|
image_picker_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_macos
|
||||||
|
sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.1+2"
|
||||||
|
image_picker_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_platform_interface
|
||||||
|
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.10.1"
|
||||||
|
image_picker_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: image_picker_windows
|
||||||
|
sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.1+1"
|
||||||
|
leak_tracker:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker
|
||||||
|
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.0.4"
|
||||||
|
leak_tracker_flutter_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_flutter_testing
|
||||||
|
sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.3"
|
||||||
|
leak_tracker_testing:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: leak_tracker_testing
|
||||||
|
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
lints:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: lints
|
||||||
|
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
matcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matcher
|
||||||
|
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.12.16+1"
|
||||||
|
material_color_utilities:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: material_color_utilities
|
||||||
|
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.8.0"
|
||||||
|
meta:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: meta
|
||||||
|
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.12.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
|
path:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: path
|
||||||
|
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.9.0"
|
||||||
|
path_provider:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: path_provider
|
||||||
|
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.5"
|
||||||
|
path_provider_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_android
|
||||||
|
sha256: "6f01f8e37ec30b07bc424b4deabac37cacb1bc7e2e515ad74486039918a37eb7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.10"
|
||||||
|
path_provider_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_foundation
|
||||||
|
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
path_provider_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_linux
|
||||||
|
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
path_provider_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_platform_interface
|
||||||
|
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
path_provider_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: path_provider_windows
|
||||||
|
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.0"
|
||||||
|
petitparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: petitparser
|
||||||
|
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.0.2"
|
||||||
|
platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: platform
|
||||||
|
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.6"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.8"
|
||||||
|
posix:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: posix
|
||||||
|
sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.5.0"
|
||||||
|
quiver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: quiver
|
||||||
|
sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.2"
|
||||||
|
sky_engine:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.99"
|
||||||
|
source_span:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_span
|
||||||
|
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.10.0"
|
||||||
|
stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stack_trace
|
||||||
|
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.11.1"
|
||||||
|
stream_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_channel
|
||||||
|
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.2"
|
||||||
|
stream_transform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: stream_transform
|
||||||
|
sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
|
string_scanner:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: string_scanner
|
||||||
|
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.0"
|
||||||
|
term_glyph:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: term_glyph
|
||||||
|
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.1"
|
||||||
|
test_api:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_api
|
||||||
|
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.7.0"
|
||||||
|
tflite_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: tflite_flutter
|
||||||
|
sha256: "48e6fde2ad97162bb66a16a142f4c4698add9e8cd397ce9d1cc7451b55537ac1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.11.0"
|
||||||
|
typed_data:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: typed_data
|
||||||
|
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.2"
|
||||||
|
vector_math:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vector_math
|
||||||
|
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.4"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "14.2.1"
|
||||||
|
web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web
|
||||||
|
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.1"
|
||||||
|
xdg_directories:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xdg_directories
|
||||||
|
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
xml:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: xml
|
||||||
|
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.5.0"
|
||||||
|
sdks:
|
||||||
|
dart: ">=3.4.0 <4.0.0"
|
||||||
|
flutter: ">=3.22.0"
|
||||||
29
pubspec.yaml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
name: coin_dex_demo
|
||||||
|
description: Coin scan demo — capture, blur check, detect contour, crop.
|
||||||
|
publish_to: 'none'
|
||||||
|
version: 1.0.0+1
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: '>=3.4.0 <4.0.0'
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
cupertino_icons: ^1.0.6
|
||||||
|
camera: 0.10.6
|
||||||
|
tflite_flutter: ^0.11.0
|
||||||
|
image: ^4.3.0
|
||||||
|
image_picker: ^1.1.2
|
||||||
|
path_provider: ^2.1.4
|
||||||
|
path: ^1.9.0
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
flutter_lints: ^3.0.0
|
||||||
|
|
||||||
|
flutter:
|
||||||
|
uses-material-design: true
|
||||||
|
assets:
|
||||||
|
- assets/ml/detect.tflite
|
||||||
|
- assets/ml/blur.tflite
|
||||||
10
test/widget_test.dart
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:coin_dex_demo/main.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('app builds', (tester) async {
|
||||||
|
await tester.pumpWidget(const CoinScanDemoApp());
|
||||||
|
expect(find.text('硬币扫描'), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
||||||