feat(ui): complete ABox app experience redesign #10

Merged
KremeCN merged 40 commits from feat/ui-ux-redesign into main 2026-08-27 23:25:08 +08:00
4 changed files with 44 additions and 13 deletions
Showing only changes of commit 26ab5095e5 - Show all commits
@@ -239,7 +239,7 @@ fun AuthenticatedNavigation(
profile.id, profile.id,
selection.deviceName, selection.deviceName,
) )
DeviceType.INFRARED -> "${Routes.INFRARED_HUB}/${Uri.encode(profile.id.value)}" DeviceType.INFRARED -> Routes.infraredHub(profile.id.value, selection.deviceName.value)
} }
}, },
selectionForRoute = { route -> profile.selectionForRoute(route) }, selectionForRoute = { route -> profile.selectionForRoute(route) },
@@ -367,9 +367,12 @@ private fun AuthenticatedNavHost(
) )
sceneExecutionDestination(onBack = navController::popBackStack) sceneExecutionDestination(onBack = navController::popBackStack)
composable( composable(
route = "${Routes.INFRARED_HUB}/{profileId}", route = Routes.INFRARED_HUB,
arguments = listOf(navArgument("profileId") { type = NavType.StringType }), arguments = listOf(
) { navArgument("profileId") { type = NavType.StringType },
navArgument("deviceName") { type = NavType.StringType },
),
) { entry ->
InfraredHubRoute( InfraredHubRoute(
onBack = { onBack = {
onClearDeviceSelection() onClearDeviceSelection()
@@ -381,7 +384,8 @@ private fun AuthenticatedNavHost(
) )
}, },
onOpenSettings = { onOpenSettings = {
navController.navigate("${Routes.INFRARED_SETTINGS}/${Uri.encode(profile.id.value)}") val deviceName = entry.arguments?.getString("deviceName").orEmpty()
navController.navigate(Routes.infraredSettings(profile.id.value, deviceName))
}, },
onSessionInvalid = onLogout, onSessionInvalid = onLogout,
) )
@@ -399,8 +403,11 @@ private fun AuthenticatedNavHost(
) )
} }
composable( composable(
route = "${Routes.INFRARED_SETTINGS}/{profileId}", route = Routes.INFRARED_SETTINGS,
arguments = listOf(navArgument("profileId") { type = NavType.StringType }), arguments = listOf(
navArgument("profileId") { type = NavType.StringType },
navArgument("deviceName") { type = NavType.StringType },
),
) { ) {
InfraredControlRoute( InfraredControlRoute(
onBack = navController::popBackStack, onBack = navController::popBackStack,
@@ -473,9 +480,9 @@ private fun ProviderProfile.selectionForRoute(route: String?): DeviceSelection?
deviceName = DeviceName(parts.drop(3).joinToString("/").decodeRoutePart()), deviceName = DeviceName(parts.drop(3).joinToString("/").decodeRoutePart()),
type = DeviceType.TEMPERATURE_HUMIDITY, type = DeviceType.TEMPERATURE_HUMIDITY,
) )
parts.size >= 2 && parts[0] == Routes.INFRARED_HUB -> DeviceSelection( parts.size >= 3 && parts[0] == "infrared_hub" -> DeviceSelection(
profileId = id, profileId = id,
deviceName = DeviceName("红外遥控器"), deviceName = DeviceName(parts[2].decodeRoutePart()),
type = DeviceType.INFRARED, type = DeviceType.INFRARED,
) )
else -> null else -> null
@@ -1,5 +1,7 @@
package com.flagship.abox.manager.ui.navigation package com.flagship.abox.manager.ui.navigation
import android.net.Uri
object Routes { object Routes {
const val STARTUP = "startup" const val STARTUP = "startup"
const val PROVIDER_SELECTION = "provider_selection" const val PROVIDER_SELECTION = "provider_selection"
@@ -7,7 +9,13 @@ object Routes {
const val AUTHENTICATED = "authenticated" const val AUTHENTICATED = "authenticated"
const val SETTINGS = "settings" const val SETTINGS = "settings"
const val INFRARED_HUB = "infrared_hub" const val INFRARED_HUB = "infrared_hub/{profileId}/{deviceName}"
const val INFRARED_PANEL = "infrared_panel" const val INFRARED_PANEL = "infrared_panel"
const val INFRARED_SETTINGS = "infrared_settings" const val INFRARED_SETTINGS = "infrared_settings/{profileId}/{deviceName}"
fun infraredHub(profileId: String, deviceName: String): String =
"infrared_hub/${Uri.encode(profileId)}/${Uri.encode(deviceName)}"
fun infraredSettings(profileId: String, deviceName: String): String =
"infrared_settings/${Uri.encode(profileId)}/${Uri.encode(deviceName)}"
} }
@@ -49,6 +49,9 @@ class InfraredHubViewModel @Inject constructor(
val profileId: ProviderProfileId? = rawProfileId?.takeIf { it.isNotBlank() }?.let { ProviderProfileId(it) } val profileId: ProviderProfileId? = rawProfileId?.takeIf { it.isNotBlank() }?.let { ProviderProfileId(it) }
private val messageFlow = MutableStateFlow<HubUiMessage?>(null) private val messageFlow = MutableStateFlow<HubUiMessage?>(null)
private val routeDeviceName: DeviceName? = savedStateHandle.get<String>("deviceName")
?.takeIf { it.isNotBlank() }
?.let(::DeviceName)
private data class HubSnapshot( private data class HubSnapshot(
val providerProfile: ProviderProfile? = null, val providerProfile: ProviderProfile? = null,
@@ -68,7 +71,12 @@ class InfraredHubViewModel @Inject constructor(
when (devResult) { when (devResult) {
is DomainResult.Failure -> flowOf(HubSnapshot(providerProfile = active, configError = devResult.error)) is DomainResult.Failure -> flowOf(HubSnapshot(providerProfile = active, configError = devResult.error))
is DomainResult.Success -> { is DomainResult.Success -> {
val deviceName = devResult.value ?: DEFAULT_DEVICE_NAME val deviceName = routeDeviceName ?: devResult.value ?: return@flatMapLatest flowOf(
HubSnapshot(
providerProfile = active,
configError = DomainError.InvalidRequest(detail = "No infrared device configured"),
),
)
combine( combine(
virtualRemoteRepository.observeRemotes(profileId, deviceName), virtualRemoteRepository.observeRemotes(profileId, deviceName),
virtualRemoteRepository.observeSlotQuota(profileId, deviceName), virtualRemoteRepository.observeSlotQuota(profileId, deviceName),
@@ -40,6 +40,9 @@ class InfraredControlViewModel @Inject constructor(
private val deviceNameInputFlow = MutableStateFlow<String?>(null) private val deviceNameInputFlow = MutableStateFlow<String?>(null)
private val messageFlow = MutableStateFlow<InfraredUiMessage?>(null) private val messageFlow = MutableStateFlow<InfraredUiMessage?>(null)
private val routeDeviceName: DeviceName? = savedStateHandle.get<String>("deviceName")
?.takeIf { it.isNotBlank() }
?.let(::DeviceName)
private data class ServerState( private data class ServerState(
val providerProfile: com.flagship.abox.manager.domain.ProviderProfile? = null, val providerProfile: com.flagship.abox.manager.domain.ProviderProfile? = null,
@@ -65,7 +68,12 @@ class InfraredControlViewModel @Inject constructor(
), ),
) )
is DomainResult.Success -> { is DomainResult.Success -> {
val deviceName = deviceResult.value ?: DEFAULT_DEVICE_NAME val deviceName = routeDeviceName ?: deviceResult.value ?: return@flatMapLatest flowOf(
ServerState(
providerProfile = active,
configError = DomainError.InvalidRequest(detail = "No infrared device configured"),
),
)
combine( combine(
virtualRemoteRepository.observeRemotes(profileId, deviceName), virtualRemoteRepository.observeRemotes(profileId, deviceName),
virtualRemoteRepository.observeSlotQuota(profileId, deviceName), virtualRemoteRepository.observeSlotQuota(profileId, deviceName),