diff --git a/README.en.md b/README.en.md
index 836bdfd..fea5f08 100644
--- a/README.en.md
+++ b/README.en.md
@@ -13,22 +13,22 @@ A production-ready Flutter quick-start template extracted from real-world projec
## Tech Stack
-| Category | Package | Purpose |
-|---|---|---|
-| State Management | flutter_riverpod | Compile-safe, testable state management |
-| Networking | dio | HTTP client with interceptor chain |
-| Local Cache | shared_preferences | Key-value persistence |
-| Network Monitor | connectivity_plus | Real-time connectivity tracking |
-| Permissions | permission_handler | Runtime permission requests |
-| Screen Adaptation | flutter_screenutil | Design-dimension-based layout |
-| Image Loading | cached_network_image | Network image caching |
-| SVG | flutter_svg | SVG rendering |
-| Pull to Refresh | pull_to_refresh | Refresh and load-more |
-| Loading HUD | flutter_easyloading | Toast and loading indicator |
-| Device Info | device_info_plus | Device metadata |
-| Package Info | package_info_plus | App version info |
-| URL Launcher | url_launcher | Open external URLs |
-| Linting | flutter_lints | Recommended Dart lint rules |
+| Category | Package | Purpose |
+| ----------------- | -------------------- | --------------------------------------- |
+| State Management | flutter_riverpod | Compile-safe, testable state management |
+| Networking | dio | HTTP client with interceptor chain |
+| Local Cache | shared_preferences | Key-value persistence |
+| Network Monitor | connectivity_plus | Real-time connectivity tracking |
+| Permissions | permission_handler | Runtime permission requests |
+| Screen Adaptation | flutter_screenutil | Design-dimension-based layout |
+| Image Loading | cached_network_image | Network image caching |
+| SVG | flutter_svg | SVG rendering |
+| Pull to Refresh | pull_to_refresh | Refresh and load-more |
+| Loading HUD | flutter_easyloading | Toast and loading indicator |
+| Device Info | device_info_plus | Device metadata |
+| Package Info | package_info_plus | App version info |
+| URL Launcher | url_launcher | Open external URLs |
+| Linting | flutter_lints | Recommended Dart lint rules |
## Directory Structure
@@ -72,7 +72,7 @@ lib/
## Getting Started
```bash
-cd flutter-template
+cd record-tool
flutter pub get
flutter analyze
flutter test
diff --git a/README.md b/README.md
index 79a0644..6f5f702 100644
--- a/README.md
+++ b/README.md
@@ -13,22 +13,22 @@
## 技术栈
-| 类别 | 依赖 | 用途 |
-|---|---|---|
-| 状态管理 | flutter_riverpod | 编译安全、可测试的状态管理 |
-| 网络请求 | dio | HTTP 客户端,支持拦截器链 |
-| 本地缓存 | shared_preferences | KV 持久化存储 |
-| 网络监听 | connectivity_plus | 实时网络状态监测 |
-| 权限申请 | permission_handler | 运行时权限请求 |
-| 屏幕适配 | flutter_screenutil | 设计稿尺寸适配 |
-| 图片加载 | cached_network_image | 网络图片缓存 |
-| SVG | flutter_svg | SVG 渲染 |
-| 下拉刷新 | pull_to_refresh | 下拉刷新 / 上拉加载 |
-| 加载提示 | flutter_easyloading | Toast 和 loading |
-| 设备信息 | device_info_plus | 设备元数据 |
-| 应用信息 | package_info_plus | 版本号等应用信息 |
-| 链接跳转 | url_launcher | 外部 URL 打开 |
-| 代码规范 | flutter_lints | Dart 推荐 lint 规则 |
+| 类别 | 依赖 | 用途 |
+| -------- | -------------------- | -------------------------- |
+| 状态管理 | flutter_riverpod | 编译安全、可测试的状态管理 |
+| 网络请求 | dio | HTTP 客户端,支持拦截器链 |
+| 本地缓存 | shared_preferences | KV 持久化存储 |
+| 网络监听 | connectivity_plus | 实时网络状态监测 |
+| 权限申请 | permission_handler | 运行时权限请求 |
+| 屏幕适配 | flutter_screenutil | 设计稿尺寸适配 |
+| 图片加载 | cached_network_image | 网络图片缓存 |
+| SVG | flutter_svg | SVG 渲染 |
+| 下拉刷新 | pull_to_refresh | 下拉刷新 / 上拉加载 |
+| 加载提示 | flutter_easyloading | Toast 和 loading |
+| 设备信息 | device_info_plus | 设备元数据 |
+| 应用信息 | package_info_plus | 版本号等应用信息 |
+| 链接跳转 | url_launcher | 外部 URL 打开 |
+| 代码规范 | flutter_lints | Dart 推荐 lint 规则 |
## 目录结构
@@ -71,7 +71,7 @@ lib/
## 快速开始
```bash
-cd flutter-template
+cd record-tool
flutter pub get
flutter analyze
flutter test
diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts
index d1f9811..883955a 100644
--- a/android/app/build.gradle.kts
+++ b/android/app/build.gradle.kts
@@ -5,8 +5,10 @@ plugins {
id("dev.flutter.flutter-gradle-plugin")
}
+val appPackageName = "com.gdfw.fxjk"
+
android {
- namespace = "com.example.flutter_template"
+ namespace = appPackageName
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
@@ -20,8 +22,7 @@ android {
}
defaultConfig {
- // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
- applicationId = "com.example.flutter_template"
+ applicationId = appPackageName
// 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
diff --git a/android/app/src/main/kotlin/com/gdfw/fxjk/AppConstants.kt b/android/app/src/main/kotlin/com/gdfw/fxjk/AppConstants.kt
new file mode 100644
index 0000000..0bce95b
--- /dev/null
+++ b/android/app/src/main/kotlin/com/gdfw/fxjk/AppConstants.kt
@@ -0,0 +1,9 @@
+package com.gdfw.fxjk
+
+object AppConstants {
+ const val PACKAGE_NAME = "com.gdfw.fxjk"
+ const val RECORDING_METHOD_CHANNEL = "$PACKAGE_NAME/recording"
+ const val RECORDING_EVENT_CHANNEL = "$PACKAGE_NAME/recording_events"
+ const val RECORDING_ACTION_START = "$PACKAGE_NAME.recording.START"
+ const val RECORDING_ACTION_STOP = "$PACKAGE_NAME.recording.STOP"
+}
diff --git a/android/app/src/main/kotlin/com/example/flutter_template/MainActivity.kt b/android/app/src/main/kotlin/com/gdfw/fxjk/MainActivity.kt
similarity index 87%
rename from android/app/src/main/kotlin/com/example/flutter_template/MainActivity.kt
rename to android/app/src/main/kotlin/com/gdfw/fxjk/MainActivity.kt
index c0bb582..af058ba 100644
--- a/android/app/src/main/kotlin/com/example/flutter_template/MainActivity.kt
+++ b/android/app/src/main/kotlin/com/gdfw/fxjk/MainActivity.kt
@@ -1,8 +1,8 @@
-package com.example.flutter_template
+package com.gdfw.fxjk
import androidx.camera.view.PreviewView
-import com.example.flutter_template.recording.RecordingPlatformHandler
-import com.example.flutter_template.recording.RecordingPreviewFactory
+import com.gdfw.fxjk.recording.RecordingPlatformHandler
+import com.gdfw.fxjk.recording.RecordingPreviewFactory
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
diff --git a/android/app/src/main/kotlin/com/example/flutter_template/recording/BatteryOptimizationHelper.kt b/android/app/src/main/kotlin/com/gdfw/fxjk/recording/BatteryOptimizationHelper.kt
similarity index 96%
rename from android/app/src/main/kotlin/com/example/flutter_template/recording/BatteryOptimizationHelper.kt
rename to android/app/src/main/kotlin/com/gdfw/fxjk/recording/BatteryOptimizationHelper.kt
index 7ee8ec0..7f65f28 100644
--- a/android/app/src/main/kotlin/com/example/flutter_template/recording/BatteryOptimizationHelper.kt
+++ b/android/app/src/main/kotlin/com/gdfw/fxjk/recording/BatteryOptimizationHelper.kt
@@ -1,4 +1,4 @@
-package com.example.flutter_template.recording
+package com.gdfw.fxjk.recording
import android.content.Context
import android.content.Intent
diff --git a/android/app/src/main/kotlin/com/example/flutter_template/recording/DoNotDisturbHelper.kt b/android/app/src/main/kotlin/com/gdfw/fxjk/recording/DoNotDisturbHelper.kt
similarity index 97%
rename from android/app/src/main/kotlin/com/example/flutter_template/recording/DoNotDisturbHelper.kt
rename to android/app/src/main/kotlin/com/gdfw/fxjk/recording/DoNotDisturbHelper.kt
index d151cfb..ec00e90 100644
--- a/android/app/src/main/kotlin/com/example/flutter_template/recording/DoNotDisturbHelper.kt
+++ b/android/app/src/main/kotlin/com/gdfw/fxjk/recording/DoNotDisturbHelper.kt
@@ -1,4 +1,4 @@
-package com.example.flutter_template.recording
+package com.gdfw.fxjk.recording
import android.app.NotificationManager
import android.content.Context
diff --git a/android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingCameraController.kt b/android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingCameraController.kt
similarity index 99%
rename from android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingCameraController.kt
rename to android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingCameraController.kt
index 3ae7c01..13f1b67 100644
--- a/android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingCameraController.kt
+++ b/android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingCameraController.kt
@@ -1,4 +1,4 @@
-package com.example.flutter_template.recording
+package com.gdfw.fxjk.recording
import android.content.Context
import android.util.Log
diff --git a/android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingForegroundService.kt b/android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingForegroundService.kt
similarity index 93%
rename from android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingForegroundService.kt
rename to android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingForegroundService.kt
index 39c7adf..6de5d12 100644
--- a/android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingForegroundService.kt
+++ b/android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingForegroundService.kt
@@ -1,4 +1,4 @@
-package com.example.flutter_template.recording
+package com.gdfw.fxjk.recording
import android.app.Notification
import android.app.NotificationChannel
@@ -15,7 +15,8 @@ import android.os.IBinder
import android.os.PowerManager
import androidx.core.app.NotificationCompat
import androidx.lifecycle.LifecycleService
-import com.example.flutter_template.MainActivity
+import com.gdfw.fxjk.AppConstants
+import com.gdfw.fxjk.MainActivity
class RecordingForegroundService : LifecycleService() {
private var wakeLock: PowerManager.WakeLock? = null
@@ -29,7 +30,7 @@ class RecordingForegroundService : LifecycleService() {
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
when (intent?.action) {
- ACTION_START -> {
+ AppConstants.RECORDING_ACTION_START -> {
acquireWakeLock()
val notification = buildNotification("正在录制")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
@@ -43,7 +44,7 @@ class RecordingForegroundService : LifecycleService() {
}
isRunning = true
}
- ACTION_STOP -> {
+ AppConstants.RECORDING_ACTION_STOP -> {
releaseWakeLock()
stopForeground(STOP_FOREGROUND_REMOVE)
isRunning = false
@@ -143,8 +144,6 @@ class RecordingForegroundService : LifecycleService() {
companion object {
const val CHANNEL_ID = "recording_foreground"
const val NOTIFICATION_ID = 1001
- const val ACTION_START = "com.example.flutter_template.recording.START"
- const val ACTION_STOP = "com.example.flutter_template.recording.STOP"
private const val WAKE_LOCK_TAG = "record_tool:recording_wake_lock"
@Volatile
@@ -156,7 +155,7 @@ class RecordingForegroundService : LifecycleService() {
fun start(context: Context) {
val intent =
Intent(context, RecordingForegroundService::class.java).apply {
- action = ACTION_START
+ action = AppConstants.RECORDING_ACTION_START
}
ContextCompatStart.startForegroundService(context, intent)
}
@@ -164,7 +163,7 @@ class RecordingForegroundService : LifecycleService() {
fun stop(context: Context) {
val intent =
Intent(context, RecordingForegroundService::class.java).apply {
- action = ACTION_STOP
+ action = AppConstants.RECORDING_ACTION_STOP
}
context.startService(intent)
}
diff --git a/android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingPlatformHandler.kt b/android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingPlatformHandler.kt
similarity index 96%
rename from android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingPlatformHandler.kt
rename to android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingPlatformHandler.kt
index ab48d84..69756cb 100644
--- a/android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingPlatformHandler.kt
+++ b/android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingPlatformHandler.kt
@@ -1,4 +1,4 @@
-package com.example.flutter_template.recording
+package com.gdfw.fxjk.recording
import android.app.Activity
import android.os.Build
@@ -7,7 +7,8 @@ import android.os.Looper
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
-import com.example.flutter_template.MainActivity
+import com.gdfw.fxjk.AppConstants
+import com.gdfw.fxjk.MainActivity
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
@@ -18,9 +19,9 @@ class RecordingPlatformHandler(
messenger: BinaryMessenger,
) : MethodChannel.MethodCallHandler, EventChannel.StreamHandler {
private val methodChannel =
- MethodChannel(messenger, "com.example.flutter_template/recording")
+ MethodChannel(messenger, AppConstants.RECORDING_METHOD_CHANNEL)
private val eventChannel =
- EventChannel(messenger, "com.example.flutter_template/recording_events")
+ EventChannel(messenger, AppConstants.RECORDING_EVENT_CHANNEL)
private val mainHandler = Handler(Looper.getMainLooper())
private var eventSink: EventChannel.EventSink? = null
diff --git a/android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingPreviewFactory.kt b/android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingPreviewFactory.kt
similarity index 91%
rename from android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingPreviewFactory.kt
rename to android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingPreviewFactory.kt
index c24b176..efbc8fa 100644
--- a/android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingPreviewFactory.kt
+++ b/android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingPreviewFactory.kt
@@ -1,9 +1,9 @@
-package com.example.flutter_template.recording
+package com.gdfw.fxjk.recording
import android.content.Context
import android.view.View
import androidx.camera.view.PreviewView
-import com.example.flutter_template.MainActivity
+import com.gdfw.fxjk.MainActivity
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
diff --git a/android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingSession.kt b/android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingSession.kt
similarity index 94%
rename from android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingSession.kt
rename to android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingSession.kt
index 54229bb..ccd3189 100644
--- a/android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingSession.kt
+++ b/android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingSession.kt
@@ -1,4 +1,4 @@
-package com.example.flutter_template.recording
+package com.gdfw.fxjk.recording
import android.content.Context
import androidx.lifecycle.LifecycleService
diff --git a/android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingState.kt b/android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingState.kt
similarity index 91%
rename from android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingState.kt
rename to android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingState.kt
index ed400a2..fa6aef4 100644
--- a/android/app/src/main/kotlin/com/example/flutter_template/recording/RecordingState.kt
+++ b/android/app/src/main/kotlin/com/gdfw/fxjk/recording/RecordingState.kt
@@ -1,4 +1,4 @@
-package com.example.flutter_template.recording
+package com.gdfw.fxjk.recording
enum class RecordingState {
IDLE,
diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
index db77bb4..2a87b89 100644
Binary files a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
index 17987b7..01e0bac 100644
Binary files a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
index 09d4391..67296df 100644
Binary files a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
index d5f1c8d..63ed1bb 100644
Binary files a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
index 4d6372e..3c6652a 100644
Binary files a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/android/build/reports/problems/problems-report.html b/android/build/reports/problems/problems-report.html
new file mode 100644
index 0000000..df50f6e
--- /dev/null
+++ b/android/build/reports/problems/problems-report.html
@@ -0,0 +1,663 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Gradle Configuration Cache
+
+
+
+
+
+
+ Loading...
+
+
+
+
+
+
+
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
index dc9ada4..9aa3aee 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
index 7353c41..3af03e0 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
index 797d452..f0f1911 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
index 6ed2d93..af81e95 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
index 4cd7b00..a0d5c32 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
index fe73094..89555f8 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
index 321773c..b22470d 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
index 797d452..f0f1911 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
index 502f463..65eaf05 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
index 0ec3034..e1b5ada 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
index 0ec3034..e1b5ada 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
index e9f5fea..cae174b 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
index 84ac32a..de36265 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
index 8953cba..653faf8 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
index 0467bf1..24ef251 100644
Binary files a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ
diff --git a/ios/Runner/RecordingPlugin.swift b/ios/Runner/RecordingPlugin.swift
index 7722915..c5e34db 100644
--- a/ios/Runner/RecordingPlugin.swift
+++ b/ios/Runner/RecordingPlugin.swift
@@ -410,6 +410,12 @@ private final class RecordingCameraController: NSObject, AVCaptureFileOutputReco
}
}
+private enum RecordingChannelNames {
+ static let packageName = "com.gdfw.fxjk"
+ static let method = "\(packageName)/recording"
+ static let events = "\(packageName)/recording_events"
+}
+
final class RecordingPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
private let controller = RecordingCameraController.shared
private var eventSink: FlutterEventSink?
@@ -421,13 +427,13 @@ final class RecordingPlugin: NSObject, FlutterPlugin, FlutterStreamHandler {
registrar.register(RecordingPreviewFactory(), withId: "recording-camera-preview")
let methodChannel = FlutterMethodChannel(
- name: "com.example.flutter_template/recording",
+ name: RecordingChannelNames.method,
binaryMessenger: messenger
)
registrar.addMethodCallDelegate(plugin, channel: methodChannel)
let eventChannel = FlutterEventChannel(
- name: "com.example.flutter_template/recording_events",
+ name: RecordingChannelNames.events,
binaryMessenger: messenger
)
eventChannel.setStreamHandler(plugin)
diff --git a/lib/app/app.dart b/lib/app/app.dart
index c6332fb..2cf4249 100644
--- a/lib/app/app.dart
+++ b/lib/app/app.dart
@@ -1,16 +1,46 @@
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
-import 'package:flutter_template/app/config/app_config.dart';
-import 'package:flutter_template/app/router/app_navigator.dart';
-import 'package:flutter_template/app/theme/app_theme.dart';
-import 'package:flutter_template/features/recording/recording_page.dart';
+import 'package:recording_tool/app/config/app_config.dart';
+import 'package:recording_tool/app/router/app_navigator.dart';
+import 'package:recording_tool/app/theme/app_theme.dart';
+import 'package:recording_tool/features/recording/recording_page.dart';
+import 'package:recording_tool/features/recording/view-model/view_model_recording.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
-class FlutterTemplateApp extends StatelessWidget {
+class FlutterTemplateApp extends ConsumerStatefulWidget {
const FlutterTemplateApp({super.key});
+ @override
+ ConsumerState createState() => _FlutterTemplateAppState();
+}
+
+class _FlutterTemplateAppState extends ConsumerState
+ with WidgetsBindingObserver {
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addObserver(this);
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ ref.read(recordingViewModelProvider.notifier).getClipboardContent();
+ });
+ }
+
+ @override
+ void didChangeAppLifecycleState(AppLifecycleState state) {
+ if (state == AppLifecycleState.resumed) {
+ ref.read(recordingViewModelProvider.notifier).getClipboardContent();
+ }
+ }
+
+ @override
+ void dispose() {
+ WidgetsBinding.instance.removeObserver(this);
+ super.dispose();
+ }
+
@override
Widget build(BuildContext context) {
return ScreenUtilInit(
diff --git a/lib/app/bootstrap.dart b/lib/app/bootstrap.dart
index 9cce0ef..d67c39d 100644
--- a/lib/app/bootstrap.dart
+++ b/lib/app/bootstrap.dart
@@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:flutter_template/app/app.dart';
-import 'package:flutter_template/app/config/app_config.dart';
-import 'package:flutter_template/core/cache/app_storage.dart';
-import 'package:flutter_template/core/logging/app_logger.dart';
+import 'package:recording_tool/app/app.dart';
+import 'package:recording_tool/app/config/app_config.dart';
+import 'package:recording_tool/core/cache/app_storage.dart';
+import 'package:recording_tool/core/logging/app_logger.dart';
import 'package:package_info_plus/package_info_plus.dart';
class AppBootstrapper {
diff --git a/lib/core/network/api_client.dart b/lib/core/network/api_client.dart
index 0cd1456..3acfdfd 100644
--- a/lib/core/network/api_client.dart
+++ b/lib/core/network/api_client.dart
@@ -1,9 +1,9 @@
import 'dart:convert';
import 'package:dio/dio.dart';
-import 'package:flutter_template/core/network/api_exception.dart';
-import 'package:flutter_template/core/network/api_response.dart';
-import 'package:flutter_template/core/network/http_method.dart';
+import 'package:recording_tool/core/network/api_exception.dart';
+import 'package:recording_tool/core/network/api_response.dart';
+import 'package:recording_tool/core/network/http_method.dart';
typedef JsonParser = T Function(dynamic json);
diff --git a/lib/core/network/header_interceptor.dart b/lib/core/network/header_interceptor.dart
index 9dc287a..87f29dc 100644
--- a/lib/core/network/header_interceptor.dart
+++ b/lib/core/network/header_interceptor.dart
@@ -1,10 +1,10 @@
import 'dart:io';
import 'package:dio/dio.dart';
-import 'package:flutter_template/app/config/app_config.dart';
-import 'package:flutter_template/core/cache/app_storage.dart';
-import 'package:flutter_template/core/cache/storage_keys.dart';
-import 'package:flutter_template/core/utils/device_utils.dart';
+import 'package:recording_tool/app/config/app_config.dart';
+import 'package:recording_tool/core/cache/app_storage.dart';
+import 'package:recording_tool/core/cache/storage_keys.dart';
+import 'package:recording_tool/core/utils/device_utils.dart';
class HeaderInterceptor extends Interceptor {
@override
diff --git a/lib/core/network/network_monitor.dart b/lib/core/network/network_monitor.dart
index a91e858..2d3c425 100644
--- a/lib/core/network/network_monitor.dart
+++ b/lib/core/network/network_monitor.dart
@@ -3,7 +3,7 @@ import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
-import 'package:flutter_template/core/network/network_state.dart';
+import 'package:recording_tool/core/network/network_state.dart';
class NetworkMonitor {
final _controller = StreamController.broadcast();
diff --git a/lib/core/network/offline_queue/offline_queue_interceptor.dart b/lib/core/network/offline_queue/offline_queue_interceptor.dart
index 099e75d..eac7405 100644
--- a/lib/core/network/offline_queue/offline_queue_interceptor.dart
+++ b/lib/core/network/offline_queue/offline_queue_interceptor.dart
@@ -1,7 +1,7 @@
import 'package:dio/dio.dart';
-import 'package:flutter_template/core/network/network_monitor.dart';
-import 'package:flutter_template/core/network/offline_queue/offline_queue_manager.dart';
-import 'package:flutter_template/core/network/offline_queue/offline_request.dart';
+import 'package:recording_tool/core/network/network_monitor.dart';
+import 'package:recording_tool/core/network/offline_queue/offline_queue_manager.dart';
+import 'package:recording_tool/core/network/offline_queue/offline_request.dart';
import 'package:uuid/uuid.dart';
class OfflineQueueInterceptor extends Interceptor {
diff --git a/lib/core/network/offline_queue/offline_queue_manager.dart b/lib/core/network/offline_queue/offline_queue_manager.dart
index 8f518a4..f60d09c 100644
--- a/lib/core/network/offline_queue/offline_queue_manager.dart
+++ b/lib/core/network/offline_queue/offline_queue_manager.dart
@@ -1,11 +1,11 @@
import 'dart:async';
import 'package:dio/dio.dart';
-import 'package:flutter_template/core/logging/app_logger.dart';
-import 'package:flutter_template/core/network/network_monitor.dart';
-import 'package:flutter_template/core/network/offline_queue/offline_queue_state.dart';
-import 'package:flutter_template/core/network/offline_queue/offline_queue_storage.dart';
-import 'package:flutter_template/core/network/offline_queue/offline_request.dart';
+import 'package:recording_tool/core/logging/app_logger.dart';
+import 'package:recording_tool/core/network/network_monitor.dart';
+import 'package:recording_tool/core/network/offline_queue/offline_queue_state.dart';
+import 'package:recording_tool/core/network/offline_queue/offline_queue_storage.dart';
+import 'package:recording_tool/core/network/offline_queue/offline_request.dart';
class OfflineQueueManager {
OfflineQueueManager({
diff --git a/lib/core/network/offline_queue/offline_queue_storage.dart b/lib/core/network/offline_queue/offline_queue_storage.dart
index c8748d4..0d32cb8 100644
--- a/lib/core/network/offline_queue/offline_queue_storage.dart
+++ b/lib/core/network/offline_queue/offline_queue_storage.dart
@@ -1,6 +1,6 @@
-import 'package:flutter_template/core/cache/app_storage.dart';
-import 'package:flutter_template/core/cache/storage_keys.dart';
-import 'package:flutter_template/core/network/offline_queue/offline_request.dart';
+import 'package:recording_tool/core/cache/app_storage.dart';
+import 'package:recording_tool/core/cache/storage_keys.dart';
+import 'package:recording_tool/core/network/offline_queue/offline_request.dart';
class OfflineQueueStorage {
Future> loadQueue() async {
diff --git a/lib/core/network/providers/dio_providers.dart b/lib/core/network/providers/dio_providers.dart
index 23bb29c..0672449 100644
--- a/lib/core/network/providers/dio_providers.dart
+++ b/lib/core/network/providers/dio_providers.dart
@@ -1,11 +1,11 @@
import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:flutter_template/app/config/app_config.dart';
-import 'package:flutter_template/core/network/api_client.dart';
-import 'package:flutter_template/core/network/header_interceptor.dart';
-import 'package:flutter_template/core/network/offline_queue/offline_queue_interceptor.dart';
-import 'package:flutter_template/core/network/providers/network_providers.dart';
-import 'package:flutter_template/core/network/providers/offline_queue_providers.dart';
+import 'package:recording_tool/app/config/app_config.dart';
+import 'package:recording_tool/core/network/api_client.dart';
+import 'package:recording_tool/core/network/header_interceptor.dart';
+import 'package:recording_tool/core/network/offline_queue/offline_queue_interceptor.dart';
+import 'package:recording_tool/core/network/providers/network_providers.dart';
+import 'package:recording_tool/core/network/providers/offline_queue_providers.dart';
final dioProvider = Provider((ref) {
final dio = Dio(
diff --git a/lib/core/network/providers/network_providers.dart b/lib/core/network/providers/network_providers.dart
index 0ca8449..93a092b 100644
--- a/lib/core/network/providers/network_providers.dart
+++ b/lib/core/network/providers/network_providers.dart
@@ -1,6 +1,6 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:flutter_template/core/network/network_monitor.dart';
-import 'package:flutter_template/core/network/network_state.dart';
+import 'package:recording_tool/core/network/network_monitor.dart';
+import 'package:recording_tool/core/network/network_state.dart';
final networkMonitorProvider = Provider((ref) {
final monitor = NetworkMonitor()..start();
diff --git a/lib/core/network/providers/offline_queue_providers.dart b/lib/core/network/providers/offline_queue_providers.dart
index c2019e9..5ef7142 100644
--- a/lib/core/network/providers/offline_queue_providers.dart
+++ b/lib/core/network/providers/offline_queue_providers.dart
@@ -1,8 +1,8 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:flutter_template/core/network/offline_queue/offline_queue_manager.dart';
-import 'package:flutter_template/core/network/offline_queue/offline_queue_state.dart';
-import 'package:flutter_template/core/network/offline_queue/offline_queue_storage.dart';
-import 'package:flutter_template/core/network/providers/network_providers.dart';
+import 'package:recording_tool/core/network/offline_queue/offline_queue_manager.dart';
+import 'package:recording_tool/core/network/offline_queue/offline_queue_state.dart';
+import 'package:recording_tool/core/network/offline_queue/offline_queue_storage.dart';
+import 'package:recording_tool/core/network/providers/network_providers.dart';
import 'package:dio/dio.dart';
final offlineQueueStorageProvider = Provider((ref) {
diff --git a/lib/features/recording/model/model_clipboard.dart b/lib/features/recording/model/model_clipboard.dart
new file mode 100644
index 0000000..0befd58
--- /dev/null
+++ b/lib/features/recording/model/model_clipboard.dart
@@ -0,0 +1,44 @@
+/// 剪切板内容数据模型
+class ClipboardRecordingModel {
+ final String title;
+ final int startTimestamp;
+ final int endTimestamp;
+ final String address;
+
+ ClipboardRecordingModel({
+ required this.title,
+ required this.startTimestamp,
+ required this.endTimestamp,
+ required this.address,
+ });
+
+ factory ClipboardRecordingModel.fromJson(Map json) {
+ return ClipboardRecordingModel(
+ title: _readString(json, 'title'),
+ startTimestamp: _readInt(json, 'startTimestamp'),
+ endTimestamp: _readInt(json, 'endTimestamp'),
+ address: _readString(json, 'address'),
+ );
+ }
+
+ Map toJson() {
+ return {
+ 'title': title,
+ 'startTimestamp': startTimestamp,
+ 'endTimestamp': endTimestamp,
+ 'address': address,
+ };
+ }
+
+ static String _readString(Map json, String key) {
+ final value = json[key];
+ if (value is String) return value;
+ throw FormatException('Clipboard field "$key" must be a String.');
+ }
+
+ static int _readInt(Map json, String key) {
+ final value = json[key];
+ if (value is int) return value;
+ throw FormatException('Clipboard field "$key" must be an int.');
+ }
+}
diff --git a/lib/features/recording/model/model_recording.dart b/lib/features/recording/model/model_recording.dart
new file mode 100644
index 0000000..2a7a4ed
--- /dev/null
+++ b/lib/features/recording/model/model_recording.dart
@@ -0,0 +1,26 @@
+import 'package:recording_tool/features/recording/model/model_clipboard.dart';
+
+class RecordingModel {
+ /// 剪切板内容
+ final ClipboardRecordingModel clipboardRecordingModel;
+
+ RecordingModel({required this.clipboardRecordingModel});
+
+ factory RecordingModel.fromJson(Map json) {
+ return RecordingModel(
+ clipboardRecordingModel: ClipboardRecordingModel.fromJson(
+ json['clipboardRecordingModel'],
+ ),
+ );
+ }
+ Map toJson() {
+ return {'clipboardRecordingModel': clipboardRecordingModel.toJson()};
+ }
+
+ RecordingModel copyWith({ClipboardRecordingModel? clipboardRecordingModel}) {
+ return RecordingModel(
+ clipboardRecordingModel:
+ clipboardRecordingModel ?? this.clipboardRecordingModel,
+ );
+ }
+}
diff --git a/lib/features/recording/recording_channel_names.dart b/lib/features/recording/recording_channel_names.dart
new file mode 100644
index 0000000..ae2f4e0
--- /dev/null
+++ b/lib/features/recording/recording_channel_names.dart
@@ -0,0 +1,5 @@
+abstract final class RecordingChannelNames {
+ static const packageName = 'com.gdfw.fxjk';
+ static const method = '$packageName/recording';
+ static const events = '$packageName/recording_events';
+}
diff --git a/lib/features/recording/recording_page.dart b/lib/features/recording/recording_page.dart
index e78b55f..a14ea52 100644
--- a/lib/features/recording/recording_page.dart
+++ b/lib/features/recording/recording_page.dart
@@ -3,11 +3,12 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:flutter_template/features/recording/recording_platform.dart';
-import 'package:flutter_template/features/recording/recording_session_controller.dart';
-import 'package:flutter_template/features/recording/widgets/camera_preview_widget.dart';
-import 'package:flutter_template/features/recording/widgets/recording_touch_lock_overlay.dart';
-import 'package:flutter_template/shared/widgets/widgets.dart';
+import 'package:recording_tool/features/recording/recording_platform.dart';
+import 'package:recording_tool/features/recording/recording_session_controller.dart';
+import 'package:recording_tool/features/recording/view-model/view_model_recording.dart';
+import 'package:recording_tool/features/recording/widgets/camera_preview_widget.dart';
+import 'package:recording_tool/features/recording/widgets/recording_touch_lock_overlay.dart';
+import 'package:recording_tool/shared/widgets/widgets.dart';
import 'package:permission_handler/permission_handler.dart';
class RecordingPage extends ConsumerStatefulWidget {
@@ -27,6 +28,7 @@ class _RecordingPageState extends ConsumerState {
}
Future _bootstrap() async {
+ await ref.read(recordingViewModelProvider.notifier).getClipboardContent();
await _enterRecordingMode();
// Allow PlatformView to attach before binding CameraX preview.
await Future.delayed(const Duration(milliseconds: 400));
diff --git a/lib/features/recording/recording_platform.dart b/lib/features/recording/recording_platform.dart
index c43dc8a..593d211 100644
--- a/lib/features/recording/recording_platform.dart
+++ b/lib/features/recording/recording_platform.dart
@@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:io';
import 'package:flutter/services.dart';
+import 'package:recording_tool/features/recording/recording_channel_names.dart';
enum RecordingState {
idle,
@@ -47,10 +48,10 @@ class RecordingPlatform {
RecordingPlatform._();
static const MethodChannel _channel = MethodChannel(
- 'com.example.flutter_template/recording',
+ RecordingChannelNames.method,
);
static const EventChannel _events = EventChannel(
- 'com.example.flutter_template/recording_events',
+ RecordingChannelNames.events,
);
static bool get isSupported =>
diff --git a/lib/features/recording/recording_session_controller.dart b/lib/features/recording/recording_session_controller.dart
index 440f6a2..70dcd0d 100644
--- a/lib/features/recording/recording_session_controller.dart
+++ b/lib/features/recording/recording_session_controller.dart
@@ -3,8 +3,8 @@ import 'dart:io';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:flutter_template/core/permission/permission_service.dart';
-import 'package:flutter_template/features/recording/recording_platform.dart';
+import 'package:recording_tool/core/permission/permission_service.dart';
+import 'package:recording_tool/features/recording/recording_platform.dart';
import 'package:permission_handler/permission_handler.dart';
class RecordingSessionState {
@@ -74,8 +74,8 @@ class RecordingSessionState {
final recordingSessionControllerProvider =
NotifierProvider(
- RecordingSessionController.new,
-);
+ RecordingSessionController.new,
+ );
class RecordingSessionController extends Notifier {
StreamSubscription? _statusSubscription;
@@ -161,9 +161,7 @@ class RecordingSessionController extends Notifier {
if (!shouldRetry) {
rethrow;
}
- await Future.delayed(
- Duration(milliseconds: 150 * (attempt + 1)),
- );
+ await Future.delayed(Duration(milliseconds: 150 * (attempt + 1)));
}
}
throw StateError('initializePreview retry exhausted');
diff --git a/lib/features/recording/view-model/view_model_recording.dart b/lib/features/recording/view-model/view_model_recording.dart
new file mode 100644
index 0000000..7fc59c0
--- /dev/null
+++ b/lib/features/recording/view-model/view_model_recording.dart
@@ -0,0 +1,56 @@
+import 'dart:convert';
+
+import 'package:flutter/services.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_riverpod/legacy.dart';
+import 'package:recording_tool/core/logging/app_logger.dart';
+import 'package:recording_tool/features/recording/model/model_clipboard.dart';
+import 'package:recording_tool/features/recording/model/model_recording.dart';
+
+final recordingViewModelProvider =
+ StateNotifierProvider((ref) {
+ return RecordingViewModel(ref);
+ });
+
+class RecordingViewModel extends StateNotifier {
+ RecordingViewModel(this.ref)
+ : super(
+ RecordingModel(
+ clipboardRecordingModel: ClipboardRecordingModel(
+ title: '',
+ startTimestamp: 0,
+ endTimestamp: 0,
+ address: '',
+ ),
+ ),
+ );
+ final Ref ref;
+
+ /// 从剪切板获取内容
+ Future getClipboardContent() async {
+ try {
+ final clipboardData = await Clipboard.getData(Clipboard.kTextPlain);
+ final text = clipboardData?.text;
+ AppLogger.debug('读取剪切板内容:$text');
+
+ if (text == null || text.trim().isEmpty) {
+ AppLogger.info('剪切板内容为空,跳过录制信息解析');
+ return;
+ }
+
+ final decoded = jsonDecode(text);
+ if (decoded is! Map) {
+ AppLogger.warning('剪切板内容不是 JSON 对象,跳过录制信息解析');
+ return;
+ }
+
+ final clipboardRecordingModel = ClipboardRecordingModel.fromJson(decoded);
+ state = state.copyWith(clipboardRecordingModel: clipboardRecordingModel);
+ AppLogger.info('剪切板录制信息解析成功:${clipboardRecordingModel.toJson()}');
+ } on FormatException catch (error) {
+ AppLogger.warning('剪切板录制信息格式错误:$error');
+ } catch (error, stackTrace) {
+ AppLogger.debug('读取剪切板录制信息失败', error: error, stackTrace: stackTrace);
+ }
+ }
+}
diff --git a/lib/main.dart b/lib/main.dart
index a0077f4..cfad6fc 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,3 +1,3 @@
-import 'package:flutter_template/app/bootstrap.dart';
+import 'package:recording_tool/app/bootstrap.dart';
Future main() => AppBootstrapper.bootstrap();
diff --git a/lib/shared/widgets/app_avatar.dart b/lib/shared/widgets/app_avatar.dart
index bcd50e2..8725e21 100644
--- a/lib/shared/widgets/app_avatar.dart
+++ b/lib/shared/widgets/app_avatar.dart
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
-import 'package:flutter_template/shared/widgets/app_network_image.dart';
+import 'package:recording_tool/shared/widgets/app_network_image.dart';
class AppAvatar extends StatelessWidget {
const AppAvatar({super.key, this.imageUrl, this.initials, this.size = 40});
diff --git a/lib/shared/widgets/app_card.dart b/lib/shared/widgets/app_card.dart
index d7989e6..d1464b6 100644
--- a/lib/shared/widgets/app_card.dart
+++ b/lib/shared/widgets/app_card.dart
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
-import 'package:flutter_template/app/theme/app_theme.dart';
+import 'package:recording_tool/app/theme/app_theme.dart';
class AppCard extends StatelessWidget {
const AppCard({
diff --git a/lib/shared/widgets/app_error_view.dart b/lib/shared/widgets/app_error_view.dart
index d1f0f02..21c0e78 100644
--- a/lib/shared/widgets/app_error_view.dart
+++ b/lib/shared/widgets/app_error_view.dart
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
-import 'package:flutter_template/shared/widgets/app_button.dart';
+import 'package:recording_tool/shared/widgets/app_button.dart';
class AppErrorView extends StatelessWidget {
const AppErrorView({
diff --git a/lib/shared/widgets/app_search_bar.dart b/lib/shared/widgets/app_search_bar.dart
index eb9cfea..52bbbc3 100644
--- a/lib/shared/widgets/app_search_bar.dart
+++ b/lib/shared/widgets/app_search_bar.dart
@@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
-import 'package:flutter_template/core/utils/rate_limiter.dart';
+import 'package:recording_tool/core/utils/rate_limiter.dart';
class AppSearchBar extends StatefulWidget {
const AppSearchBar({
diff --git a/lib/shared/widgets/app_status_view.dart b/lib/shared/widgets/app_status_view.dart
index 73f8430..9e09e2e 100644
--- a/lib/shared/widgets/app_status_view.dart
+++ b/lib/shared/widgets/app_status_view.dart
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
-import 'package:flutter_template/shared/widgets/app_empty_view.dart';
-import 'package:flutter_template/shared/widgets/app_error_view.dart';
-import 'package:flutter_template/shared/widgets/app_loading_view.dart';
+import 'package:recording_tool/shared/widgets/app_empty_view.dart';
+import 'package:recording_tool/shared/widgets/app_error_view.dart';
+import 'package:recording_tool/shared/widgets/app_loading_view.dart';
enum AppViewStatus { loading, empty, error, content }
diff --git a/lib/shared/widgets/app_toast.dart b/lib/shared/widgets/app_toast.dart
index 9d4d309..e08be24 100644
--- a/lib/shared/widgets/app_toast.dart
+++ b/lib/shared/widgets/app_toast.dart
@@ -1,7 +1,7 @@
import 'dart:async';
import 'package:flutter/material.dart';
-import 'package:flutter_template/app/router/app_navigator.dart';
+import 'package:recording_tool/app/router/app_navigator.dart';
class AppToast {
AppToast._();
diff --git a/pubspec.yaml b/pubspec.yaml
index ebbd40a..4c45bb0 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,8 +1,8 @@
-name: flutter_template
-description: "A reusable Flutter quick-start template for Android and iOS."
+name: recording_tool
+description: "A recording tool for Android and iOS."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
-publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+publish_to: "none" # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
@@ -66,7 +66,6 @@ dev_dependencies:
# The following section is specific to Flutter packages.
flutter:
-
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
diff --git a/lib/features/webview/test.html b/test.html
similarity index 100%
rename from lib/features/webview/test.html
rename to test.html
diff --git a/test/core/permission/permission_service_test.dart b/test/core/permission/permission_service_test.dart
index ac5b39c..74e94bd 100644
--- a/test/core/permission/permission_service_test.dart
+++ b/test/core/permission/permission_service_test.dart
@@ -1,10 +1,10 @@
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
-import 'package:permission_handler/permission_handler.dart';
+// ignore: depend_on_referenced_packages
import 'package:permission_handler_platform_interface/permission_handler_platform_interface.dart';
-import 'package:flutter_template/core/permission/permission_service.dart';
+import 'package:recording_tool/core/permission/permission_service.dart';
void main() {
group('PermissionService.requestMissing', () {
@@ -42,37 +42,42 @@ void main() {
expect(result[Permission.microphone], PermissionStatus.granted);
});
- test('preserves permanently denied permissions without requesting them',
- () async {
- final platform = FakePermissionHandlerPlatform(
- statuses: {
- Permission.camera: PermissionStatus.permanentlyDenied,
- Permission.microphone: PermissionStatus.denied,
- },
- requestResults: {
- Permission.microphone: PermissionStatus.granted,
- },
- );
- PermissionHandlerPlatform.instance = platform;
+ test(
+ 'preserves permanently denied permissions without requesting them',
+ () async {
+ final platform = FakePermissionHandlerPlatform(
+ statuses: {
+ Permission.camera: PermissionStatus.permanentlyDenied,
+ Permission.microphone: PermissionStatus.denied,
+ },
+ requestResults: {
+ Permission.microphone: PermissionStatus.granted,
+ },
+ );
+ PermissionHandlerPlatform.instance = platform;
- final result = await PermissionService.requestMissing([
- Permission.camera,
- Permission.microphone,
- ]);
+ final result = await PermissionService.requestMissing([
+ Permission.camera,
+ Permission.microphone,
+ ]);
- expect(platform.requestCalls, >[
- [Permission.microphone],
- ]);
- expect(result[Permission.camera], PermissionStatus.permanentlyDenied);
- expect(result[Permission.microphone], PermissionStatus.granted);
- });
+ expect(platform.requestCalls, >[
+ [Permission.microphone],
+ ]);
+ expect(result[Permission.camera], PermissionStatus.permanentlyDenied);
+ expect(result[Permission.microphone], PermissionStatus.granted);
+ },
+ );
});
group('iOS permission configuration', () {
test('Podfile enables camera and microphone permission macros', () {
final podfile = File('ios/Podfile').readAsStringSync();
- expect(podfile, contains("flutter_additional_ios_build_settings(target)"));
+ expect(
+ podfile,
+ contains('flutter_additional_ios_build_settings(target)'),
+ );
expect(podfile, contains("'PERMISSION_CAMERA=1'"));
expect(podfile, contains("'PERMISSION_MICROPHONE=1'"));
});
diff --git a/test/features/recording/model_clipboard_test.dart b/test/features/recording/model_clipboard_test.dart
new file mode 100644
index 0000000..762e0b0
--- /dev/null
+++ b/test/features/recording/model_clipboard_test.dart
@@ -0,0 +1,41 @@
+import 'package:flutter_test/flutter_test.dart';
+import 'package:recording_tool/features/recording/model/model_clipboard.dart';
+
+void main() {
+ group('ClipboardRecordingModel', () {
+ const clipboardJson = {
+ 'title': '王东方 丨李想 空中格斗赛',
+ 'startTimestamp': 1717334400,
+ 'endTimestamp': 1717334400,
+ 'address': '广州市番禺区·粤港澳大湾区青年人才双创小镇',
+ };
+
+ test('parses mini program clipboard JSON', () {
+ final model = ClipboardRecordingModel.fromJson(clipboardJson);
+
+ expect(model.title, '王东方 丨李想 空中格斗赛');
+ expect(model.startTimestamp, 1717334400);
+ expect(model.endTimestamp, 1717334400);
+ expect(model.address, '广州市番禺区·粤港澳大湾区青年人才双创小镇');
+ expect(model.toJson(), clipboardJson);
+ });
+
+ test('throws FormatException when required field is missing', () {
+ final json = Map.from(clipboardJson)..remove('title');
+
+ expect(
+ () => ClipboardRecordingModel.fromJson(json),
+ throwsA(isA()),
+ );
+ });
+
+ test('throws FormatException when required field has wrong type', () {
+ final json = {...clipboardJson, 'startTimestamp': '1717334400'};
+
+ expect(
+ () => ClipboardRecordingModel.fromJson(json),
+ throwsA(isA()),
+ );
+ });
+ });
+}
diff --git a/test/features/recording/recording_platform_test.dart b/test/features/recording/recording_platform_test.dart
index 8b02c1b..558e479 100644
--- a/test/features/recording/recording_platform_test.dart
+++ b/test/features/recording/recording_platform_test.dart
@@ -1,5 +1,5 @@
import 'package:flutter_test/flutter_test.dart';
-import 'package:flutter_template/features/recording/recording_platform.dart';
+import 'package:recording_tool/features/recording/recording_platform.dart';
void main() {
group('RecordingPlatform support', () {
diff --git a/test/features/recording/view_model_recording_test.dart b/test/features/recording/view_model_recording_test.dart
new file mode 100644
index 0000000..20ebcc9
--- /dev/null
+++ b/test/features/recording/view_model_recording_test.dart
@@ -0,0 +1,148 @@
+import 'package:flutter/services.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:recording_tool/features/recording/view-model/view_model_recording.dart';
+
+void main() {
+ TestWidgetsFlutterBinding.ensureInitialized();
+
+ const defaultClipboardTitle = '';
+ const validClipboardText =
+ '{"title":"王东方 丨李想 空中格斗赛","startTimestamp":1717334400,"endTimestamp":1717334400,"address":"广州市番禺区·粤港澳大湾区青年人才双创小镇"}';
+
+ Future setClipboardText(String? text) async {
+ TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
+ .setMockMethodCallHandler(SystemChannels.platform, (call) async {
+ if (call.method == 'Clipboard.getData') {
+ return text == null ? null : {'text': text};
+ }
+ return null;
+ });
+ }
+
+ tearDown(() {
+ TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
+ .setMockMethodCallHandler(SystemChannels.platform, null);
+ });
+
+ group('RecordingViewModel.getClipboardContent', () {
+ test(
+ 'updates state when clipboard contains valid mini program JSON',
+ () async {
+ await setClipboardText(validClipboardText);
+ final container = ProviderContainer();
+ addTearDown(container.dispose);
+
+ await container
+ .read(recordingViewModelProvider.notifier)
+ .getClipboardContent();
+
+ final clipboardModel = container
+ .read(recordingViewModelProvider)
+ .clipboardRecordingModel;
+ expect(clipboardModel.title, '王东方 丨李想 空中格斗赛');
+ expect(clipboardModel.startTimestamp, 1717334400);
+ expect(clipboardModel.endTimestamp, 1717334400);
+ expect(clipboardModel.address, '广州市番禺区·粤港澳大湾区青年人才双创小镇');
+ },
+ );
+
+ test('keeps default state when clipboard is empty', () async {
+ await setClipboardText('');
+ final container = ProviderContainer();
+ addTearDown(container.dispose);
+
+ await container
+ .read(recordingViewModelProvider.notifier)
+ .getClipboardContent();
+
+ expect(
+ container
+ .read(recordingViewModelProvider)
+ .clipboardRecordingModel
+ .title,
+ defaultClipboardTitle,
+ );
+ });
+
+ test('keeps default state when clipboard is not JSON', () async {
+ await setClipboardText('hello');
+ final container = ProviderContainer();
+ addTearDown(container.dispose);
+
+ await container
+ .read(recordingViewModelProvider.notifier)
+ .getClipboardContent();
+
+ expect(
+ container
+ .read(recordingViewModelProvider)
+ .clipboardRecordingModel
+ .title,
+ defaultClipboardTitle,
+ );
+ });
+
+ test('keeps default state when clipboard JSON is not an object', () async {
+ await setClipboardText('[1,2,3]');
+ final container = ProviderContainer();
+ addTearDown(container.dispose);
+
+ await container
+ .read(recordingViewModelProvider.notifier)
+ .getClipboardContent();
+
+ expect(
+ container
+ .read(recordingViewModelProvider)
+ .clipboardRecordingModel
+ .title,
+ defaultClipboardTitle,
+ );
+ });
+
+ test(
+ 'keeps default state when clipboard JSON misses required fields',
+ () async {
+ await setClipboardText('{"title":"王东方 丨李想 空中格斗赛"}');
+ final container = ProviderContainer();
+ addTearDown(container.dispose);
+
+ await container
+ .read(recordingViewModelProvider.notifier)
+ .getClipboardContent();
+
+ expect(
+ container
+ .read(recordingViewModelProvider)
+ .clipboardRecordingModel
+ .title,
+ defaultClipboardTitle,
+ );
+ },
+ );
+
+ test(
+ 'keeps default state when clipboard JSON has wrong field type',
+ () async {
+ await setClipboardText(
+ '{"title":"王东方 丨李想 空中格斗赛","startTimestamp":"1717334400","endTimestamp":1717334400,"address":"广州市番禺区·粤港澳大湾区青年人才双创小镇"}',
+ );
+ final container = ProviderContainer();
+ addTearDown(container.dispose);
+
+ await container
+ .read(recordingViewModelProvider.notifier)
+ .getClipboardContent();
+
+ expect(
+ container
+ .read(recordingViewModelProvider)
+ .clipboardRecordingModel
+ .title,
+ defaultClipboardTitle,
+ );
+ },
+ );
+ });
+}
diff --git a/test/widget_test.dart b/test/widget_test.dart
index 6708152..3218049 100644
--- a/test/widget_test.dart
+++ b/test/widget_test.dart
@@ -1,14 +1,13 @@
+import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
-import 'package:flutter_template/app/app.dart';
+import 'package:recording_tool/app/app.dart';
void main() {
- testWidgets('template app renders demo page', (tester) async {
+ testWidgets('recording app renders recording page', (tester) async {
await tester.pumpWidget(const ProviderScope(child: FlutterTemplateApp()));
await tester.pumpAndSettle();
- expect(find.text('Flutter Template'), findsOneWidget);
- expect(find.text('通用 Flutter 快速开发模板'), findsOneWidget);
- expect(find.text('增加计数'), findsOneWidget);
+ expect(find.byIcon(Icons.fiber_manual_record), findsOneWidget);
});
}