feat: add socket control and temperature humidity modules #7

Merged
KremeCN merged 2 commits from feat/socket-and-temperature-control into main 2026-08-25 15:41:34 +08:00
Member

Summary

  • Smart Socket Module:
    • Implemented SocketUiState and SocketViewModel with support for querying power state, toggle control, duplicate click debouncing, and state confirmation timestamping.
    • Implemented Material 3 responsive SocketScreen with power indicator badge, toggle action, explicit on/off buttons, offline banner, and error handling.
  • Temperature & Humidity Module:
    • Implemented TemperatureHumidityUiState and TemperatureHumidityViewModel for read-only environmental telemetry.
    • Implemented TemperatureHumidityScreen featuring distinct temperature and humidity cards, read-only notice, no-data placeholder, and pull-to-refresh actions.
  • Common & Infrastructure:
    • Added UiText and DomainErrorMapper for localizing domain errors (DomainError).
    • Added navigation graph extensions (deviceDetailGraph) in DeviceDetailNavigation.kt and wired into ABoxManagerApp.kt.
    • Added ReleaseModule for Hilt dependency injection in release builds.
    • Added full English and Simplified Chinese resource translations in values/strings.xml and values-zh-rCN/strings.xml.
  • Testing:
    • Added SocketViewModelTest and TemperatureHumidityViewModelTest covering normal operations, offline states, error mapping, and refresh scenarios.

Test Plan

  • Run ./gradlew test (all modules passed)
  • Run ./gradlew :android:app:testDebugUnitTest (passed)
  • Run ./gradlew assembleDebug (build successful)

?? Generated with Claude Code

## Summary - **Smart Socket Module**: - Implemented `SocketUiState` and `SocketViewModel` with support for querying power state, toggle control, duplicate click debouncing, and state confirmation timestamping. - Implemented Material 3 responsive `SocketScreen` with power indicator badge, toggle action, explicit on/off buttons, offline banner, and error handling. - **Temperature & Humidity Module**: - Implemented `TemperatureHumidityUiState` and `TemperatureHumidityViewModel` for read-only environmental telemetry. - Implemented `TemperatureHumidityScreen` featuring distinct temperature and humidity cards, read-only notice, no-data placeholder, and pull-to-refresh actions. - **Common & Infrastructure**: - Added `UiText` and `DomainErrorMapper` for localizing domain errors (`DomainError`). - Added navigation graph extensions (`deviceDetailGraph`) in `DeviceDetailNavigation.kt` and wired into `ABoxManagerApp.kt`. - Added `ReleaseModule` for Hilt dependency injection in release builds. - Added full English and Simplified Chinese resource translations in `values/strings.xml` and `values-zh-rCN/strings.xml`. - **Testing**: - Added `SocketViewModelTest` and `TemperatureHumidityViewModelTest` covering normal operations, offline states, error mapping, and refresh scenarios. ## Test Plan - [x] Run `./gradlew test` (all modules passed) - [x] Run `./gradlew :android:app:testDebugUnitTest` (passed) - [x] Run `./gradlew assembleDebug` (build successful) ?? Generated with [Claude Code](https://claude.com/claude-code)
pr-agent-bot bot reviewed 2026-08-24 14:45:35 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -14,5 +14,5 @@
@Module
@InstallIn(SingletonComponent::class)
object ReleaseModule {
@OptIn(CleartextAbsdkTransport::class)

明文传输

正式构建显式放行 CleartextAbsdkTransport,并通过它创建所有网关共用的 AbsdkBridge。这会使登录会话、设备命令和遥测数据在不可信网络中面临被窃听或篡改的风险。请为 release 使用 TLS 和服务端身份验证;若 SDK 暂不支持安全传输,应阻止该实现进入正式包。

**明文传输** 正式构建显式放行 `CleartextAbsdkTransport`,并通过它创建所有网关共用的 `AbsdkBridge`。这会使登录会话、设备命令和遥测数据在不可信网络中面临被窃听或篡改的风险。请为 release 使用 TLS 和服务端身份验证;若 SDK 暂不支持安全传输,应阻止该实现进入正式包。 <!-- pr-agent-dedup: b92da4a2b833 --> <!-- pr-agent-key-issue-location: 7d5a68567704 -->
pr-agent-bot bot reviewed 2026-08-24 14:45:35 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -13,5 +13,5 @@
object DeviceDetailRoutes {
const val SOCKET_PATTERN = "device/socket/{profileId}/{deviceName}"
const val TH_PATTERN = "device/th/{profileId}/{deviceName}"
fun socketRoute(profileId: ProviderProfileId, deviceName: DeviceName): String =

路由编码

profileId 和 deviceName 被原样拼接到路由路径中。设备名称或配置 ID 包含 /、?、# 等字符时,会被解析成额外路径段、查询参数或片段,导致目标页面无法匹配或收到截断参数。请对每个路径参数进行 URI 编码,或改用类型安全导航参数。

**路由编码** `profileId` 和 `deviceName` 被原样拼接到路由路径中。设备名称或配置 ID 包含 `/`、`?`、`#` 等字符时,会被解析成额外路径段、查询参数或片段,导致目标页面无法匹配或收到截断参数。请对每个路径参数进行 URI 编码,或改用类型安全导航参数。 <!-- pr-agent-dedup: b313370a059d --> <!-- pr-agent-key-issue-location: 48955917f6ca -->
pr-agent-bot bot reviewed 2026-08-24 14:45:35 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -76,5 +76,5 @@
}
}
}
private fun loadStatus() {

并发竞态

loadStatus() 启动前没有检查已有加载或控制操作。用户切换电源后立即点击刷新,或在离线横幅上连续重试时,可并发执行状态查询与控制请求;较晚返回的旧查询结果可能覆盖刚确认的电源状态,并提前清除加载标志。请在 ViewModel 中串行化或取消冲突请求,而不是仅依赖界面重组后的按钮禁用状态。

**并发竞态** `loadStatus()` 启动前没有检查已有加载或控制操作。用户切换电源后立即点击刷新,或在离线横幅上连续重试时,可并发执行状态查询与控制请求;较晚返回的旧查询结果可能覆盖刚确认的电源状态,并提前清除加载标志。请在 ViewModel 中串行化或取消冲突请求,而不是仅依赖界面重组后的按钮禁用状态。 <!-- pr-agent-dedup: 26949fe8183e --> <!-- pr-agent-key-issue-location: 493df4344f34 -->
pr-agent-bot bot reviewed 2026-08-24 14:45:35 +08:00
pr-agent-bot bot left a comment

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 Security concerns

明文传输:release 依赖注入显式启用了 CleartextAbsdkTransport,可能使会话信息、设备控制命令及遥测数据遭到中间人窃听或篡改。正式构建应使用经过认证的 TLS 传输。

## PR Reviewer Guide 🔍 Here are some key observations to aid the review process: <table> <tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 4 🔵🔵🔵🔵⚪</td></tr> <tr><td>🧪&nbsp;<strong>PR contains tests</strong></td></tr> <tr><td>🔒&nbsp;<strong>Security concerns</strong><br><br> 明文传输:release 依赖注入显式启用了 `CleartextAbsdkTransport`,可能使会话信息、设备控制命令及遥测数据遭到中间人窃听或篡改。正式构建应使用经过认证的 TLS 传输。</td></tr> </table>
KremeCN force-pushed feat/socket-and-temperature-control from de3a7d3626 to 4ea633a329 2026-08-25 12:05:16 +08:00 Compare
pr-agent-bot bot reviewed 2026-08-25 12:09:03 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -80,5 +80,5 @@
onLogout = sessionActionsViewModel::logout,
actionInProgress = actionState.inProgress,
errorMessage = actionState.errorMessage,
devicesSlot = {
DeviceListScreen(

设备错配

两个回调都构造固定的中文 DeviceName,而不是传递被点击设备的真实标识。只要提供商中的设备不恰好使用这两个名称,详情页就会用错误的设备键查询或控制,导致 DeviceNotFound 或操作非预期设备;应让列表项回传实际的 DeviceName。

**设备错配** 两个回调都构造固定的中文 `DeviceName`,而不是传递被点击设备的真实标识。只要提供商中的设备不恰好使用这两个名称,详情页就会用错误的设备键查询或控制,导致 `DeviceNotFound` 或操作非预期设备;应让列表项回传实际的 `DeviceName`。 <!-- pr-agent-dedup: 92be19670866 --> <!-- pr-agent-key-issue-location: b335fd4c77c4 -->
pr-agent-bot bot reviewed 2026-08-25 12:09:03 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -13,5 +13,5 @@
object DeviceDetailRoutes {
const val SOCKET_PATTERN = "device/socket/{profileId}/{deviceName}"
const val TH_PATTERN = "device/th/{profileId}/{deviceName}"
fun socketRoute(profileId: ProviderProfileId, deviceName: DeviceName): String =

路由编码

profileId.value 和 deviceName.value 被直接拼接到路由路径。设备名包含 /、?、# 或 % 时,这些字符会被解释为路由语法,导致目标无法匹配或 SavedStateHandle 收到错误参数;应对每个路径段进行 URI 编码,或改用类型安全路由。

**路由编码** `profileId.value` 和 `deviceName.value` 被直接拼接到路由路径。设备名包含 `/`、`?`、`#` 或 `%` 时,这些字符会被解释为路由语法,导致目标无法匹配或 `SavedStateHandle` 收到错误参数;应对每个路径段进行 URI 编码,或改用类型安全路由。 <!-- pr-agent-dedup: 415e3644f613 --> <!-- pr-agent-key-issue-location: 5bcef409df98 -->
pr-agent-bot bot reviewed 2026-08-25 12:09:04 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -56,5 +56,5 @@
SocketUiEvent.Refresh -> {
loadStatus()
}
SocketUiEvent.TogglePower -> {

状态误判

TogglePower 将除 ON 外的状态(包括状态查询失败后的 null)一律视为 OFF。初次查询发生非离线错误时操作仍会被启用,用户点击切换将直接发送 ON;若插座实际已开启,就不会完成预期的关闭操作。应仅在最新电源状态已确认时启用切换,或使用网关提供的原子切换操作。

**状态误判** `TogglePower` 将除 `ON` 外的状态(包括状态查询失败后的 `null`)一律视为 `OFF`。初次查询发生非离线错误时操作仍会被启用,用户点击切换将直接发送 `ON`;若插座实际已开启,就不会完成预期的关闭操作。应仅在最新电源状态已确认时启用切换,或使用网关提供的原子切换操作。 <!-- pr-agent-dedup: 435b0cd50b9c --> <!-- pr-agent-key-issue-location: 63a0922eb0b9 -->
pr-agent-bot bot reviewed 2026-08-25 12:09:04 +08:00
pr-agent-bot bot left a comment

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
## PR Reviewer Guide 🔍 Here are some key observations to aid the review process: <table> <tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 4 🔵🔵🔵🔵⚪</td></tr> <tr><td>🧪&nbsp;<strong>PR contains tests</strong></td></tr> <tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr> </table>
KremeCN force-pushed feat/socket-and-temperature-control from 4ea633a329 to f2102096f0 2026-08-25 12:49:22 +08:00 Compare
pr-agent-bot bot reviewed 2026-08-25 12:51:47 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -15,5 +15,5 @@
object DeviceDetailRoutes {
const val SOCKET_PATTERN = "device/socket/{profileId}/{deviceName}"
const val TH_PATTERN = "device/th/{profileId}/{deviceName}"
fun socketRoute(profileId: ProviderProfileId, deviceName: DeviceName): String =

参数损坏

路由使用 URLEncoder 编码,而 Navigation 已会解码路径参数,两个 ViewModel 随后又通过 URLDecoder 解码一次。设备名或配置 ID 包含字面量 +、%2F 等内容时会被二次转换,例如 A+B 会变成 A B,导致查询错误设备或找不到设备。应改用适合 URI 路径的编码方式,并移除 ViewModel 中的二次解码。

**参数损坏** 路由使用 `URLEncoder` 编码,而 Navigation 已会解码路径参数,两个 ViewModel 随后又通过 `URLDecoder` 解码一次。设备名或配置 ID 包含字面量 `+`、`%2F` 等内容时会被二次转换,例如 `A+B` 会变成 `A B`,导致查询错误设备或找不到设备。应改用适合 URI 路径的编码方式,并移除 ViewModel 中的二次解码。 <!-- pr-agent-dedup: c724c8573d3b --> <!-- pr-agent-key-issue-location: a2b312c1af6a -->
pr-agent-bot bot reviewed 2026-08-25 12:51:48 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -80,5 +80,5 @@
onLogout = sessionActionsViewModel::logout,
actionInProgress = actionState.inProgress,
errorMessage = actionState.errorMessage,
devicesSlot = {
DeviceListScreen(

目标错误

两个入口始终将固定中文 DeviceName 传给网关,而不是用户所选或配置的真实设备名。网关使用英文或自定义名称时,首次状态查询会请求不存在的设备;非中文环境中的详情页标题也会固定显示中文。应从设备列表或配置中传递实际设备名称。

**目标错误** 两个入口始终将固定中文 `DeviceName` 传给网关,而不是用户所选或配置的真实设备名。网关使用英文或自定义名称时,首次状态查询会请求不存在的设备;非中文环境中的详情页标题也会固定显示中文。应从设备列表或配置中传递实际设备名称。 <!-- pr-agent-dedup: 457a3064a89f --> <!-- pr-agent-key-issue-location: 6bac67c5129c -->
pr-agent-bot bot reviewed 2026-08-25 12:51:48 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -99,5 +99,5 @@
}
}
}
private fun loadStatus() {

并发竞态

loadStatus() 在已有加载进行中时仍会启动新协程,同时离线横幅的重试按钮在加载期间保持可点击。连续重试会产生多个并发查询;如果较新的请求先成功、较旧的请求后失败,旧结果会把界面重新覆盖为离线或错误状态,并放大网络请求。应取消前一个任务或在 ViewModel 中拒绝重复加载;温湿度读取存在相同模式。

**并发竞态** `loadStatus()` 在已有加载进行中时仍会启动新协程,同时离线横幅的重试按钮在加载期间保持可点击。连续重试会产生多个并发查询;如果较新的请求先成功、较旧的请求后失败,旧结果会把界面重新覆盖为离线或错误状态,并放大网络请求。应取消前一个任务或在 ViewModel 中拒绝重复加载;温湿度读取存在相同模式。 <!-- pr-agent-dedup: fcdaea7c543b --> <!-- pr-agent-key-issue-location: cfaff2e2577d -->
pr-agent-bot bot reviewed 2026-08-25 12:51:48 +08:00
pr-agent-bot bot left a comment

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
## PR Reviewer Guide 🔍 Here are some key observations to aid the review process: <table> <tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 4 🔵🔵🔵🔵⚪</td></tr> <tr><td>🧪&nbsp;<strong>PR contains tests</strong></td></tr> <tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr> </table>
KremeCN force-pushed feat/socket-and-temperature-control from f2102096f0 to 9872d74dfc 2026-08-25 13:12:06 +08:00 Compare
pr-agent-bot bot reviewed 2026-08-25 13:15:02 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -21,5 +21,5 @@
fun temperatureHumidityRoute(profileId: ProviderProfileId, deviceName: DeviceName): String =
"device/th/${encode(profileId.value)}/${encode(deviceName.value)}"
private fun encode(value: String): String =

参数损坏

URLEncoder 按表单规则编码路径段,而 Navigation 会先对路由参数进行 URI 解码,两个 ViewModel 随后又使用 URLDecoder 再次解码。设备名或配置 ID 包含字面量 +、%2F 等内容时会被改成空格或其他字符,导致查询错误设备。请改用 URI 路径编码,并确保参数只解码一次。

**参数损坏** `URLEncoder` 按表单规则编码路径段,而 Navigation 会先对路由参数进行 URI 解码,两个 ViewModel 随后又使用 `URLDecoder` 再次解码。设备名或配置 ID 包含字面量 `+`、`%2F` 等内容时会被改成空格或其他字符,导致查询错误设备。请改用 URI 路径编码,并确保参数只解码一次。 <!-- pr-agent-dedup: c29a805821a6 --> <!-- pr-agent-key-issue-location: e9c2126df1d9 -->
pr-agent-bot bot reviewed 2026-08-25 13:15:02 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -99,5 +99,5 @@
}
}
}
private fun loadStatus() {

请求竞态

loadStatus() 未阻止加载期间再次启动请求,而离线横幅的重试按钮在 isLoading 时仍可点击。连续点击会创建多个并发 getStatus 请求,较早请求若更晚返回,可能覆盖较新的成功状态。应禁用重试,或在 ViewModel 中取消、复用当前加载任务。

**请求竞态** `loadStatus()` 未阻止加载期间再次启动请求,而离线横幅的重试按钮在 `isLoading` 时仍可点击。连续点击会创建多个并发 `getStatus` 请求,较早请求若更晚返回,可能覆盖较新的成功状态。应禁用重试,或在 ViewModel 中取消、复用当前加载任务。 <!-- pr-agent-dedup: c733d5e17ec4 --> <!-- pr-agent-key-issue-location: 841545d47991 -->
pr-agent-bot bot reviewed 2026-08-25 13:15:03 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -86,5 +86,5 @@
}
}
}
private fun loadReading(isRefresh: Boolean) {

请求竞态

loadReading() 没有检查已有加载或刷新任务;离线和无数据卡片中的刷新按钮在 isRefreshing 时仍保持可用。重复点击会并发查询,同一设备的旧响应可能在新响应之后写回并恢复过期数据或错误状态。应增加单请求保护或取消前一个任务。

**请求竞态** `loadReading()` 没有检查已有加载或刷新任务;离线和无数据卡片中的刷新按钮在 `isRefreshing` 时仍保持可用。重复点击会并发查询,同一设备的旧响应可能在新响应之后写回并恢复过期数据或错误状态。应增加单请求保护或取消前一个任务。 <!-- pr-agent-dedup: b5fefd026594 --> <!-- pr-agent-key-issue-location: f783d87c8a1d -->
pr-agent-bot bot reviewed 2026-08-25 13:15:03 +08:00
pr-agent-bot bot left a comment

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
## PR Reviewer Guide 🔍 Here are some key observations to aid the review process: <table> <tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 4 🔵🔵🔵🔵⚪</td></tr> <tr><td>🧪&nbsp;<strong>PR contains tests</strong></td></tr> <tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr> </table>
KremeCN force-pushed feat/socket-and-temperature-control from 9872d74dfc to 8612229e93 2026-08-25 13:19:07 +08:00 Compare
pr-agent-bot bot reviewed 2026-08-25 13:23:23 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -79,5 +79,5 @@
SocketUiEvent.Refresh -> {
loadStatus()
}
SocketUiEvent.TogglePower -> {

切换误操作

当状态查询因网络、认证等非离线错误失败时,powerState 仍为 null,但操作按钮保持可用;这里会把未知状态当作关闭并发送开启命令。若插座实际已开启,用户点击切换不会将其关闭。应在状态未知时禁用切换或先重新确认状态。

**切换误操作** 当状态查询因网络、认证等非离线错误失败时,`powerState` 仍为 `null`,但操作按钮保持可用;这里会把未知状态当作关闭并发送开启命令。若插座实际已开启,用户点击切换不会将其关闭。应在状态未知时禁用切换或先重新确认状态。 <!-- pr-agent-dedup: 1dc330c04ae1 --> <!-- pr-agent-key-issue-location: 59baf676adb0 -->
pr-agent-bot bot reviewed 2026-08-25 13:23:23 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -15,5 +15,5 @@
object DeviceDetailRoutes {
const val SOCKET_PATTERN = "device/socket/{profileId}/{deviceName}"
const val TH_PATTERN = "device/th/{profileId}/{deviceName}"
fun socketRoute(profileId: ProviderProfileId, deviceName: DeviceName): String =

参数损坏

Navigation 在写入 SavedStateHandle 前已经解码路径参数,而两个 ViewModel 又使用 URLDecoder 再次解码。设备名或配置 ID 包含字面量 +、%2F 等内容时会被转换成空格或斜杠,导致网关查询错误的设备。应使用适合路径的编码方式,并确保参数只解码一次。

**参数损坏** Navigation 在写入 `SavedStateHandle` 前已经解码路径参数,而两个 ViewModel 又使用 `URLDecoder` 再次解码。设备名或配置 ID 包含字面量 `+`、`%2F` 等内容时会被转换成空格或斜杠,导致网关查询错误的设备。应使用适合路径的编码方式,并确保参数只解码一次。 <!-- pr-agent-dedup: 95fc3b6d0547 --> <!-- pr-agent-key-issue-location: 1d90e03d542c -->
pr-agent-bot bot reviewed 2026-08-25 13:23:23 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -90,5 +90,5 @@
private fun loadReading(isRefresh: Boolean) {
val profileId = _state.value.profileId ?: return
val deviceName = _state.value.deviceName ?: return
viewModelScope.launch {

竞态覆盖

每次刷新都会启动新的协程,且没有检查、取消或标记已有请求;离线和无数据卡片中的重试按钮在刷新期间仍可点击,因此连续点击会产生并发查询。较早的请求若更晚返回,会覆盖较新的温湿度数据或离线状态。应阻止重复请求、取消前一个任务或使用请求序号丢弃过期结果。

**竞态覆盖** 每次刷新都会启动新的协程,且没有检查、取消或标记已有请求;离线和无数据卡片中的重试按钮在刷新期间仍可点击,因此连续点击会产生并发查询。较早的请求若更晚返回,会覆盖较新的温湿度数据或离线状态。应阻止重复请求、取消前一个任务或使用请求序号丢弃过期结果。 <!-- pr-agent-dedup: ac64916bfacc --> <!-- pr-agent-key-issue-location: b270b65e02dc -->
pr-agent-bot bot reviewed 2026-08-25 13:23:23 +08:00
pr-agent-bot bot left a comment

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
## PR Reviewer Guide 🔍 Here are some key observations to aid the review process: <table> <tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 4 🔵🔵🔵🔵⚪</td></tr> <tr><td>🧪&nbsp;<strong>PR contains tests</strong></td></tr> <tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr> </table>
KremeCN force-pushed feat/socket-and-temperature-control from 8612229e93 to 525b97c7c5 2026-08-25 13:41:17 +08:00 Compare
pr-agent-bot bot reviewed 2026-08-25 13:45:29 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -21,5 +21,5 @@
fun temperatureHumidityRoute(profileId: ProviderProfileId, deviceName: DeviceName): String =
"device/th/${encode(profileId.value)}/${encode(deviceName.value)}"
private fun encode(value: String): String =

重复解码

NavController 会先对路径参数做 URI 解码,再将其写入 SavedStateHandle,但两个详情 ViewModel 又使用 URLDecoder 解码一次。设备名包含字面量 + 时,%2B 会先还原为 +,随后被转换为空格,导致网关使用错误名称查询。请使用适合路径段的编码方式(如 Uri.encode),并在 ViewModel 中直接读取已解码的参数。

**重复解码** `NavController` 会先对路径参数做 URI 解码,再将其写入 `SavedStateHandle`,但两个详情 ViewModel 又使用 `URLDecoder` 解码一次。设备名包含字面量 `+` 时,`%2B` 会先还原为 `+`,随后被转换为空格,导致网关使用错误名称查询。请使用适合路径段的编码方式(如 `Uri.encode`),并在 ViewModel 中直接读取已解码的参数。 <!-- pr-agent-dedup: 833499ba759a --> <!-- pr-agent-key-issue-location: c68ca69c00c3 -->
pr-agent-bot bot reviewed 2026-08-25 13:45:29 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -99,5 +99,5 @@
}
}
}
private fun loadStatus() {

并发刷新

loadStatus() 未阻止已有请求期间再次启动查询,而离线横幅的重试按钮在 isLoading 时仍可点击,因此连续点击会产生多个并发请求。任一请求先完成都会提前清除加载状态,较早请求若后返回还可能覆盖较新的状态或刚完成的开关结果。请取消或复用前一个任务,或在入口拒绝重复刷新。

**并发刷新** `loadStatus()` 未阻止已有请求期间再次启动查询,而离线横幅的重试按钮在 `isLoading` 时仍可点击,因此连续点击会产生多个并发请求。任一请求先完成都会提前清除加载状态,较早请求若后返回还可能覆盖较新的状态或刚完成的开关结果。请取消或复用前一个任务,或在入口拒绝重复刷新。 <!-- pr-agent-dedup: 3020d29bfe52 --> <!-- pr-agent-key-issue-location: 7b6412a8dc94 -->
pr-agent-bot bot reviewed 2026-08-25 13:45:29 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -86,5 +86,5 @@
}
}
}
private fun loadReading(isRefresh: Boolean) {

并发刷新

loadReading() 同样允许重入,而离线和无数据卡片中的刷新按钮在 isRefreshing 时仍保持可用。连续点击会并发查询,先完成的请求会提前清除刷新标记,后返回的旧读数可能覆盖更新的数据。请对刷新任务做互斥或取消,并让所有重试入口遵守加载状态。

**并发刷新** `loadReading()` 同样允许重入,而离线和无数据卡片中的刷新按钮在 `isRefreshing` 时仍保持可用。连续点击会并发查询,先完成的请求会提前清除刷新标记,后返回的旧读数可能覆盖更新的数据。请对刷新任务做互斥或取消,并让所有重试入口遵守加载状态。 <!-- pr-agent-dedup: aafb1a3acfd7 --> <!-- pr-agent-key-issue-location: 25221ee6876c -->
pr-agent-bot bot reviewed 2026-08-25 13:45:29 +08:00
pr-agent-bot bot left a comment

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
## PR Reviewer Guide 🔍 Here are some key observations to aid the review process: <table> <tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 4 🔵🔵🔵🔵⚪</td></tr> <tr><td>🧪&nbsp;<strong>PR contains tests</strong></td></tr> <tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr> </table>
KremeCN added 2 commits 2026-08-25 15:37:38 +08:00
Implement ViewModel state management, Compose UI screens, navigation routing,
localized strings, and comprehensive unit tests for smart socket control and
temperature/humidity telemetry monitoring.
- connect DeviceListScreen callbacks to smart socket and temperature/humidity detail routes
- set default smart socket device name to 'Zigbee插座控制器'
- add in-screen dialog to dynamically edit and switch gateway device names
- encode and decode route parameters for non-ASCII device names
- add unit test coverage for dynamic device name switching
KremeCN force-pushed feat/socket-and-temperature-control from 525b97c7c5 to dc45e6affc 2026-08-25 15:37:38 +08:00 Compare
KremeCN merged commit 30d6d39597 into main 2026-08-25 15:41:34 +08:00
KremeCN deleted branch feat/socket-and-temperature-control 2026-08-25 15:41:34 +08:00
pr-agent-bot bot reviewed 2026-08-25 15:42:04 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -21,5 +21,5 @@
fun temperatureHumidityRoute(profileId: ProviderProfileId, deviceName: DeviceName): String =
"device/th/${encode(profileId.value)}/${encode(deviceName.value)}"
private fun encode(value: String): String =

参数损坏

这里使用表单语义的 URLEncoder,而两个 ViewModel 又对 Navigation 已解码的参数调用 URLDecoder,会发生二次解码。设备名或配置 ID 含字面量 +(如 plug+1)时会被改成空格,含 %xx 的名称也可能被再次改写,最终查询错误设备或提示设备不存在。请使用适合 URI 路径段的编码方式,并确保参数只解码一次。

**参数损坏** 这里使用表单语义的 `URLEncoder`,而两个 ViewModel 又对 Navigation 已解码的参数调用 `URLDecoder`,会发生二次解码。设备名或配置 ID 含字面量 `+`(如 `plug+1`)时会被改成空格,含 `%xx` 的名称也可能被再次改写,最终查询错误设备或提示设备不存在。请使用适合 URI 路径段的编码方式,并确保参数只解码一次。 <!-- pr-agent-dedup: 4447b042dc5e --> <!-- pr-agent-key-issue-location: dec8bcb138f1 -->
pr-agent-bot bot reviewed 2026-08-25 15:42:04 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -14,5 +14,5 @@
val isOffline: Boolean = false,
val errorMessage: UiText? = null,
val lastConfirmedTimeMillis: Long? = null,
) {
val isActionEnabled: Boolean

状态误判

isActionEnabled 未要求 powerState 已知。首次状态查询发生非离线错误后,操作按钮仍会启用,而 isPowerOn 会把未知状态当作关闭;此时点击主按钮会直接发送开启命令,即使插座实际已经开启,也无法执行用户预期的切换。应在状态未知时禁用切换操作,或为显式开关命令和切换命令设置不同的启用条件。

**状态误判** `isActionEnabled` 未要求 `powerState` 已知。首次状态查询发生非离线错误后,操作按钮仍会启用,而 `isPowerOn` 会把未知状态当作关闭;此时点击主按钮会直接发送开启命令,即使插座实际已经开启,也无法执行用户预期的切换。应在状态未知时禁用切换操作,或为显式开关命令和切换命令设置不同的启用条件。 <!-- pr-agent-dedup: 29c149b676fa --> <!-- pr-agent-key-issue-location: b1909aef2858 -->
pr-agent-bot bot reviewed 2026-08-25 15:42:05 +08:00
pr-agent-bot bot left a comment

Inline comment

Inline comment
@@ -86,5 +86,5 @@
}
}
}
private fun loadReading(isRefresh: Boolean) {

请求竞态

每次刷新都会直接启动新的协程,既未拒绝已有加载,也未取消旧请求;同时无数据和离线卡片中的刷新按钮在刷新期间仍保持可点击。快速重复点击会并发执行多个 getReading,较早请求若较晚返回,就会覆盖较新的遥测结果并提前清除加载状态。建议保存并取消前一个任务,或在入口原子地阻止重复请求;SocketViewModel 的状态加载也存在相同模式。

**请求竞态** 每次刷新都会直接启动新的协程,既未拒绝已有加载,也未取消旧请求;同时无数据和离线卡片中的刷新按钮在刷新期间仍保持可点击。快速重复点击会并发执行多个 `getReading`,较早请求若较晚返回,就会覆盖较新的遥测结果并提前清除加载状态。建议保存并取消前一个任务,或在入口原子地阻止重复请求;`SocketViewModel` 的状态加载也存在相同模式。 <!-- pr-agent-dedup: 338e84eec9ad --> <!-- pr-agent-key-issue-location: c8830876f12e -->
pr-agent-bot bot reviewed 2026-08-25 15:42:05 +08:00
pr-agent-bot bot left a comment

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
## PR Reviewer Guide 🔍 Here are some key observations to aid the review process: <table> <tr><td>⏱️&nbsp;<strong>Estimated effort to review</strong>: 4 🔵🔵🔵🔵⚪</td></tr> <tr><td>🧪&nbsp;<strong>PR contains tests</strong></td></tr> <tr><td>🔒&nbsp;<strong>No security concerns identified</strong></td></tr> </table>
Sign in to join this conversation.
No Reviewers
No labels
3 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Flagship/abox-mgr#7