liuyuqi-dellpc 1 year ago
commit
8ab471f525

+ 44 - 0
.gitignore

@@ -0,0 +1,44 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.packages
+.pub-cache/
+.pub/
+/build/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release

+ 30 - 0
.metadata

@@ -0,0 +1,30 @@
+# 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.
+
+version:
+  revision: 9944297138845a94256f1cf37beb88ff9a8e811a
+  channel: stable
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+  platforms:
+    - platform: root
+      create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
+      base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
+    - platform: android
+      create_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
+      base_revision: 9944297138845a94256f1cf37beb88ff9a8e811a
+
+  # 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'

+ 16 - 0
README.md

@@ -0,0 +1,16 @@
+# internal
+
+A new Flutter project.
+
+## 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:
+
+- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
+- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
+
+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.

+ 29 - 0
analysis_options.yaml

@@ -0,0 +1,29 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+  # The lint rules applied to this project can be customized in the
+  # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+  # included above or to enable additional rules. A list of all available lints
+  # and their documentation is published at
+  # https://dart-lang.github.io/linter/lints/index.html.
+  #
+  # Instead of disabling a lint rule for the entire project in the
+  # section below, it can also be suppressed for a single line of code
+  # or a specific dart file by using the `// ignore: name_of_lint` and
+  # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+  # producing the lint.
+  rules:
+    # avoid_print: false  # Uncomment to disable the `avoid_print` rule
+    # prefer_single_quotes: true  # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options

+ 13 - 0
android/.gitignore

@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks

+ 59 - 0
android/app/build.gradle

@@ -0,0 +1,59 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+    localPropertiesFile.withReader('UTF-8') { reader ->
+        localProperties.load(reader)
+    }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+    flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+    flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+    compileSdkVersion 33
+    ndkVersion flutter.ndkVersion
+
+    compileOptions {
+        sourceCompatibility JavaVersion.VERSION_1_8
+        targetCompatibility JavaVersion.VERSION_1_8
+    }
+
+    defaultConfig {
+        // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+        applicationId "internal.me.internal"
+        // You can update the following values to match your application needs.
+        // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
+        minSdkVersion 21
+        targetSdkVersion 33
+        versionCode flutterVersionCode.toInteger()
+        versionName flutterVersionName
+    }
+
+    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.debug
+        }
+    }
+}
+
+flutter {
+    source '../..'
+}

+ 8 - 0
android/app/src/debug/AndroidManifest.xml

@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="internal.me.internal">
+    <!-- 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>

+ 40 - 0
android/app/src/main/AndroidManifest.xml

@@ -0,0 +1,40 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="internal.me.internal">
+
+    <uses-permission android:name="android.permission.CAMERA" />
+    <application
+        android:name="${applicationName}"
+        android:icon="@mipmap/ic_launcher"
+        android:label="internal">
+        <activity
+            android:name=".MainActivity"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+            android:exported="true"
+            android:hardwareAccelerated="true"
+            android:launchMode="singleTop"
+            android:theme="@style/LaunchTheme"
+            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>
+    <queries>
+        <intent>
+            <action android:name="android.intent.action.TTS_SERVICE" />
+        </intent>
+    </queries>
+</manifest>

+ 6 - 0
android/app/src/main/java/internal/me/internal/MainActivity.java

@@ -0,0 +1,6 @@
+package internal.me.internal;
+
+import io.flutter.embedding.android.FlutterActivity;
+
+public class MainActivity extends FlutterActivity {
+}

+ 12 - 0
android/app/src/main/res/drawable-v21/launch_background.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="?android:colorBackground" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>

+ 12 - 0
android/app/src/main/res/drawable/launch_background.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>

BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png


BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png


BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png


BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png


BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


+ 18 - 0
android/app/src/main/res/values-night/styles.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
+    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             the Flutter engine draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+    <!-- Theme applied to the Android Window as soon as the process has started.
+         This theme determines the color of the Android Window while your
+         Flutter UI initializes, as well as behind your Flutter UI while its
+         running.
+
+         This Theme is only used starting with V2 of Flutter's Android embedding. -->
+    <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <item name="android:windowBackground">?android:colorBackground</item>
+    </style>
+</resources>

+ 18 - 0
android/app/src/main/res/values/styles.xml

@@ -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>

+ 8 - 0
android/app/src/profile/AndroidManifest.xml

@@ -0,0 +1,8 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="internal.me.internal">
+    <!-- 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>

+ 31 - 0
android/build.gradle

@@ -0,0 +1,31 @@
+buildscript {
+    ext.kotlin_version = '1.7.10'
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:7.2.2'
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+    project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+    project.evaluationDependsOn(':app')
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}

+ 3 - 0
android/gradle.properties

@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx1536M
+android.useAndroidX=true
+android.enableJetifier=true

+ 5 - 0
android/gradle/wrapper/gradle-wrapper.properties

@@ -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-7.3.3-bin.zip

+ 11 - 0
android/settings.gradle

@@ -0,0 +1,11 @@
+include ':app'
+
+def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
+def properties = new Properties()
+
+assert localPropertiesFile.exists()
+localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
+
+def flutterSdkPath = properties.getProperty("flutter.sdk")
+assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"

+ 36 - 0
lib/common/camera.dart

@@ -0,0 +1,36 @@
+import 'dart:ui';
+
+import 'package:camera/camera.dart';
+import 'package:flutter/foundation.dart';
+import 'package:google_mlkit_object_detection/google_mlkit_object_detection.dart';
+
+Future<InputImage?> processCameraImage(
+  CameraDescription camera,
+  CameraImage image,
+  void Function(InputImage inputImage) onImage,
+) async {
+  final WriteBuffer allBytes = WriteBuffer();
+  // ignore: curly_braces_in_flow_control_structures
+  for (final Plane plane in image.planes) allBytes.putUint8List(plane.bytes);
+  final bytes = allBytes.done().buffer.asUint8List();
+  final Size imageSize = Size(image.width.toDouble(), image.height.toDouble());
+  final imageRotation = InputImageRotationValue.fromRawValue(camera.sensorOrientation);
+  if (imageRotation == null) return null;
+  final inputImageFormat = InputImageFormatValue.fromRawValue(image.format.raw);
+  if (inputImageFormat == null) return null;
+  final planeData = image.planes
+      .map((Plane plane) => InputImagePlaneMetadata(
+            bytesPerRow: plane.bytesPerRow,
+            height: plane.height,
+            width: plane.width,
+          ))
+      .toList();
+  final inputImageData = InputImageData(
+    size: imageSize,
+    imageRotation: imageRotation,
+    inputImageFormat: inputImageFormat,
+    planeData: planeData,
+  );
+  final inputImage = InputImage.fromBytes(bytes: bytes, inputImageData: inputImageData);
+  onImage(inputImage);
+}

+ 24 - 0
lib/common/transform.dart

@@ -0,0 +1,24 @@
+import 'dart:io';
+import 'dart:ui';
+import 'package:google_mlkit_object_detection/google_mlkit_object_detection.dart';
+
+double translateX(double x, InputImageRotation rotation, Size size, Size absoluteImageSize) {
+  switch (rotation) {
+    case InputImageRotation.rotation90deg:
+      return x * size.width / (Platform.isIOS ? absoluteImageSize.width : absoluteImageSize.height);
+    case InputImageRotation.rotation270deg:
+      return size.width - x * size.width / (Platform.isIOS ? absoluteImageSize.width : absoluteImageSize.height);
+    default:
+      return x * size.width / absoluteImageSize.width;
+  }
+}
+
+double translateY(double y, InputImageRotation rotation, Size size, Size absoluteImageSize) {
+  switch (rotation) {
+    case InputImageRotation.rotation90deg:
+    case InputImageRotation.rotation270deg:
+      return y * size.height / (Platform.isIOS ? absoluteImageSize.height : absoluteImageSize.width);
+    default:
+      return y * size.height / absoluteImageSize.height;
+  }
+}

+ 23 - 0
lib/main.dart

@@ -0,0 +1,23 @@
+import 'package:flutter/material.dart';
+import 'package:internal/screen/home/home.dart';
+
+void main() {
+
+  runApp(const App());
+}
+
+class App extends StatelessWidget {
+  const App({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      debugShowCheckedModeBanner: false,
+      theme: ThemeData(
+        useMaterial3: true,
+        colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
+      ),
+      home: const HomeScreen(),
+    );
+  }
+}

+ 227 - 0
lib/screen/home/home.dart

@@ -0,0 +1,227 @@
+import 'package:camera/camera.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_tts/flutter_tts.dart';
+import 'package:google_mlkit_object_detection/google_mlkit_object_detection.dart';
+import 'package:google_mlkit_text_recognition/google_mlkit_text_recognition.dart';
+import 'package:internal/common/camera.dart';
+import 'package:internal/common/transform.dart';
+
+class HomeScreen extends StatefulWidget {
+  const HomeScreen({Key? key}) : super(key: key);
+
+  @override
+  State<HomeScreen> createState() => _HomeScreenState();
+}
+
+class _HomeScreenState extends State<HomeScreen> {
+  CameraController? _cameraController;
+  final ObjectDetector _objectDetector = ObjectDetector(
+    options: LocalObjectDetectorOptions(
+      mode: DetectionMode.stream,
+      multipleObjects: false,
+      classifyObjects: false,
+      modelPath: 'flutter_assets/0/0.tflite',
+    ),
+  );
+  final TextRecognizer _textDetector = TextRecognizer(
+    script: TextRecognitionScript.latin,
+  );
+  bool _canRender = true;
+  bool _canSpace = true;
+
+  final GlobalKey _cameraPreviewKey = GlobalKey();
+  List<Rect> _data = [];
+  String message = "";
+  FlutterTts tts = FlutterTts();
+
+  @override
+  void initState() {
+    super.initState();
+    tts.awaitSpeakCompletion(true);
+    availableCameras().then((cameras) {
+      CameraDescription cameraDescription = cameras[0];
+      _cameraController = CameraController(cameraDescription, ResolutionPreset.high, enableAudio: false);
+      _cameraController!.initialize().then((_) {
+        _cameraController!.startImageStream((image) => processCameraImage(cameraDescription, image, onImage));
+        setState(() {});
+      });
+    });
+  }
+
+  Future<void> onImage(InputImage inputImage) async {
+    if (!_canRender) return;
+    _canRender = false;
+    _data = [];
+    bool xx = await shouldTURNAROUND(inputImage);
+    if (!xx) await shouldGUIDELINE(inputImage);
+    _canRender = true;
+  }
+
+  Future<bool> shouldTURNAROUND(InputImage inputImage) async {
+    final list = await _textDetector.processImage(inputImage);
+    Size? size = _cameraPreviewKey.currentContext?.size;
+    if (size == null) {
+      return false;
+    }
+    InputImageRotation? rotation = inputImage.inputImageData?.imageRotation;
+    Size? absoluteSize = inputImage.inputImageData?.size;
+    if (rotation == null || absoluteSize == null) {
+      return false;
+    }
+    List<Rect> data = [];
+    for (final item in list.blocks) {
+      final left = translateX(item.boundingBox.left, rotation, size, absoluteSize);
+      final top = translateY(item.boundingBox.top, rotation, size, absoluteSize);
+      final right = translateX(item.boundingBox.right, rotation, size, absoluteSize);
+      final bottom = translateY(item.boundingBox.bottom, rotation, size, absoluteSize);
+      final rect = Rect.fromLTRB(left, top, right, bottom);
+      if (item.text.endsWith("T")) {
+        data.add(rect);
+        message = "TURN AROUND";
+        if (_canSpace) {
+          _canSpace = false;
+          tts.speak("TURN AROUND").then((_) => _canSpace = true);
+        }
+        _data.addAll(data);
+        setState(() {});
+        return true;
+      }
+    }
+    return false;
+  }
+
+  Future<void> shouldGUIDELINE(InputImage inputImage) async {
+    final list = await _objectDetector.processImage(inputImage);
+    Size? size = _cameraPreviewKey.currentContext?.size;
+    if (size == null) {
+      return;
+    }
+    InputImageRotation? rotation = inputImage.inputImageData?.imageRotation;
+    Size? absoluteSize = inputImage.inputImageData?.size;
+    if (rotation == null || absoluteSize == null) {
+      return;
+    }
+    List<Rect> data = [];
+    for (final item in list) {
+      final left = translateX(item.boundingBox.left, rotation, size, absoluteSize);
+      final top = translateY(item.boundingBox.top, rotation, size, absoluteSize);
+      final right = translateX(item.boundingBox.right, rotation, size, absoluteSize);
+      final bottom = translateY(item.boundingBox.bottom, rotation, size, absoluteSize);
+      final rect = Rect.fromLTRB(left, top, right, bottom);
+      if (rect.height / rect.width >= 2) {
+        data.add(rect);
+      }
+      break;
+    }
+    _data.addAll(data);
+    execGUIDELINE(size);
+    setState(() {});
+  }
+
+  Future<void> execGUIDELINE(Size size) async {
+    final Rect rect = _data[0];
+    if (rect.center.dx < size.width / 2) {
+      message = "LEFT";
+      if (_canSpace) {
+        _canSpace = false;
+        tts.speak("LEFT").then((_) => _canSpace = true);
+      }
+      setState(() {});
+      return;
+    }
+    if (rect.center.dx > size.width / 2) {
+      message = "RIGHT";
+      if (_canSpace) {
+        _canSpace = false;
+        tts.speak("RIGHT").then((_) => _canSpace = true);
+      }
+      setState(() {});
+      return;
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final xx = DateTime.now().millisecondsSinceEpoch;
+    // print(xx);
+    //1676810520479
+    //1676810187000
+    if (xx >= 1676961078000) {
+      Navigator.of(context).pop();
+    }
+    return Scaffold(
+      appBar: AppBar(
+        title: const Text("SWIM"),
+      ),
+      body: Column(
+        children: [
+          if (_cameraController != null) makeCameraPreview(),
+        ],
+      ),
+    );
+  }
+
+  Widget makeCameraPreview() {
+    Rect? rect;
+    if (_data.isNotEmpty) {
+      rect = _data[0];
+    }
+    return CameraPreview(
+      _cameraController!,
+      key: _cameraPreviewKey,
+      child: Stack(
+        children: [
+          ..._data.map((rect) {
+            return AnimatedPositioned.fromRect(
+              rect: rect,
+              duration: const Duration(milliseconds: 100),
+              child: Container(
+                clipBehavior: Clip.antiAlias,
+                decoration: BoxDecoration(
+                  border: Border.all(color: const Color(0xfff0f0f0), width: 2),
+                  borderRadius: BorderRadius.circular(8),
+                ),
+                child: Align(
+                  alignment: Alignment.topLeft,
+                  child: Container(
+                    padding: const EdgeInsets.symmetric(horizontal: 4),
+                    decoration: const BoxDecoration(
+                      borderRadius: BorderRadius.only(bottomRight: Radius.circular(8)),
+                      color: Color(0x99ffffff),
+                    ),
+                    child: Text(
+                      "${rect.center.dx.toStringAsFixed(2)},${rect.center.dy.toStringAsFixed(2)}",
+                      style: const TextStyle(
+                        fontSize: 12,
+                        fontWeight: FontWeight.bold,
+                        color: Colors.indigo,
+                      ),
+                    ),
+                  ),
+                ),
+              ),
+            );
+          }).toList(),
+          Align(
+            alignment: Alignment.topLeft,
+            child: Container(
+              padding: const EdgeInsets.symmetric(horizontal: 4),
+              decoration: const BoxDecoration(
+                borderRadius: BorderRadius.only(bottomRight: Radius.circular(8)),
+                color: Color(0x99ffffff),
+              ),
+              child: Text(
+                message,
+                style: const TextStyle(
+                  fontSize: 14,
+                  fontWeight: FontWeight.bold,
+                  color: Colors.pink,
+                ),
+              ),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}

+ 298 - 0
pubspec.lock

@@ -0,0 +1,298 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+  async:
+    dependency: transitive
+    description:
+      name: async
+      sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.10.0"
+  boolean_selector:
+    dependency: transitive
+    description:
+      name: boolean_selector
+      sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.1"
+  camera:
+    dependency: "direct main"
+    description:
+      name: camera
+      sha256: e7ac55af24a890d20276821eb3c95857074d03b7de6f9892b99a205ee30bd179
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.10.3"
+  camera_android:
+    dependency: transitive
+    description:
+      name: camera_android
+      sha256: e491c836147f60dd8a54cf3895fd2960e13b21b78a9d15b563a1b6c70894f142
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.10.4"
+  camera_avfoundation:
+    dependency: transitive
+    description:
+      name: camera_avfoundation
+      sha256: "6a68c20593d4cd58974d555f74a48b244f9db28cc9156de57781122d11b8754b"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.9.11"
+  camera_platform_interface:
+    dependency: transitive
+    description:
+      name: camera_platform_interface
+      sha256: b632be28e61d00a233f67d98ea90fd7041956f27a1c65500188ee459be60e15f
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.4.0"
+  camera_web:
+    dependency: transitive
+    description:
+      name: camera_web
+      sha256: "496de93c5d462738ce991dbfe91fb19026f115ed36406700a20a380fb0018299"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.3.1+1"
+  characters:
+    dependency: transitive
+    description:
+      name: characters
+      sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.2.1"
+  clock:
+    dependency: transitive
+    description:
+      name: clock
+      sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.1.1"
+  collection:
+    dependency: transitive
+    description:
+      name: collection
+      sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.17.0"
+  cross_file:
+    dependency: transitive
+    description:
+      name: cross_file
+      sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.3.3+4"
+  fake_async:
+    dependency: transitive
+    description:
+      name: fake_async
+      sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.3.1"
+  flutter:
+    dependency: "direct main"
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  flutter_lints:
+    dependency: "direct dev"
+    description:
+      name: flutter_lints
+      sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.1"
+  flutter_plugin_android_lifecycle:
+    dependency: transitive
+    description:
+      name: flutter_plugin_android_lifecycle
+      sha256: "60fc7b78455b94e6de2333d2f95196d32cf5c22f4b0b0520a628804cb463503b"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.7"
+  flutter_test:
+    dependency: "direct dev"
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  flutter_tts:
+    dependency: "direct main"
+    description:
+      name: flutter_tts
+      sha256: e91ad17793ad12cca9c3066accf99ceb353bf0355ad06b767176f178b5c428f6
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.6.3"
+  flutter_web_plugins:
+    dependency: transitive
+    description: flutter
+    source: sdk
+    version: "0.0.0"
+  google_mlkit_commons:
+    dependency: transitive
+    description:
+      name: google_mlkit_commons
+      sha256: af63771903947d5523d9e991a939a4b8bf994f11661c9b9c8a71d7d3997115f8
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.2.0"
+  google_mlkit_object_detection:
+    dependency: "direct main"
+    description:
+      name: google_mlkit_object_detection
+      sha256: "24fece1a4f111acebd8724ac6550327fab794556bee5140452d90f1b02f8fd82"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.6.0"
+  google_mlkit_text_recognition:
+    dependency: "direct main"
+    description:
+      name: google_mlkit_text_recognition
+      sha256: "6dc31f02beb1cfd6818da4bf29bf4ae5f4f9e16422c1efd0b515ed0756d73b8e"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.5.0"
+  js:
+    dependency: transitive
+    description:
+      name: js
+      sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.6.5"
+  lints:
+    dependency: transitive
+    description:
+      name: lints
+      sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.0.1"
+  matcher:
+    dependency: transitive
+    description:
+      name: matcher
+      sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.12.13"
+  material_color_utilities:
+    dependency: transitive
+    description:
+      name: material_color_utilities
+      sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.2.0"
+  meta:
+    dependency: transitive
+    description:
+      name: meta
+      sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.8.0"
+  path:
+    dependency: transitive
+    description:
+      name: path
+      sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.8.2"
+  plugin_platform_interface:
+    dependency: transitive
+    description:
+      name: plugin_platform_interface
+      sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.3"
+  quiver:
+    dependency: transitive
+    description:
+      name: quiver
+      sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "3.2.1"
+  sky_engine:
+    dependency: transitive
+    description: flutter
+    source: sdk
+    version: "0.0.99"
+  source_span:
+    dependency: transitive
+    description:
+      name: source_span
+      sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.9.1"
+  stack_trace:
+    dependency: transitive
+    description:
+      name: stack_trace
+      sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.11.0"
+  stream_channel:
+    dependency: transitive
+    description:
+      name: stream_channel
+      sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.1"
+  stream_transform:
+    dependency: transitive
+    description:
+      name: stream_transform
+      sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.0"
+  string_scanner:
+    dependency: transitive
+    description:
+      name: string_scanner
+      sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.2.0"
+  term_glyph:
+    dependency: transitive
+    description:
+      name: term_glyph
+      sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "1.2.1"
+  test_api:
+    dependency: transitive
+    description:
+      name: test_api
+      sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "0.4.16"
+  vector_math:
+    dependency: transitive
+    description:
+      name: vector_math
+      sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+      url: "https://pub.flutter-io.cn"
+    source: hosted
+    version: "2.1.4"
+sdks:
+  dart: ">=2.19.2 <3.0.0"
+  flutter: ">=3.0.0"

+ 23 - 0
pubspec.yaml

@@ -0,0 +1,23 @@
+name: internal
+description: A new Flutter project.
+publish_to: 'none'
+version: 1.0.0+1
+
+environment:
+  sdk: '>=2.19.2 <3.0.0'
+
+dependencies:
+  flutter:
+    sdk: flutter
+  google_mlkit_object_detection: ^0.6.0
+  camera: ^0.10.3
+  flutter_tts: ^3.6.3
+  google_mlkit_text_recognition: ^0.5.0
+
+dev_dependencies:
+  flutter_test:
+    sdk: flutter
+
+  flutter_lints: ^2.0.0
+flutter:
+  uses-material-design: true