Compare commits

..

24 Commits

Author SHA1 Message Date
mxy6664
fafefa3c38 删除无用吐司 2026-08-07 08:09:11 +08:00
mxy6664
a05cebae3f wifi选择弹窗修改 2026-08-03 16:01:53 +08:00
mxy6664
638233279f wifi选择按钮优化 2026-07-30 15:47:58 +08:00
mxy6664
8b413c5a0c 修改中英文 2026-07-30 11:15:03 +08:00
mxy6664
d79f43e2c9 标题改为“驱离设置” 2026-07-30 09:37:19 +08:00
mxy6664
8e237bd24a 修改按钮高度不生效原因 2026-07-28 10:45:27 +08:00
mxy6664
9f31f12463 修改按钮圆角值,和颜色差 2026-07-28 10:35:44 +08:00
mxy6664
55cf7196a6 app所有名称改为:驭鲲智防系统 2026-07-28 09:49:15 +08:00
mxy6664
fb99e7ecf4 1、app所有名称改为:驭鲲智防系统
2、“打击”字样全部改为“驱离”
3、logo的整体只保留文字部分
4.修改吐司中英文显示
2026-07-27 18:01:53 +08:00
f154f30e2d 优化布局样式,修改text的展示错误显示 2026-07-21 17:46:48 +08:00
7114e36165 初始化 2026-07-21 10:22:18 +08:00
195fb73620 优化res加密 2026-07-18 17:24:37 +08:00
655279a8a1 个人碎片和消息记录LoadingDialog 2026-07-17 15:35:36 +08:00
46385d5358 res加密 2026-07-15 15:26:00 +08:00
ab0dd2fbf6 修改告警记录 2026-07-14 17:35:34 +08:00
a9a69ca31b 自定义下拉刷新控件 2026-07-10 17:14:55 +08:00
1d54d6aa2a logd加载动画优化 2026-07-10 10:42:28 +08:00
200086301a 优化布局,删除不需要的view背景 2026-07-09 14:14:59 +08:00
0d0838d0e1 注销确定弹窗显示 2026-07-07 16:19:21 +08:00
a132cdfd27 注销按钮优化 2026-07-07 15:31:09 +08:00
96c8cca033 个人碎片页面的注销账号和设置页面 2026-07-07 09:15:33 +08:00
07f634ae05 直播流开始和停止 2026-07-03 17:58:51 +08:00
4819dbce86 云台控制攻击 2026-07-03 11:30:41 +08:00
6942abca3b 云台控制 2026-07-01 17:59:23 +08:00
77 changed files with 2975 additions and 891 deletions

View File

@@ -0,0 +1,48 @@
kotlin version: 2.2.10
error message: java.nio.file.AccessDeniedException: C:\Users\Administrator\AppData\Local\kotlin\daemon\kotlin-daemon-client-tsmarker3368904538941318214.tmp
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
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:68)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:64)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:61)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:102)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:61)
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:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:58)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:176)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
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:30)
at org.gradle.internal.work.DefaultWorkerLeaseService.lambda$withLocksAcquired$0(DefaultWorkerLeaseService.java:269)
at org.gradle.internal.work.ResourceLockStatistics$1.measure(ResourceLockStatistics.java:42)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocksAcquired(DefaultWorkerLeaseService.java:267)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:259)
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(Executors.java:572)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)

View File

@@ -0,0 +1,48 @@
kotlin version: 2.2.10
error message: java.nio.file.AccessDeniedException: C:\Users\Administrator\AppData\Local\kotlin\daemon\kotlin-daemon-client-tsmarker2940944726750836786.tmp
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
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:68)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:64)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:61)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:102)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:61)
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:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:58)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:176)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
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:30)
at org.gradle.internal.work.DefaultWorkerLeaseService.lambda$withLocksAcquired$0(DefaultWorkerLeaseService.java:269)
at org.gradle.internal.work.ResourceLockStatistics$1.measure(ResourceLockStatistics.java:42)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocksAcquired(DefaultWorkerLeaseService.java:267)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:259)
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(Executors.java:572)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)

View File

@@ -0,0 +1,48 @@
kotlin version: 2.2.10
error message: java.nio.file.AccessDeniedException: C:\Users\Administrator\AppData\Local\kotlin\daemon\kotlin-daemon-client-tsmarker13384034999995465012.tmp
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
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:68)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:64)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:61)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:102)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:61)
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:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:58)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:176)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
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:30)
at org.gradle.internal.work.DefaultWorkerLeaseService.lambda$withLocksAcquired$0(DefaultWorkerLeaseService.java:269)
at org.gradle.internal.work.ResourceLockStatistics$1.measure(ResourceLockStatistics.java:42)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocksAcquired(DefaultWorkerLeaseService.java:267)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:259)
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(Executors.java:572)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)

View File

@@ -0,0 +1,48 @@
kotlin version: 2.2.10
error message: java.nio.file.AccessDeniedException: C:\Users\Administrator\AppData\Local\kotlin\daemon\kotlin-daemon-client-tsmarker11134052769962658558.tmp
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
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:68)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:64)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:61)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:102)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:61)
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:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:58)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:176)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
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:30)
at org.gradle.internal.work.DefaultWorkerLeaseService.lambda$withLocksAcquired$0(DefaultWorkerLeaseService.java:269)
at org.gradle.internal.work.ResourceLockStatistics$1.measure(ResourceLockStatistics.java:42)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocksAcquired(DefaultWorkerLeaseService.java:267)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:259)
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(Executors.java:572)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)

View File

@@ -0,0 +1,48 @@
kotlin version: 2.2.10
error message: java.nio.file.AccessDeniedException: C:\Users\Administrator\AppData\Local\kotlin\daemon\kotlin-daemon-client-tsmarker4236714666597388016.tmp
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
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:68)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:64)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:61)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:102)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:61)
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:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:58)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:176)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
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:30)
at org.gradle.internal.work.DefaultWorkerLeaseService.lambda$withLocksAcquired$0(DefaultWorkerLeaseService.java:269)
at org.gradle.internal.work.ResourceLockStatistics$1.measure(ResourceLockStatistics.java:42)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocksAcquired(DefaultWorkerLeaseService.java:267)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:259)
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(Executors.java:572)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)

View File

@@ -0,0 +1,48 @@
kotlin version: 2.2.10
error message: java.nio.file.AccessDeniedException: C:\Users\Administrator\AppData\Local\kotlin\daemon\kotlin-daemon-client-tsmarker12754528945444950713.tmp
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
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:68)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:64)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:61)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:102)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:61)
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:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:58)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:176)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
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:30)
at org.gradle.internal.work.DefaultWorkerLeaseService.lambda$withLocksAcquired$0(DefaultWorkerLeaseService.java:269)
at org.gradle.internal.work.ResourceLockStatistics$1.measure(ResourceLockStatistics.java:42)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocksAcquired(DefaultWorkerLeaseService.java:267)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:259)
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(Executors.java:572)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)

View File

@@ -28,6 +28,12 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.DefenseApplication">
<activity
android:name=".personal.CancelAccountActivity"
android:exported="false" />
<activity
android:name=".personal.SettingsMenusActivity"
android:exported="false" />
<activity
android:name=".home.StartActivity"
android:exported="true">
@@ -122,8 +128,7 @@
android:exported="false" />
<activity
android:name=".home.HomeActivity"
android:exported="false">
</activity>
android:exported="false" />
<activity
android:name=".login.RegisterSuccessActivity"
android:exported="false" />

View File

@@ -4,6 +4,26 @@ import android.app.Application
import android.content.Context
import com.blankj.utilcode.util.Utils
import com.ckkj.defense_device.utils.LanguageUtil
// _ooOoo_
// o8888888o
// 88" . "88
// (| -_- |)
// O\ = /O
// ____/`---'\____
// . ' \\| |// `.
// / \\||| : |||// \
// / _||||| -:- |||||- \
// | | \\\ - /// | |
// | \_| ''\---/'' | |
// \ .-\__ `-` ___/-. /
// ___`. .' /--.--\ `. . __
// ."" '< `.___\_<|>_/___.' >'"".
// | | : `- \`.;`\ _ /`;.`/ - ` : | |
// \ \ `-. \_ __\ /__ _/ .-` / /
// ======`-.____`-.___\_____/___.-`____.-'======
// `=---='
//
// 佛祖保佑 永无BUG
class MyApplication : Application() {

View File

@@ -2,6 +2,7 @@ package com.ckkj.defense_device.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.ckkj.defense_device.databinding.ItemWifiBinding
@@ -39,14 +40,25 @@ class WifiListAdapter(
override fun getItemCount(): Int = wifiList.size
fun updateData(newList: List<String>) {
val oldList = wifiList.toList()
val diffResult = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition] == newList[newItemPosition]
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition] == newList[newItemPosition]
})
wifiList.clear()
wifiList.addAll(newList)
notifyDataSetChanged()
diffResult.dispatchUpdatesTo(this)
}
fun addData(newList: List<String>) {
val startPosition = wifiList.size
val newItems = newList.filter { !wifiList.contains(it) }
if (newItems.isEmpty()) return
wifiList.addAll(newItems)
notifyItemRangeInserted(startPosition, newItems.size)
}

View File

@@ -9,11 +9,11 @@ data class DeviceBean(
val deviceFamilyId: String? = null,
val deviceImg: String? = null,
val deviceName: String? = null,//设备名称
val deviceNo: String? = null,//设备编号
val deviceSn: String? = null,//设备序列号
val deviceNo: String = "",//设备编号
val deviceSn: String = "",//设备序列号
val endTime: String? = null,//自动布放结束时间
val expirationTime: String? = null,
val fwVer: String? = null,//固件版本
val fwVer: String= "",//固件版本
val id: String? = null,
val light: String? = null,//强光开启
val patternPerception: String? = null,//识别模式

View File

@@ -0,0 +1,20 @@
package com.ckkj.defense_device.bean
data class VideoStreamInfo(
val deviceNo: String,
val streamId: String,
val app: String,
val online: Boolean,
val pushUrl: String,
val playToken: String,
val expireAt: String,
val playUrls:playUrls,
val expireSeconds: Int,
)
class playUrls(
val flv: String,
val hls: String,
val rtmp: String,
val rtsp: String,
val webrtc: String,
)

View File

@@ -0,0 +1,5 @@
package com.ckkj.defense_device.bean
data class VideoStreamRequest(
val deviceNo: String
)

View File

@@ -144,10 +144,6 @@ class AddFamilyActivity : AppCompatActivity() {
}
dialogBinding.rvWifiList.adapter = wifiListAdapter
dialogBinding.swipeRefreshLayout.setOnRefreshListener {
refreshWifiList(dialogBinding)
}
dialogBinding.rvWifiList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
@@ -168,16 +164,6 @@ class AddFamilyActivity : AppCompatActivity() {
return rect.height()
}
private fun refreshWifiList(dialogBinding: DialogWifiListBinding) {
dialogBinding.swipeRefreshLayout.isRefreshing = true
handler.postDelayed({
wifiManager.startScan()
val newWifiList = getWifiList()
wifiListAdapter?.updateData(newWifiList)
dialogBinding.swipeRefreshLayout.isRefreshing = false
}, 1000)
}
private fun loadMoreWifiList() {
isLoadingMore = true
handler.postDelayed({
@@ -204,12 +190,8 @@ class AddFamilyActivity : AppCompatActivity() {
}
private fun updateWifiList() {
popupWindow?.contentView?.findViewById<androidx.recyclerview.widget.RecyclerView>(R.id.rvWifiList)?.let { recyclerView ->
val wifiList = getWifiList()
recyclerView.adapter = WifiListAdapter(wifiList) { wifiName ->
binding.etWifiName.setText(wifiName)
popupWindow?.dismiss()
}
popupWindow?.contentView?.findViewById<androidx.recyclerview.widget.RecyclerView>(R.id.rvWifiList)?.let {
wifiListAdapter?.updateData(getWifiList())
}
}

View File

@@ -21,6 +21,7 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.alibaba.sdk.android.emas.g
import com.ckkj.defense_device.R
import com.ckkj.defense_device.adapter.DeviceAdapter
import com.ckkj.defense_device.adapter.FamilyAdapter
@@ -39,6 +40,7 @@ import com.ckkj.defense_device.utils.RetrofitNetwork
import com.ckkj.defense_device.utils.UserinfoUtil
import com.ckkj.defense_device.view.DefenseDialogs
import com.ckkj.defense_device.view.LoadingDialog
import com.ckkj.defense_device.view.PullRefreshLayout
import com.ckkj.defense_device.view.showDefenseDialog
import com.gyf.immersionbar.ImmersionBar
import com.maning.mlkitscanner.scan.MNScanManager
@@ -62,6 +64,9 @@ class HomeMenuFragment : Fragment() {
private val pageSize = 10
private var hasMore = true
private var isLoading = false
private var loadingDialog: LoadingDialog? = null
private var lastNoMoreToastTime: Long = 0L
private val noMoreToastInterval: Long = 1500L
private val requestPermissionsLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
@@ -162,7 +167,7 @@ class HomeMenuFragment : Fragment() {
}
private fun fetchInviterInfo(inviteCode: String) {
val loadingDialog = LoadingDialog(requireActivity()).apply { show() }
val loadingDialog = LoadingDialog(requireActivity()).setMessage(this.getString(R.string.loading)).apply { show() }
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService()
@@ -297,9 +302,11 @@ class HomeMenuFragment : Fragment() {
)
}
binding.swipeRefreshLayout.setOnRefreshListener {
binding.swipeRefreshLayout.setOnRefreshListener(object : PullRefreshLayout.OnRefreshListener {
override fun onRefresh() {
refreshDeviceList()
}
})
binding.rvDeviceList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
@@ -307,9 +314,24 @@ class HomeMenuFragment : Fragment() {
val layoutManager = recyclerView.layoutManager as GridLayoutManager
val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()
val totalItemCount = layoutManager.itemCount
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
if (firstVisibleItemPosition == 0) {
val firstChild = layoutManager.findViewByPosition(0)
firstChild?.let { child ->
val top = child.top
if (top >= 0) {
val offset = (top.toFloat() / dpToPx(80f)).coerceIn(0f, 1f)
binding.gradientLayout.translationY = -offset * dpToPx(20f)
}
}
}
if (!isLoading && hasMore && lastVisibleItemPosition >= totalItemCount - 1 && dy > 0) {
loadMoreDevices()
} else if (!hasMore && !isLoading && lastVisibleItemPosition >= totalItemCount - 1
&& dy > 0 && totalItemCount > 0) {
showNoMoreToast()
}
}
})
@@ -425,7 +447,7 @@ class HomeMenuFragment : Fragment() {
canViewDevice = userInfo?.role == "appadmin" && familyBean.role != "member"
updateAdminUI()
updateFamilyDisplayText(familyName)
fetchDeviceList()
fetchDeviceList(showLoading = true)
FamilySwitchEvent.notifyFamilySwitched(familyName)
} else {
Toast.makeText(requireContext(), response.msg, Toast.LENGTH_SHORT).show()
@@ -439,15 +461,20 @@ class HomeMenuFragment : Fragment() {
}
}
private fun fetchDeviceList(page: Int = 1, isLoadMore: Boolean = false) {
private fun fetchDeviceList(page: Int = 1, isLoadMore: Boolean = false, showLoading: Boolean = false) {
if (!canViewDevice || isLoading) return
isLoading = true
loadingDialog = if (showLoading) LoadingDialog(requireActivity()).setMessage(getString(R.string.loading)).apply { show() } else null
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService().getDeviceList()
withContext(Dispatchers.Main) {
if (!isAdded) return@withContext
loadingDialog?.dismiss()
loadingDialog = null
Log.d("DeviceList", "code = ${response.code}, data = ${response.data}")
if (response.code == 200 && response.data != null) {
val newDevices = response.data
@@ -470,25 +497,36 @@ class HomeMenuFragment : Fragment() {
}
}
isLoading = false
binding.swipeRefreshLayout.finishRefresh()
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
if (!isAdded) return@withContext
loadingDialog?.dismiss()
loadingDialog = null
noNetwork()
Toast.makeText(requireContext(), R.string.network_error, Toast.LENGTH_SHORT).show()
isLoading = false
if (isLoadMore) {
hasMore = false
}
binding.swipeRefreshLayout.finishRefresh()
}
}
}
}
private fun showNoMoreToast() {
val now = System.currentTimeMillis()
if (now - lastNoMoreToastTime < noMoreToastInterval) return
lastNoMoreToastTime = now
ToastUtils.showToast(requireContext(), getString(R.string.no_more_data))
}
private fun refreshDeviceList() {
currentPage = 1
hasMore = true
fetchDeviceList(currentPage, false)
binding.swipeRefreshLayout.isRefreshing = false
}
private fun loadMoreDevices() {
@@ -548,6 +586,8 @@ class HomeMenuFragment : Fragment() {
val intent = Intent(requireContext(), LiveVideoActivity::class.java)
intent.putExtra("device_id", device.deviceId)
intent.putExtra("device_name", device.name)
intent.putExtra("deviceNo", device.deviceNo)
intent.putExtra("light", device.light)
startActivity(intent)
} else {
showDefenseDialog(
@@ -564,6 +604,14 @@ class HomeMenuFragment : Fragment() {
).toInt()
}
private fun dpToPx(dp: Float): Float {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp,
resources.displayMetrics
)
}
private class GridHorizontalSpaceItemDecoration(
private val horizontalSpace: Int
) : RecyclerView.ItemDecoration() {
@@ -586,6 +634,8 @@ class HomeMenuFragment : Fragment() {
override fun onDestroyView() {
super.onDestroyView()
loadingDialog?.dismiss()
loadingDialog = null
binding.rvFamilyList.adapter = null
binding.rvDeviceList.adapter = null
MessageStatusManager.removeListener(unreadCountChangeListener)

View File

@@ -5,11 +5,14 @@ import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.ckkj.defense_device.R
import com.ckkj.defense_device.adapter.MessageListAdapter
import com.ckkj.defense_device.adapter.MessageListItem
import com.ckkj.defense_device.databinding.MessageActivityBinding
import com.ckkj.defense_device.utils.MessageStatusManager
import com.ckkj.defense_device.utils.RetrofitNetwork
import com.ckkj.defense_device.view.LoadingDialog
import com.ckkj.defense_device.view.PullRefreshLayout
import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -19,13 +22,13 @@ import kotlinx.coroutines.withContext
class MessageActivity : AppCompatActivity() {
private lateinit var binding: MessageActivityBinding
private val messageAdapter by lazy { MessageListAdapter() }
private lateinit var loadingDialog: LoadingDialog
private var currentPage = 1
private val pageSize = 10
private var isLoading = false
private var hasMoreData = true
private val allMessages = mutableListOf<com.ckkj.defense_device.bean.MessageBean>()
private var lastLoadedCount = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -41,10 +44,19 @@ class MessageActivity : AppCompatActivity() {
}
private fun initView() {
loadingDialog = LoadingDialog(this).setMessage(getString(R.string.loading))
binding.ivBack.setOnClickListener { finish() }
binding.rvMessageList.layoutManager = LinearLayoutManager(this)
binding.rvMessageList.adapter = messageAdapter
binding.swipeRefreshLayout.setOnRefreshListener(object : PullRefreshLayout.OnRefreshListener {
override fun onRefresh() {
currentPage = 1
hasMoreData = true
loadMessageList()
}
})
binding.rvMessageList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
@@ -70,20 +82,21 @@ class MessageActivity : AppCompatActivity() {
if (isLoading) return
isLoading = true
if (currentPage == 1) {
showLoading()
}
CoroutineScope(Dispatchers.IO).launch {
try {
val messageResponse = RetrofitNetwork.getNetworkService().getMessage(
pageNum = currentPage,
pageSize = pageSize
)
println("total: ${messageResponse.total}")
println("rows size: ${messageResponse.rows.size}")
println("当前页:$currentPage, 加载条数:${messageResponse.rows.size}")
val newMessages = messageResponse.rows
val loadedCount = newMessages.size
if (loadedCount < pageSize) {
if (newMessages.size < pageSize) {
hasMoreData = false
}
@@ -98,11 +111,11 @@ class MessageActivity : AppCompatActivity() {
allMessages.clear()
}
allMessages.addAll(newMessages)
lastLoadedCount = loadedCount
val items = convertToMessageListItems(allMessages)
withContext(Dispatchers.Main) {
hideLoading()
hideEmptyState()
messageAdapter.submitList(items)
@@ -116,12 +129,10 @@ class MessageActivity : AppCompatActivity() {
withContext(Dispatchers.Main) {
if (allMessages.isEmpty()) {
println("items is empty, showing empty state")
showEmptyState()
}
}
} catch (e: Exception) {
println("Exception: ${e.message}")
e.printStackTrace()
withContext(Dispatchers.Main) {
if (allMessages.isEmpty()) {
@@ -130,6 +141,12 @@ class MessageActivity : AppCompatActivity() {
}
} finally {
isLoading = false
if (currentPage == 1) {
withContext(Dispatchers.Main) {
hideLoading()
binding.swipeRefreshLayout.finishRefresh()
}
}
}
}
}
@@ -141,19 +158,31 @@ class MessageActivity : AppCompatActivity() {
loadMessageList()
}
private fun showLoading() {
binding.swipeRefreshLayout.visibility = View.GONE
binding.emptyLayout.visibility = View.GONE
binding.layoutNoNetwork.visibility = View.GONE
loadingDialog.show()
}
private fun hideLoading() {
binding.swipeRefreshLayout.visibility = View.VISIBLE
loadingDialog.dismiss()
}
private fun showEmptyState() {
binding.rvMessageList.visibility = View.GONE
binding.swipeRefreshLayout.visibility = View.GONE
binding.emptyLayout.visibility = View.VISIBLE
binding.tvNoMoreData.visibility = View.GONE
}
private fun hideEmptyState() {
binding.rvMessageList.visibility = View.VISIBLE
binding.swipeRefreshLayout.visibility = View.VISIBLE
binding.emptyLayout.visibility = View.GONE
}
private fun noNetwork() {
binding.rvMessageList.visibility = View.GONE
binding.swipeRefreshLayout.visibility = View.GONE
binding.layoutNoNetwork.visibility = View.VISIBLE
binding.tvNoMoreData.visibility = View.GONE
}
@@ -171,15 +200,12 @@ class MessageActivity : AppCompatActivity() {
val groupedMessages = messages.groupBy { extractDate(it.createTime ?: "") }
groupedMessages.forEach { (date, messageList) ->
val headerTitle = formatDate(date)
messageList.forEachIndexed { index, message ->
println(" 消息${index + 1}: id=${message.id}, categoryName=${message.categoryName}, createTime=${message.createTime}")
}
items.add(MessageListItem.Header(headerTitle))
messageList.forEach { message ->
items.add(
MessageListItem.Message(
time = extractTime(message.createTime ?: ""),
title = message.categoryName ?: "未知消息",
title = message.categoryName ?: "",
dateTime = message.createTime ?: "",
unread = false
)

View File

@@ -16,6 +16,7 @@ import com.ckkj.defense_device.liveVideo.LinkedUserActivity
import com.ckkj.defense_device.personal.FaceManagementActivity
import com.ckkj.defense_device.personal.PasswordManagementActivity
import com.ckkj.defense_device.personal.PersonalProfileActivity
import com.ckkj.defense_device.personal.SettingsMenusActivity
import com.ckkj.defense_device.utils.AppLanguage
import com.ckkj.defense_device.utils.LanguageUtil
import com.ckkj.defense_device.utils.RetrofitNetwork
@@ -24,6 +25,7 @@ import com.ckkj.defense_device.utils.ActivityManager
import com.ckkj.defense_device.view.CustomDialogFragmentText
import com.ckkj.defense_device.view.FamilySwitchDialog
import com.ckkj.defense_device.view.LanguageDialog
import com.ckkj.defense_device.view.LoadingDialog
import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -34,6 +36,7 @@ class PersonalFragment : Fragment() {
// ViewBinding
private var _binding: PersonalFragmentBinding? = null
private val binding get() = _binding!!
private var loadingDialog: LoadingDialog? = null
override fun onResume() {
super.onResume()
fetchUserInfo()
@@ -52,7 +55,7 @@ class PersonalFragment : Fragment() {
.load("")
.circleCrop()
.into(binding.ivAvatar)
binding.headerLayout.setOnClickListener {
binding.ivAvatar.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService().getUserInfo()
@@ -132,6 +135,10 @@ class PersonalFragment : Fragment() {
}
dialog.show(childFragmentManager, "logout_dialog")
}
binding.ivMenu.setOnClickListener {
val intent = Intent(requireContext(), SettingsMenusActivity::class.java)
startActivity(intent)
}
fetchCurrentFamily()
return binding.root
}
@@ -229,6 +236,7 @@ class PersonalFragment : Fragment() {
private fun fetchUserInfo() {
loadingDialog = LoadingDialog(requireContext()).setMessage(getString(R.string.loading)).apply { show() }
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService().getUserInfo()
@@ -239,6 +247,11 @@ class PersonalFragment : Fragment() {
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
withContext(Dispatchers.Main) {
loadingDialog?.dismiss()
loadingDialog = null
}
}
}
}

View File

@@ -158,32 +158,24 @@ class AlarmLogActivity : AppCompatActivity() {
private fun fetchAlarmList() {
val currentDeviceId = deviceId ?: return
Log.d("AlarmLogActivity", "=== 开始请求设备告警日志 ===")
Log.d("AlarmLogActivity", "请求参数 - deviceId: $currentDeviceId")
isLoading = true
lifecycleScope.launch {
try {
Log.d("AlarmLogActivity", "正在发送网络请求...")
val response = RetrofitNetwork.getNetworkService().getDeviceAlarmList(
deviceId = currentDeviceId,
pageNum = "1",
pageSize = "10"
)
Log.d("AlarmLogActivity", "请求完成")
Log.d("AlarmLogActivity", "总条数: ${response.total}, 当前页数据条数: ${response.rows.size}")
val newData = response.rows
originalAlarmList.clear()
originalAlarmList.addAll(newData)
filterAlarmListByDate(calendar)
currentAlarmItem = alarmList.firstOrNull()
Log.d("AlarmLogActivity", "已获取 ${newData.size} 条数据")
} catch (e: Exception) {
Log.e("AlarmLogActivity", "请求异常: ${e.message}", e)
showAlarmListNetworkError()
} finally {
isLoading = false
Log.d("AlarmLogActivity", "请求结束")
@@ -191,14 +183,22 @@ class AlarmLogActivity : AppCompatActivity() {
}
}
private fun showEmptyView() {
private fun updateAlarmListState() {
if (alarmList.isEmpty()) {
binding.alarmListRecyclerView.visibility = View.GONE
binding.emptyLayout.visibility = View.VISIBLE
binding.layoutNoPermission.visibility = View.VISIBLE
binding.layoutNoNetwork.visibility = View.GONE
} else {
binding.alarmListRecyclerView.visibility = View.VISIBLE
binding.layoutNoPermission.visibility = View.GONE
binding.layoutNoNetwork.visibility = View.GONE
}
}
private fun hideEmptyView() {
binding.alarmListRecyclerView.visibility = View.VISIBLE
binding.emptyLayout.visibility = View.GONE
private fun showAlarmListNetworkError() {
binding.alarmListRecyclerView.visibility = View.GONE
binding.layoutNoPermission.visibility = View.GONE
binding.layoutNoNetwork.visibility = View.VISIBLE
}
private fun filterAlarmListByDate(selectedDate: Calendar) {
@@ -216,12 +216,11 @@ class AlarmLogActivity : AppCompatActivity() {
Log.d("AlarmLogActivity", "日期筛选完成,筛选后条数: ${alarmList.size}")
if (alarmList.isEmpty()) {
showEmptyView()
currentAlarmItem = null
} else {
hideEmptyView()
currentAlarmItem = alarmList.firstOrNull()
}
updateAlarmListState()
}
private fun updateDateDisplay() {
@@ -407,7 +406,7 @@ class AlarmLogActivity : AppCompatActivity() {
adapter?.notifyDataSetChanged()
currentAlarmItem = alarmList.firstOrNull()
if (alarmList.isEmpty()) {
showEmptyView()
showAlarmListNetworkError()
}
} else {
Toast.makeText(this@AlarmLogActivity, response.msg, Toast.LENGTH_SHORT).show()

View File

@@ -55,8 +55,8 @@ class DeviceInfoActivity : AppCompatActivity() {
}
private fun updateDeviceInfo(device: com.ckkj.defense_device.bean.DeviceBean) {
binding.tvDeviceModelValue.text = device.deviceNo.toString()
binding.tvDeviceNoValue.text = device.deviceSn.toString()
binding.tvFwVersionValue.text = device.fwVer.toString()
binding.tvDeviceModelValue.text = device.deviceNo?.toString()?: ""
binding.tvDeviceNoValue.text = device.deviceSn?.toString() ?: ""
binding.tvFwVersionValue.text = device.fwVer?.toString()?: ""
}
}

View File

@@ -24,6 +24,7 @@ import com.ckkj.defense_device.utils.DeviceIdEvent
import com.ckkj.defense_device.utils.DeviceUserUpdateEvent
import com.ckkj.defense_device.utils.RetrofitNetwork
import com.ckkj.defense_device.view.CustomDialogFragmentText
import com.ckkj.defense_device.view.PullRefreshLayout
import com.gyf.immersionbar.ImmersionBar
import com.yanzhenjie.recyclerview.SwipeMenuItem
import kotlinx.coroutines.CoroutineScope
@@ -95,9 +96,11 @@ class LinkedUserActivity : AppCompatActivity() {
(binding.rvLinkedUser as RecyclerView).layoutManager = LinearLayoutManager(this)
binding.swipeRefresh.setOnRefreshListener {
binding.swipeRefresh.setOnRefreshListener(object : PullRefreshLayout.OnRefreshListener {
override fun onRefresh() {
fetchUserList()
}
})
binding.rvLinkedUser.setSwipeMenuCreator { leftMenu, rightMenu, position ->
if (mode == MODE_SETTINGS) {
@@ -229,7 +232,6 @@ class LinkedUserActivity : AppCompatActivity() {
}
private fun fetchUserList() {
binding.swipeRefresh.isRefreshing = true
lifecycleScope.launch {
try {
var deviceId: String? = null
@@ -238,7 +240,7 @@ class LinkedUserActivity : AppCompatActivity() {
if (deviceId.isNullOrEmpty()) {
Toast.makeText(this@LinkedUserActivity, "设备 ID 为空", Toast.LENGTH_SHORT).show()
withContext(Dispatchers.Main) {
binding.swipeRefresh.isRefreshing = false
binding.swipeRefresh.finishRefresh()
}
return@launch
}
@@ -249,7 +251,7 @@ class LinkedUserActivity : AppCompatActivity() {
RetrofitNetwork.getNetworkService().getFamilyMembers()
}
withContext(Dispatchers.Main) {
binding.swipeRefresh.isRefreshing = false
binding.swipeRefresh.finishRefresh()
if (response.code == 200 && response.data != null) {
if (mode == MODE_SETTINGS) {
updateDeviceUserList(response.data as List<DeviceUserBean>)
@@ -264,7 +266,7 @@ class LinkedUserActivity : AppCompatActivity() {
} catch (e: Exception) {
e.printStackTrace()
withContext(Dispatchers.Main) {
binding.swipeRefresh.isRefreshing = false
binding.swipeRefresh.finishRefresh()
noNetwork()
Toast.makeText(this@LinkedUserActivity, R.string.network_request_failed, Toast.LENGTH_SHORT).show()
}

View File

@@ -5,6 +5,7 @@ import android.content.res.Configuration
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.MotionEvent
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -20,8 +21,14 @@ import com.gyf.immersionbar.ImmersionBar
import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
import com.shuyu.gsyvideoplayer.utils.OrientationUtils
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import androidx.lifecycle.lifecycleScope
import android.os.Handler
import android.os.Looper
import com.ckkj.defense_device.bean.VideoStreamInfo
import com.ckkj.defense_device.bean.VideoStreamRequest
import com.ckkj.defense_device.view.MyGSYVideoPlayerLive
class LiveVideoActivity : BaseActivity() {
private lateinit var binding: LiveVideoActivityBinding
@@ -30,6 +37,7 @@ class LiveVideoActivity : BaseActivity() {
private var isLightOn = false
private var isConsoleVisible = false
private var deviceId: String? = null
private var deviceNo: String? = null
private var alarmList: MutableList<DeviceAlarmBean> = mutableListOf()
private var alarmAdapter: AlarmLogHorizontalAdapter? = null
private var currentPage = 1
@@ -39,6 +47,9 @@ class LiveVideoActivity : BaseActivity() {
private var isLoading = false
private var orientationUtils: OrientationUtils? = null
private val ptzHandler = Handler(Looper.getMainLooper())
private var currentPtzDirection: String? = null
private var streamUrl: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -54,6 +65,8 @@ class LiveVideoActivity : BaseActivity() {
deviceId = intent.getStringExtra("device_id")
val deviceName = intent.getStringExtra("device_name")
deviceNo = intent.getStringExtra("deviceNo")
val light = intent.getStringExtra("light")
if (!deviceName.isNullOrEmpty()) {
binding.tvTitle.text = deviceName
}
@@ -80,15 +93,41 @@ class LiveVideoActivity : BaseActivity() {
}
private fun initVideoPlayer() {
val videoUrl ="http://220.203.23.195:9090/live/test001.live.flv"
orientationUtils = OrientationUtils(this, binding.exoPlayContextId)
orientationUtils?.isEnable = true
hidePlayerControls()
fetchStreamUrl()
}
private fun fetchStreamUrl() {
val no = deviceNo ?: return
lifecycleScope.launch {
try {
val response = RetrofitNetwork.getNetworkService().videoStart(VideoStreamRequest(no))
Log.d("直播流开始", "code=${response.code}, msg=${response.msg}")
if (response.code == 200 && response.data != null) {
//flv(http) 支持IjkPlayer最好
//hls(.m3u8) 支持ExoPlayer/Ijk都支持
//rtmp IjkPlayer支持Exo默认不支持
//rtsp IjkPlayer支持
//webrtcGSYVideoPlayer 不支持
streamUrl = response.data.playUrls.flv
Log.d("直播流开始", "获取地址:$streamUrl")
setupVideoPlayer(streamUrl!!)
} else {
Log.e("直播流开始", response.msg)
}
} catch (e: Exception) {
Log.e("直播流开始", "异常", e)
}
}
}
private fun setupVideoPlayer(videoUrl: String) {
GSYVideoOptionBuilder()
.setUrl(videoUrl)
.setCacheWithPlay(false)
.setCacheWithPlay(false)
.setRotateViewAuto(true)
.setLockLand(false)
.setShowFullAnimation(true)
@@ -103,19 +142,31 @@ class LiveVideoActivity : BaseActivity() {
override fun onQuitFullscreen(url: String?, vararg objects: Any?) {
super.onQuitFullscreen(url, *objects)
orientationUtils?.backToProtVideo()
}
})
.build(binding.exoPlayContextId)
hidePlayerControls()
binding.exoPlayContextId.startPlayLogic()
binding.exoPlayContextId.fullscreenButton.setOnClickListener {
binding.exoPlayContextId.onPtzControlListener =
object : MyGSYVideoPlayerLive.OnPtzControlListener {
override fun onStartPtz(direction: String) {
val no = deviceNo ?: return
startPtz(deviceNo = no, direction = direction)
}
override fun onStopPtz() {
stopCurrentPtz()
}
override fun onShoot() {
deviceNo?.let { shoot(it) }
}
}
orientationUtils?.resolveByClick()
binding.exoPlayContextId.startWindowFullscreen(this, true, true)
}
}
private fun hidePlayerControls() {
binding.exoPlayContextId.backButton.visibility = View.GONE
@@ -237,6 +288,114 @@ class LiveVideoActivity : BaseActivity() {
binding.ivVoice.setImageResource(R.mipmap.voice)
}
}
binding.ivUp.setOnTouchListener { v, event ->
handlePtzTouch(
view = v,
event = event,
direction = "up",
action = { deviceNo }
)
}
binding.ivDown.setOnTouchListener { v, event ->
handlePtzTouch(
view = v,
event = event,
direction = "down",
action = { deviceNo }
)
}
binding.ivLeft.setOnTouchListener { v, event ->
handlePtzTouch(
view = v,
event = event,
direction = "left",
action = { deviceNo }
)
}
binding.ivRight.setOnTouchListener { v, event ->
handlePtzTouch(
view = v,
event = event,
direction = "right",
action = { deviceNo }
)
}
}
private fun handlePtzTouch(
view: View,
event: MotionEvent,
direction: String,
action: () -> String?
): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
view.isPressed = true
val deviceNo = action()
if (!deviceNo.isNullOrEmpty()) {
startPtz(deviceNo = deviceNo, direction = direction)
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
view.isPressed = false
stopCurrentPtz()
}
}
return true
}
private fun startPtz(deviceNo: String, direction: String) {
stopCurrentPtz()
currentPtzDirection = direction
lifecycleScope.launch {
try {
val response = when (direction) {
"up" -> RetrofitNetwork.getNetworkService().ptzUp(deviceNo)
"down" -> RetrofitNetwork.getNetworkService().ptzDown(deviceNo)
"left" -> RetrofitNetwork.getNetworkService().ptzLeft(deviceNo)
"right" -> RetrofitNetwork.getNetworkService().ptzRight(deviceNo)
else -> null
}
Log.d("LV", "PTZ $direction start response: ${response?.code}")
} catch (e: Exception) {
Log.e("LV", "PTZ $direction start failed", e)
}
}
}
private fun shoot(deviceNo: String) {
Log.d("LV", "btnCapture click, deviceNo=$deviceNo, duration=0")
lifecycleScope.launch {
try {
val response = RetrofitNetwork.getNetworkService().ptzShoot(
deviceNo = deviceNo,
duration = "0"
)
Log.d("LV", "PTZ shoot response: code=${response.code}, msg=${response.msg}")
} catch (e: Exception) {
Log.e("LV", "PTZ shoot failed", e)
}
}
}
private fun stopCurrentPtz() {
val direction = currentPtzDirection ?: return
currentPtzDirection = null
lifecycleScope.launch {
try {
val response = RetrofitNetwork.getNetworkService().ptzStop(deviceNo ?: return@launch)
Log.d("LV", "PTZ $direction stop response: ${response.code}")
} catch (e: Exception) {
Log.e("LV", "PTZ $direction stop failed", e)
}
}
}
private fun fetchAlarmList(page: Int = 1) {
@@ -264,14 +423,35 @@ class LiveVideoActivity : BaseActivity() {
}
alarmList.addAll(newData)
alarmAdapter?.notifyDataSetChanged()
updateAlarmListState()
} catch (e: Exception) {
Log.e("LiveVideoActivity", "请求告警列表失败", e)
hasMore = false
showAlarmListNetworkError()
} finally {
isLoading = false
}
}
}
private fun updateAlarmListState() {
if (alarmList.isEmpty()) {
binding.alarmLogRecyclerView.visibility = View.GONE
binding.layoutNoPermission.visibility = View.VISIBLE
binding.layoutNoNetwork.visibility = View.GONE
} else {
binding.alarmLogRecyclerView.visibility = View.VISIBLE
binding.layoutNoPermission.visibility = View.GONE
binding.layoutNoNetwork.visibility = View.GONE
}
}
private fun showAlarmListNetworkError() {
binding.alarmLogRecyclerView.visibility = View.GONE
binding.layoutNoPermission.visibility = View.GONE
binding.layoutNoNetwork.visibility = View.VISIBLE
}
override fun onResume() {
super.onResume()
binding.exoPlayContextId.onVideoResume()
@@ -284,7 +464,21 @@ class LiveVideoActivity : BaseActivity() {
override fun onDestroy() {
super.onDestroy()
ptzHandler.removeCallbacksAndMessages(null)
orientationUtils?.releaseListener()
binding.exoPlayContextId.release()
stopStream()
}
private fun stopStream() {
val no = deviceNo ?: return
GlobalScope.launch {
try {
val response = RetrofitNetwork.getNetworkService().videoStop(no)
Log.d("直播流停止", "videoStop response: code=${response.code}, msg=${response.msg}")
} catch (e: Exception) {
Log.e("直播流停止", "停止推流异常", e)
}
}
}
}

View File

@@ -75,7 +75,7 @@ class UserDetailsActivity : AppCompatActivity() {
}
private fun updateUI(data: com.ckkj.defense_device.bean.ObtainInviterInformationBean) {
binding.tvContactName.text = data.nickName ?: "未知用户"
binding.tvContactName.text = data.nickName ?: ""
binding.tvPhone.text = data.phonenumber ?: ""
binding.tvIdCard.text = data.idCard ?: ""
binding.tvMacAddress.text = data.mac ?: ""

View File

@@ -202,14 +202,6 @@ class WiFiManagementActivity : AppCompatActivity() {
}
dialogBinding.rvWifiList.adapter = adapter
// 添加下拉刷新功能
dialogBinding.swipeRefreshLayout.setOnRefreshListener {
wifiManager.startScan()
// 延迟1秒后停止刷新动画
android.os.Handler().postDelayed({
dialogBinding.swipeRefreshLayout.isRefreshing = false
}, 1000)
}
}
private fun updateBottomButtonVisibility() {
@@ -229,9 +221,6 @@ class WiFiManagementActivity : AppCompatActivity() {
}
private fun updateWifiList() {
popupWindow?.contentView?.findViewById<androidx.swiperefreshlayout.widget.SwipeRefreshLayout>(R.id.swipeRefreshLayout)?.let { swipeRefreshLayout ->
swipeRefreshLayout.isRefreshing = false
}
popupWindow?.contentView?.findViewById<androidx.recyclerview.widget.RecyclerView>(R.id.rvWifiList)?.let { recyclerView ->
val wifiList = getWifiList()
recyclerView.adapter = WifiListAdapter(wifiList) { wifiName ->

View File

@@ -13,6 +13,7 @@ import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.ckkj.defense_device.R
import com.ckkj.defense_device.databinding.FaceVerificationActivityBinding
import com.ckkj.defense_device.view.FaceAnalyzer
import com.google.mlkit.vision.face.FaceDetection
@@ -106,7 +107,7 @@ class FaceVerificationActivity : AppCompatActivity() {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
startCamera()
} else {
Toast.makeText(this, "需要相机权限才能进行人脸验证", Toast.LENGTH_SHORT).show()
Toast.makeText(this, R.string.need_camera_permission_for_face_verify, Toast.LENGTH_SHORT).show()
finish()
}
}

View File

@@ -57,10 +57,10 @@ class ForgetPasswordActivity : AppCompatActivity() {
if (isPhoneNumberValid(phoneNumber)) {
sendVerificationCode(phoneNumber)
} else {
Toast.makeText(this, "请输入有效的11位手机号", Toast.LENGTH_SHORT).show()
Toast.makeText(this, getString(R.string.phone_tip), Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(this, "请稍后再试", Toast.LENGTH_SHORT).show()
Toast.makeText(this, getString(R.string.please_try_later), Toast.LENGTH_SHORT).show()
}
}
@@ -86,7 +86,7 @@ class ForgetPasswordActivity : AppCompatActivity() {
val response = RetrofitNetwork.getNetworkService().getCode(phoneNumber)
withContext(Dispatchers.Main) {
if (response.code == 200) {
Toast.makeText(this@ForgetPasswordActivity, "验证码已发送", Toast.LENGTH_SHORT).show()
Toast.makeText(this@ForgetPasswordActivity, getString(R.string.code_sent_to), Toast.LENGTH_SHORT).show()
startCountDown()
} else {
Toast.makeText(this@ForgetPasswordActivity, response.msg, Toast.LENGTH_SHORT).show()
@@ -140,29 +140,30 @@ class ForgetPasswordActivity : AppCompatActivity() {
val confirmPassword = binding.etInviteCode.text.toString().trim()
if (!isPhoneNumberValid(phoneNumber)) {
Toast.makeText(this, "手机号格式不正确", Toast.LENGTH_SHORT).show()
Toast.makeText(this, getString(R.string.invalid_phone), Toast.LENGTH_SHORT).show()
return
}
if (TextUtils.isEmpty(smsCode)) {
Toast.makeText(this, "请输入验证码", Toast.LENGTH_SHORT).show()
Toast.makeText(this, getString(R.string.hint_code_zh), Toast.LENGTH_SHORT).show()
return
}
if (TextUtils.isEmpty(newPassword) || TextUtils.isEmpty(confirmPassword)) {
Toast.makeText(this, "密码不能为空", Toast.LENGTH_SHORT).show()
Toast.makeText(this, getString(R.string.password_cannot_empty), Toast.LENGTH_SHORT).show()
return
}
if (newPassword.length < MIN_PASSWORD_LENGTH || newPassword.length > MAX_PASSWORD_LENGTH) {
Toast.makeText(this, "密码长度需为${MIN_PASSWORD_LENGTH}-${MAX_PASSWORD_LENGTH}", Toast.LENGTH_SHORT).show()
val tip = getString(R.string.pwd_length_tip, MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH)
Toast.makeText(this, tip, Toast.LENGTH_SHORT).show()
return
}
if (!newPassword.matches(Regex(".*[A-Za-z].*")) || !newPassword.matches(Regex(".*[0-9].*"))) {
Toast.makeText(this, "密码需同时包含字母和数字", Toast.LENGTH_SHORT).show()
Toast.makeText(this, getString(R.string.password_letter_digit), Toast.LENGTH_SHORT).show()
return
}
if (newPassword != confirmPassword) {
Toast.makeText(this, "两次输入的密码不一致", Toast.LENGTH_SHORT).show()
Toast.makeText(this, getString(R.string.password_mismatch), Toast.LENGTH_SHORT).show()
return
}

View File

@@ -77,7 +77,7 @@ class LoginActivity : AppCompatActivity() {
binding.registerText.visibility = View.VISIBLE
binding.forgetText.visibility = View.GONE
updateRegisterTextConstraints(center = false)
updateRegisterTextConstraints(center = true)
//需要正确的方法名和参数
updateLoginButtonTopConstraint(R.id.smsRowLayout)
@@ -102,7 +102,6 @@ class LoginActivity : AppCompatActivity() {
private fun updateLoginButtonTopConstraint(topTargetId: Int) {
val params = binding.loginBtn.layoutParams as ConstraintLayout.LayoutParams
params.topToBottom = topTargetId
params.topMargin = 32
binding.loginBtn.layoutParams = params
}
@@ -119,7 +118,6 @@ class LoginActivity : AppCompatActivity() {
params.endToEnd = ConstraintSet.UNSET
}
params.topToBottom = binding.loginBtn.id
params.topMargin = 20
binding.registerText.layoutParams = params
}
@@ -197,7 +195,6 @@ class LoginActivity : AppCompatActivity() {
try {
val response = RetrofitNetwork.getNetworkService().getCode(phone)
withContext(Dispatchers.Main) {
Log.d("VerificationCode", "code = ${response.code}, msg = ${response.msg}")
if (response.code == 200) {
Toast.makeText(this@LoginActivity, getString(R.string.code_sent_to, phone), Toast.LENGTH_SHORT).show()
startCountDown()
@@ -222,9 +219,7 @@ class LoginActivity : AppCompatActivity() {
phonenumber = phone,
smsCode = smsCode
)
android.util.Log.d("LoginActivity", "短信登录请求: $loginRequest")
val response = RetrofitNetwork.getNetworkService().login(loginRequest)
android.util.Log.d("LoginActivity", "短信登录响应: code=${response.code}, msg=${response.msg}")
withContext(Dispatchers.Main) {
if (response.code == 200 && response.data != null) {
UserinfoUtil.saveUserInfo(response.data)
@@ -238,7 +233,6 @@ class LoginActivity : AppCompatActivity() {
}
}
} catch (e: Exception) {
android.util.Log.e("LoginActivity", "短信登录异常", e)
withContext(Dispatchers.Main) {
Toast.makeText(this@LoginActivity, R.string.network_error, Toast.LENGTH_SHORT).show()
}
@@ -255,14 +249,13 @@ class LoginActivity : AppCompatActivity() {
phonenumber = phone,
password = password
)
android.util.Log.d("LoginActivity", "密码登录请求: $loginRequest")
val response = RetrofitNetwork.getNetworkService().login(loginRequest)
android.util.Log.d("LoginActivity", "密码登录响应: code=${response.code}, msg=${response.msg}")
withContext(Dispatchers.Main) {
if (response.code == 200 && response.data != null) {
UserinfoUtil.saveUserInfo(response.data)
Toast.makeText(this@LoginActivity, R.string.password_login_success_title, Toast.LENGTH_SHORT).show()
val intent = Intent(this@LoginActivity, HomeActivity::class.java)
//清除任务栈
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
finish()
@@ -271,7 +264,6 @@ class LoginActivity : AppCompatActivity() {
}
}
} catch (e: Exception) {
android.util.Log.e("LoginActivity", "密码登录异常", e)
withContext(Dispatchers.Main) {
Toast.makeText(this@LoginActivity, R.string.network_error, Toast.LENGTH_SHORT).show()
}

View File

@@ -81,7 +81,7 @@ class RegisterActivity : AppCompatActivity() {
val response = RetrofitNetwork.getNetworkService().getCode(userName)
withContext(Dispatchers.Main) {
if (response.code == 200) {
Toast.makeText(this@RegisterActivity, "验证码已发送", Toast.LENGTH_SHORT).show()
Toast.makeText(this@RegisterActivity, R.string.code_already_sent, Toast.LENGTH_SHORT).show()
startCountDown()
} else {
Toast.makeText(this@RegisterActivity, response.msg, Toast.LENGTH_SHORT).show()
@@ -181,7 +181,8 @@ class RegisterActivity : AppCompatActivity() {
if (response.code == 200) {
Toast.makeText(this@RegisterActivity, R.string.register_success, Toast.LENGTH_SHORT).show()
val intent = Intent(this@RegisterActivity, LoginActivity::class.java)
val intent = Intent(this@RegisterActivity, RegisterSuccessActivity::class.java)
//清除任务栈
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
finish()

View File

@@ -0,0 +1,144 @@
package com.ckkj.defense_device.personal
import android.content.Intent
import android.os.Bundle
import android.os.CountDownTimer
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.ckkj.defense_device.R
import com.ckkj.defense_device.databinding.CancelAccountActivityBinding
import com.ckkj.defense_device.login.LoginActivity
import com.ckkj.defense_device.utils.RetrofitNetwork
import com.ckkj.defense_device.utils.UserinfoUtil
import com.ckkj.defense_device.view.CustomDialogFragment
import com.ckkj.defense_device.view.DefenseDialogs
import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class CancelAccountActivity : AppCompatActivity() {
private lateinit var binding: CancelAccountActivityBinding
private var countDownTimer: CountDownTimer? = null
private var phoneNumber: String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = CancelAccountActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
ImmersionBar.with(this)
.statusBarDarkFont(true)
.titleBar(binding.topBar)
.init()
getUserInfo()
initListener()
}
private fun getUserInfo() {
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService().getUserInfo()
withContext(Dispatchers.Main) {
if (response.code == 200 && response.data != null) {
phoneNumber = response.data?.phonenumber ?: ""
binding.tvPhoneNumber.text = maskPhoneNumber(phoneNumber)
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
Toast.makeText(this@CancelAccountActivity, R.string.network_error, Toast.LENGTH_SHORT).show()
}
}
}
}
private fun maskPhoneNumber(phone: String): String {
if (phone.length != 11) {
return phone
}
return phone.substring(0, 3) + "****" + phone.substring(7)
}
private fun initListener() {
binding.ivBack.setOnClickListener {
finish()
}
binding.btnSendCode.setOnClickListener {
if (phoneNumber.isEmpty()) {
Toast.makeText(this, R.string.get_user_info_fail, Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
if (phoneNumber.length != 11) {
Toast.makeText(this, R.string.phone_format_invalid, Toast.LENGTH_SHORT).show()
return@setOnClickListener
}
sendVerificationCode(phoneNumber)
}
binding.btnCancelAccount.setOnClickListener {
DefenseDialogs.showDialog(
this,
config = CustomDialogFragment.Config(
title = getString(R.string.tip),
message = getString(R.string.confirm_cancel_account_user_account),
positiveText = getString(R.string.confirm),
negativeText = getString(R.string.cancel)
),
onPositiveClick = {
Toast.makeText(this@CancelAccountActivity, getString(R.string.cancel_account), Toast.LENGTH_SHORT).show()
}
)
}
}
private fun sendVerificationCode(phone: String) {
binding.btnSendCode.isEnabled = false
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService().getCode(phone)
withContext(Dispatchers.Main) {
if (response.code == 200) {
Toast.makeText(this@CancelAccountActivity, getString(R.string.code_sent_to, phone), Toast.LENGTH_SHORT).show()
startCountdown()
} else {
binding.btnSendCode.isEnabled = true
Toast.makeText(this@CancelAccountActivity, response.msg, Toast.LENGTH_SHORT).show()
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
binding.btnSendCode.isEnabled = true
Toast.makeText(this@CancelAccountActivity, R.string.network_error, Toast.LENGTH_SHORT).show()
}
}
}
}
private fun startCountdown() {
countDownTimer = object : CountDownTimer(60000, 1000) {
override fun onTick(millisUntilFinished: Long) {
binding.btnSendCode.text = getString(R.string.countdown_retry, millisUntilFinished / 1000)
}
override fun onFinish() {
binding.btnSendCode.text = getString(R.string.resend_code)
binding.btnSendCode.isEnabled = true
countDownTimer = null
}
}.start()
}
override fun onDestroy() {
super.onDestroy()
countDownTimer?.cancel()
}
}

View File

@@ -0,0 +1,111 @@
package com.ckkj.defense_device.personal
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import com.ckkj.defense_device.databinding.SettingsMenusActivityBinding
import com.gyf.immersionbar.ImmersionBar
import java.io.File
class SettingsMenusActivity : AppCompatActivity() {
private lateinit var binding: SettingsMenusActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = SettingsMenusActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
ImmersionBar.with(this)
.statusBarDarkFont(true)
.titleBar(binding.topBar)
.init()
val versionName = packageManager.getPackageInfo(packageName, 0).versionName
binding.tvVersion.text = versionName
// 显示缓存大小
displayCacheSize()
setupClickListeners()
}
private fun setupClickListeners() {
// 返回按钮
binding.ivBack.setOnClickListener {
finish()
}
// 检查更新
binding.itemCheckUpdate.setOnClickListener {
Toast.makeText(this, "正在检查更新...", Toast.LENGTH_SHORT).show()
}
// 清除缓存
binding.itemClearCache.setOnClickListener {
clearCache()
displayCacheSize() // 更新缓存大小显示
Toast.makeText(this, "缓存已清除", Toast.LENGTH_SHORT).show()
}
// 用户协议
binding.itemUserAgreement.setOnClickListener {
}
// 隐私政策
binding.itemPrivacyPolicy.setOnClickListener {
}
// 注销账号
binding.itemLogout.setOnClickListener {
val intent = Intent(this, CancelAccountActivity::class.java)
startActivity(intent)
}
}
//缓存方法
private fun displayCacheSize() {
val cacheSize = getCacheSize()
val sizeString = when {
cacheSize >= 1024 * 1024 -> String.format("%.2f MB", cacheSize / (1024.0 * 1024.0))
else -> String.format("%d KB", cacheSize / 1024)
}
binding.tvCacheSize.text = sizeString
}
private fun getCacheSize(): Long {
val cacheDir = cacheDir
return getFolderSize(cacheDir)
}
private fun getFolderSize(dir: File?): Long {
var size = 0L
if (dir != null && dir.isDirectory) {
dir.listFiles()?.forEach { file ->
size += if (file.isFile) {
file.length()
} else {
getFolderSize(file)
}
}
}
return size
}
private fun clearCache() {
val cacheDir = cacheDir
deleteDir(cacheDir)
}
private fun deleteDir(dir: File?): Boolean {
if (dir != null && dir.isDirectory) {
dir.listFiles()?.forEach { file ->
deleteDir(file)
}
}
return dir?.delete() ?: false
}
}

View File

@@ -28,6 +28,8 @@ import com.ckkj.defense_device.bean.UpdateUserInfo
import com.ckkj.defense_device.bean.UploadResponse
import com.ckkj.defense_device.bean.UserInfoBean
import com.ckkj.defense_device.bean.VerifyOptionPasswordRequest
import com.ckkj.defense_device.bean.VideoStreamInfo
import com.ckkj.defense_device.bean.VideoStreamRequest
import okhttp3.MultipartBody
import retrofit2.http.Body
import retrofit2.http.DELETE
@@ -37,6 +39,25 @@ import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Query
// ┏┓   ┏┓
//┏┛┻━━━┛┻┓
//┃       ┃
//┃   ━   ┃
//┃ ┳┛ ┗┳ ┃
//┃       ┃
//┃   ┻   ┃
//┃       ┃
//┗━┓   ┏━┛
// ┃   ┃
// ┃   ┃
// ┃   ┗━━━┓
// ┃       ┣┓
// ┃       ┏┛
// ┗┓┓┏━┳┓┏┛
// ┃┫┫ ┃┫┫
// ┗┻┛ ┗┻┛
//神兽护体代码零Bug需求不改一次编译通过
interface ApiService {
/**
* 获取登录验证码
@@ -271,5 +292,79 @@ interface ApiService {
@POST("/app/joinTheFamily")
suspend fun joinTheFamily(@Body request: JoinTheFamilyBean): GetCodeBean<Any>
/**
* PTZ向上控制
*/
@POST("/app/ptz/{deviceNo}/up")
suspend fun ptzUp(
@retrofit2.http.Path("deviceNo") deviceNo: String,
@Query("speed") speed: String = "5",
@Query("duration") duration: String = "0"
): GetCodeBean<Any>
/**
* PTZ向下控制
*/
@POST("/app/ptz/{deviceNo}/down")
suspend fun ptzDown(
@retrofit2.http.Path("deviceNo") deviceNo: String,
@Query("speed") speed: String = "5",
@Query("duration") duration: String = "0"
): GetCodeBean<Any>
/**
* PTZ向左控制
*/
@POST("/app/ptz/{deviceNo}/left")
suspend fun ptzLeft(
@retrofit2.http.Path("deviceNo") deviceNo: String,
@Query("speed") speed: String = "5",
@Query("duration") duration: String = "0"
): GetCodeBean<Any>
/**
* PTZ向右控制
*/
@POST("/app/ptz/{deviceNo}/right")
suspend fun ptzRight(
@retrofit2.http.Path("deviceNo") deviceNo: String,
@Query("speed") speed: String = "5",
@Query("duration") duration: String = "0"
): GetCodeBean<Any>
/**
* PTZ停止控制
*/
@POST("/app/ptz/{deviceNo}/stop")
suspend fun ptzStop(
@retrofit2.http.Path("deviceNo") deviceNo: String,
): GetCodeBean<Any>
/**
* PTZ控制水枪喷水
*/
@POST("/app/ptz/{deviceNo}/shoot")
suspend fun ptzShoot(
@retrofit2.http.Path("deviceNo") deviceNo: String,
@Query("duration") duration: String,
): GetCodeBean<Any>
/**
* 开始推流
*/
@POST("/app/stream/start")
suspend fun videoStart(
@Body request: VideoStreamRequest
): GetCodeBean<VideoStreamInfo>
/**
* 停止推流
*/
@POST("/app/stream/stop/{deviceNo}")
suspend fun videoStop(
@retrofit2.http.Path("deviceNo") deviceNo: String
): GetCodeBean<Any>
}

View File

@@ -1,5 +1,7 @@
package com.ckkj.defense_device.utils
import com.ckkj.defense_device.utils.crypto.CryptoInterceptor
import com.ckkj.defense_device.utils.crypto.CryptoKeyManager
import com.google.gson.Gson
import okhttp3.OkHttpClient
import retrofit2.Retrofit
@@ -12,8 +14,8 @@ import java.util.concurrent.TimeUnit
object RetrofitNetwork {
private const val DEFAULT_TIMEOUT = 30L
const val BASE_URL = "https://stem-boozy-margarine.ngrok-free.dev"
//const val BASE_URL = "http://service.reinkun.com:8001"
//const val BASE_URL = "https://stem-boozy-margarine.ngrok-free.dev"
const val BASE_URL = "http://service.reinkun.com:8001"
private var retrofit: Retrofit? = null
private val gson = Gson()
@@ -65,12 +67,39 @@ object RetrofitNetwork {
.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS)
.addInterceptor(ApiConfig.createHeadersInterceptor())
.addInterceptor(ApiConfig.getUnifiedInterceptor()) // 使用 ApiConfig 中的统一拦截器
.addInterceptor(ApiConfig.getUnifiedInterceptor())
// 加密拦截器(密钥就绪即启用,按开关自动判断)
if (CryptoKeyManager.isCryptoEnabled && CryptoKeyManager.isKeyConfigured()) {
builder.addInterceptor(getCryptoInterceptor())
}
if (enableLogging) {
builder.addInterceptor(ApiConfig.getLoggingInterceptor()) // 使用 ApiConfig 中的日志拦截器
builder.addInterceptor(ApiConfig.getLoggingInterceptor())
}
return builder.build()
}
/**
* 获取加密拦截器实例
* 每次都重新创建,确保使用最新密钥
*/
private fun getCryptoInterceptor(): CryptoInterceptor {
// 清除旧实例,确保使用最新密钥
CryptoInterceptor.clearInstance()
return CryptoInterceptor.getInstance(
publicKey = CryptoKeyManager.requestPublicKey,
privateKey = CryptoKeyManager.responsePrivateKey
)
}
/**
* 重新初始化网络客户端(密钥更新后调用)
*/
fun resetClient() {
retrofit = null
// 强制重新创建 Retrofit
getNetworkService()
}
}

View File

@@ -0,0 +1,96 @@
package com.ckkj.defense_device.utils.crypto
import android.util.Base64
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec
/**
* AES 加密工具类
*
* 算法AES/ECB/PKCS5Padding (Android) 与 AES/ECB/PKCS7Padding (iOS) 兼容
*/
object AESUtil {
private const val ALGORITHM = "AES"
private const val TRANSFORMATION = "AES/ECB/PKCS5Padding"
/**
* AES ECB 模式加密
*
* @param key 32字节 ASCII 字符串密钥
* @param data UTF-8 编码的明文字节数组
* @return Base64 编码的密文字节数组
*/
fun encrypt(key: String, data: ByteArray): ByteArray {
val keySpec = SecretKeySpec(key.toByteArray(Charsets.UTF_8), ALGORITHM)
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.ENCRYPT_MODE, keySpec)
return cipher.doFinal(data)
}
/**
* AES ECB 模式加密,返回 Base64 字符串
*
* @param key 32字节 ASCII 字符串密钥
* @param plainText UTF-8 明文字符串
* @return Base64 编码的密文字符串
*/
fun encrypt(key: String, plainText: String): String {
val encrypted = encrypt(key, plainText.toByteArray(Charsets.UTF_8))
return Base64.encodeToString(encrypted, Base64.NO_WRAP)
}
/**
* AES ECB 模式解密
*
* @param key 32字节 ASCII 字符串密钥
* @param encryptedData Base64 编码的密文字节数组
* @return UTF-8 解密后的明文字节数组
*/
fun decrypt(key: String, encryptedData: ByteArray): ByteArray {
val keySpec = SecretKeySpec(key.toByteArray(Charsets.UTF_8), ALGORITHM)
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.DECRYPT_MODE, keySpec)
return cipher.doFinal(encryptedData)
}
/**
* AES ECB 模式解密,返回 UTF-8 字符串
*
* @param key 32字节 ASCII 字符串密钥
* @param encryptedText Base64 编码的密文字符串
* @return UTF-8 解密后的明文字符串
*/
fun decrypt(key: String, encryptedText: String): String {
val encryptedData = Base64.decode(encryptedText, Base64.NO_WRAP)
val decrypted = decrypt(key, encryptedData)
return String(decrypted, Charsets.UTF_8)
}
/**
* 生成随机 AES 密钥
*
* @param length 密钥长度,默认 32 字节
* @return 随机 ASCII 字符串密钥
*/
fun generateKey(length: Int = 32): String {
val chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
return (1..length)
.map { chars.random() }
.joinToString("")
}
/**
* Base64 编码
*/
fun base64Encode(data: ByteArray): String {
return Base64.encodeToString(data, Base64.NO_WRAP)
}
/**
* Base64 解码
*/
fun base64Decode(encoded: String): ByteArray {
return Base64.decode(encoded, Base64.NO_WRAP)
}
}

View File

@@ -0,0 +1,295 @@
package com.ckkj.defense_device.utils.crypto
import android.util.Base64
import android.util.Log
import com.google.gson.JsonParser
import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import java.nio.charset.Charset
/**
* 加密拦截器
*
* 实现 App 请求加密和服务端响应解密
*
* 加密流程:
* 1. 生成随机 AES 密钥 (32字节 ASCII)
* 2. 用 RSA 公钥加密 AES 密钥
* 3. 用 AES 加密请求体
* 4. 设置请求头 encrypt-key 为 RSA 加密后的 AES 密钥
* 5. 请求体为 AES 加密后的 Base64 字符串
*
* 解密流程(仅需要对响应解密的接口):
* 1. 从响应头获取 encrypt-key
* 2. 用 RSA 私钥解密得到 AES 密钥
* 3. 用 AES 解密响应体
*/
class CryptoInterceptor private constructor(
private val publicKey: String,
private val privateKey: String
) : Interceptor {
companion object {
private const val TAG = "CryptoInterceptor"
private const val HEADER_ENCRYPT_KEY = "encrypt-key"
private const val HEADER_IS_ENCRYPT = "isEncrypt"
private const val CONTENT_TYPE_JSON = "application/json; charset=utf-8"
// 需要加密的接口列表POST/PUT 请求)
private val ENCRYPT_ENDPOINTS = setOf(
"app/login",
"app/register",
"app/forgot",
"app/switchFamily",
"app/addDevice",
"app/updateDeviceInfo",
"app/deleteDevice",
"app/addDeviceUser",
"app/updateUserInfo",
"app/forgotPassword",
"app/retrievePassword",
"app/verifyOptionPassword",
"app/obtainInviterInformation",
"app/selectRecommendationCode",
"app/generateRecommendationCode",
"app/revokeInvitation",
"app/updateFamilyUser",
"app/selectInviterInfo",
"app/joinTheFamily",
"app/stream/start"
)
// 需要解密响应的接口列表(默认与加密列表对称 —— 加密请求的接口响应也会被加密)
// 额外加上 GET 接口GET 无 body 不加密请求,但后端仍可能加密响应)
private val DECRYPT_RESPONSE_ENDPOINTS: Set<String> = ENCRYPT_ENDPOINTS + setOf(
"app/selectFamily", // 获取家庭列表GET
"app/getdeviceList", // 获取设备列表GET
"app/deviceInfo", // 获取设备信息GET
"app/appMessage/list", // 消息列表GET
"app/appMessageInfo", // 告警消息详情GET
"app/appMessage/deviceMeslist", // 设备告警日志GET
"app/familyMembers/list", // 家庭成员列表GET
"app/deviceUser/list", // 设备关联用户列表GET
"app/userInfo", // 当前用户信息GET
"app/deleteMessage" // 删除消息DELETE
)
@Volatile
private var instance: CryptoInterceptor? = null
fun getInstance(publicKey: String, privateKey: String): CryptoInterceptor {
return instance ?: synchronized(this) {
instance ?: CryptoInterceptor(publicKey, privateKey).also {
instance = it
}
}
}
fun clearInstance() {
instance = null
}
fun isEncryptEndpoint(url: String): Boolean {
return ENCRYPT_ENDPOINTS.any { url.contains(it, ignoreCase = true) }
}
fun isDecryptEndpoint(url: String): Boolean {
return DECRYPT_RESPONSE_ENDPOINTS.any { url.contains(it, ignoreCase = true) }
}
}
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val url = request.url.toString()
// 检查是否需要加密
val needsEncrypt = isEncryptEndpoint(url)
val needsDecrypt = isDecryptEndpoint(url)
if (!needsEncrypt && !needsDecrypt) {
return chain.proceed(request)
}
// 调试日志
Log.d(TAG, "=== CryptoInterceptor Debug ===")
Log.d(TAG, "URL: $url")
Log.d(TAG, "needsEncrypt: $needsEncrypt, needsDecrypt: $needsDecrypt")
Log.d(TAG, "PublicKey available: ${publicKey.isNotBlank()}, length: ${publicKey.length}")
Log.d(TAG, "PrivateKey available: ${privateKey.isNotBlank()}, length: ${privateKey.length}")
// 加密请求
val encryptedRequest = if (needsEncrypt) {
encryptRequest(request)
} else {
request
}
// 发起请求
val response = try {
chain.proceed(encryptedRequest)
} catch (e: Exception) {
Log.e(TAG, "Request failed", e)
throw e
}
// 解密响应
return if (needsDecrypt) {
decryptResponse(response, request)
} else {
response
}
}
/**
* 加密请求
*/
private fun encryptRequest(request: Request): Request {
val requestBody = request.body ?: return request
// 只处理 JSON 请求
val contentType = requestBody.contentType()?.toString() ?: ""
if (!contentType.contains("application/json")) {
return request
}
// 只处理 POST 和 PUT
val method = request.method
if (method != "POST" && method != "PUT") {
return request
}
// 读取原始请求体
val buffer = okio.Buffer()
requestBody.writeTo(buffer)
val originalBody = buffer.readString(Charset.defaultCharset())
Log.d(TAG, "Original request body: $originalBody")
// 生成随机 AES 密钥
val aesKey = AESUtil.generateKey(32)
Log.d(TAG, "Generated AES key: $aesKey")
// 1. keyBase64 = Base64(UTF8(aesKey))
val keyBase64 = AESUtil.base64Encode(aesKey.toByteArray(Charsets.UTF_8))
Log.d(TAG, "keyBase64: $keyBase64")
// 2. encrypt-key = RSA-PKCS1-Encrypt(请求公钥, UTF8(keyBase64))
// encryptByPublicKey(String, String) 已返回 Base64 编码结果
val encryptedKeyBase64 = RSAUtil.encryptByPublicKey(publicKey, keyBase64)
Log.d(TAG, "Encrypted key (Base64): $encryptedKeyBase64")
// 3. body = AES-ECB-PKCS5-Encrypt(aesKey, UTF8(JSON))
// encrypt(String, String) 已返回 Base64 编码结果
val encryptedBodyBase64 = AESUtil.encrypt(aesKey, originalBody)
Log.d(TAG, "Encrypted body (Base64): $encryptedBodyBase64")
// 构建新请求(根据原方法选择 POST 或 PUT
val newRequestBuilder = request.newBuilder()
.header(HEADER_ENCRYPT_KEY, encryptedKeyBase64)
.header(HEADER_IS_ENCRYPT, "true")
return if (method == "PUT") {
newRequestBuilder.put(encryptedBodyBase64.toRequestBody(CONTENT_TYPE_JSON.toMediaTypeOrNull()))
} else {
newRequestBuilder.post(encryptedBodyBase64.toRequestBody(CONTENT_TYPE_JSON.toMediaTypeOrNull()))
}.build()
}
/**
* 解密响应
*
* 兼容三种后端响应形态:
* 1. 普通 JSON无 encrypt-key 头)→ 原样放行
* 2. 整 body 加密(响应头有 encrypt-key 且 body 不是合法 JSON→ 整体 AES 解密
* 3. 嵌套加密(响应头有 encrypt-keybody 是 {code,msg,data:"加密串"}
* → 只解 data 字段,保持外层 JSON 结构(与 iOS 端一致)
*/
private fun decryptResponse(response: Response, originalRequest: Request): Response {
val responseBody = response.body ?: return response
val encryptedKeyBase64 = response.header(HEADER_ENCRYPT_KEY)
if (encryptedKeyBase64.isNullOrBlank()) {
return response
}
val rawBody: String = try {
responseBody.source().apply { request(Long.MAX_VALUE) }
.buffer.clone().readString(Charset.defaultCharset())
} catch (e: Exception) {
Log.e(TAG, "Read response body failed", e)
return response
}
// 先拿到 AES 密钥RSA 私钥解密 encrypt-key 头)
val aesKey = try {
val aesKeyBase64 = RSAUtil.decryptByPrivateKeyBase64(privateKey, encryptedKeyBase64)
String(Base64.decode(aesKeyBase64, Base64.NO_WRAP), Charsets.UTF_8)
} catch (e: Exception) {
Log.e(TAG, "RSA decrypt encrypt-key failed", e)
return response
}
// 形态 3嵌套加密 —— 响应是 JSONdata 字段是加密串
val trimmed = rawBody.trim()
if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
try {
val jsonElement = JsonParser().parse(trimmed)
if (jsonElement.isJsonObject) {
val obj = jsonElement.asJsonObject
if (obj.has("data") && obj.get("data").isJsonPrimitive
&& obj.get("data").asJsonPrimitive.isString
) {
val encryptedData = obj.get("data").asString
val decryptedData = AESUtil.decrypt(aesKey, encryptedData)
// 把 data 字段替换为解密后的 JSON 字符串(让 Gson 当成对象继续解析)
obj.add("data", parseDataAsJsonElement(decryptedData))
val newBody = obj.toString().toResponseBody(CONTENT_TYPE_JSON.toMediaTypeOrNull())
return response.newBuilder()
.body(newBody)
.removeHeader(HEADER_ENCRYPT_KEY)
.build()
}
}
} catch (e: Exception) {
Log.w(TAG, "Nested decrypt failed, fallback to whole-body decrypt", e)
}
}
// 形态 2整 body 加密
return try {
val decryptedBody = AESUtil.decrypt(aesKey, rawBody)
response.newBuilder()
.body(decryptedBody.toResponseBody(CONTENT_TYPE_JSON.toMediaTypeOrNull()))
.removeHeader(HEADER_ENCRYPT_KEY)
.build()
} catch (e: Exception) {
Log.e(TAG, "Whole-body decrypt failed, return raw response", e)
response
}
}
/**
* 把解密后的字符串解析成 JsonElement如果是合法 JSON否则当成普通字符串返回
*/
private fun parseDataAsJsonElement(text: String): com.google.gson.JsonElement {
val s = text.trim()
return try {
if ((s.startsWith("{") && s.endsWith("}")) ||
(s.startsWith("[") && s.endsWith("]"))
) {
JsonParser().parse(s)
} else {
com.google.gson.JsonPrimitive(s)
}
} catch (e: Exception) {
com.google.gson.JsonPrimitive(text)
}
}
}
// 扩展函数String 转 RequestBody
private fun String.toRequestBody(mediaType: okhttp3.MediaType?): okhttp3.RequestBody {
return okhttp3.RequestBody.create(mediaType, this)
}

View File

@@ -0,0 +1,181 @@
package com.ckkj.defense_device.utils.crypto
import android.content.Context
import android.util.Log
import com.blankj.utilcode.util.SPUtils
import com.ckkj.defense_device.utils.UserinfoUtil
/**
* 密钥管理类
*
* 管理 RSA 加密所需的公钥和私钥
*
* 密钥关系:
* ┌─────────────┬──────────────────┬──────────────────┐
* │ 方向 │ App 持有 │ 服务端持有 │
* ├─────────────┼──────────────────┼──────────────────┤
* │ App请求服务端 │ 请求公钥 (RSA-P) │ 请求私钥 (RSA-S) │
* ├─────────────┼──────────────────┼──────────────────┤
* │ 服务端响应App │ 响应私钥 (RSA-S) │ 响应公钥 (RSA-P) │
* └─────────────┴──────────────────┴──────────────────┘
*
* 需要生成两套独立 RSA 2048/3072 密钥对
*/
object CryptoKeyManager {
private const val TAG = "CryptoKeyManager"
// SPUtils 用于持久化密钥配置
private val spUtils: SPUtils by lazy {
SPUtils.getInstance("crypto_keys")
}
// 配置项 Keys
private const val KEY_REQUEST_PUBLIC_KEY = "request_public_key"
private const val KEY_RESPONSE_PRIVATE_KEY = "response_private_key"
private const val KEY_CRYPTO_ENABLED = "crypto_enabled"
// 配置项默认值
// 注意:这些密钥必须与后端配置配对使用
// App端公钥 = 后端私钥App加密请求后端解密
// App端私钥 = 后端公钥后端加密响应App解密
private const val DEFAULT_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtVIQm+1AWMfkT1uVOyugKQwBeHtgpulABOrVfaL7Xzc1/rKSdLdjYZLZnnOU+rdE1jqz20MXDBozK22spH/j53d9nFLftHkMCRUlfc0MWEqtSla+2MdzElIHEbq4CSizZOcT8QXCRw7V2OuDqnC7XRQmnpA5Ks1WaKH5V0aoKWztjsqZV6vVWPqyDC94mvu/Q/9e6KVfkqAzR+afuo9OoBFewDGQ6jfqyu1TxoGZrfSr5lXOVeADf0CVfWCDze5y4f7er4JjPHWA2+yv8Scd9bqVJ4Q+/yR2Gd516Mi11OgBKc8GdkuA4/kfmXc7n91SMjgQP6DlTVuPc/6KnB/pyQIDAQAB"
private const val DEFAULT_PRIVATE_KEY = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDYg1dC9XDMRtKJ5IFj+Hd3bq7+r2YL1y8FM9HD111PN/YVy4K6O67cRA/TLG7+tl6eYiA73f5lzPgv1riOwBPSCmvVopxq2KiJs30uEnug0qGp6l2cwQOmjitFAQF8dOtAl4m6EM/jdXCZYbQrthJ6n3tR+vGj2IrGSJEjPrj3VRljK9I7Zw0Be0rhJn/vZw2CA4GoVfE/3gqa5bpl+3SFxMP7TwF5uZueCNqLaPnd7jK5jB1SUghI7+pq897Ng4EG6kdOGsiQe5kbHWkSPTmPEObMwgLJ18i/XCVg+LUkTDJOC/MAfB+WGcKryNHrhcbhB6+XstpoGT8JlOPOgh0BAgMBAAECggEBALZG7MH8bDguL7XTYHNPjRRJZJ4aAGlbgcR5edHMkEvPKyfyK16qPG5IBqKcN93Mnx/sMIL2Q+RkiVWNCdd0MbuU9m/m9JSnTkyPhYWyHc6pRV0NaD496Nrhud/gFuY2cI/yhArXeI1gI6mdrddW83u0pFfCUojEFyETmsz3UzU1LMkVbwCXNbZ1b3GfcVnONMn8T/7CMlRPlKyOuE0OPnTut4wddAybe5BKDQYnGzuosUdO+Q+2K3hqaTD6Bb33D4ATHjvR3Vt9itE07BiaBFjZkE2odP4x3fSsnc7Cw3A7zHQivpfgqcoBcGUaMcN1FdgpaF2mbe9/eHn/lYCpevkCgYEA4FoKq3fPOITAtA6bjwCHXvIQUjv4vQ0BZtt7OX7zwpRwqOhJfN0ZRkd7NMUqYnB10N9NgPRz0kKC6qff5duN+zkYIzjHrlm5lzdIEAouovRlrGXST4n8gAYteh5D5Or9wRXj7USQoaNyiWKB9A/07ortJvul09sPjJU7IVfzzXMCgYEA9w45Gh2bAvYvm2bWieKZRhcV6Pwy9R8587qmKLC7V/0gfVojBHx7xTmTEfKCGrt3fK4jB5v/2Dar8gDF+RM7g01u0YuFJt17jeFr/4o5KMimfXhoEulDbgjfvhxQOrtkrF5iHpKxiEXqIPiH+B7155PAnE43011AAJCBSylmTrsCgYEAkxr3PA9HFKwXHvklDtMt8BeQlBs2sd9BOAxZ9A1GECP86wPEi9b9p5NfOe6+J+XNmrOQwimHeCqcZPjGWpVnt35sUUv9wlia1Igu/DVw9vCBalUpXXYA1oE2eIg3xHZBBMYxuXXnz9S4WVT6GOoNlAwMDC+dQBi3TVrcdrSQ6/kCgYBA254ICh+ovmKvJGdMGY3thZ/94z+pdEIthyGZ6xOzvMMrxV8ODXQcycmfW4/mXrK2q6yMkdqvs2KejK9sfS0RgmGGZ19UXa7TB4vnsSziVRLIO7TuyggmufOrIBm74XhDfB+8MPykbt5RO43OiKo72mElZ69mMMPdohIfXkX6gwKBgH4L9qJRhNunWSfHO4/8dST+2meSV+Cpqf0b/9cV6v5FMS22m+szzW9XoBXnI9dbAqiEDlCHCLw34pRlC3OzBNlbT6HzNmYXLko2Ttg9thiDq7WS5tV+EHJRP6kLabYGq0Aghtq+6LIwwogbHzhRi8Q52o3CNmM9lThNWZIkzaBe"
/**
* 是否启用加密
*/
var isCryptoEnabled: Boolean
get() {
val stored = spUtils.getBoolean(KEY_CRYPTO_ENABLED, false)
// 如果存储值为 false 但密钥已配置,也返回 true
return stored || (DEFAULT_PUBLIC_KEY.isNotBlank() && DEFAULT_PRIVATE_KEY.isNotBlank())
}
set(value) {
spUtils.put(KEY_CRYPTO_ENABLED, value)
Log.d(TAG, "Crypto enabled: $value")
}
/**
* 请求公钥(用于 App 加密请求)
* App 使用此公钥加密 AES 密钥,服务端使用对应的私钥解密
*/
var requestPublicKey: String
get() {
val stored = spUtils.getString(KEY_REQUEST_PUBLIC_KEY, "")
return if (stored.isNotBlank()) stored else DEFAULT_PUBLIC_KEY
}
set(value) {
spUtils.put(KEY_REQUEST_PUBLIC_KEY, value)
Log.d(TAG, "Request public key updated: ${value.take(20)}...")
}
/**
* 响应私钥(用于 App 解密响应)
* 服务端使用对应的响应公钥加密响应App 使用此私钥解密
*/
var responsePrivateKey: String
get() {
val stored = spUtils.getString(KEY_RESPONSE_PRIVATE_KEY, "")
return if (stored.isNotBlank()) stored else DEFAULT_PRIVATE_KEY
}
set(value) {
spUtils.put(KEY_RESPONSE_PRIVATE_KEY, value)
Log.d(TAG, "Response private key updated: ${value.take(20)}...")
}
/**
* 是否已配置密钥
*/
fun isKeyConfigured(): Boolean {
// 检查密钥是否有内容(默认值或用户配置)
val hasPublicKey = requestPublicKey.isNotBlank()
val hasPrivateKey = responsePrivateKey.isNotBlank()
Log.d(TAG, "isKeyConfigured: publicKey=$hasPublicKey (length=${requestPublicKey.length}), privateKey=$hasPrivateKey (length=${responsePrivateKey.length})")
return hasPublicKey && hasPrivateKey
}
/**
* 从服务端配置初始化密钥
*
* 配置格式(可从服务端接口或配置中心获取):
* api-decrypt:
* enabled: true
* headerFlag: encrypt-key
* publicKey: ${APP_RESPONSE_PUBLIC_KEY}
* privateKey: ${APP_REQUEST_PRIVATE_KEY}
*
* @param enabled 是否启用加密
* @param publicKey 服务端响应公钥(用于 App 加密请求)
* @param privateKey 服务端请求私钥(用于 App 解密响应)
*/
fun initialize(enabled: Boolean, publicKey: String, privateKey: String) {
isCryptoEnabled = enabled
requestPublicKey = publicKey
responsePrivateKey = privateKey
Log.i(TAG, "CryptoKeyManager initialized: enabled=$enabled, keyConfigured=${isKeyConfigured()}")
}
/**
* 从用户登录信息初始化密钥
* 适用于密钥存储在用户账户中的场景
*
* @param publicKey 公钥
* @param privateKey 私钥
*/
fun initializeFromLogin(publicKey: String, privateKey: String) {
initialize(
enabled = publicKey.isNotBlank() && privateKey.isNotBlank(),
publicKey = publicKey,
privateKey = privateKey
)
}
/**
* 清除密钥配置
*/
fun clear() {
spUtils.clear()
Log.i(TAG, "CryptoKeyManager cleared")
}
/**
* 获取密钥信息摘要(用于调试)
*/
fun getKeySummary(): String {
return buildString {
appendLine("CryptoKeyManager Summary:")
appendLine(" Enabled: $isCryptoEnabled")
appendLine(" RequestPublicKey: ${if (requestPublicKey.isNotBlank()) "${requestPublicKey.take(20)}..." else "NOT SET"}")
appendLine(" ResponsePrivateKey: ${if (responsePrivateKey.isNotBlank()) "${responsePrivateKey.take(20)}..." else "NOT SET"}")
appendLine(" Configured: ${isKeyConfigured()}")
}
}
/**
* 验证密钥格式
*
* @return true 如果密钥是有效的 Base64 编码
*/
fun validateKeys(): Pair<Boolean, String> {
val publicKeyValid = validateBase64Key(requestPublicKey)
val privateKeyValid = validateBase64Key(responsePrivateKey)
return when {
!publicKeyValid && !privateKeyValid -> false to "Both keys are invalid"
!publicKeyValid -> false to "Request public key is invalid"
!privateKeyValid -> false to "Response private key is invalid"
else -> true to "Keys are valid"
}
}
private fun validateBase64Key(key: String): Boolean {
if (key.isBlank()) return false
return try {
android.util.Base64.decode(key, android.util.Base64.NO_WRAP)
true
} catch (e: Exception) {
false
}
}
}

View File

@@ -0,0 +1,122 @@
package com.ckkj.defense_device.utils.crypto
import android.util.Base64
import java.security.KeyFactory
import java.security.PublicKey
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.RSAPrivateKeySpec
import java.security.spec.X509EncodedKeySpec
import javax.crypto.Cipher
/**
* RSA 加密工具类
*
* 算法RSA/ECB/PKCS1Padding
* 支持 RSA 2048/3072 位密钥
*/
object RSAUtil {
private const val ALGORITHM = "RSA"
private const val TRANSFORMATION = "RSA/ECB/PKCS1Padding"
/**
* 使用公钥加密(用于 App 加密请求)
*
* @param publicKeyStr Base64 编码的公钥字符串
* @param data UTF-8 编码的明文字节数组
* @return 密文字节数组
*/
fun encryptByPublicKey(publicKeyStr: String, data: ByteArray): ByteArray {
val publicKey = parsePublicKey(publicKeyStr)
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.ENCRYPT_MODE, publicKey)
return cipher.doFinal(data)
}
/**
* 使用公钥加密,返回 Base64 字符串
*
* @param publicKeyStr Base64 编码的公钥字符串
* @param plainText UTF-8 明文字符串
* @return Base64 编码的密文字符串
*/
fun encryptByPublicKey(publicKeyStr: String, plainText: String): String {
val encrypted = encryptByPublicKey(publicKeyStr, plainText.toByteArray(Charsets.UTF_8))
return Base64.encodeToString(encrypted, Base64.NO_WRAP)
}
/**
* 使用私钥解密(用于 App 解密响应)
*
* @param privateKeySpec RSA 私钥规格
* @param encryptedData 密文字节数组
* @return UTF-8 解密后的明文字节数组
*/
fun decryptByPrivateKey(privateKeySpec: RSAPrivateKeySpec, encryptedData: ByteArray): ByteArray {
val keyFactory = KeyFactory.getInstance(ALGORITHM)
val privateKey = keyFactory.generatePrivate(privateKeySpec)
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.DECRYPT_MODE, privateKey)
return cipher.doFinal(encryptedData)
}
/**
* 使用私钥解密,返回 UTF-8 字符串
*
* @param privateKeySpec RSA 私钥规格
* @param encryptedText Base64 编码的密文字符串
* @return UTF-8 解密后的明文字符串
*/
fun decryptByPrivateKey(privateKeySpec: RSAPrivateKeySpec, encryptedText: String): String {
val encryptedData = Base64.decode(encryptedText, Base64.NO_WRAP)
val decrypted = decryptByPrivateKey(privateKeySpec, encryptedData)
return String(decrypted, Charsets.UTF_8)
}
/**
* 使用 Base64 编码的 PKCS#8 私钥字符串解密
*
* @param privateKeyBase64 Base64 编码的 PKCS#8 私钥
* @param encryptedText Base64 编码的密文字符串
* @return UTF-8 解密后的明文字符串
*/
fun decryptByPrivateKeyBase64(privateKeyBase64: String, encryptedText: String): String {
val encryptedData = Base64.decode(encryptedText, Base64.NO_WRAP)
val keyFactory = KeyFactory.getInstance(ALGORITHM)
val privateKeySpec = PKCS8EncodedKeySpec(Base64.decode(privateKeyBase64, Base64.NO_WRAP))
val privateKey = keyFactory.generatePrivate(privateKeySpec)
val cipher = Cipher.getInstance(TRANSFORMATION)
cipher.init(Cipher.DECRYPT_MODE, privateKey)
val decrypted = cipher.doFinal(encryptedData)
return String(decrypted, Charsets.UTF_8)
}
/**
* 解析公钥
*
* @param publicKeyStr Base64 编码的 X.509 公钥
* @return PublicKey 对象
*/
fun parsePublicKey(publicKeyStr: String): PublicKey {
val keyBytes = Base64.decode(publicKeyStr, Base64.NO_WRAP)
val keySpec = X509EncodedKeySpec(keyBytes)
val keyFactory = KeyFactory.getInstance(ALGORITHM)
return keyFactory.generatePublic(keySpec)
}
/**
* 从 Base64 编码的 PKCS#8 私钥创建私钥规格
*
* @param privateKeyBase64 Base64 编码的 PKCS#8 私钥
* @return RSAPrivateKeySpec
*/
fun createPrivateKeySpecFromBase64(privateKeyBase64: String): RSAPrivateKeySpec {
val keyBytes = Base64.decode(privateKeyBase64, Base64.NO_WRAP)
val keyFactory = KeyFactory.getInstance(ALGORITHM)
val keySpec = keyFactory.getKeySpec(
keyFactory.generatePrivate(PKCS8EncodedKeySpec(keyBytes)),
RSAPrivateKeySpec::class.java
)
return keySpec
}
}

View File

@@ -4,6 +4,8 @@ import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Handler
import android.os.Looper
import android.view.Gravity
import android.view.LayoutInflater
import android.view.WindowManager
@@ -13,6 +15,13 @@ class LoadingDialog(context: Context) {
private val dialog: Dialog = Dialog(context)
private val binding: DialogLoadingBinding
private val handler = Handler(Looper.getMainLooper())
private var showTime: Long = 0
private val dismissRunnable = Runnable {
if (dialog.isShowing) {
dialog.dismiss()
}
}
init {
binding = DialogLoadingBinding.inflate(LayoutInflater.from(context))
@@ -35,15 +44,24 @@ class LoadingDialog(context: Context) {
}
fun show() {
handler.removeCallbacks(dismissRunnable)
showTime = System.currentTimeMillis()
if (!dialog.isShowing) {
dialog.show()
}
}
fun dismiss() {
handler.removeCallbacks(dismissRunnable)
val elapsed = System.currentTimeMillis() - showTime
val MIN_SHOW_TIME = 1000L
if (elapsed >= MIN_SHOW_TIME) {
if (dialog.isShowing) {
dialog.dismiss()
}
} else {
handler.postDelayed(dismissRunnable, MIN_SHOW_TIME - elapsed)
}
}
fun isShowing(): Boolean {

View File

@@ -1,8 +1,10 @@
package com.ckkj.defense_device.view
import android.util.Log
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import com.ckkj.defense_device.R
import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer
@@ -12,6 +14,18 @@ class MyGSYVideoPlayerLive @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : StandardGSYVideoPlayer(context, attrs) {
companion object {
private const val TAG = "MyGSYVideoPlayerLive"
}
interface OnPtzControlListener {
fun onStartPtz(direction: String)
fun onStopPtz()
fun onShoot()
}
var onPtzControlListener: OnPtzControlListener? = null
override fun init(context: Context) {
super.init(context)
startButton.visibility = View.GONE
@@ -76,10 +90,54 @@ class MyGSYVideoPlayerLive @JvmOverloads constructor(
overlay.findViewById<View>(R.id.btnCapture)
?.setOnClickListener {
Log.d("LV", "btnCapture clicked, onShoot invoked")
onPtzControlListener?.onShoot()
}
player.initPtzControls(overlay)
return player
}
private fun initPtzControls(overlay: View) {
val ptzListener = onPtzControlListener
overlay.findViewById<View>(R.id.ivUp)?.setOnTouchListener { v, event ->
handlePtzTouch(v, event, "up", ptzListener)
}
overlay.findViewById<View>(R.id.ivDown)?.setOnTouchListener { v, event ->
handlePtzTouch(v, event, "down", ptzListener)
}
overlay.findViewById<View>(R.id.ivLeft)?.setOnTouchListener { v, event ->
handlePtzTouch(v, event, "left", ptzListener)
}
overlay.findViewById<View>(R.id.ivRight)?.setOnTouchListener { v, event ->
handlePtzTouch(v, event, "right", ptzListener)
}
}
private fun handlePtzTouch(
view: View,
event: MotionEvent,
direction: String,
listener: OnPtzControlListener?
): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
Log.d(TAG, "全屏PTZ长按开始 direction=$direction")
view.isPressed = true
listener?.onStartPtz(direction)
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
Log.d(TAG, "全屏PTZ长按结束 direction=$direction")
view.isPressed = false
listener?.onStopPtz()
}
}
return true
}
}

View File

@@ -0,0 +1,288 @@
package com.ckkj.defense_device.view
import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.view.ViewGroup
import android.view.animation.LinearInterpolator
import android.widget.ImageView
import android.widget.TextView
import com.ckkj.defense_device.R
/**
* 自定义下拉刷新控件
*下拉时列表跟随向下移动,顶部出现 Logo + 文字下拉头。
*
* 使用方式:直接包裹 RecyclerView 等可滚动子 View调用 [setOnRefreshListener] 监听刷新。
*/
class PullRefreshLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr) {
private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop
/** 触发刷新的下拉阈值Header 的高度) */
private val headerHeight: Int = dp(120)
/** 触发刷新的最大下拉距离Header 高度的 1.5 倍) */
private val maxPullDistance: Int = (headerHeight * 1.5f).toInt()
private var headerView: View
private val ivRefreshIcon: ImageView
private val tvHint: TextView
/** 子内容 View通常是 RecyclerView */
private var contentView: View? = null
private var downY = 0f
private var isDragging = false
/** 当前下拉偏移量(>= 0 */
private var currentOffset = 0f
/** 是否处于刷新状态 */
private var isRefreshing = false
set(value) {
if (field == value) return
field = value
if (value) {
startLoadingAnimation()
} else {
stopLoadingAnimation()
}
}
private var refreshListener: OnRefreshListener? = null
private var resetAnimator: ValueAnimator? = null
init {
clipToPadding = false
// 添加 Header 视图
headerView = LayoutInflater.from(context).inflate(R.layout.layout_refresh_header, this, false)
addView(headerView)
ivRefreshIcon = headerView.findViewById(R.id.ivRefreshIcon)
tvHint = headerView.findViewById(R.id.tvRefreshHint)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val width = MeasureSpec.getSize(widthMeasureSpec)
val height = MeasureSpec.getSize(heightMeasureSpec)
// 测量 Header
val headerHeightSpec = MeasureSpec.makeMeasureSpec(headerHeight, MeasureSpec.EXACTLY)
headerView.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
headerHeightSpec
)
// 测量内容 View仅第一个非 Header 子 View
for (i in 0 until childCount) {
val child = getChildAt(i)
if (child === headerView) continue
child.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
)
contentView = child
}
setMeasuredDimension(width, height)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
// Header 布局在容器顶部之上(初始隐藏,下拉时滑入)
headerView.layout(
0,
-headerHeight + currentOffset.toInt(),
width,
currentOffset.toInt()
)
// 内容 View 跟随偏移
contentView?.layout(
0,
currentOffset.toInt(),
width,
height + currentOffset.toInt()
)
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
if (isRefreshing) return false
when (ev.actionMasked) {
MotionEvent.ACTION_DOWN -> {
downY = ev.y
isDragging = false
cancelResetAnimator()
}
MotionEvent.ACTION_MOVE -> {
val dy = ev.y - downY
if (dy > touchSlop && canPullDown()) {
isDragging = true
return true
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
isDragging = false
}
}
return false
}
override fun onTouchEvent(ev: MotionEvent): Boolean {
if (isRefreshing) return false
when (ev.actionMasked) {
MotionEvent.ACTION_MOVE -> {
val dy = ev.y - downY
if (dy > 0) {
if (!isDragging) {
if (dy > touchSlop) {
isDragging = true
} else {
return true
}
}
// 添加阻尼效果:越往下拉越吃力
val damped = computePullOffset(dy)
currentOffset = damped.coerceAtMost(maxPullDistance.toFloat())
updateHeaderState()
requestLayout()
return true
}
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
if (currentOffset >= headerHeight * 0.9f) {
// 触发刷新
triggerRefresh()
} else {
// 回弹
resetToIdle()
}
isDragging = false
return true
}
}
return isDragging
}
/**
* 添加阻尼效果,让下拉越深越慢,模拟弹性手感
*/
private fun computePullOffset(rawDy: Float): Float {
// 原始距离的 0.5 倍为基本偏移,超过阈值后逐渐衰减
val resistance = 1.8f
return (rawDy / resistance).coerceAtMost(maxPullDistance.toFloat())
}
/**
* 判断是否可以从当前位置下拉(列表已经滚动到顶部)
*/
private fun canPullDown(): Boolean {
val content = contentView ?: return false
return !content.canScrollVertically(-1)
}
/**
* 根据下拉距离更新 Header 的视觉状态(旋转角度、提示文字)
*/
private fun updateHeaderState() {
val ratio = (currentOffset / headerHeight).coerceIn(0f, 1f)
// 刷新图标根据下拉比例旋转 0~360 度
ivRefreshIcon.rotation = ratio * 360f
// 文字提示
tvHint.text = when {
currentOffset >= headerHeight * 0.9f -> context.getString(R.string.refresh_hint_release)
else -> context.getString(R.string.refresh_hint_pull)
}
}
private fun triggerRefresh() {
// 平滑动画到 header 高度
val animator = ValueAnimator.ofFloat(currentOffset, headerHeight.toFloat())
animator.duration = 200L
animator.interpolator = LinearInterpolator()
animator.addUpdateListener {
currentOffset = it.animatedValue as Float
requestLayout()
}
animator.start()
resetAnimator = animator
isRefreshing = true
tvHint.text = context.getString(R.string.refresh_hint_refreshing)
refreshListener?.onRefresh()
}
/**
* 结束刷新并回弹
*/
fun finishRefresh() {
if (!isRefreshing) return
isRefreshing = false
resetToIdle()
}
private fun resetToIdle() {
cancelResetAnimator()
val animator = ValueAnimator.ofFloat(currentOffset, 0f)
animator.duration = 300L
animator.interpolator = LinearInterpolator()
animator.addUpdateListener {
currentOffset = it.animatedValue as Float
if (!isRefreshing) {
// 刷新结束后,重置图标旋转角度
ivRefreshIcon.rotation = 0f
tvHint.text = context.getString(R.string.refresh_hint_pull)
}
requestLayout()
}
animator.start()
resetAnimator = animator
}
private fun cancelResetAnimator() {
resetAnimator?.cancel()
resetAnimator = null
}
private fun startLoadingAnimation() {
// 持续旋转动画
ivRefreshIcon.animate()
.rotation(ivRefreshIcon.rotation + 360f)
.setDuration(800L)
.setInterpolator(LinearInterpolator())
.withEndAction {
if (isRefreshing) startLoadingAnimation()
}
.start()
}
private fun stopLoadingAnimation() {
ivRefreshIcon.animate().cancel()
ivRefreshIcon.rotation = 0f
tvHint.text = context.getString(R.string.refresh_hint_pull)
}
fun setOnRefreshListener(listener: OnRefreshListener) {
this.refreshListener = listener
}
interface OnRefreshListener {
fun onRefresh()
}
private fun dp(value: Int): Int {
val density = resources.displayMetrics.density
return (value * density).toInt()
}
}

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<shape>
<solid android:color="#CC3D49" />
<corners android:radius="25dp" />
</shape>
</item>
<item>
<shape>
<solid android:color="#E34D59" />
<corners android:radius="25dp" />
</shape>
</item>
</selector>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#E34D59"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-2h2v2zM13,13h-2L11,7h2v6z"/>
</vector>

View File

@@ -2,5 +2,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/green"/>
<corners android:radius="16dp"/>
<corners android:radius="4dp"/>
</shape>

View File

@@ -3,13 +3,13 @@
<item android:state_enabled="false">
<shape android:shape="rectangle">
<solid android:color="@color/gray"/>
<corners android:radius="16dp"/>
<corners android:radius="99dp"/>
</shape>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="@color/green"/>
<corners android:radius="16dp"/>
<corners android:radius="99dp"/>
</shape>
</item>
</selector>

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#E8E8E8" />
<corners android:radius="8dp" />
</shape>

View File

@@ -2,5 +2,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/white"/>
<corners android:radius="10dp"/>
<corners android:radius="99dp"/>
</shape>

View File

@@ -73,7 +73,7 @@
</LinearLayout>
</LinearLayout>
<!-- 列表区域 -->
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
<com.ckkj.defense_device.view.PullRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="0dp"
@@ -84,8 +84,10 @@
android:layout_height="match_parent"
android:paddingHorizontal="16dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
android:paddingBottom="16dp"
android:clipToPadding="false"
android:overScrollMode="never"/>
</com.ckkj.defense_device.view.PullRefreshLayout>
<!-- 底部按钮 -->
<LinearLayout

View File

@@ -25,7 +25,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/login_title"
android:textSize="26dp"
android:textSize="22dp"
android:textStyle="bold"
android:textColor="@color/black"
app:layout_constraintTop_toBottomOf="@id/logoFrame"
@@ -77,7 +77,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/phoneRowLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="50dp"
android:background="@drawable/textinput_white_bg"
app:layout_constraintTop_toBottomOf="@+id/tvSmsLogin"
app:layout_constraintStart_toStartOf="parent"
@@ -99,6 +99,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="12dp"
android:hint="@string/phone_hint"
android:textSize="14dp"
android:textColorHint="@color/gray"
android:textColor="@android:color/black"
android:background="@null"
@@ -116,7 +117,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/smsRowLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="50dp"
android:layout_marginTop="16dp"
android:background="@drawable/textinput_white_bg"
android:paddingStart="16dp"
@@ -146,6 +147,7 @@
android:textColorHint="@color/gray"
android:textColor="@android:color/black"
android:background="@null"
android:textSize="14dp"
android:inputType="number"
android:paddingVertical="16dp"
app:layout_constraintStart_toEndOf="@+id/ivSmsIcon"
@@ -158,19 +160,18 @@
android:id="@+id/sendCodeBtn"
android:layout_width="110dp"
android:layout_height="36dp"
android:minWidth="0dp"
android:minHeight="0dp"
android:text="@string/send_sms_code"
android:textSize="14sp"
android:textColor="@color/send_code_text_color"
android:background="@drawable/send_code_btn_selector"
android:elevation="0dp"
android:stateListAnimator="@null"
android:gravity="center"
app:layout_constraintTop_toTopOf="parent"
android:minWidth="0dp"
android:minHeight="0dp"
android:stateListAnimator="@null"
android:text="@string/send_sms_code"
android:textColor="@color/send_code_text_color"
android:textSize="14dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginStart="12dp" />
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -178,7 +179,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/passwordRowLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="50dp"
android:layout_marginTop="16dp"
android:background="@drawable/textinput_white_bg"
android:paddingStart="16dp"
@@ -202,11 +203,12 @@
<EditText
android:id="@+id/etPassword"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="50dp"
android:layout_marginStart="12dp"
android:hint="@string/password_hint"
android:textColorHint="@color/gray"
android:textColor="@android:color/black"
android:textSize="14dp"
android:background="@null"
android:inputType="textPassword"
android:paddingVertical="16dp"
@@ -214,7 +216,6 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -222,15 +223,15 @@
<android.widget.Button
android:id="@+id/loginBtn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="40dp"
android:layout_height="40dp"
android:text="@string/login"
android:textSize="18sp"
android:textSize="14dp"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg"
android:textColor="@color/white"
app:layout_constraintTop_toBottomOf="@+id/smsRowLayout"
android:layout_marginTop="40dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
@@ -240,7 +241,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/register"
android:textSize="14sp"
android:textSize="14dp"
android:textColor="@color/green"
android:padding="8dp"
app:layout_constraintTop_toBottomOf="@+id/loginBtn"

View File

@@ -77,12 +77,12 @@
android:layout_width="wrap_content"
android:layout_height="32dp"
android:layout_marginStart="12dp"
android:background="@drawable/rounded_blue_bg"
android:background="@drawable/send_code_btn_selector"
android:text="@string/select_wifi"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textSize="14sp" />
android:textSize="14dp" />
</LinearLayout>
<LinearLayout

View File

@@ -42,8 +42,7 @@
android:layout_height="wrap_content"
android:background="@color/white"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
android:layout_marginTop="8dp">
android:paddingVertical="12dp">
<LinearLayout
android:layout_width="match_parent"

View File

@@ -102,7 +102,7 @@
android:background="#FFFFFF" />
<LinearLayout
android:id="@+id/emptyLayout"
android:id="@+id/layoutNoPermission"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
@@ -116,6 +116,22 @@
android:layout_height="wrap_content"
android:src="@drawable/no_message" />
</LinearLayout>
<LinearLayout
android:id="@+id/layoutNoNetwork"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical"
android:background="#FFFFFF"
android:visibility="gone">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/no_network_img_name" />
</LinearLayout>
</LinearLayout>

View File

@@ -36,10 +36,6 @@
android:layout_height="24dp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="10dp"
android:background="@color/face" />
<LinearLayout
android:gravity="center_vertical"

View File

@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical"
tools:context=".personal.CancelAccountActivity">
<LinearLayout
android:id="@+id/topBar"
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical"
android:paddingHorizontal="16dp">
<ImageView
android:id="@+id/ivBack"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@mipmap/back"
android:contentDescription="@string/back" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="@string/cancel_account"
android:textColor="@color/black"
android:textSize="16dp"
android:textStyle="bold" />
<View
android:layout_width="24dp"
android:layout_height="24dp" />
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/cancel_account_warning_text"
android:textColor="@color/gray"
android:textSize="14sp"
android:lineSpacingMultiplier="1.5" />
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="@color/gray_divider"
android:layout_marginVertical="16dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/current_account"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<TextView
android:id="@+id/tvPhoneNumber"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:textColor="@color/black"
android:textStyle="bold"
android:layout_marginTop="8dp" />
<View
android:layout_width="match_parent"
android:layout_height="1px"
android:background="@color/gray_divider"
android:layout_marginVertical="16dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/verification_code"
android:textColor="@color/text_secondary"
android:textSize="14sp" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginTop="8dp"
android:background="@drawable/textinput_white_bg">
<EditText
android:id="@+id/etVerificationCode"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_toLeftOf="@+id/btnSendCode"
android:hint="@string/enter_verification_code"
android:inputType="number"
android:paddingHorizontal="16dp"
android:textColor="@color/black"
android:background="@null"
android:textColorHint="@color/gray"
android:textSize="16sp" />
<android.widget.Button
android:id="@+id/btnSendCode"
android:layout_width="110dp"
android:layout_height="36dp"
android:minWidth="0dp"
android:minHeight="0dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginRight="8dp"
android:background="@drawable/send_code_btn_selector"
android:text="@string/send_sms_code"
android:textColor="@color/send_code_text_color"
android:elevation="0dp"
android:stateListAnimator="@null"
android:gravity="center"
android:textSize="14sp" />
</RelativeLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:background="@color/white">
<android.widget.Button
android:id="@+id/btnCancelAccount"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@drawable/btn_selector_red"
android:text="@string/cancel_account_btn"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>

View File

@@ -6,11 +6,11 @@
android:orientation="vertical">
<LinearLayout
android:layout_marginStart="30dp"
android:layout_marginEnd="30dp"
android:layout_marginTop="20dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_height="30dp"
android:orientation="horizontal">
<TextView
@@ -21,6 +21,7 @@
android:text="@string/cancel"
android:textColor="@color/gray"
android:textSize="14dp"
android:textStyle="bold"
android:clickable="true"
android:focusable="true" />
@@ -37,6 +38,7 @@
android:text="@string/confirm"
android:textColor="@color/green"
android:textSize="14dp"
android:textStyle="bold"
android:clickable="true"
android:focusable="true" />
</LinearLayout>
@@ -45,6 +47,7 @@
android:id="@+id/rvMessageList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxHeight="150dp"
android:clipToPadding="false"
android:paddingStart="14dp"
android:paddingTop="12dp"

View File

@@ -20,7 +20,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="@string/uploading"
android:textColor="@color/black"
android:textColor="@color/green"
android:textSize="16sp"/>
</LinearLayout>

View File

@@ -20,18 +20,11 @@
android:layout_height="1dp"
android:background="@color/dialog_view" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvWifiList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:overScrollMode="never" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>

View File

@@ -58,7 +58,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/phoneRowLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="50dp"
android:layout_marginTop="32dp"
android:background="@drawable/textinput_white_bg"
android:paddingStart="16dp"
@@ -88,7 +88,7 @@
android:paddingVertical="16dp"
android:textColor="@android:color/black"
android:textColorHint="@color/gray"
android:textSize="16sp"
android:textSize="14dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/ivPhoneIcon"
@@ -100,7 +100,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/smsRowLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="50dp"
android:layout_marginTop="16dp"
android:background="@drawable/textinput_white_bg"
android:paddingStart="16dp"
@@ -131,6 +131,7 @@
android:textColor="@android:color/black"
android:background="@null"
android:inputType="number"
android:textSize="14dp"
android:paddingVertical="16dp"
app:layout_constraintStart_toEndOf="@+id/ivSmsIcon"
app:layout_constraintEnd_toStartOf="@+id/sendCodeBtn"
@@ -145,7 +146,7 @@
android:minWidth="0dp"
android:minHeight="0dp"
android:text="@string/send_sms_code"
android:textSize="14sp"
android:textSize="14dp"
android:textColor="@color/white"
android:background="@drawable/send_code_btn_selector"
android:elevation="0dp"
@@ -162,7 +163,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/passwordRowLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="50dp"
android:layout_marginTop="16dp"
android:background="@drawable/textinput_white_bg"
android:paddingStart="16dp"
@@ -176,6 +177,7 @@
android:id="@+id/ivPasswordIcon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@mipmap/ma"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
@@ -191,6 +193,7 @@
android:textColor="@android:color/black"
android:background="@null"
android:inputType="text"
android:textSize="14dp"
android:paddingVertical="16dp"
app:layout_constraintStart_toEndOf="@+id/ivPasswordIcon"
app:layout_constraintEnd_toEndOf="parent"
@@ -204,7 +207,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/inviteRowLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="50dp"
android:layout_marginTop="16dp"
android:background="@drawable/textinput_white_bg"
android:paddingStart="16dp"
@@ -217,6 +220,7 @@
android:id="@+id/ivInviteIcon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@mipmap/ma"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@@ -233,7 +237,7 @@
android:paddingVertical="16dp"
android:textColor="@android:color/black"
android:textColorHint="@color/gray"
android:textSize="16sp"
android:textSize="14dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/ivInviteIcon"
@@ -245,11 +249,11 @@
<android.widget.Button
android:id="@+id/btnNext"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_height="40dp"
android:layout_marginTop="32dp"
android:text="@string/determine"
android:textAllCaps="false"
android:textSize="16sp"
android:textSize="16dp"
app:cornerRadius="12dp"
android:textColor="@color/white"
android:elevation="0dp"

View File

@@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@mipmap/home_back"
tools:context=".home.HomeMenuFragment">
@@ -37,14 +38,12 @@
<TextView
android:id="@+id/tvCurrentFamily"
android:layout_width="0dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/my_family_home"
android:textColor="@color/black"
android:textSize="20dp"
android:maxLines="1"
android:ellipsize="end"
android:textSize="20sp"
android:textStyle="bold" />
<ImageView
@@ -55,11 +54,6 @@
android:src="@mipmap/triangle" />
</LinearLayout>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -71,10 +65,10 @@
android:layout_width="38dp"
android:layout_height="38dp"
android:layout_marginEnd="10dp"
android:background="@drawable/bg_home_top_icon_light"
android:padding="7dp">
android:background="@drawable/bg_home_top_icon_light">
<ImageView
android:padding="7dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@mipmap/message"/>
@@ -82,15 +76,13 @@
<TextView
android:id="@+id/tvUnreadCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_height="16dp"
android:layout_gravity="end|top"
android:background="@drawable/bg_unread_dot"
android:minWidth="16dp"
android:height="16dp"
android:gravity="center"
android:textSize="10sp"
android:textColor="@color/white"
android:visibility="gone" />
android:textSize="10dp"
android:textColor="@color/white"/>
</FrameLayout>
<FrameLayout
@@ -126,7 +118,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="14dp"
android:layout_marginEnd="16dp"
android:text="@string/device"
android:textColor="@color/black"
android:textSize="16dp"
@@ -134,7 +125,7 @@
</LinearLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
<com.ckkj.defense_device.view.PullRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_marginLeft="12dp"
@@ -152,11 +143,11 @@
android:layout_marginTop="8dp"
android:layout_marginEnd="12dp"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:overScrollMode="never"
android:paddingBottom="16dp"
android:background="@android:color/transparent"
tools:listitem="@layout/item_device" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</com.ckkj.defense_device.view.PullRefreshLayout>
<LinearLayout
android:id="@+id/layoutNoPermission"

View File

@@ -40,7 +40,6 @@
android:id="@+id/llExpireTime"
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_marginTop="8dp"
android:background="@color/white"
android:gravity="center_vertical"
android:paddingHorizontal="16dp">

View File

@@ -3,16 +3,17 @@
android:id="@+id/itemFamily1"
android:layout_width="match_parent"
android:gravity="center"
android:layout_height="30dp">
android:layout_height="wrap_content">
<TextView
android:paddingVertical="10dp"
android:id="@+id/tvFamilyName"
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/my_family"
android:textColor="@color/black"
android:textSize="16dp" />
android:textSize="14dp" />
</LinearLayout>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingTop="24dp"
android:paddingBottom="20dp">
<ImageView
android:id="@+id/ivRefreshIcon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@mipmap/setting" />
<TextView
android:id="@+id/tvRefreshHint"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/refresh_hint_pull"
android:textColor="@color/green"
android:textSize="13sp" />
</LinearLayout>

View File

@@ -48,8 +48,7 @@
android:layout_height="wrap_content"
android:background="@color/white"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
android:layout_marginTop="8dp">
android:paddingVertical="12dp">
<LinearLayout
android:layout_width="match_parent"
@@ -86,7 +85,7 @@
android:layout_weight="1">
<!-- 列表 -->
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
<com.ckkj.defense_device.view.PullRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -94,8 +93,11 @@
<com.yanzhenjie.recyclerview.SwipeRecyclerView
android:id="@+id/rvLinkedUser"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
android:layout_height="match_parent"
android:clipToPadding="false"
android:overScrollMode="never"
android:paddingBottom="16dp"/>
</com.ckkj.defense_device.view.PullRefreshLayout>
<!-- 空页面 -->
<LinearLayout

View File

@@ -252,6 +252,39 @@
android:background="#FFFFFF"
android:paddingTop="8dp" />
<LinearLayout
android:id="@+id/layoutNoPermission"
android:layout_width="match_parent"
android:layout_height="200dp"
android:gravity="center"
android:orientation="vertical"
android:paddingHorizontal="32dp"
android:background="@color/white"
android:visibility="gone">
<ImageView
android:layout_width="244dp"
android:layout_height="131dp"
android:src="@mipmap/no_message" />
</LinearLayout>
<LinearLayout
android:id="@+id/layoutNoNetwork"
android:layout_width="match_parent"
android:layout_height="200dp"
android:gravity="center"
android:orientation="vertical"
android:paddingHorizontal="32dp"
android:background="@color/white"
android:visibility="gone">
<ImageView
android:layout_width="244dp"
android:layout_height="131dp"
android:src="@mipmap/no_network_img_name" />
</LinearLayout>
</LinearLayout>

View File

@@ -39,14 +39,24 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<com.ckkj.defense_device.view.PullRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvMessageList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:clipToPadding="false"
android:overScrollMode="never"
android:paddingBottom="16dp"
tools:listitem="@layout/item_message_notice" />
</com.ckkj.defense_device.view.PullRefreshLayout>
<LinearLayout
android:id="@+id/emptyLayout"
android:layout_width="match_parent"

View File

@@ -70,6 +70,15 @@
android:textColor="#666666"
android:textSize="16dp" />
</LinearLayout>
<ImageView
android:layout_marginLeft="5dp"
android:id="@+id/ivMenu"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical|top"
android:src="@mipmap/settings"/>
</LinearLayout>
<LinearLayout

View File

@@ -59,7 +59,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/phoneRowLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="50dp"
android:layout_marginTop="32dp"
android:background="@drawable/textinput_white_bg"
android:paddingStart="16dp"
@@ -89,7 +89,7 @@
android:paddingVertical="16dp"
android:textColor="@android:color/black"
android:textColorHint="@color/gray"
android:textSize="16sp"
android:textSize="14dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/ivPhoneIcon"
@@ -100,7 +100,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/smsRowLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="50dp"
android:layout_marginTop="16dp"
android:background="@drawable/textinput_white_bg"
android:paddingStart="16dp"
@@ -129,6 +129,7 @@
android:hint="@string/sms_code_hint"
android:textColorHint="@color/gray"
android:textColor="@android:color/black"
android:textSize="14dp"
android:background="@null"
android:inputType="number"
android:paddingVertical="16dp"
@@ -145,7 +146,7 @@
android:minWidth="0dp"
android:minHeight="0dp"
android:text="@string/send_sms_code"
android:textSize="14sp"
android:textSize="14dp"
android:textColor="@color/white"
android:background="@drawable/send_code_btn_selector"
android:elevation="0dp"
@@ -162,7 +163,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/idRowLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="50dp"
android:layout_marginTop="16dp"
android:background="@drawable/textinput_white_bg"
android:paddingStart="16dp"
@@ -192,7 +193,7 @@
android:paddingVertical="16dp"
android:textColor="@android:color/black"
android:textColorHint="@color/gray"
android:textSize="16sp"
android:textSize="14dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/ivIdIcon"
@@ -204,7 +205,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/passwordRowLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="50dp"
android:layout_marginTop="16dp"
android:background="@drawable/textinput_white_bg"
android:paddingStart="16dp"
@@ -235,6 +236,7 @@
android:background="@null"
android:inputType="text"
android:paddingVertical="16dp"
android:textSize="14dp"
app:layout_constraintStart_toEndOf="@+id/ivPasswordIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
@@ -247,7 +249,7 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/inviteRowLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_height="50dp"
android:layout_marginTop="16dp"
android:background="@drawable/textinput_white_bg"
android:paddingStart="16dp"
@@ -277,7 +279,7 @@
android:paddingVertical="16dp"
android:textColor="@android:color/black"
android:textColorHint="@color/gray"
android:textSize="16sp"
android:textSize="14dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/ivInviteIcon"
@@ -289,11 +291,11 @@
<android.widget.Button
android:id="@+id/btnNext"
android:layout_width="0dp"
android:layout_height="56dp"
android:layout_height="40dp"
android:layout_marginTop="32dp"
android:text="@string/next_step"
android:textAllCaps="false"
android:textSize="16sp"
android:textSize="16dp"
app:cornerRadius="12dp"
android:textColor="@color/white"
android:elevation="0dp"

View File

@@ -35,18 +35,16 @@
android:layout_height="24dp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="10dp"
android:background="@color/face" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
android:layout_height="50dp"
android:text="@string/function_settings"
android:textColor="@color/gray"
android:textColor="@color/black"
android:background="@color/message_bg"
android:gravity="bottom"
android:paddingStart="20dp"
android:paddingBottom="7dp"
android:textSize="14dp" />
<LinearLayout
@@ -191,18 +189,16 @@
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="10dp"
android:background="@color/face" />
.
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
android:layout_height="50dp"
android:text="@string/basic_settings"
android:textColor="@color/gray"
android:textColor="@color/black"
android:background="@color/message_bg"
android:gravity="bottom"
android:paddingStart="20dp"
android:paddingBottom="7dp"
android:textSize="14dp" />
<LinearLayout

View File

@@ -0,0 +1,227 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/white"
tools:context=".personal.SettingsMenusActivity">
<!-- Top Toolbar -->
<LinearLayout
android:id="@+id/topBar"
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical"
android:paddingHorizontal="16dp">
<ImageView
android:id="@+id/ivBack"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@mipmap/back"/>
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="@string/settings_title"
android:textColor="@color/black"
android:textSize="16dp"
android:textStyle="bold" />
</LinearLayout>
<!-- Menu List Container -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="@android:color/white">
<!-- 检查更新 -->
<LinearLayout
android:id="@+id/item_check_update"
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/check_update"
android:textSize="15sp" />
<TextView
android:id="@+id/tv_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="#999999"
android:textSize="13sp" />
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:src="@mipmap/right_back"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:background="#F0F0F0" />
<!-- 清除缓存 -->
<LinearLayout
android:id="@+id/item_clear_cache"
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/clear_cache"
android:textSize="15sp" />
<TextView
android:id="@+id/tv_cache_size"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text=""
android:textColor="#999999"
android:textSize="13sp" />
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="8dp"
android:src="@mipmap/right_back"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:background="#F0F0F0" />
<!-- 用户协议 -->
<LinearLayout
android:id="@+id/item_user_agreement"
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/user_agreement"
android:textSize="15sp" />
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@mipmap/right_back"/>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:background="#F0F0F0" />
<!-- 隐私政策 -->
<LinearLayout
android:id="@+id/item_privacy_policy"
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/privacy_policy"
android:textSize="15sp" />
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@mipmap/right_back" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginStart="16dp"
android:background="#F0F0F0" />
<!-- 注销账号 -->
<LinearLayout
android:id="@+id/item_logout"
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="16dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/cancel_account"
android:textSize="15sp" />
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@mipmap/right_back"/>
</LinearLayout>
</LinearLayout>
<!-- Bottom Contact Text -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:gravity="center"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tip_contact_us"
android:textColor="#999999"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="19927623@qq.com"
android:textColor="#4A90D9"
android:textSize="12sp" />
</LinearLayout>
</LinearLayout>

View File

@@ -35,11 +35,6 @@
android:layout_height="24dp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="10dp"
android:background="@color/face" />
<LinearLayout
android:id="@+id/itemDistance"
android:layout_width="match_parent"

View File

@@ -77,12 +77,12 @@
android:layout_width="wrap_content"
android:layout_height="32dp"
android:layout_marginStart="12dp"
android:background="@drawable/rounded_blue_bg"
android:background="@drawable/send_code_btn_selector"
android:text="@string/select_wifi"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textSize="14sp" />
android:textSize="14dp" />
</LinearLayout>
<LinearLayout

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@@ -1,23 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- App Name -->
<string name="app_name">防御机</string>
<!-- Common -->
<string name="app_name">驭鲲智防系统</string>
<string name="confirm">确定</string>
<string name="cancel">取消</string>
<string name="save">保存</string>
<string name="delete">删除</string>
<string name="edit">编辑</string>
<string name="back">返回</string>
<string name="loading">加载中...</string>
<string name="network_error">网络错误,请重试</string>
<string name="send">发送</string>
<string name="submit">提交</string>
<!-- Login Activity -->
<string name="login_title">智能防御机</string>
<string name="login_title">驭鲲智防系统</string>
<string name="sms_login">验证码登录</string>
<string name="password_login">密码登录</string>
<string name="phone_hint">请输入手机号</string>
@@ -27,189 +20,77 @@
<string name="login">登录</string>
<string name="forgot_password">忘记密码?</string>
<string name="register">注册</string>
<!-- Register Activity -->
<string name="register_title">注册</string>
<string name="register_subtitle">注册用户</string>
<string name="set_password">设置密码</string>
<string name="confirm_password">确认密码</string>
<string name="password_requirement">密码长度6-20位</string>
<string name="agreement_prefix">我已阅读并同意</string>
<string name="user_agreement">用户协议</string>
<string name="and"></string>
<string name="privacy_policy">隐私政策</string>
<string name="register_success">注册成功</string>
<string name="registering">注册中...</string>
<string name="register_success_message">您的账号已成功创建</string>
<string name="go_to_login">去登录</string>
<!-- Forgot Password -->
<string name="forgot_password_title">忘记密码</string>
<string name="reset_password">重置密码</string>
<string name="new_password">新密码</string>
<string name="reset_success">密码重置成功</string>
<string name="resetting">重置中...</string>
<!-- Face Login/Verification -->
<string name="face_login">人脸登录</string>
<string name="face_verification">人脸验证</string>
<string name="face_capture_hint">请将人脸对准框内</string>
<string name="face_recognition_success">识别成功</string>
<string name="face_recognition_failed">识别失败</string>
<string name="retry">重试</string>
<!-- Home Activity -->
<string name="home">首页</string>
<string name="profile">个人</string>
<string name="admin_user">管理者</string>
<string name="normal_user">普通用户</string>
<string name="my_family_admin">%s管理员</string>
<string name="family_member">%s成员</string>
<!-- Home Menu -->
<string name="my_devices">我的设备</string>
<string name="add_device">添加设备</string>
<string name="scan_qr">扫一扫</string>
<string name="voice_control">语音控制</string>
<string name="light_control">灯光控制</string>
<string name="water_gun">水枪</string>
<string name="monitor">监控</string>
<string name="alarm_log">告警记录</string>
<string name="message">消息</string>
<string name="settings">设置</string>
<!-- Device Management -->
<string name="device_name">设备名称</string>
<string name="device_status">状态</string>
<string name="device_online">在线</string>
<string name="device_offline">离线</string>
<string name="device_id">设备ID</string>
<string name="bind_device">绑定设备</string>
<string name="unbind_device">解绑设备</string>
<string name="device_not_found">暂无设备</string>
<string name="no_permission_title">暂无权限</string>
<string name="no_permission_desc">您暂无权查看设备,请联系管理员</string>
<!-- Add Device -->
<string name="add_device_title">添加设备</string>
<string name="scan_nearby">扫描附近设备</string>
<string name="scanning">持续自动搜索中...</string>
<string name="device_nearby_hint">请将手机尽量靠近要添加的设备</string>
<string name="scan_again">重新扫描</string>
<string name="add_manually">手动添加</string>
<string name="enter_device_id">请输入设备ID</string>
<string name="bind_success">绑定成功</string>
<!-- Family Management -->
<string name="family_management">家庭管理</string>
<string name="switch_family">切换家庭</string>
<string name="current_family">当前家庭</string>
<string name="my_family">我的家庭</string>
<string name="add_family">添加家庭</string>
<string name="family_name">家庭名称</string>
<string name="enter_family_name">请输入家庭名称</string>
<string name="create_family">创建家庭</string>
<string name="join_family">加入家庭</string>
<string name="join_now">立即加入</string>
<string name="nickname_placeholder">这里是昵称</string>
<string name="invite_join_family_message">邀请你加入%s的家庭</string>
<string name="invite_join_family_message_preview">邀请你加入这里是昵称的家庭</string>
<string name="family_code">家庭码</string>
<string name="enter_family_code">请输入家庭码</string>
<string name="family_with_normal">的家庭</string>
<string name="no_device">暂无设备</string>
<!-- User Management -->
<string name="user_management">用户管理</string>
<string name="user_list">用户列表</string>
<string name="add_user">添加用户</string>
<string name="admin">管理员</string>
<string name="member">成员</string>
<string name="nickname">昵称</string>
<string name="phone_number">手机号</string>
<string name="enter_nickname">请输入昵称</string>
<!-- Password Management -->
<string name="password_management">密码管理</string>
<string name="change_password">修改密码</string>
<string name="current_password">当前密码</string>
<string name="old_password">原密码</string>
<string name="verification_code">验证码</string>
<string name="password_not_match">两次密码不一致</string>
<!-- Face Management -->
<string name="face_management">人脸管理</string>
<string name="add_face">添加人脸</string>
<string name="face_list">人脸列表</string>
<string name="delete_face_confirm">确定要删除这张人脸吗?</string>
<string name="standard_capture">标准采集</string>
<string name="strict_capture">严格采集</string>
<!-- Live Video -->
<string name="live_video">实时监控</string>
<string name="fullscreen">全屏</string>
<string name="screenshot">截图</string>
<string name="record">录像</string>
<string name="talk">对讲</string>
<string name="mute">静音</string>
<string name="unmute">取消静音</string>
<string name="video_loading">视频加载中...</string>
<string name="video_error">视频加载失败</string>
<!-- Alarm Log -->
<string name="alarm_log_title">告警记录</string>
<string name="alarm_detail">告警详情</string>
<string name="alarm_time">告警时间</string>
<string name="alarm_type">告警类型</string>
<string name="alarm_location">告警位置</string>
<string name="no_alarm_logs">暂无告警记录</string>
<string name="intrusion_detected">入侵检测</string>
<string name="abnormal_sound">异常声音</string>
<string name="motion_detected">移动侦测</string>
<!-- Message -->
<string name="message_title">消息</string>
<string name="system_message">系统</string>
<string name="alarm_message">告警</string>
<string name="device_message">设备</string>
<string name="no_messages">暂无消息</string>
<string name="mark_as_read">标记已读</string>
<string name="clear_all">清空全部</string>
<string name="message_detail">消息详情</string>
<!-- Settings -->
<string name="settings_title">设置</string>
<string name="function_settings">功能设置</string>
<string name="basic_settings">基本设置</string>
<string name="voice_settings">语音设置</string>
<string name="water_gun_settings">水枪设置</string>
<string name="water_gun_settings">驱离设置</string>
<string name="detection_settings">自动模式</string>
<string name="notification_settings">通知设置</string>
<string name="language_settings">语言</string>
<string name="chinese">中文</string>
<string name="english">English</string>
<string name="linked_user">关联用户</string>
<string name="user_details">用户详情</string>
<string name="search">搜索</string>
<string name="search_preset_text">搜索预设文案</string>
<string name="add_linked_user">添加关联用户</string>
<string name="unlink">解除关联</string>
<string name="unlink_confirm_message">确定解除该用户对本设备的关联?</string>
<string name="wiFi_management">WiFi管理</string>
<string name="about">关于</string>
<string name="version">版本</string>
<string name="logout">退出登录</string>
<string name="logout_confirm">确定要退出登录吗?</string>
<string name="logout_fail">退出登录失败</string>
<string name="warning_voice_playback">警告语音播放</string>
<string name="warning_voice_content">警告语音内容</string>
<string name="enter_warning_voice_content_system_will_generate_voice_automatically">请输入警告语音内容,系统将自动生成语音</string>
<string name="strike_interval">打击间隔</string>
<string name="strike_interval">驱离间隔</string>
<string name="switch_container">切换容器</string>
<string name="strike_position">打击位置</string>
<string name="click_model_select_strike_pos">请点击下方人体模型,选择大致打击位置</string>
<string name="confirm_modify_strike_pos">确定修改为该打击位置?</string>
<string name="submit_success">提交成功</string>
<string name="strike_position">驱离位置</string>
<string name="click_model_select_strike_pos">请点击下方人体模型,选择大致驱离位置</string>
<string name="confirm_modify_strike_pos">确定修改为该驱离位置?</string>
<string name="submit_failed">提交失败</string>
<string name="container_1">容器1</string>
<string name="container_2">容器2</string>
@@ -228,33 +109,9 @@
<string name="auto_arms_start_time">自动布防开始时间</string>
<string name="auto_arms_end_time">自动布防结束时间</string>
<string name="please_select_at_least_one_user">请至少选择一个用户</string>
<string name="selected_users_voice">已选择 %d 个用户,语音</string>
<!-- Personal Profile -->
<string name="personal_info">个人信息</string>
<string name="avatar">头像</string>
<string name="gender">性别</string>
<string name="male"></string>
<string name="female"></string>
<string name="birthday">生日</string>
<string name="address">地址</string>
<string name="enter_address">请输入地址</string>
<!-- Scan -->
<string name="scan_title">扫一扫</string>
<string name="scan_hint">将二维码/条码放入框内,即可自动扫描</string>
<string name="album">相册</string>
<string name="flashlight">手电筒</string>
<string name="scan_failed">扫描失败,请重试</string>
<!-- WiFi -->
<string name="wifi_list">WiFi列表</string>
<string name="wifi_password">WiFi密码</string>
<string name="enter_wifi_password">请输入WiFi密码</string>
<string name="connect">连接</string>
<string name="connecting">连接中...</string>
<string name="wifi_connection_failed">WiFi连接失败</string>
<string name="wifi_connection_success">WiFi连接成功</string>
<string name="connect_device">连接设备</string>
<string name="connection_success">连接成功</string>
<string name="bluetooth_keep_enabled">请保持手机蓝牙开启并将手机尽量靠近设备</string>
@@ -262,8 +119,6 @@
<string name="device_connect_network">设备连接网络</string>
<string name="device_connect_near_router">设备连接网络请将设备尽量靠近路由器</string>
<string name="keep_device_near_router">请将设备尽量靠近路由器</string>
<!-- Dialog -->
<string name="tip">提示</string>
<string name="download_alarm_video_confirm">确定下载该入侵告警视频?</string>
<string name="delete_alarm_video_confirm">确定删除该入侵告警视频?</string>
@@ -271,46 +126,19 @@
<string name="delete_alarm_photo_confirm">确定删除该入侵告警照片?</string>
<string name="download_start">开始下载</string>
<string name="delete_success">删除成功</string>
<string name="please_wait">请稍候...</string>
<string name="operation_success">操作成功</string>
<string name="operation_failed">操作失败</string>
<string name="network_unavailable">网络不可用</string>
<string name="check_network">请检查网络连接</string>
<string name="confirm_delete_user">确定删除该用户吗?</string>
<!-- Voice Recognition Mode -->
<string name="recognition_mode">识别模式</string>
<string name="mode_standard">标准模式</string>
<string name="mode_strict">严格模式</string>
<string name="mode_custom">自定义模式</string>
<!-- Time -->
<string name="today">今天</string>
<string name="yesterday">昨天</string>
<string name="this_week">本周</string>
<string name="this_month">本月</string>
<!-- Placeholder -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<!-- Additional missing strings -->
<string name="scan_device_qr">请扫描设备或说明书上的二维码</string>
<string name="workshop">工作室</string>
<string name="parents_home">父母家</string>
<string name="smart_hanging_defense">智能挂式防御机</string>
<string name="smart_defense_device">智能防御机</string>
<string name="smart_rail_defense">智能轨道防御机</string>
<string name="corridor_defense">走廊防御机</string>
<string name="door_defense">门禁防御机</string>
<string name="living_room_defense">客厅防御机</string>
<string name="balcony_defense">阳台防御机</string>
<string name="my_family_home">我的家庭</string>
<string name="device">设备</string>
<string name="smart_device_a1">智能防御机A1</string>
<string name="voice">语音</string>
<string name="light">灯光</string>
<string name="control">控制</string>
<string name="seconds">%d秒</string>
<string name="register_user">注册用户</string>
<string name="enter_platform">进入平台</string>
<string name="nickname_hint">这里是昵称</string>
@@ -329,7 +157,6 @@
<string name="complete">完成</string>
<string name="bluetooth_recognition">蓝牙识别</string>
<string name="bluetooth_face_recognition">蓝牙+人脸识别</string>
<string name="message_list">消息列表</string>
<string name="face_collection">人脸采集</string>
<string name="face_in_circle">请将人脸放入圆形区域内</string>
<string name="collect_face">采集人脸</string>
@@ -339,94 +166,52 @@
<string name="operation_password">操作密码</string>
<string name="enter_phone_number">请输入手机号</string>
<string name="enter_verification_code">请输入验证码</string>
<string name="send_code">发送验证码</string>
<string name="resend_code">重新发送</string>
<string name="enter_new_password">请输入新密码</string>
<string name="enter_password_again">请再次输入新密码</string>
<string name="determine">确定</string>
<string name="id_number">身份证号</string>
<string name="enter_id_number">请输入身份证号</string>
<string name="hint_input_correct_id_card">请输入正确的身份证号</string>
<string name="invite_code_optional">邀请码(非必填)</string>
<string name="enter_invite_code">请输入邀请码</string>
<string name="scan_add">扫描添加</string>
<string name="wifi">WiFi</string>
<string name="recording_permission_needed">需要录音权限才能使用控制功能</string>
<string name="video_ready">视频准备完成</string>
<string name="playback_complete">播放完成</string>
<string name="cannot_load_video">无法加载视频</string>
<string name="view_alarm_log">查看告警日志</string>
<string name="paused">已暂停</string>
<string name="resume_playback">继续播放</string>
<string name="entered_fullscreen">已进入全屏</string>
<string name="exited_fullscreen">已退出全屏</string>
<string name="sound_on">声音已开启</string>
<string name="sound_off">声音已关闭</string>
<string name="download">下载</string>
<string name="light_on">灯光已开启</string>
<string name="light_off">灯光已关闭</string>
<string name="start_control_mode">开始控制模式</string>
<string name="stop_control_mode">停止控制模式</string>
<string name="recording_start_failed">录音启动失败</string>
<string name="video_error_format">播放错误: %1$d, %2$d</string>
<string name="buffering_format">缓冲中: %d%%</string>
<string name="camera_not_ready">相机未准备好</string>
<string name="cannot_open_camera">无法打开相机</string>
<string name="camera_permission_needed">需要相机权限</string>
<string name="permission_denied">需要获取相机和存储权限</string>
<string name="image_selected">已选择图片</string>
<string name="please_try_later">请稍后再试</string>
<string name="phone_format_invalid">手机号格式不正确</string>
<string name="enter_verification_code_pls">请输入验证码</string>
<string name="get_code_first">请先获取验证码</string>
<string name="verification_code_error">验证码错误</string>
<string name="password_cannot_empty">密码不能为空</string>
<string name="password_letter_digit">密码需同时包含字母和数字</string>
<string name="pwd_length_tip">密码长度需为%1$d-%2$d位</string>
<string name="password_mismatch">两次输入的密码不一致</string>
<string name="redirect_face_page">跳转到人脸页面</string>
<string name="redirect_register_page">跳转到注册页面</string>
<string name="forgot_password_page">找回密码</string>
<string name="redirect_face_recognition_page">跳转到人脸识别页面</string>
<string name="camera_permission_face_verify">需要相机权限才能进行人脸验证</string>
<string name="intrusion_alarm">入侵告警</string>
<string name="device_fault">设备故障</string>
<string name="renewal_reminder">到期提醒</string>
<string name="someone_invaded">有人入侵</string>
<string name="go_check">去看一下</string>
<string name="notice">温馨提示</string>
<string name="got_it">知道了</string>
<string name="strike_distance">打击距离</string>
<string name="enter_strike_distance">请输入打击距离</string>
<string name="face_detection_failed">人脸检测失败</string>
<string name="strike_distance">驱离距离</string>
<string name="enter_strike_distance">请输入驱离距离</string>
<string name="nickname_default">这里是我的昵称</string>
<string name="device_running_days">设备已经运行\n%d天请及时维护</string>
<string name="device_offline_message">设备已离线请检查设备的WIFI及电源情况</string>
<string name="countdown_retry">%d秒后重试</string>
<string name="sms_login_success">验证码登录成功\n手机号%1$s\n验证码%2$s</string>
<string name="password_login_success">密码登录成功\n手机号%1$s\n密码%2$s</string>
<string name="code_sent_to">验证码已发送至 %s</string>
<string name="code_already_sent">验证码已发送</string>
<string name="sms_login_success_title">登录成功</string>
<string name="password_login_success_title">登录成功</string>
<string name="modify_phone">修改手机号</string>
<string name="invalid_phone">手机号格式不正确</string>
<string name="verify_and_bind">验证并绑定</string>
<string name="operation_password_switch">操作密码开关</string>
<string name="hint_operation_password">请输入6位数字密码</string>
<string name="input_op_pwd">请输入操作密码</string>
<string name="hint_confirm_password">请确认密码</string>
<string name="operation_password_enabled">操作密码已开启</string>
<string name="operation_password_disabled">操作密码已关闭</string>
<string name="password_must_be_6_digits">密码必须为6位数字</string>
<string name="please_confirm_password">请确认密码</string>
<string name="confirm_reset_password">确定重设密码</string>
<string name="get_verification_code">获取验证码</string>
<string name="hint_phone_zh">请输入手机号</string>
<string name="phone_tip">请输入有效的11位手机号</string>
<string name="hint_code_zh">请输入验证码</string>
<string name="hint_new_password_zh">请输入新密码</string>
<string name="hint_confirm_password_zh">请再次输入新密码</string>
<string name="enter_old_password">请输入旧密码</string>
<string name="password_error">密码错误</string>
<string name="operation_password_not_set">未设置操作密码</string>
<string name="confirm_reset_password_btn">确认重置</string>
<string name="user_status">用户状态</string>
<string name="contact_name">联系人姓名</string>
@@ -441,8 +226,6 @@
<string name="expire_24h">24小时</string>
<string name="expire_7d">7天</string>
<string name="qr_code_expired">二维码已过期,请重新生成</string>
<string name="generate_failed">生成失败</string>
<string name="revoke_failed">撤销失败</string>
<string name="share_failed">分享失败</string>
<string name="share_poster_tips">邀请二维码有效期%1$s内有效分享只能被接受1次</string>
<string name="invite_friend">快来加入我扫码下载APP</string>
@@ -454,25 +237,15 @@
<string name="device_no">设备编号</string>
<string name="fw_version">固件版本</string>
<string name="generate_invite_code">生成邀请码</string>
<!-- WiFi管理 -->
<string name="not_set">未设置</string>
<string name="device_id_empty">设备ID为空</string>
<string name="modify_success">修改成功</string>
<string name="modify_failed">修改失败</string>
<string name="network_request_failed">网络请求失败</string>
<string name="code_sent_success">验证码发送成功</string>
<string name="code_send_failed">验证码发送失败</string>
<!-- 登录密码重置 -->
<string name="processing">处理中...</string>
<string name="old_password_error">原密码错误</string>
<string name="password_reset_success">密码重置成功</string>
<string name="password_reset_failed">密码重置失败</string>
<string name="user_not_login">用户未登录</string>
<string name="phone_number_empty">手机号为空</string>
<string name="operation_fail">操作失败</string>
<string name="get_user_info_fail">获取用户信息失败</string>
<string name="take_photo">拍照</string>
<string name="select_from_album">从相册选择</string>
@@ -489,16 +262,22 @@
<string name="max_add_11_users">最多添加11个用户</string>
<string name="linked_user_phone_label">手机号码:%s</string>
<string name="linked_user_idcard_label">身份证号:%s</string>
<string name="direction_up">向上移动</string>
<string name="direction_down">向下移动</string>
<string name="direction_left">向左移动</string>
<string name="direction_right">向右移动</string>
<!-- 操作密码 -->
<string name="please_enable_operation_password_first">请先开启操作密码</string>
<string name="disable_operation_password_tip">确定要关闭操作密码吗?</string>
<string name="verify_failed">验证失败</string>
<string name="password_update_success">密码修改成功</string>
<string name="password_update_failed">密码修改失败</string>
<string name="update_failed">更新失败</string>
<string name="check_update">检查更新</string>
<string name="clear_cache">清除缓存</string>
<string name="cancel_account">注销账号</string>
<string name="tip_contact_us">如有问题,请联系我们:</string>
<string name="cancel_account_warning_text">注销后无法恢复,请谨慎操作</string>
<string name="current_account">当前账号</string>
<string name="cancel_account_btn">注销</string>
<string name="confirm_cancel_account_user_account">确定注销该用户吗?</string>
<string name="no_more_data">没有更多了</string>
<string name="refresh_hint_pull">下拉刷新</string>
<string name="refresh_hint_release">松开刷新</string>
<string name="refresh_hint_refreshing">正在刷新…</string>
<string name="my_family">我的家族</string>
<string name="need_camera_permission_for_face_verify">需要相机权限才能进行人脸验证</string>
</resources>

View File

@@ -23,4 +23,12 @@
<color name="gradientLayout1">#EBDCED</color>
<color name="gradientLayout2">#CAF4FF</color>
<!-- Common Colors -->
<color name="primary">#09C2BD</color>
<color name="text_primary">#333333</color>
<color name="text_secondary">#666666</color>
<color name="card_background">#F8F8F8</color>
<color name="button_disabled">#CCCCCC</color>
<color name="warning_red">#E34D59</color>
</resources>

View File

@@ -1,474 +1,242 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- App Name -->
<string name="app_name">Defense Device</string>
<!-- Common -->
<string name="app_name">Yukun Intelligent Defense System</string>
<string name="confirm">Confirm</string>
<string name="cancel">Cancel</string>
<string name="save">Save</string>
<string name="delete">Delete</string>
<string name="edit">Edit</string>
<string name="back">Back</string>
<string name="loading">Loading...</string>
<string name="network_error">Network error, please try again</string>
<string name="send">Send</string>
<string name="network_error">Network error, please retry</string>
<string name="submit">Submit</string>
<!-- Login Activity -->
<string name="login_title">Smart Defense Device</string>
<string name="login_title">Yukun Intelligent Defense System</string>
<string name="sms_login">SMS Login</string>
<string name="password_login">Password Login</string>
<string name="phone_hint">Enter phone number</string>
<string name="sms_code_hint">Enter verification code</string>
<string name="password_hint">Enter password</string>
<string name="phone_hint">Please enter phone number</string>
<string name="sms_code_hint">Please enter verification code</string>
<string name="password_hint">Please enter password</string>
<string name="send_sms_code">Send Code</string>
<string name="login">Login</string>
<string name="forgot_password">Forgot Password?</string>
<!-- Register Activity -->
<string name="register">Register</string>
<string name="register_title">Register</string>
<string name="register_subtitle">Register Account</string>
<string name="register_subtitle">Register User</string>
<string name="set_password">Set Password</string>
<string name="confirm_password">Confirm Password</string>
<string name="password_requirement">Password must be 620 characters</string>
<string name="agreement_prefix">I have read and agree to the</string>
<string name="password_requirement">Password must be 6-20 characters</string>
<string name="user_agreement">User Agreement</string>
<string name="and">and</string>
<string name="privacy_policy">Privacy Policy</string>
<string name="register_success">Registration Successful</string>
<string name="register_success">Registration successful</string>
<string name="registering">Registering...</string>
<string name="register_success_message">Your account has been created successfully</string>
<string name="go_to_login">Go to Login</string>
<!-- Forgot Password -->
<string name="forgot_password_title">Forgot Password</string>
<string name="reset_password">Reset Password</string>
<string name="new_password">New Password</string>
<string name="reset_success">Password reset successful</string>
<string name="resetting">Resetting...</string>
<!-- Face Login/Verification -->
<string name="face_capture_hint">Please align your face within the frame</string>
<string name="face_recognition_success">Recognition Successful</string>
<string name="face_recognition_failed">Recognition Failed</string>
<string name="retry">Retry</string>
<!-- Home Activity -->
<string name="face_verification">Face Verification</string>
<string name="home">Home</string>
<string name="profile">Profile</string>
<string name="admin_user">Administrator</string>
<string name="normal_user">Ordinary User</string>
<string name="family_with_normal">s Family</string>
<!-- Home Menu -->
<string name="my_devices">My Devices</string>
<string name="add_device">Add Device</string>
<string name="scan_qr">Scan QR Code</string>
<string name="voice_control">Voice Control</string>
<string name="light_control">Light Control</string>
<string name="water_gun">Water Gun</string>
<string name="monitor">Monitor</string>
<string name="message">Message</string>
<string name="settings">Settings</string>
<!-- Device Management -->
<string name="device_name">Device Name</string>
<string name="device_status">Status</string>
<string name="device_online">Online</string>
<string name="device_id">Device ID</string>
<string name="bind_device">Bind Device</string>
<string name="unbind_device">Unbind Device</string>
<string name="device_not_found">No devices found</string>
<string name="no_device">No device</string>
<string name="no_permission_title">No permission</string>
<string name="no_permission_desc">You do not have permission to view devices</string>
<!-- Add Device -->
<string name="add_device_title">Add Device</string>
<string name="scan_nearby">Scan Nearby Devices</string>
<string name="scanning">Scanning continuously...</string>
<string name="device_nearby_hint">Please keep your phone close to the device</string>
<string name="scan_again">Scan Again</string>
<string name="add_manually">Add Manually</string>
<string name="enter_device_id">Enter Device ID</string>
<string name="bind_success">Binding Successful</string>
<!-- Family Management -->
<string name="family_management">Family Management</string>
<string name="switch_family">Switch Family</string>
<string name="current_family">Current Family</string>
<string name="my_family">My Family</string>
<string name="normal_user">Normal User</string>
<string name="my_family_admin">%s (Admin)</string>
<string name="family_member">%s (Member)</string>
<string name="add_family">Add Family</string>
<string name="family_name">Family Name</string>
<string name="enter_family_name">Enter family name</string>
<string name="create_family">Create Family</string>
<string name="join_family">Join Family</string>
<string name="device_name">Device Name</string>
<string name="device_offline">Offline</string>
<string name="unbind_device">Unbind Device</string>
<string name="add_device_title">Add Device</string>
<string name="scan_nearby">Scan Nearby Devices</string>
<string name="scanning">Continuous auto scanning...</string>
<string name="device_nearby_hint">Please keep your phone close to the device to be added</string>
<string name="switch_family">Switch Family</string>
<string name="join_now">Join Now</string>
<string name="nickname_placeholder">Nickname</string>
<string name="invite_join_family_message">Inviting you to join %s\'s family</string>
<string name="invite_join_family_message_preview">Inviting you to join Nickname\'s family</string>
<string name="family_code">Family Code</string>
<string name="enter_family_code">Enter family code</string>
<!-- User Management -->
<string name="nickname_placeholder">This is nickname</string>
<string name="invite_join_family_message">Invites you to join %s\'s family</string>
<string name="invite_join_family_message_preview">Invites you to join This is nickname\'s family</string>
<string name="family_with_normal">\'s family</string>
<string name="no_device">No devices</string>
<string name="user_management">User Management</string>
<string name="user_list">User List</string>
<string name="add_user">Add User</string>
<string name="admin">Admin</string>
<string name="member">Member</string>
<string name="nickname">Nickname</string>
<string name="phone_number">Phone Number</string>
<string name="enter_nickname">Enter nickname</string>
<!-- Password Management -->
<string name="password_management">Password Management</string>
<string name="change_password">Change Password</string>
<string name="current_password">Current Password</string>
<string name="old_password">Old Password</string>
<string name="verification_code">Verification Code</string>
<string name="password_not_match">Passwords do not match</string>
<!-- Face Management -->
<string name="face_management">Face Management</string>
<string name="add_face">Add Face</string>
<string name="face_list">Face List</string>
<string name="delete_face_confirm">Are you sure you want to delete this face?</string>
<string name="strict_capture">Strict Capture</string>
<!-- Live Video -->
<string name="live_video">Live Video</string>
<string name="fullscreen">Fullscreen</string>
<string name="screenshot">Screenshot</string>
<string name="record">Record</string>
<string name="talk">Talk</string>
<string name="mute">Mute</string>
<string name="unmute">Unmute</string>
<string name="video_loading">Loading video...</string>
<string name="video_error">Video loading failed</string>
<!-- Alarm Log -->
<string name="alarm_log_title">Alarm Log</string>
<string name="alarm_detail">Alarm Detail</string>
<string name="alarm_time">Alarm Time</string>
<string name="alarm_type">Alarm Type</string>
<string name="alarm_location">Location</string>
<string name="no_alarm_logs">No alarm records</string>
<string name="intrusion_detected">Intrusion Detected</string>
<string name="abnormal_sound">Abnormal Sound</string>
<string name="motion_detected">Motion Detected</string>
<!-- Message -->
<string name="standard_capture">Standard Capture</string>
<string name="alarm_log_title">Alarm Records</string>
<string name="message_title">Messages</string>
<string name="system_message">System</string>
<string name="alarm_message">Alarm</string>
<string name="device_message">Device</string>
<string name="no_messages">No messages</string>
<string name="mark_as_read">Mark as Read</string>
<string name="clear_all">Clear All</string>
<string name="message_detail">Message Detail</string>
<!-- Settings -->
<string name="settings_title">Settings</string>
<string name="function_settings">Function Settings</string>
<string name="basic_settings">Basic Settings</string>
<string name="voice_settings">Voice Settings</string>
<string name="water_gun_settings">Water Gun Settings</string>
<string name="water_gun_settings">Repulsion Settings</string>
<string name="detection_settings">Auto Mode</string>
<string name="notification_settings">Notification Settings</string>
<string name="language_settings">Language</string>
<string name="chinese">Chinese</string>
<string name="english">English</string>
<string name="linked_user">Linked user</string>
<string name="linked_user">Linked User</string>
<string name="user_details">User Details</string>
<string name="search">search</string>
<string name="search_preset_text">Search preset text</string>
<string name="add_linked_user">Add linked user</string>
<string name="unlink">unlink</string>
<string name="unlink_confirm_message">Unlink confirm message?</string>
<string name="add_linked_user">Add Linked User</string>
<string name="unlink">Unlink</string>
<string name="unlink_confirm_message">Are you sure to unlink this user from this device?</string>
<string name="wiFi_management">WiFi Management</string>
<string name="about">About</string>
<string name="version">Version</string>
<string name="logout">Logout</string>
<string name="logout_confirm">Are you sure you want to logout?</string>
<string name="logout_fail">Logout failed</string>
<string name="warning_voice_playback">Warning Voice Playback</string>
<string name="warning_voice_content">Warning Voice Content</string>
<string name="enter_warning_voice_content_system_will_generate_voice_automatically">Enter warning voice content, system will generate voice automatically.</string>
<string name="device_info">Device info</string>
<string name="device_model">Device model</string>
<string name="device_no">Device No</string>
<string name="fw_version">FW Version</string>
<string name="auto_arms_start_time">Auto arm start time</string>
<string name="auto_arms_end_time">Auto arm End time</string>
<string name="enter_warning_voice_content_system_will_generate_voice_automatically">Please enter warning voice content, system will generate voice automatically</string>
<string name="strike_interval">Repulsion Interval</string>
<string name="switch_container">Switch Container</string>
<string name="strike_position">Repulsion Position</string>
<string name="click_model_select_strike_pos">Please click on the body model below to select the approximate repulsion position</string>
<string name="confirm_modify_strike_pos">Confirm to change to this repulsion position?</string>
<string name="submit_failed">Submit failed</string>
<string name="container_1">Container 1</string>
<string name="container_2">Container 2</string>
<string name="container_3">Container 3</string>
<string name="container_4">Container 4</string>
<string name="auto_mode_title">Auto Mode</string>
<string name="full_auto_mode">Full Auto Mode</string>
<string name="semi_auto_mode">Semi-Auto Mode</string>
<string name="auto_defense_start_time">Auto Defense Start Time</string>
<string name="auto_defense_end_time">Auto Defense End Time</string>
<string name="auto_mode_notice">Auto Mode Notice</string>
<string name="know_and_agree">Understand and Agree</string>
<string name="auto_mode_notice_content">After enabling full auto mode, the device/system will operate autonomously and execute preset procedures automatically without manual intervention. During operation, do not randomly power off, force restart, block sensing components, or disassemble the device to avoid program abnormalities and operation interruption. Full auto mode is only suitable for standard normal scenarios. For special working conditions, abnormal environments, or temporary special needs, please manually switch to manual mode. The system will automatically identify normal status and adaptively adjust. If alarms, lag, or abnormal prompts occur, immediately pause full auto mode and restart after troubleshooting. It is strictly prohibited to place debris or dangerous items within the full auto operation range. Keep the working area clean and unobstructed to prevent device collision, jamming, or safety accidents. Regular inspection of device status, battery, network, and hardware wear is required to ensure stable long-term operation of full auto mode. Minors and unrelated persons are prohibited from touching or operating the full auto mode switch and settings panel to prevent misoperation causing faults and risks.</string>
<string name="device_info">Device Info</string>
<string name="device_model">Device Model</string>
<string name="auto_arms_start_time">Auto Armed Start Time</string>
<string name="auto_arms_end_time">Auto Armed End Time</string>
<string name="please_select_at_least_one_user">Please select at least one user</string>
<string name="selected_users_voice">%d user(s) selected, voice</string>
<!-- Personal Profile -->
<string name="personal_info">Personal Info</string>
<string name="avatar">Avatar</string>
<string name="gender">Gender</string>
<string name="male">Male</string>
<string name="female">Female</string>
<string name="birthday">Birthday</string>
<string name="address">Address</string>
<string name="enter_address">Enter address</string>
<!-- Scan -->
<string name="scan_title">Scan QR Code</string>
<string name="scan_hint">Align QR code within frame to scan</string>
<string name="scan_failed">Scan failed, please try again</string>
<!-- WiFi -->
<string name="wifi_list">WiFi List</string>
<string name="wifi_password">WiFi Password</string>
<string name="enter_wifi_password">Enter WiFi password</string>
<string name="connect">Connect</string>
<string name="connecting">Connecting...</string>
<string name="wifi_connection_failed">WiFi Connection failed</string>
<string name="wifi_connection_success">WiFi Connected successfully</string>
<string name="enter_wifi_password">Please enter WiFi password</string>
<string name="connect_device">Connect Device</string>
<string name="connection_success">Connected successfully</string>
<string name="bluetooth_keep_enabled">Please keep Bluetooth on and move your phone closer to the device</string>
<string name="transmit_info_to_device">Transmitting information to the device</string>
<string name="device_connect_network">Device connecting to the network</string>
<string name="device_connect_near_router">Please move the device closer to the router while connecting to the network</string>
<string name="keep_device_near_router">Please move the device closer to the router</string>
<string name="use_smart_defense_app">Scan code to join via smart defense management APP</string>
<string name="cancel_invite">Cancel Invitation</string>
<string name="regenerate">Regenerate</string>
<string name="generate_invite_code">Generate Invite Code</string>
<!-- Dialog -->
<string name="connection_success">Connection successful</string>
<string name="bluetooth_keep_enabled">Please keep phone Bluetooth on and keep phone close to the device</string>
<string name="transmit_info_to_device">Transmit info to device</string>
<string name="device_connect_network">Device connecting to network</string>
<string name="device_connect_near_router">Please keep device close to the router</string>
<string name="keep_device_near_router">Please keep device close to the router</string>
<string name="tip">Tip</string>
<string name="download_alarm_video_confirm">Confirm to download this intrusion alarm video?</string>
<string name="delete_alarm_video_confirm">Confirm to delete this intrusion alarm video?</string>
<string name="download_alarm_photo_confirm">Confirm to download this intrusion alarm photo?</string>
<string name="delete_alarm_photo_confirm">Confirm to delete this intrusion alarm photo?</string>
<string name="download_start">Start Download</string>
<string name="download_alarm_video_confirm">Confirm download this intrusion alarm video?</string>
<string name="delete_alarm_video_confirm">Confirm delete this intrusion alarm video?</string>
<string name="download_alarm_photo_confirm">Confirm download this intrusion alarm photo?</string>
<string name="delete_alarm_photo_confirm">Confirm delete this intrusion alarm photo?</string>
<string name="download_start">Download started</string>
<string name="delete_success">Deleted successfully</string>
<string name="please_wait">Please wait...</string>
<string name="operation_success">Operation successful</string>
<string name="operation_failed">Operation failed</string>
<string name="network_unavailable">Network unavailable</string>
<string name="check_network">Please check your network connection</string>
<string name="confirm_delete_user">Are you sure to delete this user?</string>
<!-- Voice Recognition Mode -->
<string name="confirm_delete_user">Confirm delete this user?</string>
<string name="recognition_mode">Recognition Mode</string>
<string name="mode_standard">Standard Mode</string>
<string name="mode_strict">Strict Mode</string>
<string name="mode_custom">Custom Mode</string>
<!-- Time -->
<string name="today">Today</string>
<string name="yesterday">Yesterday</string>
<string name="this_week">This Week</string>
<string name="this_month">This Month</string>
<!-- Placeholder -->
<string name="hello_blank_fragment">Hello blank fragment</string>
<!-- Additional missing strings -->
<string name="scan_device_qr">Please scan the QR code on the device or manual</string>
<string name="workshop">Workshop</string>
<string name="parents_home">Parents Home</string>
<string name="smart_hanging_defense">Smart Hanging Defense</string>
<string name="smart_hanging_defense">Smart Hanging Defense Device</string>
<string name="smart_defense_device">Smart Defense Device</string>
<string name="smart_rail_defense">Smart Rail Defense</string>
<string name="corridor_defense">Corridor Defense</string>
<string name="door_defense">Door Defense</string>
<string name="living_room_defense">Living Room Defense</string>
<string name="balcony_defense">Balcony Defense</string>
<string name="album">Album</string>
<string name="flashlight">Flashlight</string>
<string name="my_family_home">My Family</string>
<string name="device">Device</string>
<string name="smart_device_a1">Smart Defense Device A1</string>
<string name="voice">Voice</string>
<string name="light">Light</string>
<string name="control">Control</string>
<string name="seconds">%ds</string>
<string name="register">Register</string>
<string name="register_user">Register User</string>
<string name="enter_platform">Enter Platform</string>
<string name="personal_info">Personal Info</string>
<string name="nickname">Nickname</string>
<string name="nickname_hint">Enter nickname</string>
<string name="nickname_hint">This is nickname</string>
<string name="bind_phone">Bind Phone</string>
<string name="face_recognition">Face Recognition</string>
<string name="face_verification">Face Verification</string>
<string name="face_security_notice">Only used to create defense mechanisms and prevent theft, ensuring your security</string>
<string name="follow_instructions">Please follow the instructions to complete face verification</string>
<string name="standard_capture">Standard Capture</string>
<string name="incomplete_capture">Incomplete Capture</string>
<string name="insufficient_light">Insufficient Light</string>
<string name="face_security_notice">Only used for creating defense mechanism and anti-theft, ensuring your safety</string>
<string name="follow_instructions">Please follow instructions to complete face verification</string>
<string name="incomplete_capture">Incomplete capture</string>
<string name="insufficient_light">Insufficient light</string>
<string name="start_verification">Start Verification</string>
<string name="select_wifi">Select Wi-Fi</string>
<string name="enter_wifi_name">Enter Wi-Fi name</string>
<string name="enter_wifi_name">Please enter WiFi name</string>
<string name="next_step">Next</string>
<string name="set_name">Set Name</string>
<string name="enter_device_name">Enter device name</string>
<string name="enter_device_name">Please enter device name</string>
<string name="complete">Complete</string>
<string name="alarm_log">Alarm Log</string>
<string name="cancel">Cancel</string>
<string name="confirm">Confirm</string>
<string name="bluetooth_recognition">Bluetooth Recognition</string>
<string name="bluetooth_face_recognition">Bluetooth + Face Recognition</string>
<string name="message_list">Messages</string>
<string name="face_collection">Face Collection</string>
<string name="face_in_circle">Please align your face within the circular area</string>
<string name="face_in_circle">Please place your face within the circle</string>
<string name="collect_face">Collect Face</string>
<string name="login_password">Login Password</string>
<string name="input_new_op_pwd">Enter new operation password</string>
<string name="enter_old_password">Enter old password</string>
<string name="confirm_reset_password">Confirm Reset Password</string>
<string name="retrieve_password">Retrieve Password</string>
<string name="input_new_op_pwd">Please enter new operation password</string>
<string name="operation_password">Operation Password</string>
<string name="enter_phone_number">Enter phone number</string>
<string name="enter_verification_code">Enter verification code</string>
<string name="send_code">Send Code</string>
<string name="resend_code">Resend</string>
<string name="enter_new_password">Enter new password</string>
<string name="enter_password_again">Enter password again</string>
<string name="enter_phone_number">Please enter phone number</string>
<string name="enter_verification_code">Please enter verification code</string>
<string name="resend_code">Resend Code</string>
<string name="enter_new_password">Please enter new password</string>
<string name="enter_password_again">Please enter new password again</string>
<string name="determine">Confirm</string>
<string name="id_number">ID Number</string>
<string name="enter_id_number">Enter ID number</string>
<string name="hint_input_correct_id_card">Please enter a valid ID card number</string>
<string name="invite_code_optional">Invite Code (Optional)</string>
<string name="enter_invite_code">Enter invite code</string>
<string name="enter_id_number">Please enter ID number</string>
<string name="hint_input_correct_id_card">Please enter correct ID number</string>
<string name="invite_code_optional">Invite code (optional)</string>
<string name="scan_add">Scan to Add</string>
<string name="wifi">WiFi</string>
<string name="face_login">Face Login</string>
<string name="recording_permission_needed">Recording permission is required for control features</string>
<string name="video_ready">Video ready</string>
<string name="playback_complete">Playback complete</string>
<string name="cannot_load_video">Cannot load video</string>
<string name="view_alarm_log">View alarm log</string>
<string name="paused">Paused</string>
<string name="resume_playback">Resume playback</string>
<string name="entered_fullscreen">Entered fullscreen</string>
<string name="exited_fullscreen">Exited fullscreen</string>
<string name="sound_on">Sound on</string>
<string name="sound_off">Sound off</string>
<string name="download">Download</string>
<string name="light_on">Light on</string>
<string name="light_off">Light off</string>
<string name="start_control_mode">Start control mode</string>
<string name="stop_control_mode">Stop control mode</string>
<string name="recording_start_failed">Recording start failed</string>
<string name="video_error_format">Playback error: %1$d, %2$d</string>
<string name="buffering_format">Buffering: %d%%</string>
<string name="camera_not_ready">Camera not ready</string>
<string name="cannot_open_camera">Cannot open camera</string>
<string name="camera_permission_needed">Camera permission is required</string>
<string name="permission_denied">Camera and storage permissions are required</string>
<string name="camera_permission_denied">Camera permission is required</string>
<string name="storage_permission_denied">Storage permission is required</string>
<string name="write_storage_permission_denied">Write storage permission is required</string>
<string name="take_photo_failed">Failed to take photo</string>
<string name="get_file_failed">Failed to get file</string>
<string name="create_file_failed">Failed to create file</string>
<string name="uploading">Uploading...</string>
<string name="upload_failed">Upload failed</string>
<string name="image_selected">Image selected</string>
<string name="permission_denied">Camera and storage permissions required</string>
<string name="please_try_later">Please try again later</string>
<string name="phone_format_invalid">Invalid phone number format</string>
<string name="enter_verification_code_pls">Please enter verification code</string>
<string name="get_code_first">Please get verification code first</string>
<string name="verification_code_error">Verification code error</string>
<string name="password_cannot_empty">Password cannot be empty</string>
<string name="password_letter_digit">Password must contain both letters and digits</string>
<string name="password_letter_digit">Password must contain both letters and numbers</string>
<string name="pwd_length_tip">Password must be %1$d-%2$d characters</string>
<string name="password_mismatch">Passwords do not match</string>
<string name="redirect_face_page">Redirecting to face page</string>
<string name="redirect_register_page">Redirecting to register page</string>
<string name="forgot_password_page">Forgot password page</string>
<string name="redirect_face_recognition_page">Redirecting to face recognition page</string>
<string name="camera_permission_face_verify">Camera permission is required for face verification</string>
<string name="intrusion_alarm">Intrusion alarm</string>
<string name="device_fault">Device fault</string>
<string name="device_offline">Device offline</string>
<string name="renewal_reminder">Renewal reminder</string>
<string name="someone_invaded">Someone invaded</string>
<string name="go_check">Go check</string>
<string name="intrusion_alarm">Intrusion Alarm</string>
<string name="someone_invaded">Intrusion Detected</string>
<string name="go_check">Check it</string>
<string name="notice">Notice</string>
<string name="got_it">Got it</string>
<string name="strike_distance">Strike Distance</string>
<string name="enter_strike_distance">Enter strike distance</string>
<string name="strike_interval">Strike Interval</string>
<string name="switch_container">Switch Container</string>
<string name="strike_position">Strike Position</string>
<string name="click_model_select_strike_pos">Please click the human model below to select the approximate strike position</string>
<string name="confirm_modify_strike_pos">Confirm to modify to this strike position?</string>
<string name="submit_success">Submitted successfully</string>
<string name="submit_failed">Submission failed</string>
<string name="container_1">Container 1</string>
<string name="container_2">Container 2</string>
<string name="container_3">Container 3</string>
<string name="container_4">Container 4</string>
<string name="face_detection_failed">Face detection failed</string>
<string name="nickname_default">My nickname</string>
<!-- Auto Mode -->
<string name="auto_mode_title">Auto Mode</string>
<string name="full_auto_mode">Full Auto Mode</string>
<string name="semi_auto_mode">Semi-Auto Mode</string>
<string name="auto_defense_start_time">Auto Arm Start Time</string>
<string name="auto_defense_end_time">Auto Arm End Time</string>
<string name="auto_mode_notice">Auto Mode Instructions</string>
<string name="know_and_agree">I have read and agree</string>
<string name="auto_mode_notice_content">After enabling Full Auto Mode, the device/system will operate independently and automatically execute preset procedures without manual intervention.During operation, do not arbitrarily power off, force a restart, block sensor components or disassemble the device privately, so as to avoid program abnormalities and operation interruption. Full Auto Mode is only applicable to standard and conventional scenarios. Under special working conditions, abnormal environments or temporary special requirements, please manually switch to manual mode.The system will automatically identify normal status and make adaptive adjustments. In case of faults such as alarms, stuttering or abnormal prompts, immediately pause Full Auto Mode, troubleshoot the problem before restarting.Do not place sundries or dangerous goods within the automatic operation range. Keep the working area clean and unobstructed to prevent equipment collision, jamming and safety accidents.Operation data and execution records of Full Auto Mode will be automatically saved. Do not arbitrarily delete or tamper with system configuration parameters to avoid operational logic disorder.Check the device status, power level, network connection and hardware loss regularly on a daily basis to ensure stable and long-term operation of Full Auto Mode. Minors and irrelevant personnel are prohibited from touching or operating the Full Auto Mode switch and setting panel to prevent malfunctions and risks caused by misoperation.</string>
<string name="modify_phone">Modify Phone</string>
<string name="invalid_phone">Invalid phone number format</string>
<string name="verify_and_bind">Verify and Bind</string>
<string name="strike_distance">Repulsion Distance</string>
<string name="enter_strike_distance">Please enter repulsion distance</string>
<string name="nickname_default">This is my nickname</string>
<string name="device_running_days">Device has been running for %d days, please maintain in time</string>
<string name="device_offline_message">Device is offline, please check the WiFi and power connection</string>
<string name="countdown_retry">%ds retry</string>
<string name="sms_login_success">SMS login successful\nPhone: %1$s\nCode: %2$s</string>
<string name="password_login_success">Password login successful\nPhone: %1$s\nPassword: %2$s</string>
<string name="device_offline_message">Device is offline, please check device WiFi and power</string>
<string name="countdown_retry">Retry in %d seconds</string>
<string name="code_sent_to">Verification code sent to %s</string>
<string name="sms_login_success_title">Login successful</string>
<string name="password_login_success_title">Login successful</string>
<string name="code_already_sent">Verification code sent</string>
<string name="sms_login_success_title">Login Successful</string>
<string name="password_login_success_title">Login Successful</string>
<string name="modify_phone">Modify Phone Number</string>
<string name="invalid_phone">Invalid phone number</string>
<string name="verify_and_bind">Verify and Bind</string>
<string name="operation_password_switch">Operation Password Switch</string>
<string name="hint_operation_password">Please enter a 6-digit numeric password</string>
<string name="input_op_pwd">Enter operation password</string>
<string name="hint_confirm_password">Please confirm your password</string>
<string name="operation_password_enabled">Operation password enabled</string>
<string name="operation_password_disabled">Operation password disabled</string>
<string name="password_must_be_6_digits">Password must be 6 digits</string>
<string name="please_confirm_password">Please confirm your password</string>
<string name="password_error">Password error</string>
<string name="operation_password_not_set">Operation password not set</string>
<string name="get_verification_code">Get Verification Code</string>
<string name="hint_phone_zh">Please enter your phone number</string>
<string name="input_op_pwd">Please enter operation password</string>
<string name="confirm_reset_password">Confirm password reset</string>
<string name="hint_phone_zh">Please enter phone number</string>
<string name="phone_tip">Please enter valid 11-digit phone number</string>
<string name="hint_code_zh">Please enter verification code</string>
<string name="hint_new_password_zh">Please enter new password</string>
<string name="hint_confirm_password_zh">Please re-enter new password</string>
<string name="hint_confirm_password_zh">Please enter new password again</string>
<string name="enter_old_password">Please enter old password</string>
<string name="password_error">Password error</string>
<string name="confirm_reset_password_btn">Confirm Reset</string>
<string name="user_status">User Status</string>
<string name="contact_name">Contact Name</string>
<string name="id_card">ID Card Number</string>
<string name="id_card">ID Number</string>
<string name="mac_address">MAC Address</string>
<string name="invite_code">Invite Code</string>
<string name="invite_code_expire_time">Invite Code Expiry</string>
<string name="qr_code_invite">Scan QR Code to Invite</string>
<string name="invite_code_tips">Invite QR code is valid for 24 hours; can only be accepted once; manual revocation or regenerating will invalidate the previous invite QR code</string>
<string name="qr_code_invite">QR Code Invite</string>
<string name="invite_code_tips">QR code valid for 24 hours, can only be accepted once; manual cancellation or regeneration will invalidate the original QR code</string>
<string name="other_invite_methods">Other Invite Methods</string>
<string name="wechat_friend">WeChat Friend</string>
<string name="expire_24h">24h</string>
<string name="expire_7d">7d</string>
<string name="qr_code_expired">QR code has expired, please regenerate</string>
<string name="generate_failed">Generate failed</string>
<string name="revoke_failed">Revoke failed</string>
<string name="expire_24h">24 hours</string>
<string name="expire_7d">7 days</string>
<string name="qr_code_expired">QR code expired, please regenerate</string>
<string name="share_failed">Share failed</string>
<string name="share_poster_tips">The invitation QR code is valid within %1$s and can only be accepted once</string>
<string name="invite_friend">Come and join me! Scan the QR code to download the app</string>
<string name="share_poster_tips">QR code valid for %1$s, share can only be accepted once</string>
<string name="invite_friend">Join me! Scan to download APP</string>
<string name="share_to">Share to</string>
<string name="no_share_app">No sharing application available</string>
<!-- WiFi Management -->
<string name="no_share_app">No share apps available</string>
<string name="use_smart_defense_app">Use Smart Defense Management APP to scan and join</string>
<string name="cancel_invite">Cancel Invite</string>
<string name="regenerate">Regenerate</string>
<string name="device_no">Device Number</string>
<string name="fw_version">Firmware Version</string>
<string name="generate_invite_code">Generate Invite Code</string>
<string name="not_set">Not set</string>
<string name="device_id_empty">Device ID is empty</string>
<string name="modify_success">Modified successfully</string>
@@ -476,35 +244,40 @@
<string name="network_request_failed">Network request failed</string>
<string name="code_sent_success">Verification code sent successfully</string>
<string name="code_send_failed">Failed to send verification code</string>
<!-- Login Password Reset -->
<string name="processing">Processing...</string>
<string name="old_password_error">Old password is incorrect</string>
<string name="password_reset_success">Password reset successful</string>
<string name="password_reset_failed">Password reset failed</string>
<string name="user_not_login">User not logged in</string>
<string name="phone_number_empty">Phone number not found</string>
<string name="operation_fail">Operation failed</string>
<string name="get_user_info_fail">Failed to get user info</string>
<string name="take_photo">Take Photo</string>
<string name="select_from_album">Select from Album</string>
<string name="select_date">Select Date</string>
<string name="select_time">Select Time</string>
<string name="max_add_11_users">A maximum of 11 users can be added</string>
<string name="camera_permission_denied">Camera permission required</string>
<string name="storage_permission_denied">Storage permission required</string>
<string name="write_storage_permission_denied">Write storage permission required</string>
<string name="take_photo_failed">Failed to take photo</string>
<string name="get_file_failed">Failed to get file</string>
<string name="create_file_failed">Failed to create file</string>
<string name="uploading">Uploading...</string>
<string name="upload_failed">Upload failed</string>
<string name="max_add_11_users">Maximum 11 users can be added</string>
<string name="linked_user_phone_label">Phone: %s</string>
<string name="linked_user_idcard_label">ID: %s</string>
<!-- Direction Control -->
<string name="direction_up">Move Up</string>
<string name="direction_down">Move Down</string>
<string name="direction_left">Move Left</string>
<string name="direction_right">Move Right</string>
<!-- Operation Password -->
<string name="please_enable_operation_password_first">Please enable operation password first</string>
<string name="disable_operation_password_tip">Are you sure you want to disable operation password?</string>
<string name="verify_failed">Verification failed</string>
<string name="linked_user_idcard_label">ID Number: %s</string>
<string name="password_update_success">Password updated successfully</string>
<string name="password_update_failed">Failed to update password</string>
<string name="password_update_failed">Password update failed</string>
<string name="update_failed">Update failed</string>
<string name="check_update">Check Update</string>
<string name="clear_cache">Clear Cache</string>
<string name="cancel_account">Cancel Account</string>
<string name="tip_contact_us">If you have questions, please contact us:</string>
<string name="cancel_account_warning_text">Account cannot be recovered after cancellation, please proceed with caution</string>
<string name="current_account">Current Account</string>
<string name="cancel_account_btn">Cancel Account</string>
<string name="confirm_cancel_account_user_account">Confirm to cancel this user account?</string>
<string name="no_more_data">No more data</string>
<string name="refresh_hint_pull">Pull down to refresh</string>
<string name="refresh_hint_release">Release to refresh</string>
<string name="refresh_hint_refreshing">Refreshing...</string>
<string name="my_family">My Family</string>
<string name="need_camera_permission_for_face_verify">Camera permission is required for face verification</string>
</resources>