Compare commits

...

50 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
02fc928f20 统一验证码按钮的效果 2026-06-30 14:23:47 +08:00
b67de551ba 资源 2026-06-30 11:44:46 +08:00
e28e167085 修改登录页面发送验证码按钮动态效果 2026-06-30 11:44:19 +08:00
8cd802a826 优化首页背景图和返回按钮虚化问题 2026-06-29 17:15:52 +08:00
7e14dd379a 修改添加设备返回页面,通知刷新首页列表,优化首页列表边距 2026-06-29 16:12:33 +08:00
4a584fa7d7 优化设置页面到操作密码传值 2026-06-29 15:20:47 +08:00
a32c165424 优化设置页面到水枪设置的操作密码传值,关闭lint这个检查 2026-06-29 11:09:23 +08:00
9177c00a3c 修改app名字 2026-06-29 09:40:46 +08:00
e937ce207c 401跳转登录界面 2026-06-26 11:48:26 +08:00
4462ba5ba2 个人碎片优化 2026-06-25 10:43:51 +08:00
0672d762c9 生成推荐吗底部按钮优化 2026-06-24 10:45:00 +08:00
efbba44250 时间戳 2026-06-23 14:08:36 +08:00
ef4dfb0307 获取邀请人信息和扫一扫加入xxx家庭 2026-06-22 16:50:18 +08:00
086f1226a1 优化推荐吗页面按钮 2026-06-21 09:12:33 +08:00
5cf30411c1 优化某某某家庭页面 2026-06-20 16:27:19 +08:00
76e4949dd7 移除页面无用功能 2026-06-20 16:22:07 +08:00
4a16010bd6 某某某家庭 2026-06-20 16:19:59 +08:00
b55d9a4b38 修改全局按钮背景阴影,生成二维码和撤销接口调用 2026-06-20 16:11:09 +08:00
6a1cfaeda9 修改按钮 2026-06-20 15:03:29 +08:00
f57975ef1e 修改base_url地址,斌一起当前版本适配 2026-06-19 08:40:56 +08:00
548d501a44 列表网络错误显示 2026-06-18 09:02:21 +08:00
a997190480 优化列表图片展示 2026-06-16 15:59:11 +08:00
631f9831c3 优化列表请求失败和没有网络的显示状态,优化渐变颜色展示 2026-06-16 15:40:20 +08:00
b2c3ed9015 退出登录 2026-06-15 16:02:24 +08:00
6bd31327db 优化家庭状态 2026-06-15 14:06:13 +08:00
7e362038b3 优化列表 2026-06-15 10:19:42 +08:00
142 changed files with 4512 additions and 1164 deletions

View File

@@ -4,7 +4,7 @@
<selectionStates>
<SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2026-05-18T09:18:19.023586Z">
<DropdownSelection timestamp="2026-06-22T08:46:41.014465400Z">
<Target type="DEFAULT_BOOT">
<handle>
<DeviceId pluginId="PhysicalDevice" identifier="serial=80bece4" />

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-tsmarker7700090853507788345.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-tsmarker12680940025563006602.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-tsmarker3894271637508823813.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-tsmarker16051875768763165132.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-tsmarker3580585840179793220.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-tsmarker11763075351170172587.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-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

@@ -36,6 +36,11 @@ android {
buildFeatures {
viewBinding=true
}
lint {
disable += "ExpiredTargetSdkVersion"
checkReleaseBuilds = false
abortOnError = false
}
}
dependencies {
@@ -69,14 +74,8 @@ dependencies {
// Kotlin 协程
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
// CameraX 用于相机预览
implementation("androidx.camera:camera-camera2:1.3.0")
implementation("androidx.camera:camera-lifecycle:1.3.0")
implementation("androidx.camera:camera-view:1.3.0")
// Google ML Kit 人脸检测
implementation("com.google.mlkit:face-detection:16.1.5")
// 图形绘制库(绘制人脸框)
implementation("com.github.bumptech.glide:glide:4.15.1")
//Glide 依赖
implementation("com.github.bumptech.glide:glide:4.16.0")
// ZXing 二维码扫描库
@@ -87,8 +86,6 @@ dependencies {
implementation("com.geyifeng.immersionbar:immersionbar-ktx:3.2.2")
implementation("com.wdullaer:materialdatetimepicker:4.2.3") // 经典稳定
implementation("androidx.core:core-ktx:1.12.0")
// Android-PickerView 时间选择器
implementation("com.contrarywind:Android-PickerView:4.1.9")
//二维码三方库
implementation("com.github.maning0303:MNMLKitScanner:V1.0.4")
implementation("com.google.mlkit:barcode-scanning:17.0.2")

View File

@@ -24,9 +24,16 @@
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/logo"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
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">
@@ -42,6 +49,9 @@
<activity
android:name=".personal.InvitationCodeActivity"
android:exported="false" />
<activity
android:name=".personal.JoinFamilyInviteActivity"
android:exported="false" />
<activity
android:name=".liveVideo.StrikePositionActivity"
android:exported="false" />
@@ -118,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

@@ -18,6 +18,8 @@ class AddDeviceAdapter(
private val onItemClick: (Device) -> Unit
) : RecyclerView.Adapter<AddDeviceAdapter.ViewHolder>() {
private val defaultIcons = listOf(R.mipmap.img1, R.mipmap.img2, R.mipmap.img3)
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val ivIcon: ImageView = itemView.findViewById(R.id.ivDeviceIcon2)
val tvName: TextView = itemView.findViewById(R.id.tvDeviceName2)
@@ -47,7 +49,9 @@ class AddDeviceAdapter(
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val device = devices[position]
holder.tvName.text = device.name
holder.ivIcon.setImageResource(R.mipmap.img)
val iconResId = defaultIcons[position % defaultIcons.size]
holder.ivIcon.setImageResource(iconResId)
// 点击事件
holder.itemView.setOnClickListener { onItemClick(device) }

View File

@@ -3,25 +3,18 @@ package com.ckkj.defense_device.adapter
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.ckkj.defense_device.R
import com.ckkj.defense_device.bean.DeviceBean
import com.ckkj.defense_device.databinding.ItemDeviceBinding
data class DeviceUi(
val name: String,
val status: String,
val deviceId: String = ""
) {
val isOnline: Boolean
get() = status == "1"
}
class DeviceAdapter(
private val onItemClick: (DeviceUi) -> Unit = {}
private val onItemClick: (DeviceBean) -> Unit = {}
) : RecyclerView.Adapter<DeviceAdapter.DeviceViewHolder>() {
private val items = mutableListOf<DeviceUi>()
private val items = mutableListOf<DeviceBean>()
fun submitList(deviceList: List<DeviceUi>) {
fun submitList(deviceList: List<DeviceBean>) {
items.clear()
items.addAll(deviceList)
notifyDataSetChanged()
@@ -33,15 +26,25 @@ class DeviceAdapter(
}
override fun onBindViewHolder(holder: DeviceViewHolder, position: Int) {
holder.bind(items[position])
holder.bind(items[position], position)
}
override fun getItemCount(): Int = items.size
private val defaultIcons = listOf(R.mipmap.img1, R.mipmap.img2, R.mipmap.img3)
inner class DeviceViewHolder(private val binding: ItemDeviceBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: DeviceUi) {
fun bind(item: DeviceBean, position: Int) {
if (item.deviceImg.isNullOrEmpty()) {
val iconResId = defaultIcons[position % defaultIcons.size]
binding.ivDeviceIcon.setImageResource(iconResId)
} else {
Glide.with(binding.root.context)
.load(item.deviceImg)
.into(binding.ivDeviceIcon)
}
binding.tvDeviceType.text = item.name
when (item.status) {
"1" -> {

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

@@ -4,7 +4,7 @@ package com.ckkj.defense_device.bean
* 统一返回数据基类(根据你的后端实际字段调整)
*/
data class BaseInfo(
val message: String? = null,
val msg: String? = null,
val code: Int = 0,
val data: Any? = null
)

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,//识别模式
@@ -27,4 +27,13 @@ data class DeviceBean(
val wifiPassword: String? = null,//WiFi密码
val workMode: String? = null,//工作模式 0-自动 1-半自动
val optionPassword: String? = null, //操作密码
)
) {
val deviceId: String
get() = id ?: ""
val name: String
get() = deviceName ?: ""
val isOnline: Boolean
get() = status == "1"
}

View File

@@ -0,0 +1,18 @@
package com.ckkj.defense_device.bean
data class InviterInfoBean(
val nickName: String? = null,
val idCard: String? = null,
val phonenumber: String? = null,
val avatar: String? = null,
val id: String? = null,
val parentId: String? = null,
val userId: String? = null,
val mac: String? = null,
val status: String? = null,
) {
fun resolveParentId(): String? =
parentId?.takeIf { it.isNotBlank() }
?: userId?.takeIf { it.isNotBlank() }
?: id?.takeIf { it.isNotBlank() }
}

View File

@@ -0,0 +1,7 @@
package com.ckkj.defense_device.bean
data class JoinTheFamilyBean(
val parentId: String,
val inviteCode: String,
)

View File

@@ -0,0 +1,8 @@
package com.ckkj.defense_device.bean
data class RecommendationCodeBean(
val inviteCode: String? = null,
val code: String? = null,
val expireTime: Long? = null,
val expire: Long? = null
)

View File

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

View File

@@ -0,0 +1,8 @@
package com.ckkj.defense_device.bean
data class UpdateFamilyUserRequest(
val id: String,
val optionPassword: String,
val optionStatus: String,
val status: String,
)

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

@@ -1,5 +1,6 @@
package com.ckkj.defense_device.home
import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import com.ckkj.defense_device.R
@@ -7,6 +8,10 @@ import com.ckkj.defense_device.base.BaseActivity
import com.ckkj.defense_device.databinding.HomeActivityBinding
class HomeActivity : BaseActivity() {
companion object {
const val EXTRA_REFRESH = "extra_refresh"
}
// ViewBinding 核心对象
private lateinit var binding: HomeActivityBinding
@@ -49,6 +54,28 @@ class HomeActivity : BaseActivity() {
binding.ivProfile.setImageResource(R.mipmap.profile_checked)
binding.tvProfile.setTextColor(resources.getColor(R.color.ic_selected))
}
handleRefreshIntent(intent)
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (intent != null) {
setIntent(intent)
handleRefreshIntent(intent)
}
}
private fun handleRefreshIntent(intent: Intent) {
if (intent.getBooleanExtra(EXTRA_REFRESH, false)) {
intent.removeExtra(EXTRA_REFRESH)
switchFragment(homeMenuFragment)
binding.ivHome.setImageResource(R.mipmap.home_checked)
binding.tvHome.setTextColor(resources.getColor(R.color.ic_selected))
binding.ivProfile.setImageResource(R.mipmap.profile)
binding.tvProfile.setTextColor(resources.getColor(R.color.ic_normal))
homeMenuFragment.refreshHomeData()
}
}
/**

View File

@@ -7,7 +7,7 @@ import android.content.pm.PackageManager
import android.graphics.Color
import android.os.Build
import android.graphics.Rect
import android.graphics.drawable.GradientDrawable
import android.os.Bundle
import android.util.Log
import android.util.TypedValue
@@ -21,15 +21,17 @@ 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.DeviceUi
import com.ckkj.defense_device.adapter.FamilyAdapter
import com.ckkj.defense_device.bean.DeviceBean
import com.ckkj.defense_device.bean.FamilyBean
import com.ckkj.defense_device.bean.SwitchFamilyRequest
import com.ckkj.defense_device.bean.SelectInviterInfoBean
import com.ckkj.defense_device.databinding.HomeMenuFragmentBinding
import com.ckkj.defense_device.liveVideo.LiveVideoActivity
import com.ckkj.defense_device.personal.JoinFamilyInviteActivity
import com.ckkj.defense_device.utils.DeviceIdEvent
import com.ckkj.defense_device.utils.DeviceUpdateEvent
import com.ckkj.defense_device.utils.FamilySwitchEvent
@@ -37,6 +39,8 @@ import com.ckkj.defense_device.utils.MessageStatusManager
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
@@ -45,6 +49,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONObject
//主页碎片
class HomeMenuFragment : Fragment() {
// ViewBinding
@@ -59,18 +64,21 @@ 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()
) { permissions ->
val cameraGranted = permissions[Manifest.permission.CAMERA] ?: false
val storageGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissions[Manifest.permission.READ_MEDIA_IMAGES] ?: false
} else {
permissions[Manifest.permission.READ_EXTERNAL_STORAGE] ?: false
}
if (cameraGranted && storageGranted) {
startScan()
} else {
@@ -83,7 +91,7 @@ class HomeMenuFragment : Fragment() {
requireContext(),
Manifest.permission.CAMERA
)
val storagePermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ContextCompat.checkSelfPermission(
requireContext(),
@@ -96,7 +104,7 @@ class HomeMenuFragment : Fragment() {
)
}
if (cameraPermission == PackageManager.PERMISSION_GRANTED &&
if (cameraPermission == PackageManager.PERMISSION_GRANTED &&
storagePermission == PackageManager.PERMISSION_GRANTED) {
startScan()
} else {
@@ -122,23 +130,86 @@ class HomeMenuFragment : Fragment() {
MNScanManager.RESULT_SUCCESS -> {
val result = data?.getStringArrayListExtra(MNScanManager.INTENT_KEY_RESULT_SUCCESS)
if (!result.isNullOrEmpty()) {
ToastUtils.showToast(requireContext(), "扫码成功: $result")
val inviteCode = parseInviteCode(result[0])
if (inviteCode.isNullOrEmpty()) {
Toast.makeText(requireContext(), "无效的邀请码", Toast.LENGTH_SHORT).show()
} else {
fetchInviterInfo(inviteCode)
}
} else {
ToastUtils.showToast(requireContext(), "扫码结果为空")
Toast.makeText(requireContext(), "扫码结果为空", Toast.LENGTH_SHORT).show()
}
}
MNScanManager.RESULT_FAIL -> {
val error = data?.getStringExtra(MNScanManager.INTENT_KEY_RESULT_ERROR)
ToastUtils.showToast(requireContext(), "扫码失败: $error")
Toast.makeText(requireContext(), "扫码失败: $error", Toast.LENGTH_SHORT).show()
}
MNScanManager.RESULT_CANCLE -> {
ToastUtils.showToast(requireContext(), "扫码取消")
Toast.makeText(requireContext(), "扫码取消", Toast.LENGTH_SHORT).show()
}
}
}
})
}
private fun parseInviteCode(scanResult: String): String? {
val trimmed = scanResult.trim()
if (trimmed.isEmpty()) return null
return try {
if (trimmed.startsWith("{")) {
JSONObject(trimmed).optString("code").takeIf { it.isNotEmpty() }
} else {
trimmed
}
} catch (e: Exception) {
trimmed
}
}
private fun fetchInviterInfo(inviteCode: String) {
val loadingDialog = LoadingDialog(requireActivity()).setMessage(this.getString(R.string.loading)).apply { show() }
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService()
.selectInviterInfo(SelectInviterInfoBean(code = inviteCode))
withContext(Dispatchers.Main) {
loadingDialog.dismiss()
if (response.code == 200 && response.data != null) {
val inviter = response.data
val parentId = inviter.resolveParentId()
if (parentId.isNullOrEmpty()) {
Toast.makeText(
requireContext(),
getString(R.string.network_error),
Toast.LENGTH_SHORT
).show()
return@withContext
}
val intent = Intent(requireContext(), JoinFamilyInviteActivity::class.java).apply {
putExtra(JoinFamilyInviteActivity.EXTRA_INVITE_CODE, inviteCode)
putExtra(JoinFamilyInviteActivity.EXTRA_NICK_NAME, inviter.nickName)
putExtra(JoinFamilyInviteActivity.EXTRA_AVATAR, inviter.avatar)
putExtra(JoinFamilyInviteActivity.EXTRA_PARENT_ID, parentId)
putExtra(JoinFamilyInviteActivity.EXTRA_USER_ID, inviter.userId)
}
startActivity(intent)
} else {
Toast.makeText(
requireContext(),
response.msg.ifBlank { getString(R.string.network_error) },
Toast.LENGTH_SHORT
).show()
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
loadingDialog.dismiss()
Toast.makeText(requireContext(), R.string.network_error, Toast.LENGTH_SHORT).show()
}
}
}
}
private val unreadCountChangeListener = object : MessageStatusManager.UnreadCountChangeListener {
override fun onUnreadCountChanged(count: Int) {
updateUnreadCountUI(count)
@@ -197,13 +268,8 @@ class HomeMenuFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// 为 LinearLayout 设置渐变背景
setGradientBackground(
view = binding.gradientLayout,
startColor = Color.parseColor("#EBDCED"),
endColor = Color.parseColor("#CAF4FF")
)
binding.message.setOnClickListener {
binding.gradientLayout.setBackgroundColor(Color.TRANSPARENT)
binding.message.setOnClickListener {
val intent = Intent(requireContext(), MessageActivity::class.java)
startActivity(intent)
}
@@ -236,9 +302,11 @@ class HomeMenuFragment : Fragment() {
)
}
binding.swipeRefreshLayout.setOnRefreshListener {
refreshDeviceList()
}
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) {
@@ -246,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()
}
}
})
@@ -285,6 +368,10 @@ class HomeMenuFragment : Fragment() {
}
}
fun refreshHomeData() {
fetchFamilyList()
}
private fun fetchFamilyList() {
CoroutineScope(Dispatchers.IO).launch {
try {
@@ -321,18 +408,22 @@ class HomeMenuFragment : Fragment() {
Log.d("HomeMenuFragment", "canViewDevice = $canViewDevice")
if (canViewDevice) {
binding.tvDeviceTitle.visibility = View.VISIBLE
binding.swipeRefreshLayout.visibility = View.VISIBLE
binding.rvDeviceList.visibility = View.VISIBLE
binding.message.visibility = View.VISIBLE
binding.addFamily.visibility = View.VISIBLE
binding.add.visibility = View.VISIBLE
binding.layoutNoPermission.visibility = View.GONE
binding.layoutNoNetwork.visibility = View.GONE
} else {
binding.tvDeviceTitle.visibility = View.GONE
binding.swipeRefreshLayout.visibility = View.GONE
binding.rvDeviceList.visibility = View.GONE
binding.message.visibility = View.GONE
binding.addFamily.visibility = View.GONE
binding.add.visibility = View.GONE
binding.layoutNoPermission.visibility = View.VISIBLE
binding.layoutNoNetwork.visibility = View.GONE
}
}
@@ -356,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()
@@ -370,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
@@ -390,32 +486,47 @@ class HomeMenuFragment : Fragment() {
hasMore = newDevices.size >= pageSize
}
Log.d("DeviceList", "deviceBeanList size = ${deviceBeanList.size}")
showDeviceContent()
updateDeviceList()
} else {
Toast.makeText(requireContext(), response.msg, Toast.LENGTH_SHORT).show()
noNetwork()
val errorMsg = response.msg.ifBlank { getString(R.string.network_error) }
Toast.makeText(requireContext(), errorMsg, Toast.LENGTH_SHORT).show()
if (isLoadMore) {
hasMore = false
}
}
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() {
@@ -445,26 +556,38 @@ class HomeMenuFragment : Fragment() {
}
}
private fun updateDeviceList() {
val deviceUiList = deviceBeanList.map {
DeviceUi(it.deviceName ?: "", it.status ?: "", it.id ?: "")
}
private fun showDeviceContent() {
binding.swipeRefreshLayout.visibility = View.VISIBLE
binding.rvDeviceList.visibility = View.VISIBLE
binding.layoutNoPermission.visibility = View.GONE
binding.layoutNoNetwork.visibility = View.GONE
}
if (deviceUiList.isEmpty()) {
private fun noNetwork() {
binding.swipeRefreshLayout.visibility = View.GONE
binding.rvDeviceList.visibility = View.GONE
binding.layoutNoPermission.visibility = View.GONE
binding.layoutNoNetwork.visibility = View.VISIBLE
}
private fun updateDeviceList() {
if (deviceBeanList.isEmpty()) {
deviceAdapter.submitList(listOf(
DeviceUi(getString(R.string.no_device), "0")
DeviceBean(deviceName = getString(R.string.no_device), status = "0")
))
} else {
deviceAdapter.submitList(deviceUiList)
deviceAdapter.submitList(deviceBeanList)
}
}
private fun onDeviceClicked(device: DeviceUi) {
private fun onDeviceClicked(device: DeviceBean) {
if (device.isOnline) {
DeviceIdEvent.setDeviceId(device.deviceId)
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(
@@ -481,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() {
@@ -501,19 +632,10 @@ class HomeMenuFragment : Fragment() {
}
}
private fun setGradientBackground(
view: View,
startColor: Int,
endColor: Int,
orientation: GradientDrawable.Orientation = GradientDrawable.Orientation.TL_BR
) {
val gradientDrawable = GradientDrawable(orientation, intArrayOf(startColor, endColor))
gradientDrawable.gradientType = GradientDrawable.LINEAR_GRADIENT
view.background = gradientDrawable
}
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,9 +44,18 @@ 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) {
@@ -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,20 +129,24 @@ 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()) {
showEmptyState()
noNetwork()
}
}
} finally {
isLoading = false
if (currentPage == 1) {
withContext(Dispatchers.Main) {
hideLoading()
binding.swipeRefreshLayout.finishRefresh()
}
}
}
}
}
@@ -141,16 +158,34 @@ 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.swipeRefreshLayout.visibility = View.GONE
binding.layoutNoNetwork.visibility = View.VISIBLE
binding.tvNoMoreData.visibility = View.GONE
}
private fun showNoMoreDataHint() {
binding.tvNoMoreData.visibility = View.VISIBLE
@@ -165,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,13 +16,16 @@ 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
import com.ckkj.defense_device.utils.UserinfoUtil
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
@@ -33,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()
@@ -48,10 +52,10 @@ class PersonalFragment : Fragment() {
.titleBar(binding.headerLayout)
.init()
Glide.with(this)
.load(R.mipmap.img)
.load("")
.circleCrop()
.into(binding.ivAvatar)
binding.headerLayout.setOnClickListener {
binding.ivAvatar.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService().getUserInfo()
@@ -100,27 +104,40 @@ class PersonalFragment : Fragment() {
}
// 退出登录点击事件
binding.btnLogout.setOnClickListener {
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService().logout()
withContext(Dispatchers.Main) {
if (response.code == 200) {
UserinfoUtil.clearUserInfo()
val intent = Intent(requireContext(), com.ckkj.defense_device.login.LoginActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
requireActivity().finish()
} else {
Toast.makeText(requireContext(), getString(R.string.logout_fail), Toast.LENGTH_SHORT).show()
val dialog = CustomDialogFragmentText()
.setTitle(getString(R.string.tip))
.setMessage(getString(R.string.logout_confirm))
.setShowInput(false)
.setNegativeText(getString(R.string.cancel))
.setPositiveText(getString(R.string.confirm))
.setOnConfirmListener {
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService().logout()
withContext(Dispatchers.Main) {
if (response.code == 200) {
UserinfoUtil.clearUserInfo()
val intent = Intent(requireContext(), com.ckkj.defense_device.login.LoginActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
requireActivity().finish()
} else {
Toast.makeText(requireContext(), getString(R.string.logout_fail), Toast.LENGTH_SHORT).show()
}
}
} catch (e: Exception) {
e.printStackTrace()
withContext(Dispatchers.Main) {
Toast.makeText(requireContext(), getString(R.string.logout_fail), Toast.LENGTH_SHORT).show()
}
}
}
} catch (e: Exception) {
e.printStackTrace()
withContext(Dispatchers.Main) {
Toast.makeText(requireContext(), getString(R.string.logout_fail), Toast.LENGTH_SHORT).show()
}
}
}
dialog.show(childFragmentManager, "logout_dialog")
}
binding.ivMenu.setOnClickListener {
val intent = Intent(requireContext(), SettingsMenusActivity::class.java)
startActivity(intent)
}
fetchCurrentFamily()
return binding.root
@@ -219,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()
@@ -229,6 +247,11 @@ class PersonalFragment : Fragment() {
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
withContext(Dispatchers.Main) {
loadingDialog?.dismiss()
loadingDialog = null
}
}
}
}
@@ -257,6 +280,7 @@ class PersonalFragment : Fragment() {
} else {
getString(R.string.member)
}
binding.tvRoleTag.visibility = View.VISIBLE
}
}

View File

@@ -60,7 +60,9 @@ class SetNameActivity : AppCompatActivity() {
withContext(Dispatchers.Main) {
if (result.code == 200) {
Toast.makeText(this@SetNameActivity, "设备添加成功", Toast.LENGTH_SHORT).show()
startActivity(Intent(this@SetNameActivity, LiveVideoActivity::class.java))
startActivity(Intent(this@SetNameActivity, HomeActivity::class.java).apply {
putExtra(HomeActivity.EXTRA_REFRESH, true)
})
finish()
} else {
Toast.makeText(this@SetNameActivity, result.msg ?: "添加失败", Toast.LENGTH_SHORT).show()

View File

@@ -3,6 +3,7 @@ package com.ckkj.defense_device.liveVideo
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
@@ -95,19 +96,33 @@ class AddLinkedUserActivity : AppCompatActivity() {
Toast.makeText(this@AddLinkedUserActivity, "暂无家庭成员数据", Toast.LENGTH_SHORT).show()
}
} else {
hide()
Toast.makeText(this@AddLinkedUserActivity, "请求失败: ${response.msg}", Toast.LENGTH_SHORT).show()
}
}
} catch (e: Exception) {
android.util.Log.e("AddLinkedUserActivity", "网络请求异常", e)
withContext(Dispatchers.Main) {
binding.swipeRefresh.isRefreshing = false
noNetwork()
Toast.makeText(this@AddLinkedUserActivity, "网络请求失败: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
}
private fun hide() {
binding.searchLayout.visibility = View.GONE
binding.rvAddLinkedUser.visibility = View.GONE
binding.butting?.visibility = View.GONE
binding.layoutNoPermission?.visibility = View.VISIBLE
}
private fun noNetwork() {
binding.searchLayout.visibility = View.GONE
binding.rvAddLinkedUser.visibility = View.GONE
binding.butting?.visibility = View.GONE
binding.layoutNoNetwork?.visibility = View.VISIBLE
}
private fun updateFamilyMemberList(familyMembersList: List<FamilyMembersBean>) {
fullUserItemList.clear()
familyMembersList.forEach { member ->

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() {
binding.alarmListRecyclerView.visibility = View.GONE
binding.emptyLayout.visibility = View.VISIBLE
private fun updateAlarmListState() {
if (alarmList.isEmpty()) {
binding.alarmListRecyclerView.visibility = View.GONE
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) {
@@ -214,14 +214,13 @@ class AlarmLogActivity : AppCompatActivity() {
}
adapter?.notifyDataSetChanged()
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

@@ -31,6 +31,7 @@ class AutoModeActivity : AppCompatActivity() {
private var selectedStartMinute = 0
private var selectedEndHour = 10
private var selectedEndMinute = 0
private var optionPassword: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -42,6 +43,8 @@ class AutoModeActivity : AppCompatActivity() {
.titleBar(binding.topBar)
.init()
optionPassword = intent.getStringExtra("optionPassword")
initListener()
loadCurrentSettings()
@@ -177,7 +180,8 @@ class AutoModeActivity : AppCompatActivity() {
id = deviceId,
workMode = selectedMode,
startTime = startTime,
endTime = endTime
endTime = endTime,
optionPassword = optionPassword
)
val response = RetrofitNetwork.getNetworkService().updateDeviceInfo(deviceBean)
withContext(Dispatchers.Main) {

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 {
fetchUserList()
}
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>)
@@ -257,18 +259,32 @@ class LinkedUserActivity : AppCompatActivity() {
updateFamilyMembersList(response.data as List<FamilyMembersBean>)
}
} else {
hide()
Toast.makeText(this@LinkedUserActivity, response.msg, Toast.LENGTH_SHORT).show()
}
}
} catch (e: Exception) {
e.printStackTrace()
withContext(Dispatchers.Main) {
binding.swipeRefresh.isRefreshing = false
Toast.makeText(this@LinkedUserActivity, "网络请求失败", Toast.LENGTH_SHORT).show()
binding.swipeRefresh.finishRefresh()
noNetwork()
Toast.makeText(this@LinkedUserActivity, R.string.network_request_failed, Toast.LENGTH_SHORT).show()
}
}
}
}
private fun hide() {
binding.searchLayout.visibility = View.GONE
binding.rvLinkedUser.visibility = View.GONE
binding.butting?.visibility = View.GONE
binding.layoutNoPermission?.visibility = View.VISIBLE
}
private fun noNetwork() {
binding.searchLayout.visibility = View.GONE
binding.rvLinkedUser.visibility = View.GONE
binding.butting?.visibility = View.GONE
binding.layoutNoNetwork?.visibility = View.VISIBLE
}
private fun updateDeviceUserList(deviceUserList: List<DeviceUserBean>) {
fullUserList.clear()

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 = "https://www.w3schools.com/html/movie.mp4"//"https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"
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

@@ -27,6 +27,7 @@ class SettingsActivity : BaseActivity() {
private var alertAudio: String? = null
private var audioContent: String? = null
private var isPasswordVerified = false
private var verifiedPassword: String? = null
override fun onResume() {
super.onResume()
@@ -55,15 +56,15 @@ class SettingsActivity : BaseActivity() {
if (isPasswordVerified) {
val dialog = RecognitionModeDialog(this)
dialog.setOnConfirmListener { mode ->
updateRecognitionMode(mode)
updateRecognitionMode(mode, verifiedPassword)
}
dialog.show()
} else {
showPasswordDialog {
showPasswordDialog { inputPassword ->
isPasswordVerified = true
val dialog = RecognitionModeDialog(this)
dialog.setOnConfirmListener { mode ->
updateRecognitionMode(mode)
updateRecognitionMode(mode, inputPassword)
}
dialog.show()
}
@@ -94,22 +95,24 @@ class SettingsActivity : BaseActivity() {
}
binding.itemWaterGunSetting.setOnClickListener {
if (isPasswordVerified) {
navigateToWaterGunSettings()
navigateToWaterGunSettings(verifiedPassword)
} else {
showPasswordDialog {
showPasswordDialog { inputPassword ->
isPasswordVerified = true
navigateToWaterGunSettings()
navigateToWaterGunSettings(inputPassword)
}
}
}
binding.itemAutoMode.setOnClickListener {
if (isPasswordVerified) {
val intent = Intent(this, AutoModeActivity::class.java)
intent.putExtra("optionPassword", verifiedPassword)
startActivity(intent)
} else {
showPasswordDialog {
showPasswordDialog { inputPassword ->
isPasswordVerified = true
val intent = Intent(this, AutoModeActivity::class.java)
intent.putExtra("optionPassword", inputPassword)
startActivity(intent)
}
}
@@ -258,7 +261,7 @@ class SettingsActivity : BaseActivity() {
}
}
private fun updateRecognitionMode(mode: String) {
private fun updateRecognitionMode(mode: String, optionPassword: String?) {
val deviceId = DeviceIdEvent.getDeviceId()
if (deviceId.isNullOrEmpty()) {
Toast.makeText(this, "设备ID为空", Toast.LENGTH_SHORT).show()
@@ -276,6 +279,7 @@ class SettingsActivity : BaseActivity() {
val deviceBean = com.ckkj.defense_device.bean.DeviceBean(
id = deviceId,
patternPerception = patternPerception,
optionPassword = optionPassword
)
val response = RetrofitNetwork.getNetworkService().updateDeviceInfo(deviceBean)
withContext(Dispatchers.Main) {
@@ -328,7 +332,7 @@ class SettingsActivity : BaseActivity() {
}
}
private fun showPasswordDialog(onVerified: () -> Unit) {
private fun showPasswordDialog(onVerified: (String) -> Unit) {
passwordDialog = PasswordInputDialog(this).apply {
setOnConfirmListener { inputPassword ->
CoroutineScope(Dispatchers.IO).launch {
@@ -338,7 +342,8 @@ class SettingsActivity : BaseActivity() {
withContext(Dispatchers.Main) {
if (response.code == 200) {
dismiss()
onVerified()
verifiedPassword = inputPassword
onVerified(inputPassword)
} else {
Toast.makeText(
this@SettingsActivity,
@@ -365,8 +370,9 @@ class SettingsActivity : BaseActivity() {
passwordDialog.show()
}
private fun navigateToWaterGunSettings() {
private fun navigateToWaterGunSettings(optionPassword: String? = null) {
val intent = Intent(this, WaterGunSettingsActivity::class.java)
intent.putExtra("optionPassword", optionPassword)
val deviceId = DeviceIdEvent.getDeviceId()
if (!deviceId.isNullOrEmpty()) {
CoroutineScope(Dispatchers.IO).launch {

View File

@@ -7,6 +7,7 @@ import androidx.lifecycle.lifecycleScope
import com.ckkj.defense_device.R
import com.ckkj.defense_device.bean.DeleteDeviceUserRequest
import com.ckkj.defense_device.bean.ObtainInviterInformationRequest
import com.ckkj.defense_device.bean.UpdateFamilyUserRequest
import com.ckkj.defense_device.bean.VerifyOptionPasswordRequest
import com.ckkj.defense_device.personal.PasswordInputDialog
import com.ckkj.defense_device.utils.DeviceIdEvent
@@ -26,6 +27,7 @@ class UserDetailsActivity : AppCompatActivity() {
private var userId: String = ""
private var isPasswordVerified: Boolean = false
private lateinit var passwordDialog: PasswordInputDialog
private var optionPassword: String = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -73,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 ?: ""
@@ -87,10 +89,12 @@ class UserDetailsActivity : AppCompatActivity() {
binding.swUserStatus.setOnCheckedChangeListener { _, isChecked ->
if (!isPasswordVerified) {
binding.swUserStatus.isChecked = !isChecked
showPasswordDialog {
showPasswordDialog(isChecked) {
isPasswordVerified = true
binding.swUserStatus.isChecked = isChecked
}
} else {
updateFamilyUserStatus(isChecked)
}
}
@@ -99,7 +103,7 @@ class UserDetailsActivity : AppCompatActivity() {
}
}
private fun showPasswordDialog(onVerified: () -> Unit) {
private fun showPasswordDialog(status: Boolean, onVerified: () -> Unit) {
passwordDialog = PasswordInputDialog(this).apply {
setOnConfirmListener { inputPassword ->
lifecycleScope.launch {
@@ -109,6 +113,7 @@ class UserDetailsActivity : AppCompatActivity() {
withContext(Dispatchers.Main) {
if (response.code == 200) {
dismiss()
updateFamilyUserStatus(status, inputPassword)
onVerified()
} else {
Toast.makeText(
@@ -135,6 +140,48 @@ class UserDetailsActivity : AppCompatActivity() {
}
private fun updateFamilyUserStatus(status: Boolean) {
updateFamilyUserStatus(status, optionPassword)
}
private fun updateFamilyUserStatus(status: Boolean, password: String) {
if (userId.isEmpty()) {
Toast.makeText(this, "用户ID为空", Toast.LENGTH_SHORT).show()
return
}
if (password.isNotEmpty()) {
optionPassword = password
}
lifecycleScope.launch {
try {
val request = UpdateFamilyUserRequest(
id = userId,
optionPassword = optionPassword,
optionStatus = "status",
status = if (status) "1" else "0"
)
val response = RetrofitNetwork.getNetworkService().updateFamilyUser(request)
withContext(Dispatchers.Main) {
if (response.code == 200) {
Toast.makeText(this@UserDetailsActivity, "状态修改成功", Toast.LENGTH_SHORT).show()
DeviceUserUpdateEvent.notifyUpdate()
} else {
Toast.makeText(this@UserDetailsActivity, response.msg ?: "状态修改失败", Toast.LENGTH_SHORT).show()
binding.swUserStatus.isChecked = !status
}
}
} catch (e: Exception) {
e.printStackTrace()
withContext(Dispatchers.Main) {
Toast.makeText(this@UserDetailsActivity, "网络请求失败", Toast.LENGTH_SHORT).show()
binding.swUserStatus.isChecked = !status
}
}
}
}
private fun showDeleteDialog() {
val dialog = CustomDialogFragmentText()
dialog.setTitle(getString(R.string.tip))
@@ -146,7 +193,7 @@ class UserDetailsActivity : AppCompatActivity() {
if (isPasswordVerified) {
deleteDeviceUser()
} else {
showPasswordDialog {
showPasswordDialog(false) {
isPasswordVerified = true
deleteDeviceUser()
}

View File

@@ -79,29 +79,31 @@ class VoiceSettingsActivity : AppCompatActivity() {
private fun updateDeviceAlertAudio() {
deviceId?.let { id ->
val newAlertAudio = if (binding.swWarningVoice.isChecked) "1" else "0"
// 只有状态发生变化时才请求
if (newAlertAudio != originalAlertAudio) {
CoroutineScope(Dispatchers.IO).launch {
try {
val deviceBean = DeviceBean(id = id, alertAudio = newAlertAudio)
val response = apiService.updateDeviceInfo(deviceBean)
withContext(Dispatchers.Main) {
if (response.code == 200 && response.data != null) {
Toast.makeText(this@VoiceSettingsActivity, "修改成功", Toast.LENGTH_SHORT).show()
} else {
// 点击返回时总是触发网络请求
CoroutineScope(Dispatchers.IO).launch {
try {
val deviceBean = DeviceBean(id = id, alertAudio = newAlertAudio)
val response = apiService.updateDeviceInfo(deviceBean)
withContext(Dispatchers.Main) {
if (response.code == 200 && response.data != null) {
Toast.makeText(this@VoiceSettingsActivity, "修改成功", Toast.LENGTH_SHORT).show()
} else {
// 仅在状态实际发生变化时才显示错误提示
if (newAlertAudio != originalAlertAudio) {
Toast.makeText(this@VoiceSettingsActivity, response.msg, Toast.LENGTH_SHORT).show()
}
finish()
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
finish()
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
// 仅在状态实际发生变化时才显示失败提示
if (newAlertAudio != originalAlertAudio) {
Toast.makeText(this@VoiceSettingsActivity, "修改失败", Toast.LENGTH_SHORT).show()
finish()
}
finish()
}
}
} else {
finish()
}
}
}

View File

@@ -24,6 +24,7 @@ class WaterGunSettingsActivity : BaseActivity() {
private var currentStrikeRange: String = "7"
private var currentStrikeInterval: String = "3"
private var currentContainer: String = "容器4"
private var optionPassword: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -41,6 +42,7 @@ class WaterGunSettingsActivity : BaseActivity() {
currentStrikeRange = intent.getStringExtra("strikeRange") ?: "7"
currentStrikeInterval = intent.getStringExtra("strikeInterval") ?: "3"
currentContainer = intent.getStringExtra("container") ?: "容器4"
optionPassword = intent.getStringExtra("optionPassword")
// 显示初始值
binding.tvDistance.text = "${currentStrikeRange}m"
@@ -100,7 +102,8 @@ class WaterGunSettingsActivity : BaseActivity() {
try {
val deviceBean = DeviceBean(
id = deviceId,
strikeRange = distance
strikeRange = distance,
optionPassword = optionPassword
)
val response = RetrofitNetwork.getNetworkService().updateDeviceInfo(deviceBean)
withContext(Dispatchers.Main) {
@@ -131,7 +134,8 @@ class WaterGunSettingsActivity : BaseActivity() {
try {
val deviceBean = DeviceBean(
id = deviceId,
strikeInterval = interval
strikeInterval = interval,
optionPassword = optionPassword
)
val response = RetrofitNetwork.getNetworkService().updateDeviceInfo(deviceBean)
withContext(Dispatchers.Main) {
@@ -162,7 +166,8 @@ class WaterGunSettingsActivity : BaseActivity() {
try {
val deviceBean = DeviceBean(
id = deviceId,
container = container
container = container,
optionPassword = optionPassword
)
val response = RetrofitNetwork.getNetworkService().updateDeviceInfo(deviceBean)
withContext(Dispatchers.Main) {

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 ->
@@ -253,4 +242,4 @@ class WiFiManagementActivity : AppCompatActivity() {
unregisterReceiver(wifiScanReceiver)
popupWindow?.dismiss()
}
}
}

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

@@ -10,6 +10,7 @@ import bean.ForgotRequest
import com.ckkj.defense_device.R
import com.ckkj.defense_device.databinding.ForgetPasswordActivityBinding
import com.ckkj.defense_device.utils.RetrofitNetwork
import com.ckkj.defense_device.view.LoadingDialog
import com.google.firebase.crashlytics.buildtools.reloc.org.apache.http.util.TextUtils
import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.CoroutineScope
@@ -20,6 +21,7 @@ import java.util.regex.Pattern
class ForgetPasswordActivity : AppCompatActivity() {
private lateinit var binding: ForgetPasswordActivityBinding
private var isLoading = false
private var countDownTimer: CountDownTimer? = null
private var isSendingCode = false
@@ -55,15 +57,17 @@ 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()
}
}
binding.btnNext.setOnClickListener {
performResetPassword()
if (!isLoading) {
performResetPassword()
}
}
}
@@ -82,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()
@@ -104,11 +108,10 @@ class ForgetPasswordActivity : AppCompatActivity() {
* 开始验证码按钮倒计时
*/
private fun startCountDown() {
binding.sendCodeBtn.isEnabled = false
countDownTimer = object : CountDownTimer(COUNTDOWN_DURATION, COUNTDOWN_INTERVAL) {
override fun onTick(millisUntilFinished: Long) {
val secondsRemaining = (millisUntilFinished / 1000).toInt()
binding.sendCodeBtn.text = "${secondsRemaining}秒后重试"
binding.sendCodeBtn.isEnabled = false
binding.sendCodeBtn.text = getString(R.string.countdown_retry, millisUntilFinished / 1000)
}
override fun onFinish() {
@@ -123,7 +126,7 @@ class ForgetPasswordActivity : AppCompatActivity() {
private fun resetSendButton() {
countDownTimer?.cancel()
countDownTimer = null
binding.sendCodeBtn.text = "发送验证码"
binding.sendCodeBtn.text = getString(R.string.resend_code)
binding.sendCodeBtn.isEnabled = true
}
@@ -137,34 +140,38 @@ 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
}
isLoading = true
binding.btnNext.isEnabled = false
binding.btnNext.text = "重置中..."
val loadingDialog = LoadingDialog(this)
.setMessage(getString(R.string.resetting))
.apply { show() }
CoroutineScope(Dispatchers.IO).launch {
try {
@@ -175,8 +182,9 @@ class ForgetPasswordActivity : AppCompatActivity() {
)
val response = RetrofitNetwork.getNetworkService().forgotPassword(forgotRequest)
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
binding.btnNext.isEnabled = true
binding.btnNext.text = "确定"
if (response.code == 200) {
Toast.makeText(this@ForgetPasswordActivity, R.string.reset_success, Toast.LENGTH_SHORT).show()
@@ -187,8 +195,9 @@ class ForgetPasswordActivity : AppCompatActivity() {
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
binding.btnNext.isEnabled = true
binding.btnNext.text = "确定"
Toast.makeText(this@ForgetPasswordActivity, R.string.network_error, Toast.LENGTH_SHORT).show()
}
}

View File

@@ -5,6 +5,7 @@ import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.os.Bundle
import android.os.CountDownTimer
import android.util.Log
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
@@ -14,6 +15,7 @@ import bean.LoginRequest
import com.ckkj.defense_device.R
import com.ckkj.defense_device.databinding.ActivityLoginBinding
import com.ckkj.defense_device.home.HomeActivity
import com.ckkj.defense_device.utils.ApiConfig
import com.ckkj.defense_device.utils.RetrofitNetwork
import com.ckkj.defense_device.utils.UserinfoUtil
import com.gyf.immersionbar.ImmersionBar
@@ -26,11 +28,13 @@ class LoginActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginBinding
private var currentLoginMode = LoginMode.SMS
private var countDownTimer: CountDownTimer? = null
enum class LoginMode { SMS, PASSWORD }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ApiConfig.resetAuthRedirectFlag()
binding = ActivityLoginBinding.inflate(layoutInflater)
setContentView(binding.root)
@@ -73,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)
@@ -98,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
}
@@ -115,7 +118,6 @@ class LoginActivity : AppCompatActivity() {
params.endToEnd = ConstraintSet.UNSET
}
params.topToBottom = binding.loginBtn.id
params.topMargin = 20
binding.registerText.layoutParams = params
}
@@ -166,25 +168,24 @@ class LoginActivity : AppCompatActivity() {
binding.registerText.setOnClickListener {
val intent= Intent(this, RegisterActivity::class.java)
startActivity(intent)
Toast.makeText(this, R.string.redirect_register_page, Toast.LENGTH_SHORT).show()
}
binding.forgetText.setOnClickListener {
val intent= Intent(this, ForgetPasswordActivity::class.java)
startActivity(intent)
Toast.makeText(this, R.string.forgot_password_page, Toast.LENGTH_SHORT).show()
}
}
private fun startCountDown() {
binding.sendCodeBtn.isEnabled = false
object : CountDownTimer(60000, 1000) {
countDownTimer = object : CountDownTimer(60000, 1000) {
override fun onTick(millisUntilFinished: Long) {
binding.sendCodeBtn.text = getString(R.string.countdown_retry, millisUntilFinished / 1000)
}
override fun onFinish() {
binding.sendCodeBtn.text = getString(R.string.send_code)
binding.sendCodeBtn.text = getString(R.string.resend_code)
binding.sendCodeBtn.isEnabled = true
countDownTimer = null
}
}.start()
}
@@ -218,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)
@@ -234,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()
}
@@ -251,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()
@@ -267,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()
}
@@ -285,4 +281,10 @@ class LoginActivity : AppCompatActivity() {
gradientDrawable.gradientType = GradientDrawable.LINEAR_GRADIENT
view.background = gradientDrawable
}
override fun onDestroy() {
super.onDestroy()
countDownTimer?.cancel()
countDownTimer = null
}
}

View File

@@ -11,6 +11,7 @@ import bean.RegisterRequest
import com.ckkj.defense_device.R
import com.ckkj.defense_device.databinding.RegisterActivityBinding
import com.ckkj.defense_device.utils.RetrofitNetwork
import com.ckkj.defense_device.view.LoadingDialog
import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -80,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()
@@ -99,11 +100,10 @@ class RegisterActivity : AppCompatActivity() {
}
private fun startCountDown() {
binding.sendCodeBtn.isEnabled = false
countDownTimer = object : CountDownTimer(COUNTDOWN_DURATION, COUNTDOWN_INTERVAL) {
override fun onTick(millisUntilFinished: Long) {
val secondsRemaining = (millisUntilFinished / 1000).toInt()
binding.sendCodeBtn.text = "${secondsRemaining}秒后重试"
binding.sendCodeBtn.isEnabled = false
binding.sendCodeBtn.text = getString(R.string.countdown_retry, millisUntilFinished / 1000)
}
override fun onFinish() {
@@ -115,7 +115,7 @@ class RegisterActivity : AppCompatActivity() {
private fun resetSendButton() {
countDownTimer?.cancel()
countDownTimer = null
binding.sendCodeBtn.text = getString(R.string.send_code)
binding.sendCodeBtn.text = getString(R.string.resend_code)
binding.sendCodeBtn.isEnabled = true
isSendingCode = false
}
@@ -158,7 +158,9 @@ class RegisterActivity : AppCompatActivity() {
isLoading = true
binding.btnNext.isEnabled = false
binding.btnNext.text = getString(R.string.registering)
val loadingDialog = LoadingDialog(this)
.setMessage(getString(R.string.registering))
.apply { show() }
CoroutineScope(Dispatchers.IO).launch {
try {
@@ -174,12 +176,13 @@ class RegisterActivity : AppCompatActivity() {
val response = RetrofitNetwork.getNetworkService().register(registerRequest)
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
binding.btnNext.isEnabled = true
binding.btnNext.text = getString(R.string.next_step)
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()
@@ -190,8 +193,8 @@ class RegisterActivity : AppCompatActivity() {
} catch (e: Exception) {
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
binding.btnNext.isEnabled = true
binding.btnNext.text = getString(R.string.next_step)
Toast.makeText(this@RegisterActivity, R.string.network_error, Toast.LENGTH_SHORT).show()
}
}

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

@@ -1,32 +1,45 @@
package com.ckkj.defense_device.personal
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.bottomsheet.BottomSheetDialog
import androidx.core.content.ContextCompat
import com.ckkj.defense_device.R
import com.ckkj.defense_device.bean.RecommendationCodeBean
import com.ckkj.defense_device.bean.SelectInviterInfoBean
import com.ckkj.defense_device.bean.VerifyOptionPasswordRequest
import com.ckkj.defense_device.databinding.DialogExpireTimeBinding
import com.ckkj.defense_device.databinding.InvitationCodeActivityBinding
import com.ckkj.defense_device.utils.RetrofitNetwork
import com.ckkj.defense_device.view.LoadingDialog
import com.google.gson.Gson
import com.google.zxing.BarcodeFormat
import com.google.zxing.EncodeHintType
import com.google.zxing.qrcode.QRCodeWriter
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import com.gyf.immersionbar.ImmersionBar
import com.maning.mlkitscanner.scan.MNScanManager
import com.maning.mlkitscanner.scan.callback.act.MNScanCallback
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONObject
import java.io.File
import java.io.FileOutputStream
import java.util.Hashtable
@@ -44,7 +57,24 @@ class InvitationCodeActivity : AppCompatActivity() {
private var isLoading = false // 是否正在加载
private var hasGeneratedQrCode = false // 是否已经生成过二维码
private val requestPermissionsLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
val cameraGranted = permissions[Manifest.permission.CAMERA] ?: false
val storageGranted = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
permissions[Manifest.permission.READ_MEDIA_IMAGES] ?: false
} else {
permissions[Manifest.permission.READ_EXTERNAL_STORAGE] ?: false
}
if (cameraGranted && storageGranted) {
startScan()
} else {
Toast.makeText(this, R.string.permission_denied, Toast.LENGTH_SHORT).show()
}
}
companion object {
private const val TAG = "InvitationCodeActivity"
const val EXPIRE_24H = 24 * 60 * 60 * 1000L
const val EXPIRE_7D = 7 * 24 * 60 * 60 * 1000L
}
@@ -90,15 +120,136 @@ class InvitationCodeActivity : AppCompatActivity() {
// 撤销邀请按钮
binding.btnCancelInvite.setOnClickListener {
showPasswordDialog {
cancelInvitation()
if (generatedInviteCode.isEmpty()) {
ToastUtils.showError(this, getString(R.string.qr_code_expired))
return@setOnClickListener
}
showPasswordDialog { password, dialog ->
cancelInvitation(password, dialog)
}
}
// 生成/重新生成按钮
binding.btnRegenerate.setOnClickListener {
showPasswordDialog {
generateOrRegenerateInvitation()
showPasswordDialog { password, dialog ->
generateOrRegenerateInvitation(password, dialog)
}
}
binding.sys.setOnClickListener {
checkPermissionsAndScan()
}
}
private fun checkPermissionsAndScan() {
val cameraPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
val storagePermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES)
} else {
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
if (cameraPermission == PackageManager.PERMISSION_GRANTED &&
storagePermission == PackageManager.PERMISSION_GRANTED
) {
startScan()
} else {
val permissionsToRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.READ_MEDIA_IMAGES
)
} else {
arrayOf(
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
}
requestPermissionsLauncher.launch(permissionsToRequest)
}
}
private fun startScan() {
MNScanManager.startScan(this, object : MNScanCallback {
override fun onActivityResult(resultCode: Int, data: Intent?) {
when (resultCode) {
MNScanManager.RESULT_SUCCESS -> {
val result = data?.getStringArrayListExtra(MNScanManager.INTENT_KEY_RESULT_SUCCESS)
if (!result.isNullOrEmpty()) {
val inviteCode = parseInviteCode(result[0])
if (inviteCode.isNullOrEmpty()) {
Toast.makeText(this@InvitationCodeActivity, "无效的邀请码", Toast.LENGTH_SHORT).show()
} else {
fetchInviterInfo(inviteCode)
}
} else {
Toast.makeText(this@InvitationCodeActivity, "扫码结果为空", Toast.LENGTH_SHORT).show()
}
}
MNScanManager.RESULT_FAIL -> {
val error = data?.getStringExtra(MNScanManager.INTENT_KEY_RESULT_ERROR)
Toast.makeText(this@InvitationCodeActivity, "扫码失败: $error", Toast.LENGTH_SHORT).show()
}
MNScanManager.RESULT_CANCLE -> {
Toast.makeText(this@InvitationCodeActivity, "扫码取消", Toast.LENGTH_SHORT).show()
}
}
}
})
}
private fun parseInviteCode(scanResult: String): String? {
val trimmed = scanResult.trim()
if (trimmed.isEmpty()) return null
return try {
if (trimmed.startsWith("{")) {
JSONObject(trimmed).optString("code").takeIf { it.isNotEmpty() }
} else {
trimmed
}
} catch (e: Exception) {
trimmed
}
}
private fun fetchInviterInfo(inviteCode: String) {
val loadingDialog = LoadingDialog(this).apply { show() }
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService()
.selectInviterInfo(SelectInviterInfoBean(code = inviteCode))
withContext(Dispatchers.Main) {
loadingDialog.dismiss()
if (response.code == 200 && response.data != null) {
val inviter = response.data
val parentId = inviter.resolveParentId()
if (parentId.isNullOrEmpty()) {
Toast.makeText(
this@InvitationCodeActivity,
getString(R.string.network_error),
Toast.LENGTH_SHORT
).show()
return@withContext
}
val intent = Intent(this@InvitationCodeActivity, JoinFamilyInviteActivity::class.java).apply {
putExtra(JoinFamilyInviteActivity.EXTRA_INVITE_CODE, inviteCode)
putExtra(JoinFamilyInviteActivity.EXTRA_NICK_NAME, inviter.nickName)
putExtra(JoinFamilyInviteActivity.EXTRA_AVATAR, inviter.avatar)
putExtra(JoinFamilyInviteActivity.EXTRA_PARENT_ID, parentId)
putExtra(JoinFamilyInviteActivity.EXTRA_USER_ID, inviter.userId)
}
startActivity(intent)
} else {
Toast.makeText(
this@InvitationCodeActivity,
response.msg.ifBlank { getString(R.string.network_error) },
Toast.LENGTH_SHORT
).show()
}
}
} catch (e: Exception) {
withContext(Dispatchers.Main) {
loadingDialog.dismiss()
Toast.makeText(this@InvitationCodeActivity, R.string.network_error, Toast.LENGTH_SHORT).show()
}
}
}
}
@@ -106,14 +257,56 @@ class InvitationCodeActivity : AppCompatActivity() {
/**
* 生成或重新生成邀请码
*/
private fun generateOrRegenerateInvitation() {
// 根据当前选择的时效生成邀请码
generateQRCode()
showQrCode()
// 更新按钮文字为"重新生成"
binding.btnRegenerate.text = getString(R.string.regenerate)
hasGeneratedQrCode = true
ToastUtils.showSuccess(this, getString(R.string.operation_success))
private fun generateOrRegenerateInvitation(password: String, passwordDialog: PasswordInputDialog) {
if (isLoading) return
isLoading = true
val loadingDialog = LoadingDialog(this).apply { show() }
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService()
.generateRecommendationCode(VerifyOptionPasswordRequest(password))
Log.d(TAG, "生成推荐码响应: code=${response.code}, msg=${response.msg}, data=${response.data}")
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
if (response.code == 200) {
val parsed = parseRecommendationCode(response.data)
if (parsed != null) {
passwordDialog.dismiss()
generatedInviteCode = parsed.first
expireTimestamp = parsed.second
qrCodeContent =
"{\"type\":\"invite\",\"code\":\"$generatedInviteCode\",\"expire\":$expireTimestamp}"
Log.d(
TAG,
"推荐码生成成功: Code=$generatedInviteCode, expireTimestamp=$expireTimestamp, qrContent=$qrCodeContent"
)
generateQRCode()
showQrCode()
binding.btnRegenerate.text = getString(R.string.regenerate)
hasGeneratedQrCode = true
ToastUtils.showSuccess(this@InvitationCodeActivity, getString(R.string.operation_success))
} else {
Log.w(TAG, "推荐码解析失败, raw data=${response.data}")
passwordDialog.showError(getString(R.string.network_error))
}
} else {
Log.w(TAG, "生成推荐码失败: code=${response.code}, msg=${response.msg}")
passwordDialog.showError(
response.msg.ifEmpty { getString(R.string.password_error) }
)
}
}
} catch (e: Exception) {
Log.e(TAG, "生成推荐码异常", e)
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
passwordDialog.showError(getString(R.string.network_error))
}
}
}
}
private fun showExpireTimeDialog() {
@@ -151,9 +344,6 @@ class InvitationCodeActivity : AppCompatActivity() {
binding.tvExpireTime.text = getString(R.string.expire_7d)
}
}
// 显示二维码并生成
showQrCode()
generateQRCode()
expireTimeDialog?.dismiss()
}
@@ -173,22 +363,28 @@ class InvitationCodeActivity : AppCompatActivity() {
}
private fun generateQRCode() {
// 检查二维码是否过期,如果过期则生成新的邀请码
val isExpired = expireTimestamp > 0 && System.currentTimeMillis() > expireTimestamp
val content = if (qrCodeContent.isNotEmpty() && !isExpired) qrCodeContent else generateInviteCode()
val qrBitmap = createQRCodeWithLogo(content, 400, 400)
if (qrCodeContent.isEmpty()) return
val qrBitmap = createQRCodeWithLogo(qrCodeContent, 400, 400)
binding.ivQrCode.setImageBitmap(qrBitmap)
}
private fun generateInviteCode(): String {
val timestamp = System.currentTimeMillis()
expireTimestamp = timestamp + expireTimeInMillis
generatedInviteCode = "INV${timestamp}"
qrCodeContent = "{\"type\":\"invite\",\"code\":\"$generatedInviteCode\",\"expire\":$expireTimestamp}"
// 输出日志,方便调试验证
android.util.Log.d("InvitationCode", "生成的二维码内容: $qrCodeContent")
android.util.Log.d("InvitationCode", "推荐码: $generatedInviteCode, 过期时间戳: $expireTimestamp")
return qrCodeContent
private fun parseRecommendationCode(data: Any?): Pair<String, Long>? {
if (data == null) return null
return when (data) {
is String -> {
if (data.isEmpty()) null
else Pair(data, System.currentTimeMillis() + expireTimeInMillis)
}
else -> {
val bean = Gson().fromJson(Gson().toJson(data), RecommendationCodeBean::class.java)
val code = bean.inviteCode?.takeIf { it.isNotEmpty() }
?: bean.code?.takeIf { it.isNotEmpty() }
?: return null
val expire = bean.expireTime ?: bean.expire
?: (System.currentTimeMillis() + expireTimeInMillis)
Pair(code, expire)
}
}
}
/*
* 这段代码生成带Logo的二维码
@@ -415,56 +611,62 @@ class InvitationCodeActivity : AppCompatActivity() {
/**
* 撤销邀请
*/
private fun cancelInvitation() {
// 撤销邀请逻辑
hideQrCode()
generatedInviteCode = ""
qrCodeContent = ""
expireTimestamp = 0
hasGeneratedQrCode = false
// 恢复按钮文字为"生成邀请码"
binding.btnRegenerate.text = getString(R.string.generate_invite_code)
// 显示提示
ToastUtils.showSuccess(this, getString(R.string.operation_success))
}
/**
* 显示密码输入对话框
* @param onSuccess 验证成功后的回调
*/
private fun showPasswordDialog(onSuccess: () -> Unit) {
val dialog = PasswordInputDialog(this)
dialog.setTitle(getString(R.string.input_op_pwd))
dialog.setOnConfirmListener { password ->
verifyPassword(password, dialog, onSuccess)
private fun cancelInvitation(password: String, passwordDialog: PasswordInputDialog) {
if (isLoading) return
if (generatedInviteCode.isEmpty()) {
passwordDialog.showError(getString(R.string.qr_code_expired))
return
}
dialog.show()
}
isLoading = true
val loadingDialog = LoadingDialog(this).apply { show() }
/**
* 验证操作密码
*/
private fun verifyPassword(password: String, dialog: PasswordInputDialog, onSuccess: () -> Unit) {
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService()
.verifyOptionPassword(VerifyOptionPasswordRequest(password))
.revokeInvitation(VerifyOptionPasswordRequest(password))
Log.d(TAG, "撤销邀请响应: code=${response.code}, msg=${response.msg}")
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
if (response.code == 200) {
dialog.dismiss()
onSuccess.invoke()
passwordDialog.dismiss()
hideQrCode()
generatedInviteCode = ""
qrCodeContent = ""
expireTimestamp = 0
hasGeneratedQrCode = false
binding.btnRegenerate.text = getString(R.string.generate_invite_code)
ToastUtils.showSuccess(this@InvitationCodeActivity, getString(R.string.operation_success))
} else {
dialog.showError(getString(R.string.password_error))
passwordDialog.showError(
response.msg.ifEmpty { getString(R.string.password_error) }
)
}
}
} catch (e: Exception) {
Log.e(TAG, "撤销邀请异常", e)
withContext(Dispatchers.Main) {
dialog.showError(getString(R.string.network_error))
isLoading = false
loadingDialog.dismiss()
passwordDialog.showError(getString(R.string.network_error))
}
}
}
}
/**
* 显示密码输入对话框
*/
private fun showPasswordDialog(onConfirm: (String, PasswordInputDialog) -> Unit) {
val dialog = PasswordInputDialog(this)
dialog.setTitle(getString(R.string.input_op_pwd))
dialog.setOnConfirmListener { password ->
onConfirm(password, dialog)
}
dialog.show()
}
override fun onDestroy() {
super.onDestroy()
expireTimeDialog?.dismiss()

View File

@@ -0,0 +1,123 @@
package com.ckkj.defense_device.personal
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.ckkj.defense_device.R
import com.ckkj.defense_device.bean.JoinTheFamilyBean
import com.ckkj.defense_device.databinding.JoinFamilyInviteActivityBinding
import com.ckkj.defense_device.home.HomeActivity
import com.ckkj.defense_device.utils.RetrofitNetwork
import com.ckkj.defense_device.view.LoadingDialog
import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
// 邀请加入家庭
class JoinFamilyInviteActivity : AppCompatActivity() {
private lateinit var binding: JoinFamilyInviteActivityBinding
private var isLoading = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = JoinFamilyInviteActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
ImmersionBar.with(this)
.statusBarDarkFont(true)
.titleBar(binding.topBar)
.init()
binding.ivBack.setOnClickListener { finish() }
binding.btnJoinNow.setOnClickListener { joinFamily() }
displayInviterInfo()
}
private fun joinFamily() {
if (isLoading) return
val parentId = intent.getStringExtra(EXTRA_USER_ID).orEmpty()
val inviteCode = intent.getStringExtra(EXTRA_INVITE_CODE).orEmpty()
if (parentId.isEmpty() || inviteCode.isEmpty()) {
Log.e(TAG, "加入家庭参数无效: parentId=$parentId, inviteCode=$inviteCode")
Toast.makeText(this, R.string.network_error, Toast.LENGTH_SHORT).show()
return
}
Log.e(TAG, "加入家庭请求: parentId=$parentId, inviteCode=$inviteCode")
isLoading = true
val loadingDialog = LoadingDialog(this).apply { show() }
CoroutineScope(Dispatchers.IO).launch {
try {
val response = RetrofitNetwork.getNetworkService()
.joinTheFamily(JoinTheFamilyBean(parentId = parentId, inviteCode = inviteCode))
Log.e(TAG, "加入家庭响应: code=${response.code}, msg=${response.msg}, data=${response.data}")
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
if (response.code == 200) {
Log.e(TAG, "加入家庭成功,返回首页刷新")
ToastUtils.showSuccess(this@JoinFamilyInviteActivity, getString(R.string.operation_success))
val homeIntent = Intent(this@JoinFamilyInviteActivity, HomeActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
putExtra(HomeActivity.EXTRA_REFRESH, true)
}
startActivity(homeIntent)
finish()
} else {
Log.e(TAG, "加入家庭失败: code=${response.code}, msg=${response.msg}")
Toast.makeText(
this@JoinFamilyInviteActivity,
response.msg.ifBlank { getString(R.string.network_error) },
Toast.LENGTH_SHORT
).show()
}
}
} catch (e: Exception) {
Log.e(TAG, "加入家庭请求异常", e)
withContext(Dispatchers.Main) {
isLoading = false
loadingDialog.dismiss()
Toast.makeText(this@JoinFamilyInviteActivity, R.string.network_error, Toast.LENGTH_SHORT).show()
}
}
}
}
private fun displayInviterInfo() {
val nickName = intent.getStringExtra(EXTRA_NICK_NAME).orEmpty()
val avatar = intent.getStringExtra(EXTRA_AVATAR)
binding.tvNickname.text = nickName.ifEmpty { getString(R.string.nickname_placeholder) }
binding.tvInviteMessage.text = getString(
R.string.invite_join_family_message,
nickName.ifEmpty { getString(R.string.nickname_placeholder) }
)
val requestOptions = RequestOptions()
.placeholder(R.mipmap.img)
.error(R.mipmap.img)
Glide.with(this)
.load(avatar)
.apply(requestOptions)
.circleCrop()
.into(binding.ivAvatar)
}
companion object {
private const val TAG = "JoinFamilyInviteActivity"
const val EXTRA_INVITE_CODE = "invite_code"
const val EXTRA_NICK_NAME = "nick_name"
const val EXTRA_AVATAR = "avatar"
const val EXTRA_PARENT_ID = "parent_id"
const val EXTRA_USER_ID = "user_id"
}
}

View File

@@ -163,7 +163,7 @@ class RetrievePasswordActivity : AppCompatActivity() {
override fun onFinish() {
isCountingDown = false
binding.btnGetCode.isEnabled = true
binding.btnGetCode.text = getString(R.string.get_verification_code)
binding.btnGetCode.text = getString(R.string.resend_code)
}
}.start()
}

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

@@ -1,8 +1,14 @@
package com.ckkj.defense_device.utils
import android.annotation.SuppressLint
import android.content.Intent
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.widget.Toast
import com.blankj.utilcode.util.Utils
import com.ckkj.defense_device.bean.BaseInfo
import com.ckkj.defense_device.login.LoginActivity
import com.google.gson.Gson
import com.google.gson.JsonParser
import okhttp3.Interceptor
@@ -24,10 +30,17 @@ object ApiConfig {
@Volatile
public var authToken: String = ""
@Volatile
private var isRedirectingToLogin = false
fun updateAuthToken(token: String) {
authToken = token
}
fun resetAuthRedirectFlag() {
isRedirectingToLogin = false
}
fun hasAuthToken(): Boolean {
return authToken.isNotBlank()
}
@@ -116,8 +129,8 @@ object ApiConfig {
val businessCode = if (jsonObject.has("code")) jsonObject.get("code").asInt else 0
if (httpCode == 401 || httpCode == 424 || businessCode == 401) {
clearUserLoginInfo()
val msg = if (jsonObject.has("msg")) jsonObject.get("msg").asString else "登录已过期,请重新登录"
redirectToLogin(msg)
return@Interceptor buildFakeErrorResponse(request, msg, code = if (businessCode != 0) businessCode else httpCode)
}
@@ -138,7 +151,7 @@ object ApiConfig {
*/
private fun buildFakeErrorResponse(request: Request, msg: String, code: Int = 1): Response {
val baseInfo = BaseInfo(
message = msg,
msg = msg,
code = code,
data = null,
)
@@ -179,12 +192,36 @@ object ApiConfig {
private fun buildAuthorizationValue(token: String): String {
return if (token.startsWith("Bearer ")) token else "Bearer $token"
}
//401跳转登录界面
private fun redirectToLogin(msg: String) {
Handler(Looper.getMainLooper()).post {
if (isRedirectingToLogin) return@post
val current = ActivityManager.getCurrentActivity()
if (current is LoginActivity) return@post
isRedirectingToLogin = true
clearUserLoginInfo()
val context = current ?: Utils.getApp()
ActivityManager.finishAllActivities()
val intent = Intent(context, LoginActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
context.startActivity(intent)
if (msg.isNotBlank()) {
Toast.makeText(Utils.getApp(), msg, Toast.LENGTH_SHORT).show()
}
}
}
private fun clearUserLoginInfo() {
try {
authToken = ""
UserinfoUtil.clearUserInfo()
} catch (e: Exception) {
e.printStackTrace()
authToken = ""
}
}
}

View File

@@ -14,16 +14,22 @@ import com.ckkj.defense_device.bean.DeleteBean
import com.ckkj.defense_device.bean.DeleteDeviceUserRequest
import com.ckkj.defense_device.bean.DeviceAlarmResponse
import com.ckkj.defense_device.bean.FamilyMembersBean
import com.ckkj.defense_device.bean.InviterInfoBean
import com.ckkj.defense_device.bean.JoinTheFamilyBean
import com.ckkj.defense_device.bean.MessageBean
import com.ckkj.defense_device.bean.MessageListResponse
import com.ckkj.defense_device.bean.ObtainInviterInformationBean
import com.ckkj.defense_device.bean.ObtainInviterInformationRequest
import com.ckkj.defense_device.bean.SelectInviterInfoBean
import com.ckkj.defense_device.bean.SwitchFamilyRequest
import com.ckkj.defense_device.bean.UpdateFamilyUserRequest
import com.ckkj.defense_device.bean.UpdatePassword
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
@@ -33,6 +39,25 @@ import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Query
// ┏┓   ┏┓
//┏┛┻━━━┛┻┓
//┃       ┃
//┃   ━   ┃
//┃ ┳┛ ┗┳ ┃
//┃       ┃
//┃   ┻   ┃
//┃       ┃
//┗━┓   ┏━┛
// ┃   ┃
// ┃   ┃
// ┃   ┗━━━┓
// ┃       ┣┓
// ┃       ┏┛
// ┗┓┓┏━┳┓┏┛
// ┃┫┫ ┃┫┫
// ┗┻┛ ┗┻┛
//神兽护体代码零Bug需求不改一次编译通过
interface ApiService {
/**
* 获取登录验证码
@@ -191,7 +216,7 @@ interface ApiService {
/**
* 获取邀请人信息
* 获取家庭成员信息
*/
@POST("/app/obtainInviterInformation")
suspend fun obtainInviterInformation(@Body request: ObtainInviterInformationRequest): GetCodeBean<ObtainInviterInformationBean>
@@ -250,7 +275,95 @@ interface ApiService {
/**
* 修改家庭用户状态
*/
//@POST("/app/updateFamilyUser")
@POST("/app/updateFamilyUser")
suspend fun updateFamilyUser(@Body request: UpdateFamilyUserRequest): GetCodeBean<Any>
/**
* 获取邀请人信息
*/
@POST("/app/selectInviterInfo")
suspend fun selectInviterInfo(@Body request: SelectInviterInfoBean): GetCodeBean<InviterInfoBean>
/**
* 加入xxx家庭
*/
@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,7 +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 = "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()
@@ -64,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,14 +44,23 @@ class LoadingDialog(context: Context) {
}
fun show() {
handler.removeCallbacks(dismissRunnable)
showTime = System.currentTimeMillis()
if (!dialog.isShowing) {
dialog.show()
}
}
fun dismiss() {
if (dialog.isShowing) {
dialog.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)
}
}

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,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:color="@color/white"/>
<item android:color="@color/white"/>
</selector>

View File

@@ -5,5 +5,5 @@
<stroke
android:width="1dp"
android:color="@color/green" />
<corners android:radius="8dp" />
<corners android:radius="25dp" />
</shape>

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="8dp" />
<corners android:radius="25dp" />
</shape>

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

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false">
<shape android:shape="rectangle">
<solid android:color="@color/gray"/>
<corners android:radius="99dp"/>
</shape>
</item>
<item>
<shape android:shape="rectangle">
<solid android:color="@color/green"/>
<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,28 +147,31 @@
android:textColorHint="@color/gray"
android:textColor="@android:color/black"
android:background="@null"
android:inputType="number"
android:paddingVertical="16dp"
app:layout_constraintStart_toEndOf="@+id/ivSmsIcon"
app:layout_constraintEnd_toStartOf="@+id/sendCodeBtn"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
android:textSize="14dp"
android:inputType="number"
android:paddingVertical="16dp"
app:layout_constraintStart_toEndOf="@+id/ivSmsIcon"
app:layout_constraintEnd_toStartOf="@+id/sendCodeBtn"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
<!-- 发送验证码按钮 -->
<android.widget.Button
android:id="@+id/sendCodeBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="110dp"
android:layout_height="36dp"
android:background="@drawable/send_code_btn_selector"
android:elevation="0dp"
android:gravity="center"
android:minWidth="0dp"
android:minHeight="0dp"
android:stateListAnimator="@null"
android:text="@string/send_sms_code"
android:textSize="16sp"
android:textColor="@color/white"
android:background="@drawable/rounded_blue_bg"
android:paddingHorizontal="12dp"
android:maxWidth="120dp"
app:layout_constraintTop_toTopOf="parent"
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>
@@ -175,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"
@@ -199,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"
@@ -211,7 +216,6 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
@@ -219,13 +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" />
@@ -235,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

@@ -123,6 +123,8 @@
android:textColor="@color/white"
android:textSize="16dp"
android:textStyle="bold"
android:elevation="0dp"
android:stateListAnimator="@null"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -77,10 +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:textSize="14sp" />
android:elevation="0dp"
android:stateListAnimator="@null"
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"
@@ -88,8 +87,41 @@
android:paddingBottom="16dp"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<!-- 空页面 -->
<LinearLayout
android:id="@+id/layoutNoPermission"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="244dp"
android:layout_height="131dp"
android:src="@mipmap/default_page"/>
</LinearLayout>
<LinearLayout
android:id="@+id/layoutNoNetwork"
android:layout_width="match_parent"
android:layout_height="match_parent"
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
android:id="@+id/butting"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
@@ -102,6 +134,8 @@
android:text="@string/confirm"
android:textColor="@color/white"
android:textSize="14dp"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg" />
</LinearLayout>

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

@@ -105,6 +105,8 @@
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="16sp"
android:elevation="0dp"
android:stateListAnimator="@null"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/etConfirmPassword" />

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"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvWifiList"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
android:layout_weight="1"
android:overScrollMode="never" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvWifiList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:overScrollMode="never" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -287,6 +287,8 @@
android:textColor="@color/white"
android:textSize="14dp"
android:textStyle="bold"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"

View File

@@ -107,6 +107,8 @@
android:textSize="17sp"
android:textStyle="bold"
android:background="@drawable/bg_capture_face_button"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textColor="#F2FFFFFF" />
</LinearLayout>

View File

@@ -15,9 +15,7 @@
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:paddingStart="16dp"
android:paddingEnd="16dp">
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:id="@+id/backIcon"
android:layout_width="24dp"
@@ -60,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"
@@ -90,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"
@@ -102,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"
@@ -133,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"
@@ -142,14 +141,17 @@
<!-- 发送验证码按钮 -->
<android.widget.Button
android:id="@+id/sendCodeBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/send_code"
android:textSize="16sp"
android:layout_width="110dp"
android:layout_height="36dp"
android:minWidth="0dp"
android:minHeight="0dp"
android:text="@string/send_sms_code"
android:textSize="14dp"
android:textColor="@color/white"
android:background="@drawable/rounded_blue_bg"
android:paddingHorizontal="12dp"
android:maxWidth="120dp"
android:background="@drawable/send_code_btn_selector"
android:elevation="0dp"
android:stateListAnimator="@null"
android:gravity="center"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -161,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"
@@ -175,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" />
@@ -190,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"
@@ -203,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"
@@ -216,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" />
@@ -232,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"
@@ -244,13 +249,15 @@
<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:text="@string/determine"
android:textAllCaps="false"
android:textSize="16sp"
android:textSize="16dp"
app:cornerRadius="12dp"
android:textColor="@color/white"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -3,6 +3,8 @@
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">
<LinearLayout
@@ -36,15 +38,13 @@
<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:textStyle="bold" />
android:textSize="20sp"
android:textStyle="bold" />
<ImageView
android:id="@+id/ivFamilyArrow"
@@ -54,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"
@@ -70,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"/>
@@ -81,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
@@ -125,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"
@@ -133,11 +125,15 @@
</LinearLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
<com.ckkj.defense_device.view.PullRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:layout_marginTop="12dp"
android:layout_height="0dp"
android:layout_weight="1">
android:layout_weight="1"
android:background="@android:color/transparent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvDeviceList"
@@ -147,10 +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"
@@ -168,6 +165,22 @@
android:src="@mipmap/default_page" />
</LinearLayout>
<LinearLayout
android:id="@+id/layoutNoNetwork"
android:layout_width="match_parent"
android:layout_height="match_parent"
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

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">
@@ -118,6 +117,7 @@
</FrameLayout>
<!-- 使用智能文字和图标 -->
<LinearLayout
android:id="@+id/sys"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
@@ -125,16 +125,16 @@
android:gravity="center_vertical">
<ImageView
android:layout_width="20dp"
android:layout_height="20dp"
android:src="@drawable/syis" />
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@mipmap/syis" />
<TextView
android:textColor="@color/green"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/use_smart_defense_app"
android:textColor="@color/black"
android:textSize="14sp" />
</LinearLayout>
@@ -196,9 +196,10 @@
<!-- 底部按钮区域 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_height="0dp"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="42dp"
android:layout_gravity="bottom"
android:layout_marginBottom="10dp"
android:orientation="horizontal"
android:gravity="bottom">
@@ -211,6 +212,8 @@
android:layout_marginEnd="12dp"
android:text="@string/cancel_invite"
android:textColor="@color/green"
android:elevation="0dp"
android:stateListAnimator="@null"
android:textSize="16sp"
android:background="@drawable/bg_blue_border_round"
android:textAllCaps="false" />
@@ -223,6 +226,8 @@
android:text="@string/regenerate"
android:textColor="@color/white"
android:textSize="16sp"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/bg_blue_round"
android:textAllCaps="false" />
</LinearLayout>

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,90 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".personal.JoinFamilyInviteActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/topBar"
android:layout_width="match_parent"
android:layout_height="56dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/ivBack"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:contentDescription="@string/back"
android:src="@mipmap/back"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/contentLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical"
android:paddingHorizontal="32dp"
app:layout_constraintBottom_toTopOf="@id/btnJoinNow"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/topBar"
app:layout_constraintVertical_bias="0.35">
<ImageView
android:id="@+id/ivAvatar"
android:layout_width="88dp"
android:layout_height="88dp"
android:contentDescription="@string/avatar"
android:scaleType="centerCrop"
android:src="@mipmap/img"/>
<TextView
android:id="@+id/tvNickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:text="@string/nickname_placeholder"
android:textColor="@color/black"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvInviteMessage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="@string/invite_join_family_message_preview"
android:textColor="@color/gray"
android:textSize="14sp" />
</LinearLayout>
<android.widget.Button
android:id="@+id/btnJoinNow"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginHorizontal="16dp"
android:layout_marginBottom="40dp"
android:background="@drawable/rounded_blue_bg"
android:text="@string/join_now"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
android:elevation="0dp"
android:stateListAnimator="@null"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

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

@@ -35,60 +35,109 @@
android:textStyle="bold" />
</LinearLayout>
<!-- 搜索区域 -->
<LinearLayout
android:id="@+id/searchLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
android:layout_marginTop="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/bg_user_manage"
android:gravity="center_vertical"
android:paddingHorizontal="16dp">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@mipmap/search"
android:layout_marginRight="8dp"/>
<EditText
android:id="@+id/etSearch"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@null"
android:hint="@string/search_preset_text"
android:textColorHint="@color/gray"
android:textColor="@color/black"
android:textSize="14sp"
android:inputType="text"
android:imeOptions="actionSearch"
android:singleLine="true"/>
</LinearLayout>
</LinearLayout>
<!-- 列表区域 -->
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<com.yanzhenjie.recyclerview.SwipeRecyclerView
android:id="@+id/rvLinkedUser"
android:layout_weight="1"
android:orientation="vertical">
<!-- 搜索区域 -->
<LinearLayout
android:id="@+id/searchLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:paddingHorizontal="16dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
android:paddingVertical="12dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/bg_user_manage"
android:gravity="center_vertical"
android:paddingHorizontal="16dp">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@mipmap/search"
android:layout_marginRight="8dp"/>
<EditText
android:id="@+id/etSearch"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@null"
android:hint="@string/search_preset_text"
android:textColorHint="@color/gray"
android:textColor="@color/black"
android:textSize="14sp"
android:inputType="text"
android:imeOptions="actionSearch"
android:singleLine="true"/>
</LinearLayout>
</LinearLayout>
<!-- 内容区域 -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<!-- 列表 -->
<com.ckkj.defense_device.view.PullRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.yanzhenjie.recyclerview.SwipeRecyclerView
android:id="@+id/rvLinkedUser"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:overScrollMode="never"
android:paddingBottom="16dp"/>
</com.ckkj.defense_device.view.PullRefreshLayout>
<!-- 空页面 -->
<LinearLayout
android:id="@+id/layoutNoPermission"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:visibility="gone">
<ImageView
android:layout_width="244dp"
android:layout_height="131dp"
android:src="@mipmap/default_page"/>
</LinearLayout>
<LinearLayout
android:id="@+id/layoutNoNetwork"
android:layout_width="match_parent"
android:layout_height="match_parent"
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>
</FrameLayout>
</LinearLayout>
<!-- 底部按钮 -->
<LinearLayout
android:id="@+id/butting"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -102,6 +151,8 @@
android:text="@string/add_linked_user"
android:textColor="@color/white"
android:textSize="14dp"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg" />
<!-- 悬浮加号按钮 -->
<ImageView

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,13 +39,23 @@
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvMessageList"
<com.ckkj.defense_device.view.PullRefreshLayout
android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
tools:listitem="@layout/item_message_notice" />
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvMessageList"
android:layout_width="match_parent"
android:layout_height="match_parent"
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"
@@ -58,7 +68,22 @@
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/no_message" />
android:src="@mipmap/default_page_img_name" />
</LinearLayout>
<LinearLayout
android:id="@+id/layoutNoNetwork"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical"
android:paddingHorizontal="32dp"
android:visibility="gone">
<ImageView
android:layout_width="244dp"
android:layout_height="131dp"
android:src="@mipmap/no_network_img_name" />
</LinearLayout>
<TextView

View File

@@ -41,7 +41,7 @@
android:id="@+id/tvNickname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/enter_nickname"
android:text=""
android:textColor="@color/black"
android:textSize="16dp"
android:textStyle="bold" />
@@ -54,10 +54,11 @@
android:background="@drawable/rounded_blue_bg"
android:gravity="center"
android:paddingHorizontal="8dp"
android:text="@string/admin"
android:text=""
android:textColor="@color/white"
android:textSize="11sp"
android:textStyle="bold" />
android:textStyle="bold"
android:visibility="gone"/>
</LinearLayout>
<TextView
@@ -65,10 +66,19 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:text="13589898989"
android:text=""
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"
@@ -140,14 +141,17 @@
<!-- 发送验证码按钮 -->
<android.widget.Button
android:id="@+id/sendCodeBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_width="110dp"
android:layout_height="36dp"
android:minWidth="0dp"
android:minHeight="0dp"
android:text="@string/send_sms_code"
android:textSize="16sp"
android:textSize="14dp"
android:textColor="@color/white"
android:background="@drawable/rounded_blue_bg"
android:paddingHorizontal="12dp"
android:maxWidth="120dp"
android:background="@drawable/send_code_btn_selector"
android:elevation="0dp"
android:stateListAnimator="@null"
android:gravity="center"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
@@ -159,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"
@@ -189,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"
@@ -201,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"
@@ -232,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"
@@ -244,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"
@@ -274,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"
@@ -286,13 +291,15 @@
<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"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@@ -35,6 +35,7 @@ tools:context=".login.RegisterSuccessActivity">
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="@id/backIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@@ -80,6 +81,8 @@ tools:context=".login.RegisterSuccessActivity">
android:textColor="@color/white"
android:textSize="14dp"
android:textStyle="bold"
android:elevation="0dp"
android:stateListAnimator="@null"
android:background="@drawable/rounded_blue_bg"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"

Some files were not shown because too many files have changed in this diff Show More