app scaffold
@@ -0,0 +1,33 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
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
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.flutter-plugins-dependencies
|
||||
/build/
|
||||
/coverage/
|
||||
@@ -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: "6b182d2c7585eba26d4edce0f97630effd256c33"
|
||||
channel: "stable"
|
||||
|
||||
project_type: plugin
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: 6b182d2c7585eba26d4edce0f97630effd256c33
|
||||
base_revision: 6b182d2c7585eba26d4edce0f97630effd256c33
|
||||
- platform: android
|
||||
create_revision: 6b182d2c7585eba26d4edce0f97630effd256c33
|
||||
base_revision: 6b182d2c7585eba26d4edce0f97630effd256c33
|
||||
- platform: ios
|
||||
create_revision: 6b182d2c7585eba26d4edce0f97630effd256c33
|
||||
base_revision: 6b182d2c7585eba26d4edce0f97630effd256c33
|
||||
|
||||
# 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'
|
||||
@@ -0,0 +1,3 @@
|
||||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
||||
@@ -0,0 +1 @@
|
||||
TODO: Add your license here.
|
||||
@@ -0,0 +1,15 @@
|
||||
# native_scan
|
||||
|
||||
A new Flutter plugin project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter
|
||||
[plug-in package](https://flutter.dev/to/develop-plugins),
|
||||
a specialized package that includes platform-specific implementation code for
|
||||
Android and/or iOS.
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
include: ../../analysis_options.yaml
|
||||
@@ -0,0 +1,9 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.cxx
|
||||
@@ -0,0 +1,77 @@
|
||||
group = "com.conti.native_scan"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
buildscript {
|
||||
val kotlinVersion = "2.3.20"
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath("com.android.tools.build:gradle:9.0.1")
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.conti.native_scan"
|
||||
|
||||
compileSdk = 36
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
getByName("main") {
|
||||
java.srcDirs("src/main/kotlin")
|
||||
}
|
||||
getByName("test") {
|
||||
java.srcDirs("src/test/kotlin")
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
all {
|
||||
it.useJUnitPlatform()
|
||||
|
||||
it.outputs.upToDateWhen { false }
|
||||
|
||||
it.testLogging {
|
||||
events("passed", "skipped", "failed", "standardOut", "standardError")
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
testImplementation("org.mockito:mockito-core:5.0.0")
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
rootProject.name = "native_scan"
|
||||
@@ -0,0 +1,3 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.conti.native_scan">
|
||||
</manifest>
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.conti.native_scan
|
||||
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
|
||||
/**
|
||||
* 扫码插件的 Android 入口。
|
||||
*
|
||||
* TODO(native): 手写原生实现本次不在范围内,见 conti-docs/07-native-integration.md。
|
||||
* 补完时需要做的事:
|
||||
* 1. 让某个类实现 pigeon 生成的 `ScanHostApi`(见 ScanApi.g.kt,不要手改生成物);
|
||||
* 2. 在 onAttachedToEngine 里 `ScanHostApi.setUp(binding.binaryMessenger, impl)`;
|
||||
* 3. 实现 ActivityAware 拿到 Activity(扫码要起页面、要申请相机权限);
|
||||
* 4. 取消 / 权限拒绝 / 能力不可用必须回 FlutterError,code 用
|
||||
* CANCELLED / PERMISSION_DENIED / UNAVAILABLE —— 与 Dart 侧
|
||||
* NativeScanErrorCode 一一对应,**不允许返回占位假数据**。
|
||||
*
|
||||
* 在此之前,Dart 侧调用会收到 MissingPluginException,
|
||||
* 并被 NativeScan 转成 UNSUPPORTED_PLATFORM,属预期行为。
|
||||
*/
|
||||
class NativeScanPlugin : FlutterPlugin {
|
||||
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
// TODO(native): ScanHostApi.setUp(binding.binaryMessenger, ScanApiImpl(...))
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
// TODO(native): ScanHostApi.setUp(binding.binaryMessenger, null)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,445 @@
|
||||
// Autogenerated from Pigeon (v27.3.0), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
|
||||
|
||||
package com.conti.native_scan
|
||||
|
||||
import android.util.Log
|
||||
import io.flutter.plugin.common.BasicMessageChannel
|
||||
import io.flutter.plugin.common.BinaryMessenger
|
||||
import io.flutter.plugin.common.EventChannel
|
||||
import io.flutter.plugin.common.MessageCodec
|
||||
import io.flutter.plugin.common.StandardMethodCodec
|
||||
import io.flutter.plugin.common.StandardMessageCodec
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.nio.ByteBuffer
|
||||
private object ScanApiPigeonUtils {
|
||||
|
||||
fun wrapResult(result: Any?): List<Any?> {
|
||||
return listOf(result)
|
||||
}
|
||||
|
||||
fun wrapError(exception: Throwable): List<Any?> {
|
||||
return if (exception is FlutterError) {
|
||||
listOf(
|
||||
exception.code,
|
||||
exception.message,
|
||||
exception.details
|
||||
)
|
||||
} else {
|
||||
listOf(
|
||||
exception.javaClass.simpleName,
|
||||
exception.toString(),
|
||||
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
|
||||
)
|
||||
}
|
||||
}
|
||||
fun doubleEquals(a: Double, b: Double): Boolean {
|
||||
// Normalize -0.0 to 0.0 and handle NaN equality.
|
||||
return (if (a == 0.0) 0.0 else a) == (if (b == 0.0) 0.0 else b) || (a.isNaN() && b.isNaN())
|
||||
}
|
||||
|
||||
fun floatEquals(a: Float, b: Float): Boolean {
|
||||
// Normalize -0.0 to 0.0 and handle NaN equality.
|
||||
return (if (a == 0.0f) 0.0f else a) == (if (b == 0.0f) 0.0f else b) || (a.isNaN() && b.isNaN())
|
||||
}
|
||||
|
||||
fun doubleHash(d: Double): Int {
|
||||
// Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
|
||||
val normalized = if (d == 0.0) 0.0 else d
|
||||
val bits = java.lang.Double.doubleToLongBits(normalized)
|
||||
return (bits xor (bits ushr 32)).toInt()
|
||||
}
|
||||
|
||||
fun floatHash(f: Float): Int {
|
||||
// Normalize -0.0 to 0.0 and handle NaN to ensure consistent hash codes.
|
||||
val normalized = if (f == 0.0f) 0.0f else f
|
||||
return java.lang.Float.floatToIntBits(normalized)
|
||||
}
|
||||
|
||||
fun deepEquals(a: Any?, b: Any?): Boolean {
|
||||
if (a === b) {
|
||||
return true
|
||||
}
|
||||
if (a == null || b == null) {
|
||||
return false
|
||||
}
|
||||
if (a is ByteArray && b is ByteArray) {
|
||||
return a.contentEquals(b)
|
||||
}
|
||||
if (a is IntArray && b is IntArray) {
|
||||
return a.contentEquals(b)
|
||||
}
|
||||
if (a is LongArray && b is LongArray) {
|
||||
return a.contentEquals(b)
|
||||
}
|
||||
if (a is DoubleArray && b is DoubleArray) {
|
||||
if (a.size != b.size) return false
|
||||
for (i in a.indices) {
|
||||
if (!doubleEquals(a[i], b[i])) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (a is FloatArray && b is FloatArray) {
|
||||
if (a.size != b.size) return false
|
||||
for (i in a.indices) {
|
||||
if (!floatEquals(a[i], b[i])) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (a is Array<*> && b is Array<*>) {
|
||||
if (a.size != b.size) return false
|
||||
for (i in a.indices) {
|
||||
if (!deepEquals(a[i], b[i])) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (a is List<*> && b is List<*>) {
|
||||
if (a.size != b.size) return false
|
||||
val iterA = a.iterator()
|
||||
val iterB = b.iterator()
|
||||
while (iterA.hasNext() && iterB.hasNext()) {
|
||||
if (!deepEquals(iterA.next(), iterB.next())) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (a is Map<*, *> && b is Map<*, *>) {
|
||||
if (a.size != b.size) return false
|
||||
for (entry in a) {
|
||||
val key = entry.key
|
||||
var found = false
|
||||
for (bEntry in b) {
|
||||
if (deepEquals(key, bEntry.key)) {
|
||||
if (deepEquals(entry.value, bEntry.value)) {
|
||||
found = true
|
||||
break
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if (a is Double && b is Double) {
|
||||
return doubleEquals(a, b)
|
||||
}
|
||||
if (a is Float && b is Float) {
|
||||
return floatEquals(a, b)
|
||||
}
|
||||
return a == b
|
||||
}
|
||||
|
||||
fun deepHash(value: Any?): Int {
|
||||
return when (value) {
|
||||
null -> 0
|
||||
is ByteArray -> value.contentHashCode()
|
||||
is IntArray -> value.contentHashCode()
|
||||
is LongArray -> value.contentHashCode()
|
||||
is DoubleArray -> {
|
||||
var result = 1
|
||||
for (item in value) {
|
||||
result = 31 * result + doubleHash(item)
|
||||
}
|
||||
result
|
||||
}
|
||||
is FloatArray -> {
|
||||
var result = 1
|
||||
for (item in value) {
|
||||
result = 31 * result + floatHash(item)
|
||||
}
|
||||
result
|
||||
}
|
||||
is Array<*> -> {
|
||||
var result = 1
|
||||
for (item in value) {
|
||||
result = 31 * result + deepHash(item)
|
||||
}
|
||||
result
|
||||
}
|
||||
is List<*> -> {
|
||||
var result = 1
|
||||
for (item in value) {
|
||||
result = 31 * result + deepHash(item)
|
||||
}
|
||||
result
|
||||
}
|
||||
is Map<*, *> -> {
|
||||
var result = 0
|
||||
for (entry in value) {
|
||||
result += ((deepHash(entry.key) * 31) xor deepHash(entry.value))
|
||||
}
|
||||
result
|
||||
}
|
||||
is Double -> doubleHash(value)
|
||||
is Float -> floatHash(value)
|
||||
else -> value.hashCode()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Error class for passing custom error details to Flutter via a thrown PlatformException.
|
||||
* @property code The error code.
|
||||
* @property message The error message.
|
||||
* @property details The error details. Must be a datatype supported by the api codec.
|
||||
*/
|
||||
class FlutterError (
|
||||
val code: String,
|
||||
override val message: String? = null,
|
||||
val details: Any? = null
|
||||
) : RuntimeException()
|
||||
|
||||
/**
|
||||
* 识别类型。
|
||||
*
|
||||
* **即使首版只做条码,这个参数也必须先留出来**(07 §「待确认:VIN 码与车牌
|
||||
* 识别的技术路径」):车牌走的是专用 OCR、VIN 印刷字符走通用 OCR + 校验位
|
||||
* 过滤,技术路径还没定。参数先在 schema 里占好位,后面加识别类型就不用改
|
||||
* 接口签名——改签名意味着三端生成物和所有调用点一起动。
|
||||
*/
|
||||
enum class ScanMode(val raw: Int) {
|
||||
/** 二维码 / 条形码(商品、库位)。 */
|
||||
BARCODE(0),
|
||||
/** VIN 码。可能是 Code 39 条码,也可能只有印刷字符。 */
|
||||
VIN(1),
|
||||
/** 车牌。 */
|
||||
PLATE(2);
|
||||
|
||||
companion object {
|
||||
fun ofRaw(raw: Int): ScanMode? {
|
||||
return values().firstOrNull { it.raw == raw }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫码入参。
|
||||
*
|
||||
* Generated class from Pigeon that represents data sent in messages.
|
||||
*/
|
||||
data class ScanOptions (
|
||||
/** 识别类型。 */
|
||||
val mode: ScanMode,
|
||||
/** 超时毫秒数。null 表示不超时,由用户手动取消。 */
|
||||
val timeoutMs: Long? = null,
|
||||
/** 是否默认打开闪光灯。 */
|
||||
val torchEnabled: Boolean? = null,
|
||||
/** 扫码页标题。由调用方传,`native_scan` 不依赖任何 i18n 资源。 */
|
||||
val title: String? = null
|
||||
)
|
||||
{
|
||||
companion object {
|
||||
fun fromList(pigeonVar_list: List<Any?>): ScanOptions {
|
||||
val mode = pigeonVar_list[0] as ScanMode
|
||||
val timeoutMs = pigeonVar_list[1] as Long?
|
||||
val torchEnabled = pigeonVar_list[2] as Boolean?
|
||||
val title = pigeonVar_list[3] as String?
|
||||
return ScanOptions(mode, timeoutMs, torchEnabled, title)
|
||||
}
|
||||
}
|
||||
fun toList(): List<Any?> {
|
||||
return listOf(
|
||||
mode,
|
||||
timeoutMs,
|
||||
torchEnabled,
|
||||
title,
|
||||
)
|
||||
}
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other == null || other.javaClass != javaClass) {
|
||||
return false
|
||||
}
|
||||
if (this === other) {
|
||||
return true
|
||||
}
|
||||
val other = other as ScanOptions
|
||||
return ScanApiPigeonUtils.deepEquals(this.mode, other.mode) && ScanApiPigeonUtils.deepEquals(this.timeoutMs, other.timeoutMs) && ScanApiPigeonUtils.deepEquals(this.torchEnabled, other.torchEnabled) && ScanApiPigeonUtils.deepEquals(this.title, other.title)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = javaClass.hashCode()
|
||||
result = 31 * result + ScanApiPigeonUtils.deepHash(this.mode)
|
||||
result = 31 * result + ScanApiPigeonUtils.deepHash(this.timeoutMs)
|
||||
result = 31 * result + ScanApiPigeonUtils.deepHash(this.torchEnabled)
|
||||
result = 31 * result + ScanApiPigeonUtils.deepHash(this.title)
|
||||
return result
|
||||
}
|
||||
override fun toString(): String {
|
||||
return "ScanOptions(mode=$mode, timeoutMs=$timeoutMs, torchEnabled=$torchEnabled, title=$title)"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 扫码结果。
|
||||
*
|
||||
* Generated class from Pigeon that represents data sent in messages.
|
||||
*/
|
||||
data class ScanResult (
|
||||
/** 实际生效的识别类型。 */
|
||||
val mode: ScanMode,
|
||||
/** 识别到的文本。 */
|
||||
val value: String,
|
||||
/** 从打开扫码页到出结果的耗时,供埋点用(见 13 的 `scan_succeeded`)。 */
|
||||
val durationMs: Long,
|
||||
/** 原始码制(如 `CODE_39` / `QR_CODE`)。OCR 路径下为 null。 */
|
||||
val rawFormat: String? = null
|
||||
)
|
||||
{
|
||||
companion object {
|
||||
fun fromList(pigeonVar_list: List<Any?>): ScanResult {
|
||||
val mode = pigeonVar_list[0] as ScanMode
|
||||
val value = pigeonVar_list[1] as String
|
||||
val durationMs = pigeonVar_list[2] as Long
|
||||
val rawFormat = pigeonVar_list[3] as String?
|
||||
return ScanResult(mode, value, durationMs, rawFormat)
|
||||
}
|
||||
}
|
||||
fun toList(): List<Any?> {
|
||||
return listOf(
|
||||
mode,
|
||||
value,
|
||||
durationMs,
|
||||
rawFormat,
|
||||
)
|
||||
}
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other == null || other.javaClass != javaClass) {
|
||||
return false
|
||||
}
|
||||
if (this === other) {
|
||||
return true
|
||||
}
|
||||
val other = other as ScanResult
|
||||
return ScanApiPigeonUtils.deepEquals(this.mode, other.mode) && ScanApiPigeonUtils.deepEquals(this.value, other.value) && ScanApiPigeonUtils.deepEquals(this.durationMs, other.durationMs) && ScanApiPigeonUtils.deepEquals(this.rawFormat, other.rawFormat)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = javaClass.hashCode()
|
||||
result = 31 * result + ScanApiPigeonUtils.deepHash(this.mode)
|
||||
result = 31 * result + ScanApiPigeonUtils.deepHash(this.value)
|
||||
result = 31 * result + ScanApiPigeonUtils.deepHash(this.durationMs)
|
||||
result = 31 * result + ScanApiPigeonUtils.deepHash(this.rawFormat)
|
||||
return result
|
||||
}
|
||||
override fun toString(): String {
|
||||
return "ScanResult(mode=$mode, value=$value, durationMs=$durationMs, rawFormat=$rawFormat)"
|
||||
}
|
||||
}
|
||||
private open class ScanApiPigeonCodec : StandardMessageCodec() {
|
||||
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
||||
return when (type) {
|
||||
129.toByte() -> {
|
||||
return (readValue(buffer) as Long?)?.let {
|
||||
ScanMode.ofRaw(it.toInt())
|
||||
}
|
||||
}
|
||||
130.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
ScanOptions.fromList(it)
|
||||
}
|
||||
}
|
||||
131.toByte() -> {
|
||||
return (readValue(buffer) as? List<Any?>)?.let {
|
||||
ScanResult.fromList(it)
|
||||
}
|
||||
}
|
||||
else -> super.readValueOfType(type, buffer)
|
||||
}
|
||||
}
|
||||
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
|
||||
when (value) {
|
||||
is ScanMode -> {
|
||||
stream.write(129)
|
||||
writeValue(stream, value.raw.toLong())
|
||||
}
|
||||
is ScanOptions -> {
|
||||
stream.write(130)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
is ScanResult -> {
|
||||
stream.write(131)
|
||||
writeValue(stream, value.toList())
|
||||
}
|
||||
else -> super.writeValue(stream, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Dart → 原生。
|
||||
*
|
||||
* 用户取消、权限拒绝、平台未实现这三类都通过 `FlutterError` 抛出,
|
||||
* 由 Dart 侧的公共 API 转成 `NativeScanException`——**原生异常类型
|
||||
* (`PlatformException`)不允许直接抛到业务代码里**(07 §使用规则)。
|
||||
*
|
||||
* Generated interface from Pigeon that represents a handler of messages from Flutter.
|
||||
*/
|
||||
interface ScanHostApi {
|
||||
/**
|
||||
* 打开扫码页并等待一次结果。
|
||||
*
|
||||
* 用户取消时抛 code 为 `CANCELLED` 的错误,而不是返回 null——
|
||||
* 「取消」和「扫到了空字符串」必须能区分开。
|
||||
*/
|
||||
fun startScan(options: ScanOptions, callback: (Result<ScanResult>) -> Unit)
|
||||
/**
|
||||
* 当前平台是否支持指定识别类型。
|
||||
*
|
||||
* 车牌识别的技术路径未定(见上),首版可能只有部分平台支持;
|
||||
* 调用方应当先查这个再决定要不要显示入口,而不是等 `startScan` 抛错。
|
||||
*/
|
||||
fun isModeSupported(mode: ScanMode): Boolean
|
||||
|
||||
companion object {
|
||||
/** The codec used by ScanHostApi. */
|
||||
val codec: MessageCodec<Any?> by lazy {
|
||||
ScanApiPigeonCodec()
|
||||
}
|
||||
/** Sets up an instance of `ScanHostApi` to handle messages through the `binaryMessenger`. */
|
||||
@JvmOverloads
|
||||
fun setUp(binaryMessenger: BinaryMessenger, api: ScanHostApi?, messageChannelSuffix: String = "") {
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.native_scan.ScanHostApi.startScan$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val optionsArg = args[0] as ScanOptions
|
||||
api.startScan(optionsArg) { result: Result<ScanResult> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(ScanApiPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
val data = result.getOrNull()
|
||||
reply.reply(ScanApiPigeonUtils.wrapResult(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.native_scan.ScanHostApi.isModeSupported$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val modeArg = args[0] as ScanMode
|
||||
val wrapped: List<Any?> = try {
|
||||
listOf(api.isModeSupported(modeArg))
|
||||
} catch (exception: Throwable) {
|
||||
ScanApiPigeonUtils.wrapError(exception)
|
||||
}
|
||||
reply.reply(wrapped)
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
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-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
/coverage/
|
||||
|
||||
# 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
|
||||
@@ -0,0 +1,17 @@
|
||||
# native_scan_example
|
||||
|
||||
Demonstrates how to use the native_scan plugin.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
|
||||
- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources)
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
@@ -0,0 +1 @@
|
||||
include: ../../../analysis_options.yaml
|
||||
@@ -0,0 +1,14 @@
|
||||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
.cxx/
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
@@ -0,0 +1,45 @@
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.conti.native_scan_example"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.conti.native_scan_example"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = flutter.minSdkVersion
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// TODO: Add your own signing config for the release build.
|
||||
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||
signingConfig = signingConfigs.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,45 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application
|
||||
android:label="native_scan_example"
|
||||
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">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<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>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.conti.native_scan_example
|
||||
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
|
||||
class MainActivity : FlutterActivity()
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
After Width: | Height: | Size: 544 B |
|
After Width: | Height: | Size: 442 B |
|
After Width: | Height: | Size: 721 B |
|
After Width: | Height: | Size: 1.0 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,24 @@
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
val newBuildDir: Directory =
|
||||
rootProject.layout.buildDirectory
|
||||
.dir("../../build")
|
||||
.get()
|
||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||
|
||||
subprojects {
|
||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
# This newDsl flag was added by the Flutter template
|
||||
android.newDsl=false
|
||||
# This builtInKotlin flag was added by the Flutter template
|
||||
android.builtInKotlin=false
|
||||
@@ -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-9.1.0-all.zip
|
||||
@@ -0,0 +1,26 @@
|
||||
pluginManagement {
|
||||
val flutterSdkPath =
|
||||
run {
|
||||
val properties = java.util.Properties()
|
||||
file("local.properties").inputStream().use { properties.load(it) }
|
||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||
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 "9.0.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.3.20" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
@@ -0,0 +1,19 @@
|
||||
// 原生侧接通后才能跑的集成测试。
|
||||
//
|
||||
// 现在 Kotlin / Swift 实现还是空的(见 07),所以只验证「能力查询」这条
|
||||
// 不会崩、并且**如实返回 false**——静默返回 true 才是危险的假象。
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:native_scan/native_scan.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
testWidgets('isModeSupported 在原生未实现时返回 false 而不是抛错', (WidgetTester tester) async {
|
||||
final NativeScan plugin = NativeScan();
|
||||
|
||||
// TODO(native): 原生实现落地后改为 expect(..., isTrue),并补 startScan 的用例。
|
||||
expect(await plugin.isModeSupported(ScanMode.barcode), isFalse);
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,24 @@
|
||||
<?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>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1 @@
|
||||
#include "Generated.xcconfig"
|
||||
@@ -0,0 +1,648 @@
|
||||
// !$*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 */; };
|
||||
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
|
||||
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>"; };
|
||||
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
|
||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
|
||||
784666492D4C4C64000A1A5F /* FlutterFramework */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterFramework; path = Flutter/ephemeral/Packages/.packages/FlutterFramework; sourceTree = "<group>"; };
|
||||
78DABEA22ED26510000E7860 /* native_scan */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = native_scan; path = ../../ios/native_scan; 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 = (
|
||||
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
|
||||
);
|
||||
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 = (
|
||||
78DABEA22ED26510000E7860 /* native_scan */,
|
||||
784666492D4C4C64000A1A5F /* FlutterFramework */,
|
||||
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
|
||||
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 */,
|
||||
7884E8672EC3CC0400C636F2 /* SceneDelegate.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;
|
||||
packageProductDependencies = (
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
|
||||
);
|
||||
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;
|
||||
packageReferences = (
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
|
||||
);
|
||||
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 */,
|
||||
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift 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 = 13.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.conti.nativeScanExample;
|
||||
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.conti.nativeScanExample.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.conti.nativeScanExample.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.conti.nativeScanExample.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 = 13.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 = 13.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.conti.nativeScanExample;
|
||||
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.conti.nativeScanExample;
|
||||
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 */
|
||||
|
||||
/* Begin XCLocalSwiftPackageReference section */
|
||||
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
|
||||
isa = XCLocalSwiftPackageReference;
|
||||
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
|
||||
};
|
||||
/* End XCLocalSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = FlutterGeneratedPluginSwiftPackage;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
};
|
||||
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||
}
|
||||
@@ -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>
|
||||
@@ -0,0 +1,119 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1510"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<PreActions>
|
||||
<ExecutionAction
|
||||
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
|
||||
<ActionContent
|
||||
title = "Run Prepare Flutter Framework Script"
|
||||
scriptText = "/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" prepare ">
|
||||
<EnvironmentBuildable>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||
BuildableName = "Runner.app"
|
||||
BlueprintName = "Runner"
|
||||
ReferencedContainer = "container:Runner.xcodeproj">
|
||||
</BuildableReference>
|
||||
</EnvironmentBuildable>
|
||||
</ActionContent>
|
||||
</ExecutionAction>
|
||||
</PreActions>
|
||||
<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"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
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"
|
||||
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,16 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
@main
|
||||
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||
) -> Bool {
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
|
||||
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
|
||||
}
|
||||
}
|
||||
@@ -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 |
@@ -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"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
|
After Width: | Height: | Size: 68 B |
@@ -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.
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Native Scan</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>native_scan_example</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>$(FLUTTER_BUILD_NUMBER)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<false/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict>
|
||||
<key>UIWindowSceneSessionRoleApplication</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UISceneClassName</key>
|
||||
<string>UIWindowScene</string>
|
||||
<key>UISceneConfigurationName</key>
|
||||
<string>flutter</string>
|
||||
<key>UISceneDelegateClassName</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
|
||||
<key>UISceneStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIMainStoryboardFile</key>
|
||||
<string>Main</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1 @@
|
||||
#import "GeneratedPluginRegistrant.h"
|
||||
@@ -0,0 +1,6 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
class SceneDelegate: FlutterSceneDelegate {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
import XCTest
|
||||
|
||||
// If your plugin has been explicitly set to "type: .dynamic" in the Package.swift,
|
||||
// you will need to add your plugin as a dependency of RunnerTests within Xcode.
|
||||
|
||||
@testable import native_scan
|
||||
|
||||
// This demonstrates a simple unit test of the Swift portion of this plugin's implementation.
|
||||
//
|
||||
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
|
||||
|
||||
class RunnerTests: XCTestCase {
|
||||
|
||||
func testGetPlatformVersion() {
|
||||
let plugin = NativeScanPlugin()
|
||||
|
||||
let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: [])
|
||||
|
||||
let resultExpectation = expectation(description: "result block must be called.")
|
||||
plugin.handle(call) { result in
|
||||
XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion)
|
||||
resultExpectation.fulfill()
|
||||
}
|
||||
waitForExpectations(timeout: 1)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// native_scan 的最小演示壳。
|
||||
//
|
||||
// 只用来手工验证原生实现接没接上:Kotlin / Swift 侧还没写(见 07),
|
||||
// 所以现在点「扫码」必然走到 UNSUPPORTED_PLATFORM 分支——这是预期结果,
|
||||
// 也正好演示了 07 §「OHOS 后续演进」要求的**不静默返回假数据**。
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:native_scan/native_scan.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const ExampleApp());
|
||||
}
|
||||
|
||||
/// 演示用的根 Widget。
|
||||
class ExampleApp extends StatefulWidget {
|
||||
/// 构造。
|
||||
const ExampleApp({super.key});
|
||||
|
||||
@override
|
||||
State<ExampleApp> createState() => _ExampleAppState();
|
||||
}
|
||||
|
||||
class _ExampleAppState extends State<ExampleApp> {
|
||||
final NativeScan _scan = NativeScan();
|
||||
String _result = '未扫码';
|
||||
|
||||
Future<void> _startScan() async {
|
||||
try {
|
||||
final ScanResult result = await _scan.startScan(
|
||||
ScanOptions(mode: ScanMode.barcode, title: '扫描商品条码'),
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_result = '${result.value}(${result.durationMs}ms)';
|
||||
});
|
||||
} on NativeScanException catch (e) {
|
||||
if (!mounted) return;
|
||||
// 取消不是错误,静默回到原状态即可(07 §使用规则)。
|
||||
setState(() {
|
||||
_result = e.isCancelled ? '已取消' : '${e.code}: ${e.message}';
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
appBar: AppBar(title: const Text('native_scan example')),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Text(_result),
|
||||
const SizedBox(height: 16),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
unawaited(_startScan());
|
||||
},
|
||||
child: const Text('扫码'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
name: native_scan_example
|
||||
description: native_scan 的手动验证入口。补完原生实现后用它单独跑扫码,不用起整个 App。
|
||||
publish_to: none
|
||||
resolution: workspace
|
||||
|
||||
environment:
|
||||
sdk: ^3.12.0
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
native_scan: ^0.1.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^6.0.0
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
integration_test:
|
||||
sdk: flutter
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
@@ -0,0 +1,17 @@
|
||||
// example 的 smoke test:只验证壳能起来。
|
||||
//
|
||||
// 真正的扫码行为依赖原生实现,单测里跑不了;原生接通后请写
|
||||
// integration_test 而不是在这里 mock。
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:native_scan_example/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('渲染出扫码入口', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(const ExampleApp());
|
||||
|
||||
expect(find.text('未扫码'), findsOneWidget);
|
||||
expect(find.byWidgetPredicate((Widget w) => w is FilledButton), findsOneWidget);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
.idea/
|
||||
.vagrant/
|
||||
.sconsign.dblite
|
||||
.svn/
|
||||
|
||||
.DS_Store
|
||||
*.swp
|
||||
profile
|
||||
|
||||
DerivedData/
|
||||
build/
|
||||
GeneratedPluginRegistrant.h
|
||||
GeneratedPluginRegistrant.m
|
||||
|
||||
.generated/
|
||||
|
||||
*.pbxuser
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.perspectivev3
|
||||
|
||||
!default.pbxuser
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.perspectivev3
|
||||
|
||||
xcuserdata
|
||||
|
||||
*.moved-aside
|
||||
|
||||
*.pyc
|
||||
*sync/
|
||||
Icon?
|
||||
.tags*
|
||||
|
||||
/Flutter/Generated.xcconfig
|
||||
/Flutter/ephemeral/
|
||||
/Flutter/flutter_export_environment.sh
|
||||
@@ -0,0 +1,29 @@
|
||||
#
|
||||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
|
||||
# Run `pod lib lint native_scan.podspec` to validate before publishing.
|
||||
#
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'native_scan'
|
||||
s.version = '0.0.1'
|
||||
s.summary = 'A new Flutter plugin project.'
|
||||
s.description = <<-DESC
|
||||
A new Flutter plugin project.
|
||||
DESC
|
||||
s.homepage = 'http://example.com'
|
||||
s.license = { :file => '../LICENSE' }
|
||||
s.author = { 'Your Company' => 'email@example.com' }
|
||||
s.source = { :path => '.' }
|
||||
s.source_files = 'native_scan/Sources/native_scan/**/*'
|
||||
s.dependency 'Flutter'
|
||||
s.platform = :ios, '13.0'
|
||||
|
||||
# Flutter.framework does not contain a i386 slice.
|
||||
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
|
||||
s.swift_version = '5.0'
|
||||
|
||||
# If your plugin requires a privacy manifest, for example if it uses any
|
||||
# required reason APIs, update the PrivacyInfo.xcprivacy file to describe your
|
||||
# plugin's privacy impact, and then uncomment this line. For more information,
|
||||
# see https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
|
||||
# s.resource_bundles = {'native_scan_privacy' => ['native_scan/Sources/native_scan/PrivacyInfo.xcprivacy']}
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
// swift-tools-version: 5.9
|
||||
// The swift-tools-version declares the minimum version of Swift required to build this package.
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "native_scan",
|
||||
platforms: [
|
||||
.iOS("13.0")
|
||||
],
|
||||
products: [
|
||||
.library(name: "native-scan", targets: ["native_scan"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(name: "FlutterFramework", path: "../FlutterFramework")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "native_scan",
|
||||
dependencies: [
|
||||
.product(name: "FlutterFramework", package: "FlutterFramework")
|
||||
],
|
||||
resources: [
|
||||
// If your plugin requires a privacy manifest, for example if it uses any required
|
||||
// reason APIs, update the PrivacyInfo.xcprivacy file to describe your plugin's
|
||||
// privacy impact, and then uncomment these lines. For more information, see
|
||||
// https://developer.apple.com/documentation/bundleresources/privacy_manifest_files
|
||||
// .process("PrivacyInfo.xcprivacy"),
|
||||
|
||||
// If you have other resources that need to be bundled with your plugin, refer to
|
||||
// the following instructions to add them:
|
||||
// https://developer.apple.com/documentation/xcode/bundling-resources-with-a-swift-package
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
import Flutter
|
||||
import UIKit
|
||||
|
||||
/// 扫码插件的 iOS 入口。
|
||||
///
|
||||
/// TODO(native): 手写原生实现本次不在范围内,见 conti-docs/07-native-integration.md。
|
||||
/// 补完时需要做的事:
|
||||
/// 1. 让某个类实现 pigeon 生成的 `ScanHostApi`(见 ScanApi.g.swift,不要手改生成物);
|
||||
/// 2. 在 register 里 `ScanHostApiSetup.setUp(binaryMessenger:api:)`;
|
||||
/// 3. Info.plist 需要 NSCameraUsageDescription,否则相机权限弹窗会直接崩;
|
||||
/// 4. 取消 / 权限拒绝 / 能力不可用必须回 FlutterError,code 用
|
||||
/// CANCELLED / PERMISSION_DENIED / UNAVAILABLE —— 与 Dart 侧
|
||||
/// NativeScanErrorCode 一一对应,**不允许返回占位假数据**。
|
||||
///
|
||||
/// 在此之前,Dart 侧调用会收到 MissingPluginException,
|
||||
/// 并被 NativeScan 转成 UNSUPPORTED_PLATFORM,属预期行为。
|
||||
public class NativeScanPlugin: NSObject, FlutterPlugin {
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
// TODO(native): ScanHostApiSetup.setUp(binaryMessenger: registrar.messenger(), api: ScanApiImpl())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?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>NSPrivacyTrackingDomains</key>
|
||||
<array/>
|
||||
<key>NSPrivacyAccessedAPITypes</key>
|
||||
<array/>
|
||||
<key>NSPrivacyCollectedDataTypes</key>
|
||||
<array/>
|
||||
<key>NSPrivacyTracking</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,432 @@
|
||||
// Autogenerated from Pigeon (v27.3.0), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
|
||||
import Foundation
|
||||
|
||||
#if os(iOS)
|
||||
import Flutter
|
||||
#elseif os(macOS)
|
||||
import FlutterMacOS
|
||||
#else
|
||||
#error("Unsupported platform.")
|
||||
#endif
|
||||
|
||||
/// Error class for passing custom error details to Dart side.
|
||||
final class PigeonError: Error {
|
||||
let code: String
|
||||
let message: String?
|
||||
let details: Sendable?
|
||||
|
||||
init(code: String, message: String?, details: Sendable?) {
|
||||
self.code = code
|
||||
self.message = message
|
||||
self.details = details
|
||||
}
|
||||
|
||||
var localizedDescription: String {
|
||||
return
|
||||
"PigeonError(code: \(code), message: \(message ?? "<nil>"), details: \(details ?? "<nil>")"
|
||||
}
|
||||
}
|
||||
|
||||
private func wrapResult(_ result: Any?) -> [Any?] {
|
||||
return [result]
|
||||
}
|
||||
|
||||
private func wrapError(_ error: Any) -> [Any?] {
|
||||
if let pigeonError = error as? PigeonError {
|
||||
return [
|
||||
pigeonError.code,
|
||||
pigeonError.message,
|
||||
pigeonError.details,
|
||||
]
|
||||
}
|
||||
if let flutterError = error as? FlutterError {
|
||||
return [
|
||||
flutterError.code,
|
||||
flutterError.message,
|
||||
flutterError.details,
|
||||
]
|
||||
}
|
||||
return [
|
||||
"\(error)",
|
||||
"\(Swift.type(of: error))",
|
||||
"Stacktrace: \(Thread.callStackSymbols)",
|
||||
]
|
||||
}
|
||||
|
||||
enum ScanApiPigeonInternal {
|
||||
static func isNullish(_ value: Any?) -> Bool {
|
||||
guard let innerValue = value else {
|
||||
return true
|
||||
}
|
||||
|
||||
if case Optional<Any>.some(Optional<Any>.none) = value {
|
||||
return true
|
||||
}
|
||||
|
||||
return innerValue is NSNull
|
||||
}
|
||||
static func doubleEquals(_ lhs: Double, _ rhs: Double) -> Bool {
|
||||
return (lhs.isNaN && rhs.isNaN) || lhs == rhs
|
||||
}
|
||||
|
||||
static func doubleHash(_ value: Double, _ hasher: inout Hasher) {
|
||||
if value.isNaN {
|
||||
hasher.combine(0x7FF8000000000000)
|
||||
} else {
|
||||
// Normalize -0.0 to 0.0
|
||||
hasher.combine(value == 0 ? 0 : value)
|
||||
}
|
||||
}
|
||||
|
||||
static func deepEquals(_ lhs: Any?, _ rhs: Any?) -> Bool {
|
||||
let cleanLhs = nilOrValue(lhs) as Any?
|
||||
let cleanRhs = nilOrValue(rhs) as Any?
|
||||
switch (cleanLhs, cleanRhs) {
|
||||
case (nil, nil):
|
||||
return true
|
||||
|
||||
case (nil, _), (_, nil):
|
||||
return false
|
||||
|
||||
case (let lhs as AnyObject, let rhs as AnyObject) where lhs === rhs:
|
||||
return true
|
||||
|
||||
case is (Void, Void):
|
||||
return true
|
||||
|
||||
case (let lhsArray, let rhsArray) as ([Any?], [Any?]):
|
||||
guard lhsArray.count == rhsArray.count else { return false }
|
||||
for (index, element) in lhsArray.enumerated() {
|
||||
if !deepEquals(element, rhsArray[index]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
case (let lhsArray, let rhsArray) as ([Double], [Double]):
|
||||
guard lhsArray.count == rhsArray.count else { return false }
|
||||
for (index, element) in lhsArray.enumerated() {
|
||||
if !doubleEquals(element, rhsArray[index]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
|
||||
case (let lhsDictionary, let rhsDictionary) as ([AnyHashable: Any?], [AnyHashable: Any?]):
|
||||
guard lhsDictionary.count == rhsDictionary.count else { return false }
|
||||
for (lhsKey, lhsValue) in lhsDictionary {
|
||||
var found = false
|
||||
for (rhsKey, rhsValue) in rhsDictionary {
|
||||
if deepEquals(lhsKey, rhsKey) {
|
||||
if deepEquals(lhsValue, rhsValue) {
|
||||
found = true
|
||||
break
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
if !found { return false }
|
||||
}
|
||||
return true
|
||||
|
||||
case (let lhs as Double, let rhs as Double):
|
||||
return doubleEquals(lhs, rhs)
|
||||
|
||||
case (let lhsHashable, let rhsHashable) as (AnyHashable, AnyHashable):
|
||||
return lhsHashable == rhsHashable
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
static func deepHash(value: Any?, hasher: inout Hasher) {
|
||||
let cleanValue = nilOrValue(value) as Any?
|
||||
if let cleanValue = cleanValue {
|
||||
if let doubleValue = cleanValue as? Double {
|
||||
doubleHash(doubleValue, &hasher)
|
||||
} else if let valueList = cleanValue as? [Any?] {
|
||||
for item in valueList {
|
||||
deepHash(value: item, hasher: &hasher)
|
||||
}
|
||||
} else if let valueList = cleanValue as? [Double] {
|
||||
for item in valueList {
|
||||
doubleHash(item, &hasher)
|
||||
}
|
||||
} else if let valueDict = cleanValue as? [AnyHashable: Any?] {
|
||||
var result = 0
|
||||
for (key, value) in valueDict {
|
||||
var entryKeyHasher = Hasher()
|
||||
deepHash(value: key, hasher: &entryKeyHasher)
|
||||
var entryValueHasher = Hasher()
|
||||
deepHash(value: value, hasher: &entryValueHasher)
|
||||
result = result &+ ((entryKeyHasher.finalize() &* 31) ^ entryValueHasher.finalize())
|
||||
}
|
||||
hasher.combine(result)
|
||||
} else if let hashableValue = cleanValue as? AnyHashable {
|
||||
hasher.combine(hashableValue)
|
||||
} else {
|
||||
hasher.combine(String(describing: cleanValue))
|
||||
}
|
||||
} else {
|
||||
hasher.combine(0)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private func nilOrValue<T>(_ value: Any?) -> T? {
|
||||
if value is NSNull { return nil }
|
||||
return value as! T?
|
||||
}
|
||||
|
||||
|
||||
/// 识别类型。
|
||||
///
|
||||
/// **即使首版只做条码,这个参数也必须先留出来**(07 §「待确认:VIN 码与车牌
|
||||
/// 识别的技术路径」):车牌走的是专用 OCR、VIN 印刷字符走通用 OCR + 校验位
|
||||
/// 过滤,技术路径还没定。参数先在 schema 里占好位,后面加识别类型就不用改
|
||||
/// 接口签名——改签名意味着三端生成物和所有调用点一起动。
|
||||
enum ScanMode: Int, CaseIterable {
|
||||
/// 二维码 / 条形码(商品、库位)。
|
||||
case barcode = 0
|
||||
/// VIN 码。可能是 Code 39 条码,也可能只有印刷字符。
|
||||
case vin = 1
|
||||
/// 车牌。
|
||||
case plate = 2
|
||||
}
|
||||
|
||||
/// 扫码入参。
|
||||
///
|
||||
/// Generated class from Pigeon that represents data sent in messages.
|
||||
struct ScanOptions: Hashable, CustomStringConvertible {
|
||||
/// 识别类型。
|
||||
var mode: ScanMode
|
||||
/// 超时毫秒数。null 表示不超时,由用户手动取消。
|
||||
var timeoutMs: Int64? = nil
|
||||
/// 是否默认打开闪光灯。
|
||||
var torchEnabled: Bool? = nil
|
||||
/// 扫码页标题。由调用方传,`native_scan` 不依赖任何 i18n 资源。
|
||||
var title: String? = nil
|
||||
|
||||
|
||||
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||
static func fromList(_ pigeonVar_list: [Any?]) -> ScanOptions? {
|
||||
let mode = pigeonVar_list[0] as! ScanMode
|
||||
let timeoutMs: Int64? = nilOrValue(pigeonVar_list[1])
|
||||
let torchEnabled: Bool? = nilOrValue(pigeonVar_list[2])
|
||||
let title: String? = nilOrValue(pigeonVar_list[3])
|
||||
|
||||
return ScanOptions(
|
||||
mode: mode,
|
||||
timeoutMs: timeoutMs,
|
||||
torchEnabled: torchEnabled,
|
||||
title: title
|
||||
)
|
||||
}
|
||||
func toList() -> [Any?] {
|
||||
return [
|
||||
mode,
|
||||
timeoutMs,
|
||||
torchEnabled,
|
||||
title,
|
||||
]
|
||||
}
|
||||
static func == (lhs: ScanOptions, rhs: ScanOptions) -> Bool {
|
||||
if Swift.type(of: lhs) != Swift.type(of: rhs) {
|
||||
return false
|
||||
}
|
||||
return ScanApiPigeonInternal.deepEquals(lhs.mode, rhs.mode) && ScanApiPigeonInternal.deepEquals(lhs.timeoutMs, rhs.timeoutMs) && ScanApiPigeonInternal.deepEquals(lhs.torchEnabled, rhs.torchEnabled) && ScanApiPigeonInternal.deepEquals(lhs.title, rhs.title)
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine("ScanOptions")
|
||||
ScanApiPigeonInternal.deepHash(value: mode, hasher: &hasher)
|
||||
ScanApiPigeonInternal.deepHash(value: timeoutMs, hasher: &hasher)
|
||||
ScanApiPigeonInternal.deepHash(value: torchEnabled, hasher: &hasher)
|
||||
ScanApiPigeonInternal.deepHash(value: title, hasher: &hasher)
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return "ScanOptions(mode: \(String(describing: mode)), timeoutMs: \(String(describing: timeoutMs)), torchEnabled: \(String(describing: torchEnabled)), title: \(String(describing: title)))"
|
||||
}
|
||||
}
|
||||
|
||||
/// 扫码结果。
|
||||
///
|
||||
/// Generated class from Pigeon that represents data sent in messages.
|
||||
struct ScanResult: Hashable, CustomStringConvertible {
|
||||
/// 实际生效的识别类型。
|
||||
var mode: ScanMode
|
||||
/// 识别到的文本。
|
||||
var value: String
|
||||
/// 从打开扫码页到出结果的耗时,供埋点用(见 13 的 `scan_succeeded`)。
|
||||
var durationMs: Int64
|
||||
/// 原始码制(如 `CODE_39` / `QR_CODE`)。OCR 路径下为 null。
|
||||
var rawFormat: String? = nil
|
||||
|
||||
|
||||
// swift-format-ignore: AlwaysUseLowerCamelCase
|
||||
static func fromList(_ pigeonVar_list: [Any?]) -> ScanResult? {
|
||||
let mode = pigeonVar_list[0] as! ScanMode
|
||||
let value = pigeonVar_list[1] as! String
|
||||
let durationMs = pigeonVar_list[2] as! Int64
|
||||
let rawFormat: String? = nilOrValue(pigeonVar_list[3])
|
||||
|
||||
return ScanResult(
|
||||
mode: mode,
|
||||
value: value,
|
||||
durationMs: durationMs,
|
||||
rawFormat: rawFormat
|
||||
)
|
||||
}
|
||||
func toList() -> [Any?] {
|
||||
return [
|
||||
mode,
|
||||
value,
|
||||
durationMs,
|
||||
rawFormat,
|
||||
]
|
||||
}
|
||||
static func == (lhs: ScanResult, rhs: ScanResult) -> Bool {
|
||||
if Swift.type(of: lhs) != Swift.type(of: rhs) {
|
||||
return false
|
||||
}
|
||||
return ScanApiPigeonInternal.deepEquals(lhs.mode, rhs.mode) && ScanApiPigeonInternal.deepEquals(lhs.value, rhs.value) && ScanApiPigeonInternal.deepEquals(lhs.durationMs, rhs.durationMs) && ScanApiPigeonInternal.deepEquals(lhs.rawFormat, rhs.rawFormat)
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine("ScanResult")
|
||||
ScanApiPigeonInternal.deepHash(value: mode, hasher: &hasher)
|
||||
ScanApiPigeonInternal.deepHash(value: value, hasher: &hasher)
|
||||
ScanApiPigeonInternal.deepHash(value: durationMs, hasher: &hasher)
|
||||
ScanApiPigeonInternal.deepHash(value: rawFormat, hasher: &hasher)
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return "ScanResult(mode: \(String(describing: mode)), value: \(String(describing: value)), durationMs: \(String(describing: durationMs)), rawFormat: \(String(describing: rawFormat)))"
|
||||
}
|
||||
}
|
||||
|
||||
private class ScanApiPigeonCodecReader: FlutterStandardReader {
|
||||
override func readValue(ofType type: UInt8) -> Any? {
|
||||
switch type {
|
||||
case 129:
|
||||
let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?)
|
||||
if let enumResultAsInt = enumResultAsInt {
|
||||
return ScanMode(rawValue: enumResultAsInt)
|
||||
}
|
||||
return nil
|
||||
case 130:
|
||||
return ScanOptions.fromList(self.readValue() as! [Any?])
|
||||
case 131:
|
||||
return ScanResult.fromList(self.readValue() as! [Any?])
|
||||
default:
|
||||
return super.readValue(ofType: type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ScanApiPigeonCodecWriter: FlutterStandardWriter {
|
||||
override func writeValue(_ value: Any) {
|
||||
if let value = value as? ScanMode {
|
||||
super.writeByte(129)
|
||||
super.writeValue(value.rawValue)
|
||||
} else if let value = value as? ScanOptions {
|
||||
super.writeByte(130)
|
||||
super.writeValue(value.toList())
|
||||
} else if let value = value as? ScanResult {
|
||||
super.writeByte(131)
|
||||
super.writeValue(value.toList())
|
||||
} else {
|
||||
super.writeValue(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ScanApiPigeonCodecReaderWriter: FlutterStandardReaderWriter {
|
||||
override func reader(with data: Data) -> FlutterStandardReader {
|
||||
return ScanApiPigeonCodecReader(data: data)
|
||||
}
|
||||
|
||||
override func writer(with data: NSMutableData) -> FlutterStandardWriter {
|
||||
return ScanApiPigeonCodecWriter(data: data)
|
||||
}
|
||||
}
|
||||
|
||||
class ScanApiPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable {
|
||||
static let shared = ScanApiPigeonCodec(readerWriter: ScanApiPigeonCodecReaderWriter())
|
||||
}
|
||||
|
||||
|
||||
/// Dart → 原生。
|
||||
///
|
||||
/// 用户取消、权限拒绝、平台未实现这三类都通过 `FlutterError` 抛出,
|
||||
/// 由 Dart 侧的公共 API 转成 `NativeScanException`——**原生异常类型
|
||||
/// (`PlatformException`)不允许直接抛到业务代码里**(07 §使用规则)。
|
||||
///
|
||||
/// Generated protocol from Pigeon that represents a handler of messages from Flutter.
|
||||
protocol ScanHostApi {
|
||||
/// 打开扫码页并等待一次结果。
|
||||
///
|
||||
/// 用户取消时抛 code 为 `CANCELLED` 的错误,而不是返回 null——
|
||||
/// 「取消」和「扫到了空字符串」必须能区分开。
|
||||
func startScan(options: ScanOptions, completion: @escaping (Result<ScanResult, Error>) -> Void)
|
||||
/// 当前平台是否支持指定识别类型。
|
||||
///
|
||||
/// 车牌识别的技术路径未定(见上),首版可能只有部分平台支持;
|
||||
/// 调用方应当先查这个再决定要不要显示入口,而不是等 `startScan` 抛错。
|
||||
func isModeSupported(mode: ScanMode) throws -> Bool
|
||||
}
|
||||
|
||||
/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`.
|
||||
class ScanHostApiSetup {
|
||||
static var codec: FlutterStandardMessageCodec { ScanApiPigeonCodec.shared }
|
||||
/// Sets up an instance of `ScanHostApi` to handle messages through the `binaryMessenger`.
|
||||
static func setUp(binaryMessenger: FlutterBinaryMessenger, api: ScanHostApi?, messageChannelSuffix: String = "") {
|
||||
let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : ""
|
||||
/// 打开扫码页并等待一次结果。
|
||||
///
|
||||
/// 用户取消时抛 code 为 `CANCELLED` 的错误,而不是返回 null——
|
||||
/// 「取消」和「扫到了空字符串」必须能区分开。
|
||||
let startScanChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.native_scan.ScanHostApi.startScan\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
startScanChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let optionsArg = args[0] as! ScanOptions
|
||||
api.startScan(options: optionsArg) { result in
|
||||
switch result {
|
||||
case .success(let res):
|
||||
reply(wrapResult(res))
|
||||
case .failure(let error):
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
startScanChannel.setMessageHandler(nil)
|
||||
}
|
||||
/// 当前平台是否支持指定识别类型。
|
||||
///
|
||||
/// 车牌识别的技术路径未定(见上),首版可能只有部分平台支持;
|
||||
/// 调用方应当先查这个再决定要不要显示入口,而不是等 `startScan` 抛错。
|
||||
let isModeSupportedChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.native_scan.ScanHostApi.isModeSupported\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec)
|
||||
if let api = api {
|
||||
isModeSupportedChannel.setMessageHandler { message, reply in
|
||||
let args = message as! [Any?]
|
||||
let modeArg = args[0] as! ScanMode
|
||||
do {
|
||||
let result = try api.isModeSupported(mode: modeArg)
|
||||
reply(wrapResult(result))
|
||||
} catch {
|
||||
reply(wrapError(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
isModeSupportedChannel.setMessageHandler(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/// 扫码能力的对外 API。来源:conti-docs/07-native-integration.md。
|
||||
///
|
||||
/// **调用方只允许 import 这个文件**,不允许直接 import `src/generated/` 里的
|
||||
/// 生成代码(07 §使用规则)。调用方包括 `feature_scan` 和 `core_webview` 的
|
||||
/// JSBridge——后者正是 01 里「`core_*` 允许依赖 `native_*`」这条例外存在的
|
||||
/// 原因。
|
||||
///
|
||||
/// 本包按 01 的硬约束**不依赖仓库内任何其他包**(连 `core_foundation` 也不),
|
||||
/// 所以这里抛的是包内自定义的 [NativeScanException];转成统一错误体系里的
|
||||
/// `NativeException` 由调用方完成。
|
||||
library;
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'src/generated/scan_api.g.dart';
|
||||
|
||||
export 'src/generated/scan_api.g.dart' show ScanMode, ScanOptions, ScanResult;
|
||||
|
||||
/// 扫码失败的错误码。
|
||||
///
|
||||
/// 取值与 12 的 `NativeException.code` 对齐,调用方可以直接透传。
|
||||
abstract final class NativeScanErrorCode {
|
||||
/// 用户主动取消。
|
||||
///
|
||||
/// 这不是异常流程,调用方通常应当静默返回,**不弹错误提示、不上报**。
|
||||
static const String cancelled = 'CANCELLED';
|
||||
|
||||
/// 相机权限被拒绝。
|
||||
static const String permissionDenied = 'PERMISSION_DENIED';
|
||||
|
||||
/// 能力暂时不可用(相机被占用、初始化失败等)。
|
||||
static const String unavailable = 'UNAVAILABLE';
|
||||
|
||||
/// 当前平台没有实现。
|
||||
///
|
||||
/// 见 07 §「OHOS 后续演进」:**不允许静默返回空值或占位假数据**——
|
||||
/// 静默返回会让"这个平台其实没实现"的问题一直藏到用户手里。
|
||||
static const String unsupportedPlatform = 'UNSUPPORTED_PLATFORM';
|
||||
|
||||
/// 超时。
|
||||
static const String timeout = 'TIMEOUT';
|
||||
}
|
||||
|
||||
/// 扫码相关的异常。
|
||||
class NativeScanException implements Exception {
|
||||
/// [code] 取自 [NativeScanErrorCode]。
|
||||
const NativeScanException(this.code, this.message);
|
||||
|
||||
/// 稳定错误码。
|
||||
final String code;
|
||||
|
||||
/// 面向开发者的描述。**不要直接展示给用户**——文案由调用方按 12 的
|
||||
/// `ErrorPresenter` 决定。
|
||||
final String message;
|
||||
|
||||
/// 是否是用户主动取消。
|
||||
bool get isCancelled => code == NativeScanErrorCode.cancelled;
|
||||
|
||||
@override
|
||||
String toString() => 'NativeScanException($code): $message';
|
||||
}
|
||||
|
||||
/// 扫码。
|
||||
class NativeScan {
|
||||
/// [api] 仅供测试注入;生产走默认实例。
|
||||
NativeScan({ScanHostApi? api}) : _api = api ?? ScanHostApi();
|
||||
|
||||
final ScanHostApi _api;
|
||||
|
||||
/// 打开扫码页并等待一次结果。
|
||||
///
|
||||
/// 用户取消时抛 [NativeScanException](`code == CANCELLED`)而不是返回 null——
|
||||
/// 「取消」和「扫到了空字符串」必须能区分开。
|
||||
Future<ScanResult> startScan(ScanOptions options) async {
|
||||
try {
|
||||
return await _api.startScan(options);
|
||||
} on PlatformException catch (e) {
|
||||
// 原生异常类型不外泄(07 §使用规则)。
|
||||
throw NativeScanException(e.code, e.message ?? '扫码失败');
|
||||
} on MissingPluginException {
|
||||
throw const NativeScanException(NativeScanErrorCode.unsupportedPlatform, '当前平台未实现扫码能力');
|
||||
}
|
||||
}
|
||||
|
||||
/// 当前平台是否支持指定识别类型。
|
||||
///
|
||||
/// 调用方应当先查这个再决定要不要显示入口,而不是等 [startScan] 抛错——
|
||||
/// 车牌识别的技术路径尚未确定(07 待确认项),首版可能只有部分平台支持。
|
||||
Future<bool> isModeSupported(ScanMode mode) async {
|
||||
try {
|
||||
return await _api.isModeSupported(mode);
|
||||
} on PlatformException {
|
||||
return false;
|
||||
} on MissingPluginException {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,335 @@
|
||||
// Autogenerated from Pigeon (v27.3.0), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
// ignore_for_file: unused_import, unused_shown_name
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data' show Float64List, Int32List, Int64List;
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
|
||||
|
||||
Object? _extractReplyValueOrThrow(
|
||||
List<Object?>? replyList,
|
||||
String channelName, {
|
||||
required bool isNullValid,
|
||||
}) {
|
||||
if (replyList == null) {
|
||||
throw PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel: "$channelName".',
|
||||
);
|
||||
} else if (replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: replyList[0]! as String,
|
||||
message: replyList[1] as String?,
|
||||
details: replyList[2],
|
||||
);
|
||||
} else if (!isNullValid && (replyList.isNotEmpty && replyList[0] == null)) {
|
||||
throw PlatformException(
|
||||
code: 'null-error',
|
||||
message: 'Host platform returned null value for non-null return value.',
|
||||
);
|
||||
}
|
||||
return replyList.firstOrNull;
|
||||
}
|
||||
|
||||
bool _deepEquals(Object? a, Object? b) {
|
||||
if (identical(a, b)) {
|
||||
return true;
|
||||
}
|
||||
if (a is double && b is double) {
|
||||
if (a.isNaN && b.isNaN) {
|
||||
return true;
|
||||
}
|
||||
return a == b;
|
||||
}
|
||||
if (a is List && b is List) {
|
||||
return a.length == b.length &&
|
||||
a.indexed.every(((int, dynamic) item) => _deepEquals(item.$2, b[item.$1]));
|
||||
}
|
||||
if (a is Map && b is Map) {
|
||||
if (a.length != b.length) {
|
||||
return false;
|
||||
}
|
||||
for (final MapEntry<Object?, Object?> entryA in a.entries) {
|
||||
bool found = false;
|
||||
for (final MapEntry<Object?, Object?> entryB in b.entries) {
|
||||
if (_deepEquals(entryA.key, entryB.key)) {
|
||||
if (_deepEquals(entryA.value, entryB.value)) {
|
||||
found = true;
|
||||
break;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return a == b;
|
||||
}
|
||||
|
||||
int _deepHash(Object? value) {
|
||||
if (value is List) {
|
||||
return Object.hashAll(value.map(_deepHash));
|
||||
}
|
||||
if (value is Map) {
|
||||
int result = 0;
|
||||
for (final MapEntry<Object?, Object?> entry in value.entries) {
|
||||
result += (_deepHash(entry.key) * 31) ^ _deepHash(entry.value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (value is double && value.isNaN) {
|
||||
// Normalize NaN to a consistent hash.
|
||||
return 0x7FF8000000000000.hashCode;
|
||||
}
|
||||
if (value is double && value == 0.0) {
|
||||
// Normalize -0.0 to 0.0 so they have the same hash code.
|
||||
return 0.0.hashCode;
|
||||
}
|
||||
return value.hashCode;
|
||||
}
|
||||
|
||||
/// 识别类型。
|
||||
///
|
||||
/// **即使首版只做条码,这个参数也必须先留出来**(07 §「待确认:VIN 码与车牌
|
||||
/// 识别的技术路径」):车牌走的是专用 OCR、VIN 印刷字符走通用 OCR + 校验位
|
||||
/// 过滤,技术路径还没定。参数先在 schema 里占好位,后面加识别类型就不用改
|
||||
/// 接口签名——改签名意味着三端生成物和所有调用点一起动。
|
||||
enum ScanMode {
|
||||
/// 二维码 / 条形码(商品、库位)。
|
||||
barcode,
|
||||
|
||||
/// VIN 码。可能是 Code 39 条码,也可能只有印刷字符。
|
||||
vin,
|
||||
|
||||
/// 车牌。
|
||||
plate,
|
||||
}
|
||||
|
||||
/// 扫码入参。
|
||||
class ScanOptions {
|
||||
ScanOptions({required this.mode, this.timeoutMs, this.torchEnabled, this.title});
|
||||
|
||||
/// 识别类型。
|
||||
ScanMode mode;
|
||||
|
||||
/// 超时毫秒数。null 表示不超时,由用户手动取消。
|
||||
int? timeoutMs;
|
||||
|
||||
/// 是否默认打开闪光灯。
|
||||
bool? torchEnabled;
|
||||
|
||||
/// 扫码页标题。由调用方传,`native_scan` 不依赖任何 i18n 资源。
|
||||
String? title;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[mode, timeoutMs, torchEnabled, title];
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList();
|
||||
}
|
||||
|
||||
static ScanOptions decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return ScanOptions(
|
||||
mode: result[0]! as ScanMode,
|
||||
timeoutMs: result[1] as int?,
|
||||
torchEnabled: result[2] as bool?,
|
||||
title: result[3] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
bool operator ==(Object other) {
|
||||
if (other is! ScanOptions || other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
return _deepEquals(mode, other.mode) &&
|
||||
_deepEquals(timeoutMs, other.timeoutMs) &&
|
||||
_deepEquals(torchEnabled, other.torchEnabled) &&
|
||||
_deepEquals(title, other.title);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ScanOptions(mode: $mode, timeoutMs: $timeoutMs, torchEnabled: $torchEnabled, title: $title)';
|
||||
}
|
||||
}
|
||||
|
||||
/// 扫码结果。
|
||||
class ScanResult {
|
||||
ScanResult({required this.mode, required this.value, required this.durationMs, this.rawFormat});
|
||||
|
||||
/// 实际生效的识别类型。
|
||||
ScanMode mode;
|
||||
|
||||
/// 识别到的文本。
|
||||
String value;
|
||||
|
||||
/// 从打开扫码页到出结果的耗时,供埋点用(见 13 的 `scan_succeeded`)。
|
||||
int durationMs;
|
||||
|
||||
/// 原始码制(如 `CODE_39` / `QR_CODE`)。OCR 路径下为 null。
|
||||
String? rawFormat;
|
||||
|
||||
List<Object?> _toList() {
|
||||
return <Object?>[mode, value, durationMs, rawFormat];
|
||||
}
|
||||
|
||||
Object encode() {
|
||||
return _toList();
|
||||
}
|
||||
|
||||
static ScanResult decode(Object result) {
|
||||
result as List<Object?>;
|
||||
return ScanResult(
|
||||
mode: result[0]! as ScanMode,
|
||||
value: result[1]! as String,
|
||||
durationMs: result[2]! as int,
|
||||
rawFormat: result[3] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
bool operator ==(Object other) {
|
||||
if (other is! ScanResult || other.runtimeType != runtimeType) {
|
||||
return false;
|
||||
}
|
||||
if (identical(this, other)) {
|
||||
return true;
|
||||
}
|
||||
return _deepEquals(mode, other.mode) &&
|
||||
_deepEquals(value, other.value) &&
|
||||
_deepEquals(durationMs, other.durationMs) &&
|
||||
_deepEquals(rawFormat, other.rawFormat);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: avoid_equals_and_hash_code_on_mutable_classes
|
||||
int get hashCode => _deepHash(<Object?>[runtimeType, ..._toList()]);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ScanResult(mode: $mode, value: $value, durationMs: $durationMs, rawFormat: $rawFormat)';
|
||||
}
|
||||
}
|
||||
|
||||
class _PigeonCodec extends StandardMessageCodec {
|
||||
const _PigeonCodec();
|
||||
@override
|
||||
void writeValue(WriteBuffer buffer, Object? value) {
|
||||
if (value is int) {
|
||||
buffer.putUint8(4);
|
||||
buffer.putInt64(value);
|
||||
} else if (value is ScanMode) {
|
||||
buffer.putUint8(129);
|
||||
writeValue(buffer, value.index);
|
||||
} else if (value is ScanOptions) {
|
||||
buffer.putUint8(130);
|
||||
writeValue(buffer, value.encode());
|
||||
} else if (value is ScanResult) {
|
||||
buffer.putUint8(131);
|
||||
writeValue(buffer, value.encode());
|
||||
} else {
|
||||
super.writeValue(buffer, value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||
switch (type) {
|
||||
case 129:
|
||||
final value = readValue(buffer) as int?;
|
||||
return value == null ? null : ScanMode.values[value];
|
||||
case 130:
|
||||
return ScanOptions.decode(readValue(buffer)!);
|
||||
case 131:
|
||||
return ScanResult.decode(readValue(buffer)!);
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dart → 原生。
|
||||
///
|
||||
/// 用户取消、权限拒绝、平台未实现这三类都通过 `FlutterError` 抛出,
|
||||
/// 由 Dart 侧的公共 API 转成 `NativeScanException`——**原生异常类型
|
||||
/// (`PlatformException`)不允许直接抛到业务代码里**(07 §使用规则)。
|
||||
class ScanHostApi {
|
||||
/// Constructor for [ScanHostApi]. The [binaryMessenger] named argument is
|
||||
/// available for dependency injection. If it is left null, the default
|
||||
/// BinaryMessenger will be used which routes to the host platform.
|
||||
ScanHostApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty
|
||||
? '.$messageChannelSuffix'
|
||||
: '';
|
||||
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||
|
||||
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||
|
||||
final String pigeonVar_messageChannelSuffix;
|
||||
|
||||
/// 打开扫码页并等待一次结果。
|
||||
///
|
||||
/// 用户取消时抛 code 为 `CANCELLED` 的错误,而不是返回 null——
|
||||
/// 「取消」和「扫到了空字符串」必须能区分开。
|
||||
Future<ScanResult> startScan(ScanOptions options) async {
|
||||
final pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.native_scan.ScanHostApi.startScan$pigeonVar_messageChannelSuffix';
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[options]);
|
||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
|
||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||
pigeonVar_replyList,
|
||||
pigeonVar_channelName,
|
||||
isNullValid: false,
|
||||
);
|
||||
return pigeonVar_replyValue! as ScanResult;
|
||||
}
|
||||
|
||||
/// 当前平台是否支持指定识别类型。
|
||||
///
|
||||
/// 车牌识别的技术路径未定(见上),首版可能只有部分平台支持;
|
||||
/// 调用方应当先查这个再决定要不要显示入口,而不是等 `startScan` 抛错。
|
||||
Future<bool> isModeSupported(ScanMode mode) async {
|
||||
final pigeonVar_channelName =
|
||||
'dev.flutter.pigeon.native_scan.ScanHostApi.isModeSupported$pigeonVar_messageChannelSuffix';
|
||||
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[mode]);
|
||||
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||
|
||||
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||
pigeonVar_replyList,
|
||||
pigeonVar_channelName,
|
||||
isNullValid: false,
|
||||
);
|
||||
return pigeonVar_replyValue! as bool;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// 唯一手写的接口契约文件。来源:07 §「Pigeon 的工程化」。
|
||||
//
|
||||
// 这不是可执行代码,只是给 pigeon 生成器读的 schema。改需求就改这里重新生成,
|
||||
// **生成产物不手动修改**:
|
||||
// dart run pigeon --input pigeons/scan_api.dart
|
||||
// (或 melos run gen:pigeon)
|
||||
|
||||
@ConfigurePigeon(
|
||||
PigeonOptions(
|
||||
dartOut: 'lib/src/generated/scan_api.g.dart',
|
||||
dartOptions: DartOptions(),
|
||||
kotlinOut: 'android/src/main/kotlin/com/conti/native_scan/ScanApi.g.kt',
|
||||
kotlinOptions: KotlinOptions(package: 'com.conti.native_scan'),
|
||||
swiftOut: 'ios/native_scan/Sources/native_scan/ScanApi.g.swift',
|
||||
swiftOptions: SwiftOptions(),
|
||||
dartPackageName: 'native_scan',
|
||||
),
|
||||
)
|
||||
library;
|
||||
|
||||
import 'package:pigeon/pigeon.dart';
|
||||
|
||||
/// 识别类型。
|
||||
///
|
||||
/// **即使首版只做条码,这个参数也必须先留出来**(07 §「待确认:VIN 码与车牌
|
||||
/// 识别的技术路径」):车牌走的是专用 OCR、VIN 印刷字符走通用 OCR + 校验位
|
||||
/// 过滤,技术路径还没定。参数先在 schema 里占好位,后面加识别类型就不用改
|
||||
/// 接口签名——改签名意味着三端生成物和所有调用点一起动。
|
||||
enum ScanMode {
|
||||
/// 二维码 / 条形码(商品、库位)。
|
||||
barcode,
|
||||
|
||||
/// VIN 码。可能是 Code 39 条码,也可能只有印刷字符。
|
||||
vin,
|
||||
|
||||
/// 车牌。
|
||||
plate,
|
||||
}
|
||||
|
||||
/// 扫码入参。
|
||||
class ScanOptions {
|
||||
ScanOptions({required this.mode, this.timeoutMs, this.torchEnabled, this.title});
|
||||
|
||||
/// 识别类型。
|
||||
ScanMode mode;
|
||||
|
||||
/// 超时毫秒数。null 表示不超时,由用户手动取消。
|
||||
int? timeoutMs;
|
||||
|
||||
/// 是否默认打开闪光灯。
|
||||
bool? torchEnabled;
|
||||
|
||||
/// 扫码页标题。由调用方传,`native_scan` 不依赖任何 i18n 资源。
|
||||
String? title;
|
||||
}
|
||||
|
||||
/// 扫码结果。
|
||||
class ScanResult {
|
||||
ScanResult({required this.mode, required this.value, required this.durationMs, this.rawFormat});
|
||||
|
||||
/// 实际生效的识别类型。
|
||||
ScanMode mode;
|
||||
|
||||
/// 识别到的文本。
|
||||
String value;
|
||||
|
||||
/// 从打开扫码页到出结果的耗时,供埋点用(见 13 的 `scan_succeeded`)。
|
||||
int durationMs;
|
||||
|
||||
/// 原始码制(如 `CODE_39` / `QR_CODE`)。OCR 路径下为 null。
|
||||
String? rawFormat;
|
||||
}
|
||||
|
||||
/// Dart → 原生。
|
||||
///
|
||||
/// 用户取消、权限拒绝、平台未实现这三类都通过 `FlutterError` 抛出,
|
||||
/// 由 Dart 侧的公共 API 转成 `NativeScanException`——**原生异常类型
|
||||
/// (`PlatformException`)不允许直接抛到业务代码里**(07 §使用规则)。
|
||||
@HostApi()
|
||||
abstract class ScanHostApi {
|
||||
/// 打开扫码页并等待一次结果。
|
||||
///
|
||||
/// 用户取消时抛 code 为 `CANCELLED` 的错误,而不是返回 null——
|
||||
/// 「取消」和「扫到了空字符串」必须能区分开。
|
||||
@async
|
||||
ScanResult startScan(ScanOptions options);
|
||||
|
||||
/// 当前平台是否支持指定识别类型。
|
||||
///
|
||||
/// 车牌识别的技术路径未定(见上),首版可能只有部分平台支持;
|
||||
/// 调用方应当先查这个再决定要不要显示入口,而不是等 `startScan` 抛错。
|
||||
bool isModeSupported(ScanMode mode);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
name: native_scan
|
||||
description: 扫码能力(条码 / VIN / 车牌)。Pigeon 定义跨语言接口。
|
||||
publish_to: none
|
||||
version: 0.1.0
|
||||
resolution: workspace
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# native_* 的硬约束(01):只依赖 Flutter SDK 和 Pigeon 产物,
|
||||
# **不依赖仓库内任何包**——包括 core_foundation。
|
||||
# 因此本包抛的是包内自定义的 NativeScanException,由调用方转成 NativeException。
|
||||
# ---------------------------------------------------------------------------
|
||||
environment:
|
||||
sdk: ^3.12.0
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
dev_dependencies:
|
||||
flutter_lints: ^6.0.0
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
pigeon: ^27.3.0
|
||||
|
||||
flutter:
|
||||
plugin:
|
||||
platforms:
|
||||
android:
|
||||
package: com.conti.native_scan
|
||||
pluginClass: NativeScanPlugin
|
||||
ios:
|
||||
pluginClass: NativeScanPlugin
|
||||
@@ -0,0 +1,60 @@
|
||||
// 唯一值得单测的东西:原生异常有没有被挡在包边界内(07 §使用规则)。
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:native_scan/native_scan.dart';
|
||||
import 'package:native_scan/src/generated/scan_api.g.dart';
|
||||
|
||||
// pigeon 给 @HostApi 生成的是具体类(内部持有 BinaryMessenger),
|
||||
// 所以这里只能 extends 覆写方法,不能 implements。
|
||||
class _ThrowingApi extends ScanHostApi {
|
||||
_ThrowingApi(this.error);
|
||||
|
||||
final Object error;
|
||||
|
||||
@override
|
||||
Future<ScanResult> startScan(ScanOptions options) => Future<ScanResult>.error(error);
|
||||
|
||||
@override
|
||||
Future<bool> isModeSupported(ScanMode mode) => Future<bool>.error(error);
|
||||
}
|
||||
|
||||
void main() {
|
||||
final ScanOptions options = ScanOptions(mode: ScanMode.barcode);
|
||||
|
||||
test('PlatformException 被转成 NativeScanException,原生类型不外泄', () async {
|
||||
final NativeScan scan = NativeScan(
|
||||
api: _ThrowingApi(PlatformException(code: 'CANCELLED', message: '用户取消')),
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
scan.startScan(options),
|
||||
throwsA(
|
||||
isA<NativeScanException>()
|
||||
.having((NativeScanException e) => e.code, 'code', NativeScanErrorCode.cancelled)
|
||||
.having((NativeScanException e) => e.isCancelled, 'isCancelled', isTrue),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('原生未实现时抛 UNSUPPORTED_PLATFORM,而不是静默返回', () async {
|
||||
final NativeScan scan = NativeScan(api: _ThrowingApi(MissingPluginException()));
|
||||
|
||||
await expectLater(
|
||||
scan.startScan(options),
|
||||
throwsA(
|
||||
isA<NativeScanException>().having(
|
||||
(NativeScanException e) => e.code,
|
||||
'code',
|
||||
NativeScanErrorCode.unsupportedPlatform,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('isModeSupported 出错时降级为 false,调用方据此隐藏入口', () async {
|
||||
final NativeScan scan = NativeScan(api: _ThrowingApi(MissingPluginException()));
|
||||
|
||||
expect(await scan.isModeSupported(ScanMode.plate), isFalse);
|
||||
});
|
||||
}
|
||||