diff --git a/android/.kotlin/errors/errors-1768801901551.log b/android/.kotlin/errors/errors-1768801901551.log
new file mode 100644
index 00000000..72d1a39d
--- /dev/null
+++ b/android/.kotlin/errors/errors-1768801901551.log
@@ -0,0 +1,198 @@
+kotlin version: 2.2.20
+error message: Daemon compilation failed: null
+java.lang.Exception
+ at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:69)
+ at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:65)
+ at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:240)
+ at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
+ at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
+ at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
+ at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
+ at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
+ at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
+ at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
+ at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
+ at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
+ at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
+ at org.gradle.internal.Factories$1.create(Factories.java:31)
+ at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
+ at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
+ at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
+ at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
+ at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
+ at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
+ at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
+ at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
+ at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
+ at java.base/java.lang.Thread.run(Unknown Source)
+Caused by: java.lang.AssertionError: java.lang.Exception: Could not close incremental caches in D:\Code\Flutter\FlutterApp\build\shared_preferences_android\kotlin\compileDebugKotlin\cacheable\caches-jvm\jvm\kotlin: class-fq-name-to-source.tab, source-to-classes.tab, internal-name-to-source.tab
+ at org.jetbrains.kotlin.com.google.common.io.Closer.close(Closer.java:218)
+ at org.jetbrains.kotlin.incremental.IncrementalCachesManager.close(IncrementalCachesManager.kt:55)
+ at kotlin.io.CloseableKt.closeFinally(Closeable.kt:46)
+ at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:293)
+ at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:128)
+ at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:684)
+ at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:94)
+ at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1810)
+ at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
+ at java.base/java.lang.reflect.Method.invoke(Unknown Source)
+ at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
+ at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
+ at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
+ at java.base/java.security.AccessController.doPrivileged(Unknown Source)
+ at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
+ at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
+ at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
+ at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
+ at java.base/java.security.AccessController.doPrivileged(Unknown Source)
+ at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
+ ... 3 more
+Caused by: java.lang.Exception: Could not close incremental caches in D:\Code\Flutter\FlutterApp\build\shared_preferences_android\kotlin\compileDebugKotlin\cacheable\caches-jvm\jvm\kotlin: class-fq-name-to-source.tab, source-to-classes.tab, internal-name-to-source.tab
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:95)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.close(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.com.google.common.io.Closer.close(Closer.java:205)
+ ... 22 more
+ Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\CC\AppData\Local\Pub\Cache\hosted\pub.flutter-io.cn\shared_preferences_android-2.4.18\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and D:\Code\Flutter\FlutterApp\android.
+ at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117)
+ at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128)
+ at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:33)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:30)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:447)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:426)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:106)
+ at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136)
+ at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87)
+ ... 24 more
+ Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\CC\AppData\Local\Pub\Cache\hosted\pub.flutter-io.cn\shared_preferences_android-2.4.18\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and D:\Code\Flutter\FlutterApp\android.
+ at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117)
+ at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128)
+ at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30)
+ at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109)
+ at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153)
+ at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:1007)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:455)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:426)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:106)
+ at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108)
+ at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136)
+ at org.jetbrains.kotlin.incremental.storage.AppendableSetBasicMap.close(BasicMap.kt:157)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87)
+ ... 24 more
+ Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\CC\AppData\Local\Pub\Cache\hosted\pub.flutter-io.cn\shared_preferences_android-2.4.18\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and D:\Code\Flutter\FlutterApp\android.
+ at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117)
+ at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128)
+ at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:33)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:30)
+ at org.jetbrains.kotlin.incremental.storage.AppendableCollectionExternalizer.save(LazyStorage.kt:151)
+ at org.jetbrains.kotlin.incremental.storage.AppendableCollectionExternalizer.save(LazyStorage.kt:142)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:447)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:426)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:106)
+ at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108)
+ at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136)
+ at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87)
+ ... 24 more
+ Suppressed: java.lang.Exception: Could not close incremental caches in D:\Code\Flutter\FlutterApp\build\shared_preferences_android\kotlin\compileDebugKotlin\cacheable\caches-jvm\lookups: id-to-file.tab, file-to-id.tab
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:95)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.close(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.LookupStorage.close(LookupStorage.kt:155)
+ ... 23 more
+ Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\CC\AppData\Local\Pub\Cache\hosted\pub.flutter-io.cn\shared_preferences_android-2.4.18\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and D:\Code\Flutter\FlutterApp\android.
+ at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117)
+ at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128)
+ at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24)
+ at org.jetbrains.kotlin.incremental.storage.LegacyFileExternalizer.save(IdToFileMap.kt:51)
+ at org.jetbrains.kotlin.incremental.storage.LegacyFileExternalizer.save(IdToFileMap.kt:48)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:447)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:426)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:106)
+ at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136)
+ at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87)
+ ... 25 more
+ Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\CC\AppData\Local\Pub\Cache\hosted\pub.flutter-io.cn\shared_preferences_android-2.4.18\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and D:\Code\Flutter\FlutterApp\android.
+ at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117)
+ at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128)
+ at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30)
+ at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109)
+ at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153)
+ at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:1007)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:455)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:426)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:106)
+ at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136)
+ at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87)
+ ... 25 more
+ Suppressed: java.lang.Exception: Could not close incremental caches in D:\Code\Flutter\FlutterApp\build\shared_preferences_android\kotlin\compileDebugKotlin\cacheable\caches-jvm\inputs: source-to-output.tab
+ ... 25 more
+ Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\CC\AppData\Local\Pub\Cache\hosted\pub.flutter-io.cn\shared_preferences_android-2.4.18\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and D:\Code\Flutter\FlutterApp\android.
+ at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117)
+ at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128)
+ at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30)
+ at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109)
+ at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153)
+ at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:1007)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:455)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:426)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:106)
+ at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108)
+ at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136)
+ at org.jetbrains.kotlin.incremental.storage.AppendableSetBasicMap.close(BasicMap.kt:157)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87)
+ ... 24 more
+
+
diff --git a/android/.kotlin/errors/errors-1768812536616.log b/android/.kotlin/errors/errors-1768812536616.log
new file mode 100644
index 00000000..53eba616
--- /dev/null
+++ b/android/.kotlin/errors/errors-1768812536616.log
@@ -0,0 +1,198 @@
+kotlin version: 2.2.20
+error message: Daemon compilation failed: null
+java.lang.Exception
+ at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:69)
+ at org.jetbrains.kotlin.daemon.common.CompileService$CallResult$Error.get(CompileService.kt:65)
+ at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:240)
+ at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
+ at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
+ at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
+ at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:66)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:62)
+ at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:62)
+ at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
+ at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:210)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:205)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:67)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:60)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:167)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:60)
+ at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:54)
+ at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
+ at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:59)
+ at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:174)
+ at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
+ at org.gradle.internal.Factories$1.create(Factories.java:31)
+ at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
+ at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
+ at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
+ at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
+ at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Unknown Source)
+ at java.base/java.util.concurrent.FutureTask.run(Unknown Source)
+ at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
+ at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
+ at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
+ at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
+ at java.base/java.lang.Thread.run(Unknown Source)
+Caused by: java.lang.AssertionError: java.lang.Exception: Could not close incremental caches in D:\Code\Flutter\FlutterApp\build\shared_preferences_android\kotlin\compileReleaseKotlin\cacheable\caches-jvm\jvm\kotlin: class-fq-name-to-source.tab, source-to-classes.tab, internal-name-to-source.tab
+ at org.jetbrains.kotlin.com.google.common.io.Closer.close(Closer.java:218)
+ at org.jetbrains.kotlin.incremental.IncrementalCachesManager.close(IncrementalCachesManager.kt:55)
+ at kotlin.io.CloseableKt.closeFinally(Closeable.kt:46)
+ at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compileNonIncrementally(IncrementalCompilerRunner.kt:293)
+ at org.jetbrains.kotlin.incremental.IncrementalCompilerRunner.compile(IncrementalCompilerRunner.kt:128)
+ at org.jetbrains.kotlin.daemon.CompileServiceImplBase.execIncrementalCompiler(CompileServiceImpl.kt:684)
+ at org.jetbrains.kotlin.daemon.CompileServiceImplBase.access$execIncrementalCompiler(CompileServiceImpl.kt:94)
+ at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1810)
+ at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source)
+ at java.base/java.lang.reflect.Method.invoke(Unknown Source)
+ at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source)
+ at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
+ at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source)
+ at java.base/java.security.AccessController.doPrivileged(Unknown Source)
+ at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source)
+ at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source)
+ at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source)
+ at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source)
+ at java.base/java.security.AccessController.doPrivileged(Unknown Source)
+ at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source)
+ ... 3 more
+Caused by: java.lang.Exception: Could not close incremental caches in D:\Code\Flutter\FlutterApp\build\shared_preferences_android\kotlin\compileReleaseKotlin\cacheable\caches-jvm\jvm\kotlin: class-fq-name-to-source.tab, source-to-classes.tab, internal-name-to-source.tab
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:95)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.close(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.com.google.common.io.Closer.close(Closer.java:205)
+ ... 22 more
+ Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\CC\AppData\Local\Pub\Cache\hosted\pub.flutter-io.cn\shared_preferences_android-2.4.18\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and D:\Code\Flutter\FlutterApp\android.
+ at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117)
+ at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128)
+ at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:33)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:30)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:447)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:426)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:106)
+ at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136)
+ at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87)
+ ... 24 more
+ Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\CC\AppData\Local\Pub\Cache\hosted\pub.flutter-io.cn\shared_preferences_android-2.4.18\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and D:\Code\Flutter\FlutterApp\android.
+ at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117)
+ at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128)
+ at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30)
+ at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109)
+ at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153)
+ at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:1007)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:455)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:426)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:106)
+ at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108)
+ at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136)
+ at org.jetbrains.kotlin.incremental.storage.AppendableSetBasicMap.close(BasicMap.kt:157)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87)
+ ... 24 more
+ Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\CC\AppData\Local\Pub\Cache\hosted\pub.flutter-io.cn\shared_preferences_android-2.4.18\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and D:\Code\Flutter\FlutterApp\android.
+ at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117)
+ at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128)
+ at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:33)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.save(FileToPathConverter.kt:30)
+ at org.jetbrains.kotlin.incremental.storage.AppendableCollectionExternalizer.save(LazyStorage.kt:151)
+ at org.jetbrains.kotlin.incremental.storage.AppendableCollectionExternalizer.save(LazyStorage.kt:142)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:447)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:426)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:106)
+ at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108)
+ at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136)
+ at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87)
+ ... 24 more
+ Suppressed: java.lang.Exception: Could not close incremental caches in D:\Code\Flutter\FlutterApp\build\shared_preferences_android\kotlin\compileReleaseKotlin\cacheable\caches-jvm\lookups: id-to-file.tab, file-to-id.tab
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:95)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.close(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.LookupStorage.close(LookupStorage.kt:155)
+ ... 23 more
+ Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\CC\AppData\Local\Pub\Cache\hosted\pub.flutter-io.cn\shared_preferences_android-2.4.18\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and D:\Code\Flutter\FlutterApp\android.
+ at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117)
+ at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128)
+ at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24)
+ at org.jetbrains.kotlin.incremental.storage.LegacyFileExternalizer.save(IdToFileMap.kt:51)
+ at org.jetbrains.kotlin.incremental.storage.LegacyFileExternalizer.save(IdToFileMap.kt:48)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:447)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:426)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:106)
+ at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136)
+ at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87)
+ ... 25 more
+ Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\CC\AppData\Local\Pub\Cache\hosted\pub.flutter-io.cn\shared_preferences_android-2.4.18\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and D:\Code\Flutter\FlutterApp\android.
+ at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117)
+ at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128)
+ at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30)
+ at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109)
+ at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153)
+ at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:1007)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:455)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:426)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:106)
+ at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136)
+ at org.jetbrains.kotlin.incremental.storage.PersistentStorageWrapper.close(PersistentStorage.kt:124)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87)
+ ... 25 more
+ Suppressed: java.lang.Exception: Could not close incremental caches in D:\Code\Flutter\FlutterApp\build\shared_preferences_android\kotlin\compileReleaseKotlin\cacheable\caches-jvm\inputs: source-to-output.tab
+ ... 25 more
+ Suppressed: java.lang.IllegalArgumentException: this and base files have different roots: C:\Users\CC\AppData\Local\Pub\Cache\hosted\pub.flutter-io.cn\shared_preferences_android-2.4.18\android\src\main\kotlin\io\flutter\plugins\sharedpreferences\MessagesAsync.g.kt and D:\Code\Flutter\FlutterApp\android.
+ at kotlin.io.FilesKt__UtilsKt.toRelativeString(Utils.kt:117)
+ at kotlin.io.FilesKt__UtilsKt.relativeTo(Utils.kt:128)
+ at org.jetbrains.kotlin.incremental.storage.RelocatableFileToPathConverter.toPath(RelocatableFileToPathConverter.kt:24)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:50)
+ at org.jetbrains.kotlin.incremental.storage.FileDescriptor.getHashCode(FileToPathConverter.kt:30)
+ at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.hashKey(LinkedCustomHashMap.java:109)
+ at org.jetbrains.kotlin.com.intellij.util.containers.LinkedCustomHashMap.remove(LinkedCustomHashMap.java:153)
+ at org.jetbrains.kotlin.com.intellij.util.containers.SLRUMap.remove(SLRUMap.java:89)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.flushAppendCache(PersistentMapImpl.java:1007)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.doPut(PersistentMapImpl.java:455)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentMapImpl.put(PersistentMapImpl.java:426)
+ at org.jetbrains.kotlin.com.intellij.util.io.PersistentHashMap.put(PersistentHashMap.java:106)
+ at org.jetbrains.kotlin.incremental.storage.LazyStorage.set(LazyStorage.kt:80)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.applyChanges(InMemoryStorage.kt:108)
+ at org.jetbrains.kotlin.incremental.storage.AppendableInMemoryStorage.applyChanges(InMemoryStorage.kt:179)
+ at org.jetbrains.kotlin.incremental.storage.InMemoryStorage.close(InMemoryStorage.kt:136)
+ at org.jetbrains.kotlin.incremental.storage.AppendableSetBasicMap.close(BasicMap.kt:157)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner$close$1.invoke(BasicMapsOwner.kt:53)
+ at org.jetbrains.kotlin.incremental.storage.BasicMapsOwner.forEachMapSafe(BasicMapsOwner.kt:87)
+ ... 24 more
+
+
diff --git a/assets/svgs/remote_alert.svg b/assets/svgs/remote_alert.svg
new file mode 100644
index 00000000..ceb6d434
--- /dev/null
+++ b/assets/svgs/remote_alert.svg
@@ -0,0 +1,9 @@
+
diff --git a/assets/svgs/remote_back.svg b/assets/svgs/remote_back.svg
new file mode 100644
index 00000000..d3160964
--- /dev/null
+++ b/assets/svgs/remote_back.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_battery_charging.svg b/assets/svgs/remote_battery_charging.svg
new file mode 100644
index 00000000..3533dc67
--- /dev/null
+++ b/assets/svgs/remote_battery_charging.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_battery_full.svg b/assets/svgs/remote_battery_full.svg
new file mode 100644
index 00000000..61af371b
--- /dev/null
+++ b/assets/svgs/remote_battery_full.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_battery_low.svg b/assets/svgs/remote_battery_low.svg
new file mode 100644
index 00000000..4ca262e8
--- /dev/null
+++ b/assets/svgs/remote_battery_low.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_battery_middle.svg b/assets/svgs/remote_battery_middle.svg
new file mode 100644
index 00000000..0e09af1f
--- /dev/null
+++ b/assets/svgs/remote_battery_middle.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_battery_none.svg b/assets/svgs/remote_battery_none.svg
new file mode 100644
index 00000000..11b82f55
--- /dev/null
+++ b/assets/svgs/remote_battery_none.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_battery_warning.svg b/assets/svgs/remote_battery_warning.svg
new file mode 100644
index 00000000..974d81a0
--- /dev/null
+++ b/assets/svgs/remote_battery_warning.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_down.svg b/assets/svgs/remote_down.svg
new file mode 100644
index 00000000..1b7db61a
--- /dev/null
+++ b/assets/svgs/remote_down.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_expand.svg b/assets/svgs/remote_expand.svg
new file mode 100644
index 00000000..5db5d6ee
--- /dev/null
+++ b/assets/svgs/remote_expand.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_flame.svg b/assets/svgs/remote_flame.svg
new file mode 100644
index 00000000..7772efda
--- /dev/null
+++ b/assets/svgs/remote_flame.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_flame_minus.svg b/assets/svgs/remote_flame_minus.svg
new file mode 100644
index 00000000..d9fa11d0
--- /dev/null
+++ b/assets/svgs/remote_flame_minus.svg
@@ -0,0 +1,25 @@
+
diff --git a/assets/svgs/remote_flame_plus.svg b/assets/svgs/remote_flame_plus.svg
new file mode 100644
index 00000000..ac7ee1ad
--- /dev/null
+++ b/assets/svgs/remote_flame_plus.svg
@@ -0,0 +1,33 @@
+
diff --git a/assets/svgs/remote_fresh.svg b/assets/svgs/remote_fresh.svg
new file mode 100644
index 00000000..b0edef94
--- /dev/null
+++ b/assets/svgs/remote_fresh.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_is_controlling.svg b/assets/svgs/remote_is_controlling.svg
new file mode 100644
index 00000000..a4fe22d0
--- /dev/null
+++ b/assets/svgs/remote_is_controlling.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_layers.svg b/assets/svgs/remote_layers.svg
new file mode 100644
index 00000000..0aa0fa81
--- /dev/null
+++ b/assets/svgs/remote_layers.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_lock.svg b/assets/svgs/remote_lock.svg
new file mode 100644
index 00000000..207dd2fa
--- /dev/null
+++ b/assets/svgs/remote_lock.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_lock_open.svg b/assets/svgs/remote_lock_open.svg
new file mode 100644
index 00000000..dcdf5b0e
--- /dev/null
+++ b/assets/svgs/remote_lock_open.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_minus.svg b/assets/svgs/remote_minus.svg
new file mode 100644
index 00000000..3dedcc64
--- /dev/null
+++ b/assets/svgs/remote_minus.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_net.svg b/assets/svgs/remote_net.svg
new file mode 100644
index 00000000..30b39244
--- /dev/null
+++ b/assets/svgs/remote_net.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_plus.svg b/assets/svgs/remote_plus.svg
new file mode 100644
index 00000000..12fd70c5
--- /dev/null
+++ b/assets/svgs/remote_plus.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_power.svg b/assets/svgs/remote_power.svg
new file mode 100644
index 00000000..5af32604
--- /dev/null
+++ b/assets/svgs/remote_power.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_scissors.svg b/assets/svgs/remote_scissors.svg
new file mode 100644
index 00000000..1c4e26ed
--- /dev/null
+++ b/assets/svgs/remote_scissors.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_unexpand.svg b/assets/svgs/remote_unexpand.svg
new file mode 100644
index 00000000..4828b06a
--- /dev/null
+++ b/assets/svgs/remote_unexpand.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_up.svg b/assets/svgs/remote_up.svg
new file mode 100644
index 00000000..f270841d
--- /dev/null
+++ b/assets/svgs/remote_up.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/svgs/remote_video_left.svg b/assets/svgs/remote_video_left.svg
new file mode 100644
index 00000000..20ecb9ed
--- /dev/null
+++ b/assets/svgs/remote_video_left.svg
@@ -0,0 +1,36 @@
+
diff --git a/assets/svgs/remote_video_left_off.svg b/assets/svgs/remote_video_left_off.svg
new file mode 100644
index 00000000..696e3ed6
--- /dev/null
+++ b/assets/svgs/remote_video_left_off.svg
@@ -0,0 +1,38 @@
+
diff --git a/assets/svgs/remote_video_right.svg b/assets/svgs/remote_video_right.svg
new file mode 100644
index 00000000..846318ef
--- /dev/null
+++ b/assets/svgs/remote_video_right.svg
@@ -0,0 +1,40 @@
+
diff --git a/assets/svgs/remote_video_right_off.svg b/assets/svgs/remote_video_right_off.svg
new file mode 100644
index 00000000..e2cd2bea
--- /dev/null
+++ b/assets/svgs/remote_video_right_off.svg
@@ -0,0 +1,43 @@
+
diff --git a/assets/svgs/remote_zap.svg b/assets/svgs/remote_zap.svg
new file mode 100644
index 00000000..41b6dfe8
--- /dev/null
+++ b/assets/svgs/remote_zap.svg
@@ -0,0 +1,7 @@
+
diff --git a/assets/www/webrtc/jswebrtc.min.js b/assets/www/webrtc/jswebrtc.min.js
new file mode 100644
index 00000000..41d6b412
--- /dev/null
+++ b/assets/www/webrtc/jswebrtc.min.js
@@ -0,0 +1 @@
+var JSWebrtc={Player:null,VideoElement:null,CreateVideoElements:function(){var elements=document.querySelectorAll(".jswebrtc");for(var i=0;i=0)query_string=query_string.split("?")[1];var queries=query_string.split("&");for(var i=0;i=0){var params=app.substr(app.indexOf("?"));app=app.substr(0,app.indexOf("?"));if(params.indexOf("vhost=")>0){vhost=params.substr(params.indexOf("vhost=")+"vhost=".length);if(vhost.indexOf("&")>0){vhost=vhost.substr(0,vhost.indexOf("&"))}}}if(a.hostname==vhost){var re=/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;if(re.test(a.hostname))vhost="__defaultVhost__"}var schema="rtmp";if(rtmp_url.indexOf("://")>0)schema=rtmp_url.substr(0,rtmp_url.indexOf("://"));var port=a.port;if(!port){if(schema==="http"){port=80}else if(schema==="https"){port=443}else if(schema==="rtmp"){port=1935}else if(schema==="webrtc"||schema==="rtc"){port=1985}}var ret={url:rtmp_url,schema:schema,server:a.hostname,port:port,vhost:vhost,app:app,stream:stream};JSWebrtc.FillQuery(a.search,ret);return ret},HttpPost:function(url,data){return new Promise(function(resolve,reject){var xhr=new XMLHttpRequest;xhr.onreadystatechange=function(){if(xhr.readyState===4&&(xhr.status>=200&&xhr.status<300)){var respone=JSON.parse(xhr.responseText);xhr.onreadystatechange=new Function;xhr=null;resolve(respone)}};xhr.open("POST",url,true);xhr.timeout=5e3;xhr.responseType="text";xhr.setRequestHeader("Content-Type","application/json");xhr.send(data)})}};if(document.readyState==="complete"){JSWebrtc.CreateVideoElements()}else{document.addEventListener("DOMContentLoaded",JSWebrtc.CreateVideoElements)}JSWebrtc.VideoElement=function(){"use strict";var VideoElement=function(element){var url=element.dataset.url;if(!url){throw"VideoElement has no `data-url` attribute"}var addStyles=function(element,styles){for(var name in styles){element.style[name]=styles[name]}};this.container=element;addStyles(this.container,{display:"inline-block",position:"relative",minWidth:"80px",minHeight:"80px"});this.video=document.createElement("video");this.video.width=960;this.video.height=540;addStyles(this.video,{display:"block",width:"100%"});this.container.appendChild(this.video);this.playButton=document.createElement("div");this.playButton.innerHTML=VideoElement.PLAY_BUTTON;addStyles(this.playButton,{zIndex:2,position:"absolute",top:"0",bottom:"0",left:"0",right:"0",maxWidth:"75px",maxHeight:"75px",margin:"auto",opacity:"0.7",cursor:"pointer"});this.container.appendChild(this.playButton);var options={video:this.video};for(var option in element.dataset){try{options[option]=JSON.parse(element.dataset[option])}catch(err){options[option]=element.dataset[option]}}this.player=new JSWebrtc.Player(url,options);element.playerInstance=this.player;if(options.poster&&!options.autoplay){options.decodeFirstFrame=false;this.poster=new Image;this.poster.src=options.poster;this.poster.addEventListener("load",this.posterLoaded);addStyles(this.poster,{display:"block",zIndex:1,position:"absolute",top:0,left:0,bottom:0,right:0});this.container.appendChild(this.poster)}if(!this.player.options.streaming){this.container.addEventListener("click",this.onClick.bind(this))}if(options.autoplay){this.playButton.style.display="none"}if(this.player.audioOut&&!this.player.audioOut.unlocked){var unlockAudioElement=this.container;if(options.autoplay){this.unmuteButton=document.createElement("div");this.unmuteButton.innerHTML=VideoElement.UNMUTE_BUTTON;addStyles(this.unmuteButton,{zIndex:2,position:"absolute",bottom:"10px",right:"20px",width:"75px",height:"75px",margin:"auto",opacity:"0.7",cursor:"pointer"});this.container.appendChild(this.unmuteButton);unlockAudioElement=this.unmuteButton}this.unlockAudioBound=this.onUnlockAudio.bind(this,unlockAudioElement);unlockAudioElement.addEventListener("touchstart",this.unlockAudioBound,false);unlockAudioElement.addEventListener("click",this.unlockAudioBound,true)}};VideoElement.prototype.onUnlockAudio=function(element,ev){if(this.unmuteButton){ev.preventDefault();ev.stopPropagation()}this.player.audioOut.unlock(function(){if(this.unmuteButton){this.unmuteButton.style.display="none"}element.removeEventListener("touchstart",this.unlockAudioBound);element.removeEventListener("click",this.unlockAudioBound)}.bind(this))};VideoElement.prototype.onClick=function(ev){if(this.player.isPlaying){this.player.pause();this.playButton.style.display="block"}else{this.player.play();this.playButton.style.display="none";if(this.poster){this.poster.style.display="none"}}};VideoElement.PLAY_BUTTON='";VideoElement.UNMUTE_BUTTON='";return VideoElement}();JSWebrtc.Player=function(){"use strict";var Player=function(url,options){this.options=options||{};if(!url.match(/^webrtc?:\/\//)){throw"JSWebrtc just work with webrtc"}if(!this.options.video){throw"VideoElement is null"}this.urlParams=JSWebrtc.ParseUrl(url);this.pc=null;this.autoplay=!!options.autoplay||false;this.paused=true;if(this.autoplay)this.options.video.muted=true;this.startLoading()};Player.prototype.startLoading=function(){var _self=this;if(_self.pc){_self.pc.close()}_self.pc=new RTCPeerConnection(null);_self.pc.ontrack=function(event){_self.options.video["srcObject"]=event.streams[0]};_self.pc.addTransceiver("audio",{direction:"recvonly"});_self.pc.addTransceiver("video",{direction:"recvonly"});_self.pc.createOffer().then(function(offer){return _self.pc.setLocalDescription(offer).then(function(){return offer})}).then(function(offer){return new Promise(function(resolve,reject){var port=_self.urlParams.port||1985;var api=_self.urlParams.user_query.play||"/rtc/v1/play/";if(api.lastIndexOf("/")!=api.length-1){api+="/"}var url="http://"+_self.urlParams.server+":"+port+api;for(var key in _self.urlParams.user_query){if(key!="api"&&key!="play"){url+="&"+key+"="+_self.urlParams.user_query[key]}}var data={api:url,streamurl:_self.urlParams.url,clientip:null,sdp:offer.sdp};console.log("offer: "+JSON.stringify(data));JSWebrtc.HttpPost(url,JSON.stringify(data)).then(function(res){console.log("answer: "+JSON.stringify(res));resolve(res.sdp)},function(rej){reject(rej)})})}).then(function(answer){return _self.pc.setRemoteDescription(new RTCSessionDescription({type:"answer",sdp:answer}))}).catch(function(reason){throw reason});if(this.autoplay){this.play()}};Player.prototype.play=function(ev){if(this.animationId){return}this.animationId=requestAnimationFrame(this.update.bind(this));this.paused=false};Player.prototype.pause=function(ev){if(this.paused){return}cancelAnimationFrame(this.animationId);this.animationId=null;this.isPlaying=false;this.paused=true;this.options.video.pause();if(this.options.onPause){this.options.onPause(this)}};Player.prototype.stop=function(ev){this.pause()};Player.prototype.destroy=function(){this.pause();this.pc&&this.pc.close()&&this.pc.destroy();this.audioOut&&this.audioOut.destroy()};Player.prototype.update=function(){this.animationId=requestAnimationFrame(this.update.bind(this));if(this.options.video.readyState<4){return}if(!this.isPlaying){this.isPlaying=true;this.options.video.play();if(this.options.onPlay){this.options.onPlay(this)}}};return Player}();
\ No newline at end of file
diff --git a/assets/www/webrtc/websocket.js b/assets/www/webrtc/websocket.js
deleted file mode 100644
index 782351c7..00000000
--- a/assets/www/webrtc/websocket.js
+++ /dev/null
@@ -1,117 +0,0 @@
-class WebSocketClient {
- constructor(url) {
- this.url = url;
- this.socket = null;
- this.connected = false;
- this.reconnecting = false;
- this.reconnectInterval = 3000; // 重连间隔(ms)
- this.maxReconnectAttempts = 10; // 最大重连次数
- this.reconnectAttempts = 0;
- this.messageQueue = []; // 消息队列
-
- // 事件回调
- this.onConnect = null;
- this.onMessage = null;
- this.onClose = null;
- this.onError = null;
- }
-
- // 连接WebSocket服务器
- connect() {
- if (this.socket && (this.socket.readyState === WebSocket.CONNECTING || this.socket.readyState === WebSocket.OPEN)) {
- return;
- }
-
- this.socket = new WebSocket(this.url);
-
- this.socket.onopen = (event) => {
- this.connected = true;
- this.reconnecting = false;
- this.reconnectAttempts = 0;
- console.log('WebSocket连接已建立');
-
- // 发送队列中的所有消息
- this._sendQueuedMessages();
-
- if (typeof this.onConnect === 'function') {
- this.onConnect(event);
- }
- };
-
- this.socket.onmessage = (event) => {
- console.log('收到消息:', event.data);
- this.onMessage(event.data);
- };
-
- this.socket.onclose = (event) => {
- this.connected = false;
- console.log('WebSocket连接已关闭,代码:', event.code, '原因:', event.reason);
-
- if (typeof this.onClose === 'function') {
- this.onClose(event);
- }
-
- // 非主动关闭时尝试重连
- if (!this.reconnecting && event.code !== 1000) {
- this._scheduleReconnect();
- }
- };
-
- this.socket.onerror = (error) => {
- console.error('WebSocket错误:', error);
- if (typeof this.onError === 'function') {
- this.onError(error);
- }
- };
- }
-
- // 发送消息
- send(message) {
- if (this.connected && this.socket.readyState === WebSocket.OPEN) {
- this.socket.send(message);
- } else {
- // 连接未建立,将消息加入队列
- this.messageQueue.push(message);
- if (!this.reconnecting) {
- this._scheduleReconnect();
- }
- }
- }
-
- // 关闭连接
- close(code = 1000, reason = '') {
- this.reconnecting = false;
- if (this.socket) {
- this.socket.close(code, reason);
- }
- }
-
- // 安排重连
- _scheduleReconnect() {
- if (this.reconnectAttempts < this.maxReconnectAttempts) {
- this.reconnecting = true;
- this.reconnectAttempts++;
- const delay = this.reconnectInterval * Math.min(1, this.reconnectAttempts / 3); // 指数退避
-
- console.log(`尝试重连 (${this.reconnectAttempts}/${this.maxReconnectAttempts}),${delay/1000}秒后...`);
- setTimeout(() => {
- console.log('正在重连...');
- this.connect();
- }, delay);
- } else {
- console.error('达到最大重连次数,停止重连');
- this.reconnecting = false;
- }
- }
-
- // 发送队列中的消息
- _sendQueuedMessages() {
- if (this.messageQueue.length > 0) {
- console.log(`发送队列中的${this.messageQueue.length}条消息`);
- this.messageQueue.forEach(message => {
- this.socket.send(message);
- });
- this.messageQueue = [];
- }
- }
- }
\ No newline at end of file
diff --git a/devtools_options.yaml b/devtools_options.yaml
new file mode 100644
index 00000000..fa0b357c
--- /dev/null
+++ b/devtools_options.yaml
@@ -0,0 +1,3 @@
+description: This file stores settings for Dart & Flutter DevTools.
+documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
+extensions:
diff --git a/lib/core/theme/AppTheme.dart b/lib/core/theme/AppTheme.dart
index d25acfb7..ce4ad355 100644
--- a/lib/core/theme/AppTheme.dart
+++ b/lib/core/theme/AppTheme.dart
@@ -1,3 +1,5 @@
+import 'dart:io';
+
import 'package:flutter/material.dart';
class AppTheme {
@@ -11,6 +13,7 @@ class AppTheme {
return ThemeData(
useMaterial3: true,
brightness: Brightness.light,
+ fontFamily: (Platform.isWindows) ? 'Microsoft YaHei' : null,
// 1. 整体背景色
scaffoldBackgroundColor: _offWhite,
diff --git a/lib/features/remote_control/presentation/pages/remote_control_page.dart b/lib/features/remote_control/presentation/pages/remote_control_page.dart
index 9b10ff0f..55930ca5 100644
--- a/lib/features/remote_control/presentation/pages/remote_control_page.dart
+++ b/lib/features/remote_control/presentation/pages/remote_control_page.dart
@@ -13,7 +13,7 @@ import '../widgets/emergency_overlay.dart';
import '../widgets/left_joystick_area.dart';
import '../widgets/my_video_player.dart';
import '../widgets/right_joystick_area.dart';
-import '../widgets/top_status_bar.dart'; // 假设路径
+import '../widgets/top_status_bar.dart';
class RemoteControlPage extends StatefulWidget {
const RemoteControlPage({super.key});
@@ -26,17 +26,17 @@ class _RemoteControlPageState extends State {
@override
void initState() {
super.initState();
- // 强制横屏与沉浸式
+ // 1. 锁定横屏
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
+ // 2. 隐藏状态栏和虚拟按键
SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
}
@override
void dispose() {
- // 恢复竖屏
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
super.dispose();
@@ -44,16 +44,12 @@ class _RemoteControlPageState extends State {
@override
Widget build(BuildContext context) {
- // 1. 监听全局状态 (实现方案二:掉线实时刷新)
+ // 监听全局状态(这些通常不随摇杆频繁变化)
final userState = context.watch().state;
final deviceState = context.watch().state;
final currentDevice = deviceState.selectedDevice;
- // 2. 监听局部遥控状态 (对应 Android Compose 的 collectAsState)
- final remoteState = context.watch().state;
-
- // 如果设备突然掉线,显示遮罩 (对应 Android 的 finishEvent 逻辑)
- if (currentDevice == null /*|| !currentDevice.isOnline*/ ) {
+ if (currentDevice == null) {
return _buildOfflineScaffold();
}
@@ -61,56 +57,99 @@ class _RemoteControlPageState extends State {
backgroundColor: Colors.black,
body: Stack(
children: [
- // 底层:视频流 (传入 device 和 user)
+ // 【优化层 1】底层视频:传入固定参数,加上 RepaintBoundary
Positioned.fill(
child: MyVideoPlayer(
deviceIp: TCPConsts.TCP_IP,
device: currentDevice,
- user: userState.user!, // 假设 AppUserCubit 存有 user 实体
+ user: userState.user!,
),
),
- // 中层:急停呼吸灯光晕 (对应 Android 的 EmergencyBreathingOverlay)
- if (remoteState.isEmergency)
- const Positioned.fill(child: EmergencyOverlay()),
+ // 【优化层 2】中层:急停效果(只在 isEmergency 变化时才触发显示/隐藏)
+ BlocBuilder(
+ buildWhen: (p, c) => p.isEmergency != c.isEmergency,
+ builder: (context, state) {
+ return state.isEmergency ? const Positioned.fill(child: EmergencyOverlay()) : const SizedBox.shrink();
+ },
+ ),
- // 顶层:UI 控制层
+ // 【优化层 3】顶层:UI 控制层
SafeArea(
child: Column(
children: [
- // 顶部胶囊状态条
const TopStatusBar(),
-
- // 左右摇杆及中间控制区
Expanded(
- child: Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: 20,
- vertical: 10,
- ),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.end,
- children: [
- // 左摇杆:前后控制
- LeftJoystickArea(isLocked: remoteState.isLocked),
+ child: LayoutBuilder(
+ builder: (context, constraints) {
+ final double sideAreaWidth = constraints.maxWidth * 0.25;
+ final double centerAreaWidth = constraints.maxWidth * 0.5;
- // 中间区:底盘升降/急停按钮
- const CenterControlArea(),
+ // 关键:使用 BlocBuilder 局部刷新摇杆,不要 watch 整个 Page
+ return BlocBuilder(
+ // 只有 isLocked 改变时才重构摇杆区域,摇杆坐标改变由内部处理
+ buildWhen: (p, c) => p.isLocked != c.isLocked,
+ builder: (context, remoteState) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 10),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ crossAxisAlignment: CrossAxisAlignment.end,
+ children: [
+ // 左摇杆
+ SizedBox(
+ width: sideAreaWidth,
+ child: Align(
+ alignment: Alignment.bottomCenter,
+ child: LeftJoystickArea(
+ isLocked: remoteState.isLocked,
+ width: sideAreaWidth * 0.5 * 0.7,
+ ),
+ ),
+ ),
- // 右摇杆:左右控制
- RightJoystickArea(isLocked: remoteState.isLocked),
- ],
- ),
+ // 中间区
+ SizedBox(
+ width: centerAreaWidth,
+ child: Align(
+ alignment: Alignment.bottomCenter,
+ child: CenterControlArea(totalWidth: centerAreaWidth),
+ ),
+ ),
+
+ // 右摇杆
+ SizedBox(
+ width: sideAreaWidth,
+ child: Align(
+ alignment: Alignment.bottomCenter,
+ child: RightJoystickArea(
+ isLocked: remoteState.isLocked,
+ width: sideAreaWidth * 0.5 * 0.7,
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ },
+ );
+ },
),
),
],
),
),
- // 权限请求弹窗 (对应 Android 的 PermissionRequestDialog)
- if (remoteState.showPermissionRequestDialog)
- _buildPermissionDialog(context, remoteState),
+ // 权限弹窗
+ BlocBuilder(
+ buildWhen: (p, c) => p.showPermissionRequestDialog != c.showPermissionRequestDialog,
+ builder: (context, state) {
+ if (state.showPermissionRequestDialog) {
+ return _buildPermissionDialog(context, state);
+ }
+ return const SizedBox.shrink();
+ },
+ ),
],
),
);
diff --git a/lib/features/remote_control/presentation/widgets/center_control_area.dart b/lib/features/remote_control/presentation/widgets/center_control_area.dart
index 74e45a08..0ce1c3aa 100644
--- a/lib/features/remote_control/presentation/widgets/center_control_area.dart
+++ b/lib/features/remote_control/presentation/widgets/center_control_area.dart
@@ -1,93 +1,90 @@
+import 'package:cc_ui_kit/cc_ui_kit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../bloc/remote_control_cubit.dart';
import 'emergency_stop_button.dart';
-import 'middle_expandslider.dart';
class CenterControlArea extends StatelessWidget {
- const CenterControlArea({super.key});
+ final double totalWidth;
+
+ const CenterControlArea({super.key, required this.totalWidth});
@override
Widget build(BuildContext context) {
final remoteState = context.watch().state;
- // 如果处于急停激活状态,根据 Compose 逻辑,中间只显示急停状态
if (remoteState.isEmergency) {
return const Column(
mainAxisSize: MainAxisSize.min,
- children: [
- EmergencyStopButton(), // 刚才写的带 3 秒解除逻辑的按钮
- SizedBox(height: 16),
- ],
+ children: [EmergencyStopButton(), SizedBox(height: 16)],
);
}
- return Container(
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: Row(
- crossAxisAlignment: CrossAxisAlignment.end,
- children: [
- // 左推杆:底盘升降 (对应 Compose 的第一个 MiddleExpandSlider)
- _buildVerticalSlider(
- label: "底盘",
- onTop: () =>
- context.read().updateChassisLift(1),
- onMiddle: () =>
- context.read().updateChassisLift(0),
- onBottom: () =>
- context.read().updateChassisLift(2),
- iconTop: Icons.expand_less,
- iconMiddle: Icons.layers,
- iconBottom: Icons.expand_more,
+ return LayoutBuilder(
+ builder: (context, constraints) {
+ final double expandedWidth = totalWidth * 0.25;
+ final double emergencyStopButtonWidth = constraints.maxWidth * 0.5;
+
+ return Container(
+ height: 180,
+ // 这里不再使用 Row 包装三个 Expanded,而是直接固定宽度
+ child: Row(
+ //mainAxisSize: MainAxisSize.min, // 极其重要:防止在 spaceBetween 下挤压
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ // 左摇杆 (3份)
+ _buildSliderBox(expandedWidth, "底盘", true),
+
+ // 急停 (2份)
+ SizedBox(
+ width: emergencyStopButtonWidth,
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ EmergencyStopButton(width: emergencyStopButtonWidth),
+
+ SizedBox(height: totalWidth * 0.05),
+
+ const Text(
+ '双击空白处切换视角',
+ style: TextStyle(
+ fontSize: 8,
+ color: Colors.grey,
+ ),
+ ),
+ ],
+ ),
+ ),
+
+ // 右摇杆 (3份)
+ _buildSliderBox(expandedWidth, "备用", false),
+ ],
),
+ );
+ },
+ );
+ }
- const SizedBox(width: 24),
-
- // 中间:急停按钮
- const EmergencyStopButton(),
-
- const SizedBox(width: 24),
-
- // 右推杆:备用/其他 (对应 Compose 的第二个 MiddleExpandSlider)
- _buildVerticalSlider(
- label: "云台",
- onTop: () {}, // 预留接口
- onMiddle: () {},
- onBottom: () {},
- iconTop: Icons.keyboard_arrow_up,
- iconMiddle: Icons.videocam,
- iconBottom: Icons.keyboard_arrow_down,
+ Widget _buildSliderBox(double boxWidth, String label, bool isLeft) {
+ return SizedBox(
+ width: boxWidth,
+ child: Center(
+ child: SizedBox(
+ width: boxWidth * 0.6,
+ height: boxWidth * 0.6 * 3,
+ child: CCExpandSlider(
+ label: label,
+ svgStart: "assets/svgs/remote_up.svg",
+ svgCenter: "assets/svgs/remote_layers.svg",
+ svgEnd: "assets/svgs/remote_down.svg",
+ onStart: () => print("$label start"),
+ onCenter: () => print("$label center"),
+ onEnd: () => print("$label end"),
+ iconSize: boxWidth * 0.6 * 0.35,
),
- ],
+ ),
),
);
}
-
- // 快捷构建推杆的方法
- Widget _buildVerticalSlider({
- required String label,
- required VoidCallback onTop,
- required VoidCallback onMiddle,
- required VoidCallback onBottom,
- required IconData iconTop,
- required IconData iconMiddle,
- required IconData iconBottom,
- }) {
- return Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- MiddleExpandSlider(
- onTop: onTop,
- onMiddle: onMiddle,
- onBottom: onBottom,
- iconTop: iconTop,
- iconMiddle: iconMiddle,
- iconBottom: iconBottom,
- ),
- const SizedBox(height: 8),
- Text(label, style: const TextStyle(color: Colors.grey, fontSize: 12)),
- ],
- );
- }
}
diff --git a/lib/features/remote_control/presentation/widgets/emergency_stop_button.dart b/lib/features/remote_control/presentation/widgets/emergency_stop_button.dart
index c2f38383..4626ebee 100644
--- a/lib/features/remote_control/presentation/widgets/emergency_stop_button.dart
+++ b/lib/features/remote_control/presentation/widgets/emergency_stop_button.dart
@@ -1,11 +1,16 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flutter_svg/svg.dart';
import '../bloc/remote_control_cubit.dart';
class EmergencyStopButton extends StatefulWidget {
- const EmergencyStopButton({super.key});
+ final double width;
+ const EmergencyStopButton({
+ Key? key,
+ this.width = 120,
+ }) : super(key: key);
@override
State createState() => _EmergencyStopButtonState();
@@ -19,7 +24,6 @@ class _EmergencyStopButtonState extends State
@override
void initState() {
super.initState();
- // 对应 Compose 中的 durationMs = 3000L
_progressController = AnimationController(
vsync: this,
duration: const Duration(seconds: 3),
@@ -40,6 +44,9 @@ class _EmergencyStopButtonState extends State
.state
.isEmergency;
+ final width = widget.width * 0.65;
+ final double indicatorSize = width + 10;
+
return GestureDetector(
// 1. 处理点击:仅在未急停时触发
onTap: () {
@@ -75,20 +82,19 @@ class _EmergencyStopButtonState extends State
// 对应 Compose 的 Canvas 绘制进度环
if (_isPressing)
SizedBox(
- width: 130,
- height: 130,
+ width: indicatorSize,
+ height: indicatorSize,
child: CircularProgressIndicator(
value: _progressController.value,
strokeWidth: 10,
color: Colors.red.withOpacity(0.8),
- backgroundColor: Colors.red.withOpacity(0.2),
+ backgroundColor: Colors.red.withValues(alpha: 0.2),
),
),
- // 按钮本体 (对应 Android 的 120.dp Box)
Container(
- width: 120,
- height: 120,
+ width: width,
+ height: width,
decoration: BoxDecoration(
shape: BoxShape.circle,
// 根据状态切换颜色,对应 Color(0x80C53030)
@@ -98,7 +104,7 @@ class _EmergencyStopButtonState extends State
boxShadow: isEmergencyActive
? [
BoxShadow(
- color: Colors.red.withOpacity(0.5),
+ color: Colors.red.withValues(alpha: 0.5),
blurRadius: 20,
),
]
@@ -107,16 +113,12 @@ class _EmergencyStopButtonState extends State
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
- const Icon(
- Icons.report_problem_outlined, // 对应 remote_alert 图标
- color: Colors.white,
- size: 45,
- ),
+ SvgPicture.asset('assets/svgs/remote_alert.svg',width: width * 0.4,color: Colors.white.withValues(alpha: 0.5),),
const SizedBox(height: 6),
Text(
isEmergencyActive ? "急停中" : "急停",
- style: const TextStyle(
- color: Colors.white,
+ style: TextStyle(
+ color: Colors.white.withValues(alpha: 0.5),
fontSize: 14,
fontWeight: FontWeight.w900,
),
diff --git a/lib/features/remote_control/presentation/widgets/left_joystick_area.dart b/lib/features/remote_control/presentation/widgets/left_joystick_area.dart
index d920e3af..04653f8e 100644
--- a/lib/features/remote_control/presentation/widgets/left_joystick_area.dart
+++ b/lib/features/remote_control/presentation/widgets/left_joystick_area.dart
@@ -7,8 +7,13 @@ import '../bloc/remote_control_cubit.dart';
class LeftJoystickArea extends StatelessWidget {
final bool isLocked;
+ final double width;
- const LeftJoystickArea({super.key, required this.isLocked});
+ const LeftJoystickArea({
+ Key? key,
+ required this.isLocked,
+ required this.width,
+ }) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -22,7 +27,7 @@ class LeftJoystickArea extends StatelessWidget {
duration: const Duration(milliseconds: 300),
opacity: isLocked ? 0.3 : 1.0, // 锁定后变透明
child: CCJoystick(
- radius: 100, // 对应 size(200.dp)
+ radius: width, // 对应 size(200.dp)
axisHint: AxisHint.forwardBackward,
onValueChanged: (value) {
// 对应 viewModel.updateOriginY(y)
@@ -35,15 +40,16 @@ class LeftJoystickArea extends StatelessWidget {
),
),
),
- const SizedBox(height: 8),
- const Text(
+ const SizedBox(height: 18),
+ Text(
"前后控制",
style: TextStyle(
- color: Colors.grey,
+ color: Colors.white.withValues(alpha: 0.5),
fontSize: 12,
fontWeight: FontWeight.w500,
),
),
+ const SizedBox(height: 2),
],
);
}
diff --git a/lib/features/remote_control/presentation/widgets/my_video_player.dart b/lib/features/remote_control/presentation/widgets/my_video_player.dart
index 5bc74bd4..d1a849da 100644
--- a/lib/features/remote_control/presentation/widgets/my_video_player.dart
+++ b/lib/features/remote_control/presentation/widgets/my_video_player.dart
@@ -1,5 +1,5 @@
import 'dart:io';
-
+import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:maibu_satabot_v2/core/domain/entities/user_entity.dart';
@@ -9,6 +9,7 @@ class MyVideoPlayer extends StatefulWidget {
final String deviceIp;
final DeviceEntity device;
final UserEntity user;
+
const MyVideoPlayer({
Key? key,
required this.deviceIp,
@@ -21,11 +22,10 @@ class MyVideoPlayer extends StatefulWidget {
}
class _MyVideoPlayerState extends State {
- // 1. 定义本地服务器
InAppLocalhostServer? _localServer;
InAppWebViewController? _webViewController;
bool _isServerRunning = false;
- var actualPort;
+ int? actualPort;
@override
void initState() {
@@ -33,79 +33,73 @@ class _MyVideoPlayerState extends State {
_startServer();
}
- // 2. 启动服务器 (适用于 Windows, Android, iOS)
Future _startServer() async {
- // 1. 手动找一个系统分配的空闲端口
int availablePort = 0;
try {
- // 绑定到端口 0,系统会随机分配一个
var socket = await ServerSocket.bind(InternetAddress.loopbackIPv4, 0);
availablePort = socket.port;
- await socket.close(); // 立即释放,给 LocalhostServer 用
+ await socket.close();
} catch (e) {
- availablePort = 8080; // 万一失败,给个保底
+ availablePort = 8080;
}
- // 2. 使用找到的明确端口启动
_localServer = InAppLocalhostServer(port: availablePort);
await _localServer!.start();
-
- // 确认端口(有些版本需要通过这种方式确认)
actualPort = availablePort;
- debugPrint("服务器启动在端口: $actualPort");
-
- setState(() {
- _isServerRunning = true;
- });
+ if (mounted) {
+ setState(() {
+ _isServerRunning = true;
+ });
+ }
}
@override
void dispose() {
- _localServer?.close(); // 页面销毁时关闭服务器
+ _localServer?.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
+ // 防止父级因其他状态 setState 导致视频闪烁
if (!_isServerRunning) {
- return const Center(child: CircularProgressIndicator());
+ return const Center(child: CircularProgressIndicator(color: Colors.white));
}
- return InAppWebView(
- // 3. 通过 localhost 地址访问,而不是 file://
- initialUrlRequest: URLRequest(
- url: WebUri("http://localhost:$actualPort/assets/www/playwebrtc.html"),
+ // [性能关键] RepaintBoundary 必须包裹渲染密集型的 WebView
+ return RepaintBoundary(
+ child: InAppWebView(
+ initialUrlRequest: URLRequest(
+ url: WebUri("http://localhost:$actualPort/assets/www/webrtc/playwebrtc.html"),
+ ),
+ initialSettings: InAppWebViewSettings(
+ javaScriptEnabled: true,
+ mediaPlaybackRequiresUserGesture: false,
+ allowsInlineMediaPlayback: true,
+ preferredContentMode: UserPreferredContentMode.MOBILE,
+ // 优化性能:禁用滚动,开启硬件加速
+ supportZoom: false,
+ disableHorizontalScroll: true,
+ disableVerticalScroll: true,
+ hardwareAcceleration: true,
+ ),
+ onWebViewCreated: (controller) => _webViewController = controller,
+ onPermissionRequest: (controller, request) async {
+ return PermissionResponse(
+ resources: request.resources,
+ action: PermissionResponseAction.GRANT,
+ );
+ },
+ onLoadStop: (controller, url) {
+ _initWebRTC(widget.deviceIp, widget.device.deviceName, widget.user.token);
+ },
),
- initialSettings: InAppWebViewSettings(
- javaScriptEnabled: true,
- mediaPlaybackRequiresUserGesture: false,
- allowsInlineMediaPlayback: true,
- // Windows/Android 开启硬件加速优化图传
- preferredContentMode: UserPreferredContentMode.DESKTOP,
- ),
- onWebViewCreated: (controller) => _webViewController = controller,
- onPermissionRequest: (controller, request) async {
- return PermissionResponse(
- resources: request.resources,
- action: PermissionResponseAction.GRANT,
- );
- },
- onLoadStop: (controller, url) {
- _initWebRTC(
- widget.deviceIp,
- widget.device.deviceName,
- widget.user.token,
- );
- },
);
}
void _initWebRTC(String deviceIp, String deviceId, String token) {
- final streamUrl =
- "webrtc://$deviceIp/live/livestream/$deviceId?token=$token";
- _webViewController?.evaluateJavascript(
- source: "setStreamUrl('$streamUrl')",
- );
+ final streamUrl = "webrtc://$deviceIp/live/livestream/$deviceId?token=$token";
+ _webViewController?.evaluateJavascript(source: "setStreamUrl('$streamUrl')");
}
-}
+}
\ No newline at end of file
diff --git a/lib/features/remote_control/presentation/widgets/right_joystick_area.dart b/lib/features/remote_control/presentation/widgets/right_joystick_area.dart
index 6c92c3bf..011a04a6 100644
--- a/lib/features/remote_control/presentation/widgets/right_joystick_area.dart
+++ b/lib/features/remote_control/presentation/widgets/right_joystick_area.dart
@@ -7,8 +7,13 @@ import '../bloc/remote_control_cubit.dart';
class RightJoystickArea extends StatelessWidget {
final bool isLocked;
+ final double width;
- const RightJoystickArea({super.key, required this.isLocked});
+ const RightJoystickArea({
+ Key? key,
+ required this.isLocked,
+ required this.width,
+ }) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -22,7 +27,7 @@ class RightJoystickArea extends StatelessWidget {
duration: const Duration(milliseconds: 300),
opacity: isLocked ? 0.3 : 1.0, // 锁定后变透明/灰色
child: CCJoystick(
- radius: 100, // 对应 size(200.dp)
+ radius: width, // 对应 size(200.dp)
axisHint: AxisHint.leftRight, // 关键:指定为左右控制
onValueChanged: (value) {
// 对应 viewModel.updateOriginX(x)
@@ -35,12 +40,12 @@ class RightJoystickArea extends StatelessWidget {
),
),
),
- const SizedBox(height: 8),
+ const SizedBox(height: 18),
// 2. 底部文字:对应 Text("左右控制", color = Color.Gray)
- const Text(
+ Text(
"左右控制",
style: TextStyle(
- color: Colors.grey,
+ color: Colors.white.withValues(alpha: 0.5),
fontSize: 12,
fontWeight: FontWeight.w500,
),
diff --git a/lib/main.dart b/lib/main.dart
index e7201b37..636173db 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,3 +1,5 @@
+import 'dart:io';
+
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
@@ -39,4 +41,5 @@ class MyApp extends StatelessWidget {
),
);
}
-}
+
+}
\ No newline at end of file
diff --git a/packages/cc_ui_kit/lib/cc_ui_kit.dart b/packages/cc_ui_kit/lib/cc_ui_kit.dart
index 2a27a7fd..77aa68ce 100644
--- a/packages/cc_ui_kit/lib/cc_ui_kit.dart
+++ b/packages/cc_ui_kit/lib/cc_ui_kit.dart
@@ -3,3 +3,4 @@ library cc_ui_kit;
export 'src/cc_joystick.dart';
export 'src/cc_primary_button.dart';
export 'src/cc_primary_image_button.dart';
+export 'src/cc_expand_slider.dart';
diff --git a/packages/cc_ui_kit/lib/src/cc_expand_slider.dart b/packages/cc_ui_kit/lib/src/cc_expand_slider.dart
new file mode 100644
index 00000000..23a231a6
--- /dev/null
+++ b/packages/cc_ui_kit/lib/src/cc_expand_slider.dart
@@ -0,0 +1,223 @@
+import 'dart:async';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+
+enum SliderAxis { horizontal, vertical }
+
+class CCExpandSlider extends StatefulWidget {
+ final SliderAxis axis;
+ final bool isSpring;
+ final String label;
+ final String svgStart;
+ final String svgCenter;
+ final String svgEnd;
+ final double iconSize;
+ final double borderRadius;
+ final VoidCallback? onStart;
+ final VoidCallback? onCenter;
+ final VoidCallback? onEnd;
+
+ const CCExpandSlider({
+ super.key,
+ this.axis = SliderAxis.vertical,
+ this.isSpring = true,
+ required this.svgStart,
+ required this.svgCenter,
+ required this.svgEnd,
+ required this.label,
+ this.iconSize = 22.0,
+ this.borderRadius = 12.0,
+ this.onStart,
+ this.onCenter,
+ this.onEnd,
+ });
+
+ @override
+ State createState() => _CCExpandSliderState();
+}
+
+class _CCExpandSliderState extends State {
+ double _alignmentValue = 0.0;
+ bool _isExpanded = false;
+ Timer? _tickTimer;
+
+ @override
+ void dispose() {
+ _tickTimer?.cancel();
+ super.dispose();
+ }
+
+ void _startTicking() {
+ _tickTimer?.cancel();
+ _tickTimer = Timer.periodic(const Duration(milliseconds: 100), (timer) {
+ if (!_isExpanded) return;
+ if (_alignmentValue < -0.5) {
+ widget.onStart?.call();
+ } else if (_alignmentValue > 0.5) {
+ widget.onEnd?.call();
+ } else {
+ widget.onCenter?.call();
+ }
+ });
+ }
+
+ void _safeSetExpanded(bool expanded) {
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ if (mounted) {
+ setState(() {
+ _isExpanded = expanded;
+ if (!expanded) {
+ _alignmentValue = 0.0;
+ _tickTimer?.cancel();
+ }
+ });
+ if (expanded) _startTicking();
+ }
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return LayoutBuilder(builder: (context, constraints) {
+ final isVertical = widget.axis == SliderAxis.vertical;
+ final double maxLength = isVertical ? constraints.maxHeight : constraints.maxWidth;
+ final double minLength = isVertical ? constraints.maxWidth : constraints.maxHeight;
+ final double currentLength = _isExpanded ? maxLength : minLength;
+ final double dragArea = (maxLength - minLength) / 2;
+
+ return GestureDetector(
+ behavior: HitTestBehavior.opaque,
+ onPanDown: (_) => _safeSetExpanded(true),
+ onPanUpdate: (details) {
+ if (!_isExpanded) return;
+ setState(() {
+ final delta = isVertical ? details.delta.dy : details.delta.dx;
+ _alignmentValue = (_alignmentValue + delta / (dragArea > 0 ? dragArea : 1)).clamp(-1.0, 1.0);
+ });
+ },
+ onPanEnd: (_) {
+ if (widget.isSpring) widget.onCenter?.call();
+ HapticFeedback.lightImpact();
+ _safeSetExpanded(false);
+ },
+ onPanCancel: () => _safeSetExpanded(false),
+ child: Stack(
+ alignment: Alignment.center,
+ clipBehavior: Clip.none,
+ children: [
+ // 1. 轨道背景:从中向外展开
+ AnimatedContainer(
+ duration: const Duration(milliseconds: 200),
+ curve: Curves.easeOutCubic,
+ width: isVertical ? minLength : currentLength,
+ height: isVertical ? currentLength : minLength,
+ decoration: BoxDecoration(
+ color: Colors.white.withOpacity(0),
+ borderRadius: BorderRadius.circular(widget.borderRadius),
+ border: Border.all(color: Colors.white.withOpacity(0.3)),
+ ),
+ child: _buildSegmentedContent(isVertical, maxLength, minLength),
+ ),
+
+ // 2. 核心修改:文字位置固定逻辑
+ // 我们用一个和“缩起状态”一样大的 SizedBox 作为参考
+ if (!_isExpanded)
+ IgnorePointer( // 防止文字遮挡点击
+ child: SizedBox(
+ width: minLength,
+ height: minLength,
+ child: Stack(
+ clipBehavior: Clip.none,
+ alignment: Alignment.center,
+ children: [
+ Positioned(
+ bottom: -22, // 这里的 -22 是相对于“缩起时正方形”的底部
+ child: Text(
+ widget.label,
+ style: TextStyle(
+ color: Colors.white.withValues(alpha: 0.5),
+ fontSize: 10,
+ fontWeight: FontWeight.w500
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ });
+ }
+
+ Widget _buildSegmentedContent(bool isVertical, double maxLength, double minLength) {
+ final double segmentHeight = maxLength / 3;
+
+ return Stack(
+ alignment: Alignment.center,
+ children: [
+ if (_isExpanded) ...[
+ Positioned(
+ top: 0,
+ left: 0,
+ right: 0,
+ height: segmentHeight,
+ child: Center(child: _svgItem(widget.svgStart, -1)),
+ ),
+ Positioned(
+ bottom: 0,
+ left: 0,
+ right: 0,
+ height: segmentHeight,
+ child: Center(child: _svgItem(widget.svgEnd, 1)),
+ ),
+ ],
+
+ Center(
+ child: SizedBox(
+ width: minLength,
+ height: minLength,
+ child: Center(child: _svgItem(widget.svgCenter, 0)),
+ ),
+ ),
+
+ if (_isExpanded)
+ Padding(
+ padding: const EdgeInsets.all(4.0),
+ child: Align(
+ alignment: isVertical ? Alignment(0, _alignmentValue) : Alignment(_alignmentValue, 0),
+ child: Container(
+ width: minLength * 0.82,
+ height: minLength * 0.82,
+ decoration: BoxDecoration(
+ color: Colors.white.withOpacity(0.28),
+ borderRadius: BorderRadius.circular(widget.borderRadius * 0.7),
+ ),
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+
+ Widget _svgItem(String assetPath, int type) {
+ Color color = Colors.white30;
+ if (_isExpanded) {
+ if (type == -1 && _alignmentValue < -0.5) color = const Color(0xFF0076F9);
+ else if (type == 1 && _alignmentValue > 0.5) color = const Color(0xFF0076F9);
+ else if (type == 0 && _alignmentValue.abs() <= 0.5) color = const Color(0xFF0076F9);
+ else color = Colors.white;
+ } else {
+ color = Colors.white;
+ }
+ return SvgPicture.asset(
+ assetPath,
+ width: widget.iconSize,
+ height: widget.iconSize,
+ fit: BoxFit.contain,
+ colorFilter: ColorFilter.mode(color, BlendMode.srcIn),
+ );
+ }
+}
\ No newline at end of file
diff --git a/packages/cc_ui_kit/lib/src/cc_joystick.dart b/packages/cc_ui_kit/lib/src/cc_joystick.dart
index a6f90ffd..28998b59 100644
--- a/packages/cc_ui_kit/lib/src/cc_joystick.dart
+++ b/packages/cc_ui_kit/lib/src/cc_joystick.dart
@@ -132,16 +132,16 @@ class JoystickPainter extends CustomPainter {
paint
..color = const Color(0x6600BFFF)
..style = PaintingStyle.stroke
- ..strokeWidth = 2;
+ ..strokeWidth = 1;
canvas.drawCircle(center, radius + 8, paint);
// 2. 绘制箭头 (对应 drawArrow 逻辑)
paint
..color = Colors.cyan.withOpacity(0.5)
..strokeCap = StrokeCap.round
- ..strokeWidth = 4;
+ ..strokeWidth = 2;
- const arrowSize = 20.0;
+ const arrowSize = 10.0;
if (axisHint == AxisHint.forwardBackward) {
_drawArrow(
canvas,
diff --git a/packages/cc_ui_kit/pubspec.yaml b/packages/cc_ui_kit/pubspec.yaml
index e41ecc92..601e30d9 100644
--- a/packages/cc_ui_kit/pubspec.yaml
+++ b/packages/cc_ui_kit/pubspec.yaml
@@ -11,6 +11,8 @@ dependencies:
flutter:
sdk: flutter
+ flutter_svg: ^2.2.3
+
dev_dependencies:
flutter_test:
sdk: flutter
diff --git a/pubspec.yaml b/pubspec.yaml
index 2c86d3d1..c80ee226 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -84,6 +84,9 @@ dependencies:
# ===== 图片 =====
flutter_svg: ^2.2.3
+ # ===== 屏幕适配,高刷等 =====
+# flutter_displaymode: ^0.7.0
+
dev_dependencies:
flutter_test:
@@ -122,7 +125,8 @@ flutter:
# - images/a_dot_ham.jpeg
assets:
- assets/images/
- - assets/www/
+ - assets/www/webrtc/
+ - assets/svgs/
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images