fix(ui): stabilize theme and navigation behavior
This commit is contained in:
-2
@@ -57,7 +57,6 @@ class SettingsScreenTest {
|
||||
"settings-theme-mode",
|
||||
"settings-color-style",
|
||||
"settings-color-standard",
|
||||
"settings-predictive-back",
|
||||
"settings-interface-scale",
|
||||
)
|
||||
composeRule.onAllNodesWithTag("settings-renderer-selector").assertCountEquals(0)
|
||||
@@ -74,7 +73,6 @@ class SettingsScreenTest {
|
||||
"settings-blur",
|
||||
"settings-floating-navigation",
|
||||
"settings-liquid-glass",
|
||||
"settings-predictive-back",
|
||||
"settings-interface-scale",
|
||||
)
|
||||
composeRule.onAllNodesWithTag("settings-renderer-selector").assertCountEquals(0)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<application
|
||||
android:name=".ABoxManagerApplication"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:enableOnBackInvokedCallback="false"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
|
||||
@@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.safeDrawing
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -20,6 +19,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.flagship.abox.manager.designsystem.ABoxLoadingIndicator
|
||||
import com.flagship.abox.manager.designsystem.ABoxSurface
|
||||
import com.flagship.abox.manager.designsystem.ABoxTheme
|
||||
import com.flagship.abox.manager.designsystem.ABoxBackLayer
|
||||
import com.flagship.abox.manager.domain.ProviderProfile
|
||||
import com.flagship.abox.manager.domain.Username
|
||||
import com.flagship.abox.manager.ui.login.LoginRoute
|
||||
@@ -108,13 +108,7 @@ private fun AuthenticatedNavHost(
|
||||
onShowAccountDialogChange: (Boolean) -> Unit,
|
||||
onShowSettingsChange: (Boolean) -> Unit,
|
||||
) {
|
||||
if (showSettings) {
|
||||
BackHandler { onShowSettingsChange(false) }
|
||||
SettingsRoute(
|
||||
experienceState = settingsExperienceState,
|
||||
onExit = { onShowSettingsChange(false) },
|
||||
)
|
||||
} else {
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
AuthenticatedNavigation(
|
||||
username = username,
|
||||
profile = profile,
|
||||
@@ -122,6 +116,17 @@ private fun AuthenticatedNavHost(
|
||||
onProviderClick = { onShowAccountDialogChange(true) },
|
||||
onAvatarClick = { onShowAccountDialogChange(true) },
|
||||
)
|
||||
if (showSettings) {
|
||||
ABoxBackLayer(
|
||||
onBack = { onShowSettingsChange(false) },
|
||||
enabled = settingsExperienceState.isHome,
|
||||
) {
|
||||
SettingsRoute(
|
||||
experienceState = settingsExperienceState,
|
||||
onExit = { onShowSettingsChange(false) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showAccountDialog) {
|
||||
@@ -131,6 +136,7 @@ private fun AuthenticatedNavHost(
|
||||
onDismissRequest = { onShowAccountDialogChange(false) },
|
||||
onOpenSettings = {
|
||||
onShowAccountDialogChange(false)
|
||||
settingsExperienceState.showTheme(returnToHome = false)
|
||||
onShowSettingsChange(true)
|
||||
},
|
||||
onSwitchProvider = {
|
||||
|
||||
+46
-78
@@ -1,15 +1,11 @@
|
||||
package com.flagship.abox.manager.ui.navigation
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.PredictiveBackHandler
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -17,14 +13,11 @@ import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.Saver
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
@@ -60,9 +53,6 @@ import com.flagship.abox.manager.ui.shell.MainAppShell
|
||||
import com.flagship.abox.manager.ui.shell.MainTab
|
||||
import com.flagship.abox.manager.ui.shell.detailPresentation
|
||||
import com.flagship.abox.manager.ui.shell.widthClass
|
||||
import com.flagship.abox.manager.designsystem.LocalABoxPredictiveBackEnabled
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import com.flagship.abox.manager.ui.socket.SocketScreen
|
||||
import com.flagship.abox.manager.ui.socket.SocketUiEvent
|
||||
import com.flagship.abox.manager.ui.socket.SocketViewModel
|
||||
@@ -169,12 +159,11 @@ fun AuthenticatedNavigationStateHost(
|
||||
}
|
||||
|
||||
val selection = selectedDevice ?: return@LaunchedEffect
|
||||
if (savedSelection != selection) {
|
||||
savedSelection = selection
|
||||
}
|
||||
|
||||
when (detailPresentation(windowSizeClass, selection)) {
|
||||
DetailPresentation.PANE -> {
|
||||
if (savedSelection != selection) {
|
||||
savedSelection = selection
|
||||
}
|
||||
if (currentRoute != MainTab.DEVICES.route) {
|
||||
navController.popBackStack(MainTab.DEVICES.route, inclusive = false)
|
||||
}
|
||||
@@ -184,6 +173,9 @@ fun AuthenticatedNavigationStateHost(
|
||||
navController.navigate(deviceRoute(selection)) {
|
||||
launchSingleTop = true
|
||||
}
|
||||
savedSelection = null
|
||||
} else if (savedSelection != null) {
|
||||
savedSelection = null
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,60 +186,25 @@ fun AuthenticatedNavigationStateHost(
|
||||
selectedDevice = selectedDevice,
|
||||
detailPresentation = selectedPresentation,
|
||||
)
|
||||
val canNavigateBack = selectedDevice != null || currentRoute != MainTab.DEVICES.route
|
||||
val predictiveBackEnabled = LocalABoxPredictiveBackEnabled.current &&
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
||||
var backProgress by remember { mutableFloatStateOf(0f) }
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
.clip(RoundedCornerShape((28f * backProgress).dp))
|
||||
.graphicsLayer {
|
||||
val scale = 1f - 0.04f * backProgress
|
||||
scaleX = scale
|
||||
scaleY = scale
|
||||
translationX = 28.dp.toPx() * backProgress
|
||||
},
|
||||
) {
|
||||
content(
|
||||
snapshot,
|
||||
{ tab ->
|
||||
savedSelection = null
|
||||
if (tab == currentTab) {
|
||||
navController.popBackStack(tab.route, inclusive = false)
|
||||
} else {
|
||||
navController.navigate(tab.route) {
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
popUpTo(navController.graph.startDestinationId) {
|
||||
saveState = true
|
||||
}
|
||||
content(
|
||||
snapshot,
|
||||
{ tab ->
|
||||
savedSelection = null
|
||||
if (tab == currentTab) {
|
||||
navController.popBackStack(tab.route, inclusive = false)
|
||||
} else {
|
||||
navController.navigate(tab.route) {
|
||||
launchSingleTop = true
|
||||
restoreState = true
|
||||
popUpTo(navController.graph.startDestinationId) {
|
||||
saveState = true
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deviceName, type ->
|
||||
savedSelection = DeviceSelection(profileId, deviceName, type)
|
||||
},
|
||||
{ savedSelection = null },
|
||||
)
|
||||
}
|
||||
|
||||
PredictiveBackHandler(enabled = canNavigateBack && predictiveBackEnabled) { events ->
|
||||
try {
|
||||
events.collect { event -> backProgress = event.progress }
|
||||
savedSelection = null
|
||||
navController.popCurrentDestinationUnlessRoot()
|
||||
} catch (cancelled: CancellationException) {
|
||||
throw cancelled
|
||||
} finally {
|
||||
backProgress = 0f
|
||||
}
|
||||
}
|
||||
|
||||
BackHandler(enabled = canNavigateBack && !predictiveBackEnabled) {
|
||||
savedSelection = null
|
||||
navController.popCurrentDestinationUnlessRoot()
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deviceName, type -> savedSelection = DeviceSelection(profileId, deviceName, type) },
|
||||
{ savedSelection = null },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -355,6 +312,23 @@ private fun AuthenticatedNavHost(
|
||||
onLogout: () -> Unit,
|
||||
onOpenSettings: () -> Unit,
|
||||
) {
|
||||
val activeBackStackEntry by navController.currentBackStackEntryAsState()
|
||||
val activeRoute = activeBackStackEntry?.actualRoute()
|
||||
var backNavigationInFlight by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(activeRoute) {
|
||||
backNavigationInFlight = false
|
||||
}
|
||||
val navigateBackFromDetail: () -> Unit = {
|
||||
val liveRoute = navController.currentBackStackEntry?.actualRoute()
|
||||
val isMainDestination = MainTab.entries.any { it.route == liveRoute }
|
||||
if (!backNavigationInFlight && !isMainDestination) {
|
||||
backNavigationInFlight = true
|
||||
if (!navController.popBackStack()) {
|
||||
backNavigationInFlight = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = MainTab.DEVICES.route,
|
||||
@@ -427,14 +401,14 @@ private fun AuthenticatedNavHost(
|
||||
deviceDetailGraph(
|
||||
onBackClick = {
|
||||
onClearDeviceSelection()
|
||||
navController.popBackStack()
|
||||
navigateBackFromDetail()
|
||||
},
|
||||
)
|
||||
sceneEditorDestination(
|
||||
onBack = navController::popBackStack,
|
||||
onBack = navigateBackFromDetail,
|
||||
onSaved = { navController.popBackStack(MainTab.SCENES.route, inclusive = false) },
|
||||
)
|
||||
sceneExecutionDestination(onBack = navController::popBackStack)
|
||||
sceneExecutionDestination(onBack = navigateBackFromDetail)
|
||||
composable(
|
||||
route = Routes.INFRARED_HUB,
|
||||
arguments = listOf(
|
||||
@@ -445,7 +419,7 @@ private fun AuthenticatedNavHost(
|
||||
InfraredHubRoute(
|
||||
onBack = {
|
||||
onClearDeviceSelection()
|
||||
navController.popBackStack()
|
||||
navigateBackFromDetail()
|
||||
},
|
||||
onOpenRemote = { remoteId ->
|
||||
navController.navigate(
|
||||
@@ -467,7 +441,7 @@ private fun AuthenticatedNavHost(
|
||||
),
|
||||
) {
|
||||
RemotePanelRoute(
|
||||
onBack = navController::popBackStack,
|
||||
onBack = navigateBackFromDetail,
|
||||
onSessionInvalid = onLogout,
|
||||
)
|
||||
}
|
||||
@@ -479,7 +453,7 @@ private fun AuthenticatedNavHost(
|
||||
),
|
||||
) {
|
||||
InfraredControlRoute(
|
||||
onBack = navController::popBackStack,
|
||||
onBack = navigateBackFromDetail,
|
||||
onSessionInvalid = onLogout,
|
||||
)
|
||||
}
|
||||
@@ -561,9 +535,3 @@ private fun NavBackStackEntry.actualRoute(): String? {
|
||||
}
|
||||
return route
|
||||
}
|
||||
|
||||
private fun NavHostController.popCurrentDestinationUnlessRoot() {
|
||||
if (currentBackStackEntry?.actualRoute() != MainTab.DEVICES.route) {
|
||||
popBackStack()
|
||||
}
|
||||
}
|
||||
|
||||
+66
-44
@@ -1,6 +1,5 @@
|
||||
package com.flagship.abox.manager.ui.preferences
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -58,6 +57,9 @@ import com.flagship.abox.manager.designsystem.LocalABoxShapes
|
||||
import com.flagship.abox.manager.designsystem.icon.ABoxIcon
|
||||
import com.flagship.abox.manager.designsystem.icon.ABoxIconKey
|
||||
import com.flagship.abox.manager.preferences.AccentPreference
|
||||
import com.flagship.abox.manager.designsystem.tokens.ABoxAccentPalette
|
||||
import com.flagship.abox.manager.designsystem.ABoxBackLayer
|
||||
import com.flagship.abox.manager.designsystem.ABoxSurface
|
||||
import com.flagship.abox.manager.preferences.AppThemeMode
|
||||
import com.flagship.abox.manager.preferences.DesignRenderer
|
||||
import com.flagship.abox.manager.preferences.InterfaceScale
|
||||
@@ -70,11 +72,27 @@ private enum class SettingsDestination { HOME, THEME }
|
||||
@Stable
|
||||
class SettingsExperienceState(initialDestination: String = SettingsDestination.HOME.name) {
|
||||
var destination: String by mutableStateOf(initialDestination)
|
||||
var themeBackReturnsHome: Boolean by mutableStateOf(true)
|
||||
val isHome: Boolean get() = destination == SettingsDestination.HOME.name
|
||||
|
||||
fun showHome() {
|
||||
destination = SettingsDestination.HOME.name
|
||||
}
|
||||
|
||||
fun showTheme(returnToHome: Boolean = true) {
|
||||
themeBackReturnsHome = returnToHome
|
||||
destination = SettingsDestination.THEME.name
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Saver: Saver<SettingsExperienceState, String> = Saver(
|
||||
save = { it.destination },
|
||||
restore = { SettingsExperienceState(it) },
|
||||
save = { "${it.destination}|${it.themeBackReturnsHome}" },
|
||||
restore = { saved ->
|
||||
val parts = saved.split('|', limit = 2)
|
||||
SettingsExperienceState(parts.first()).apply {
|
||||
themeBackReturnsHome = parts.getOrNull(1)?.toBooleanStrictOrNull() ?: true
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -98,22 +116,29 @@ fun SettingsExperience(
|
||||
) {
|
||||
val destination = runCatching { SettingsDestination.valueOf(experienceState.destination) }
|
||||
.getOrDefault(SettingsDestination.HOME)
|
||||
BackHandler {
|
||||
if (destination == SettingsDestination.THEME) experienceState.destination = SettingsDestination.HOME.name
|
||||
else onExit()
|
||||
}
|
||||
when (destination) {
|
||||
SettingsDestination.HOME -> SettingsHomeScreen(
|
||||
state = state,
|
||||
onOpenTheme = { experienceState.destination = SettingsDestination.THEME.name },
|
||||
onBack = onExit,
|
||||
onAction = onAction,
|
||||
)
|
||||
SettingsDestination.THEME -> ThemeSettingsScreen(
|
||||
state = state,
|
||||
onBack = { experienceState.destination = SettingsDestination.HOME.name },
|
||||
onAction = onAction,
|
||||
)
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
ABoxSurface(modifier = Modifier.fillMaxSize()) {
|
||||
SettingsHomeScreen(
|
||||
state = state,
|
||||
onOpenTheme = { experienceState.showTheme(returnToHome = true) },
|
||||
onBack = onExit,
|
||||
onAction = onAction,
|
||||
)
|
||||
}
|
||||
if (destination == SettingsDestination.THEME) {
|
||||
val leaveTheme = {
|
||||
if (experienceState.themeBackReturnsHome) experienceState.showHome() else onExit()
|
||||
}
|
||||
ABoxBackLayer(onBack = leaveTheme) {
|
||||
ABoxSurface(modifier = Modifier.fillMaxSize()) {
|
||||
ThemeSettingsScreen(
|
||||
state = state,
|
||||
onBack = leaveTheme,
|
||||
onAction = onAction,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,6 +243,7 @@ private fun ThemeSettingsScreen(
|
||||
|
||||
@Composable
|
||||
private fun ColumnScope.MaterialThemeOptions(state: SettingsUiState, onAction: (SettingsUiAction) -> Unit) {
|
||||
val manualColorsEnabled = !state.preferences.dynamicColorEnabled || Build.VERSION.SDK_INT < Build.VERSION_CODES.S
|
||||
SettingsGroup(modifier = Modifier.testTag("settings-material-colors")) {
|
||||
SettingsSwitchRow(
|
||||
icon = ABoxIconKey.THEME,
|
||||
@@ -238,6 +264,7 @@ private fun ColumnScope.MaterialThemeOptions(state: SettingsUiState, onAction: (
|
||||
values = ColorStyle.entries,
|
||||
label = { colorStyleLabel(it) },
|
||||
onSelected = { onAction(SettingsUiAction.SetColorStyle(it)) },
|
||||
enabled = manualColorsEnabled,
|
||||
)
|
||||
EnumSettingRow(
|
||||
modifier = Modifier.testTag("settings-color-standard"),
|
||||
@@ -247,8 +274,8 @@ private fun ColumnScope.MaterialThemeOptions(state: SettingsUiState, onAction: (
|
||||
values = ColorStandard.entries,
|
||||
label = { colorStandardLabel(it) },
|
||||
onSelected = { onAction(SettingsUiAction.SetColorStandard(it)) },
|
||||
enabled = manualColorsEnabled,
|
||||
)
|
||||
PredictiveBackSetting(state, onAction)
|
||||
SettingsGroup(modifier = Modifier.testTag("settings-interface-scale")) { ScaleSelector(state, onAction) }
|
||||
}
|
||||
|
||||
@@ -303,26 +330,9 @@ private fun ColumnScope.MiuixThemeOptions(state: SettingsUiState, onAction: (Set
|
||||
},
|
||||
) { onAction(SettingsUiAction.SetLiquidGlass(it)) }
|
||||
}
|
||||
PredictiveBackSetting(state, onAction)
|
||||
SettingsGroup(modifier = Modifier.testTag("settings-interface-scale")) { ScaleSelector(state, onAction) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PredictiveBackSetting(state: SettingsUiState, onAction: (SettingsUiAction) -> Unit) {
|
||||
val supported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
||||
SettingsGroup(modifier = Modifier.testTag("settings-predictive-back")) {
|
||||
SettingsSwitchRow(
|
||||
icon = ABoxIconKey.BACK,
|
||||
title = stringResource(R.string.settings_predictive_back),
|
||||
description = stringResource(R.string.settings_predictive_back_description),
|
||||
checked = state.preferences.predictiveBackEnabled,
|
||||
tag = "settings-predictive-back-switch",
|
||||
enabled = supported,
|
||||
unavailableDescription = if (supported) null else stringResource(R.string.settings_requires_android_13),
|
||||
) { onAction(SettingsUiAction.SetPredictiveBack(it)) }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingsPage(
|
||||
title: String,
|
||||
@@ -448,15 +458,17 @@ private fun <T> EnumSettingRow(
|
||||
values: List<T>,
|
||||
label: @Composable (T) -> String,
|
||||
onSelected: (T) -> Unit,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
SettingsGroup(modifier = modifier) {
|
||||
Box(Modifier.fillMaxWidth()) {
|
||||
Box(Modifier.fillMaxWidth().alpha(if (enabled) 1f else 0.42f)) {
|
||||
SettingsLinkRow(
|
||||
icon = icon,
|
||||
title = title,
|
||||
description = stringResource(R.string.settings_tap_to_choose),
|
||||
value = value,
|
||||
enabled = enabled,
|
||||
onClick = { expanded = true },
|
||||
)
|
||||
DropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
|
||||
@@ -478,18 +490,29 @@ private fun <T> EnumSettingRow(
|
||||
|
||||
@Composable
|
||||
private fun AccentSelector(state: SettingsUiState, onAction: (SettingsUiAction) -> Unit) {
|
||||
val dark = when (state.preferences.themeMode) {
|
||||
AppThemeMode.DARK -> true
|
||||
AppThemeMode.LIGHT -> false
|
||||
AppThemeMode.SYSTEM -> androidx.compose.foundation.isSystemInDarkTheme()
|
||||
}
|
||||
val manualColorsEnabled = !state.preferences.dynamicColorEnabled || Build.VERSION.SDK_INT < Build.VERSION_CODES.S
|
||||
Column(Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||
Text(stringResource(R.string.settings_accent_label), style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().alpha(if (state.preferences.dynamicColorEnabled) 0.42f else 1f),
|
||||
modifier = Modifier.fillMaxWidth().alpha(if (manualColorsEnabled) 1f else 0.42f),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
AccentPreference.entries.forEach { accent ->
|
||||
val color = accentColor(accent)
|
||||
val color = ABoxAccentPalette.preview(
|
||||
accent = accent,
|
||||
style = state.preferences.colorStyle,
|
||||
standard = state.preferences.colorStandard,
|
||||
dark = dark,
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier.size(44.dp).background(color, CircleShape)
|
||||
.then(if (state.preferences.accentPreference == accent) Modifier.border(3.dp, MaterialTheme.colorScheme.onSurface, CircleShape) else Modifier)
|
||||
.clickable(enabled = !state.preferences.dynamicColorEnabled) { onAction(SettingsUiAction.SetAccent(accent)) },
|
||||
.clickable(enabled = manualColorsEnabled) { onAction(SettingsUiAction.SetAccent(accent)) },
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
if (state.preferences.accentPreference == accent) Icon(Icons.Outlined.Check, null, tint = Color.White)
|
||||
@@ -518,8 +541,8 @@ private fun ScaleSelector(state: SettingsUiState, onAction: (SettingsUiAction) -
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SettingsLinkRow(icon: ABoxIconKey, title: String, description: String, value: String?, modifier: Modifier = Modifier, onClick: () -> Unit) {
|
||||
Row(modifier.fillMaxWidth().clickable(onClick = onClick).padding(14.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
private fun SettingsLinkRow(icon: ABoxIconKey, title: String, description: String, value: String?, modifier: Modifier = Modifier, enabled: Boolean = true, onClick: () -> Unit) {
|
||||
Row(modifier.fillMaxWidth().clickable(enabled = enabled, onClick = onClick).padding(14.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||
SettingsRowHeader(icon, title, description, Modifier.weight(1f))
|
||||
if (value != null) Text(value, color = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
Icon(Icons.Outlined.ChevronRight, null, tint = MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
@@ -570,4 +593,3 @@ private fun SettingsRowHeader(icon: ABoxIconKey, title: String, description: Str
|
||||
@Composable private fun themeLabel(mode: AppThemeMode) = stringResource(when (mode) { AppThemeMode.SYSTEM -> R.string.settings_theme_system; AppThemeMode.LIGHT -> R.string.settings_theme_light; AppThemeMode.DARK -> R.string.settings_theme_dark })
|
||||
@Composable private fun colorStyleLabel(value: ColorStyle) = stringResource(when (value) { ColorStyle.TONAL_SPOT -> R.string.settings_color_style_tonal; ColorStyle.EXPRESSIVE -> R.string.settings_color_style_expressive; ColorStyle.FIDELITY -> R.string.settings_color_style_fidelity })
|
||||
@Composable private fun colorStandardLabel(value: ColorStandard) = stringResource(when (value) { ColorStandard.SPEC_2021 -> R.string.settings_color_standard_2021; ColorStandard.SPEC_2025 -> R.string.settings_color_standard_2025 })
|
||||
private fun accentColor(accent: AccentPreference) = when (accent) { AccentPreference.BLUE -> Color(0xFF4F6FA9); AccentPreference.GREEN -> Color(0xFF2F7D57); AccentPreference.AMBER -> Color(0xFFA66700); AccentPreference.ROSE -> Color(0xFFA94C68); AccentPreference.VIOLET -> Color(0xFF7659A8) }
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.Logout
|
||||
import androidx.compose.material.icons.outlined.CloudDone
|
||||
import androidx.compose.material.icons.outlined.Settings
|
||||
import androidx.compose.material.icons.outlined.Palette
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -107,7 +107,7 @@ fun AccountProviderDialog(
|
||||
}
|
||||
|
||||
AccountSheetAction(
|
||||
icon = Icons.Outlined.Settings,
|
||||
icon = Icons.Outlined.Palette,
|
||||
label = stringResource(R.string.account_interface_settings),
|
||||
description = stringResource(R.string.account_interface_settings_desc),
|
||||
tag = "account_dialog_open_settings",
|
||||
|
||||
@@ -265,7 +265,7 @@
|
||||
<string name="settings_style_section">样式</string>
|
||||
<string name="settings_renderer_label">渲染器</string>
|
||||
<string name="settings_renderer_description">选择应用使用的界面渲染方式</string>
|
||||
<string name="settings_renderer_material3">Material 3</string>
|
||||
<string name="settings_renderer_material3">Material</string>
|
||||
<string name="settings_renderer_miuix">Miuix</string>
|
||||
<string name="settings_theme_section">主题</string>
|
||||
<string name="settings_theme_label">主题模式</string>
|
||||
@@ -291,7 +291,7 @@
|
||||
<string name="settings_theme_entry">主题</string>
|
||||
<string name="settings_theme_entry_description">配色、明暗、视觉效果与缩放</string>
|
||||
<string name="settings_theme_detail_title">主题</string>
|
||||
<string name="settings_renderer_primary">Material 3</string>
|
||||
<string name="settings_renderer_primary">Material</string>
|
||||
<string name="settings_renderer_secondary">Miuix</string>
|
||||
<string name="settings_navigation_floating_description">让底部导航独立悬浮在页面内容上方</string>
|
||||
<string name="settings_accent_label">强调色</string>
|
||||
@@ -344,8 +344,8 @@
|
||||
<string name="account_current">当前账户</string>
|
||||
<string name="account_greeting">%1$s,您好</string>
|
||||
<string name="account_connection_method">连接方式</string>
|
||||
<string name="account_interface_settings">界面设置</string>
|
||||
<string name="account_interface_settings_desc">主题、渲染风格与交互偏好</string>
|
||||
<string name="account_interface_settings">主题</string>
|
||||
<string name="account_interface_settings_desc">配色、明暗、视觉效果与缩放</string>
|
||||
<string name="account_logout">退出当前会话</string>
|
||||
<string name="account_logout_desc">结束当前连接并返回登录流程</string>
|
||||
<string name="account_logout_confirm">确定退出当前会话吗?</string>
|
||||
|
||||
@@ -265,7 +265,7 @@
|
||||
<string name="settings_style_section">Style</string>
|
||||
<string name="settings_renderer_label">Renderer</string>
|
||||
<string name="settings_renderer_description">Choose the interface renderer used by the app</string>
|
||||
<string name="settings_renderer_material3">Material 3</string>
|
||||
<string name="settings_renderer_material3">Material</string>
|
||||
<string name="settings_renderer_miuix">Miuix</string>
|
||||
<string name="settings_theme_section">Theme</string>
|
||||
<string name="settings_theme_label">Theme mode</string>
|
||||
@@ -291,7 +291,7 @@
|
||||
<string name="settings_theme_entry">Theme</string>
|
||||
<string name="settings_theme_entry_description">Colors, brightness, effects and scale</string>
|
||||
<string name="settings_theme_detail_title">Theme</string>
|
||||
<string name="settings_renderer_primary">Material 3</string>
|
||||
<string name="settings_renderer_primary">Material</string>
|
||||
<string name="settings_renderer_secondary">Miuix</string>
|
||||
<string name="settings_navigation_floating_description">Use a detached navigation surface above page content</string>
|
||||
<string name="settings_accent_label">Accent color</string>
|
||||
@@ -344,8 +344,8 @@
|
||||
<string name="account_current">Current account</string>
|
||||
<string name="account_greeting">Hello, %1$s</string>
|
||||
<string name="account_connection_method">Connection method</string>
|
||||
<string name="account_interface_settings">Interface settings</string>
|
||||
<string name="account_interface_settings_desc">Theme, renderer and interaction preferences</string>
|
||||
<string name="account_interface_settings">Theme</string>
|
||||
<string name="account_interface_settings_desc">Colors, brightness, effects and scale</string>
|
||||
<string name="account_logout">Sign out</string>
|
||||
<string name="account_logout_desc">End the current connection and return to sign-in</string>
|
||||
<string name="account_logout_confirm">Sign out of the current session?</string>
|
||||
|
||||
@@ -38,6 +38,7 @@ dependencies {
|
||||
implementation(libs.compose.ui.tooling.preview)
|
||||
implementation(libs.compose.material3)
|
||||
implementation(libs.compose.material.icons.extended)
|
||||
implementation(libs.androidx.activity.compose)
|
||||
implementation(libs.miuix.ui.android)
|
||||
debugImplementation(libs.compose.ui.tooling)
|
||||
screenshotTestImplementation(libs.compose.screenshot.test)
|
||||
|
||||
+18
@@ -1,5 +1,7 @@
|
||||
package com.flagship.abox.manager.designsystem
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -45,6 +47,9 @@ import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@@ -677,3 +682,16 @@ fun RowScope.ABoxNavigationBarItem(
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ABoxBackLayer(
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
BackHandler(enabled = enabled, onBack = onBack)
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -101,7 +101,7 @@ internal fun ABoxThemeContent(
|
||||
LocalABoxBlurEnabled provides resolution.blurEnabled,
|
||||
LocalABoxNavigationPresentation provides preferences.navigationPresentation,
|
||||
LocalABoxLiquidGlassEnabled provides preferences.liquidGlassEnabled,
|
||||
LocalABoxPredictiveBackEnabled provides preferences.predictiveBackEnabled,
|
||||
LocalABoxPredictiveBackEnabled provides false,
|
||||
) {
|
||||
renderer.Render(
|
||||
resolution = resolution,
|
||||
|
||||
+91
-9
@@ -2,10 +2,13 @@ package com.flagship.abox.manager.designsystem.tokens
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.lerp
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.flagship.abox.manager.preferences.AppThemeMode
|
||||
import com.flagship.abox.manager.preferences.AccentPreference
|
||||
import com.flagship.abox.manager.preferences.ColorStandard
|
||||
import com.flagship.abox.manager.preferences.ColorStyle
|
||||
import com.flagship.abox.manager.preferences.DesignRenderer
|
||||
import com.flagship.abox.manager.preferences.UiPreferences
|
||||
|
||||
@@ -122,7 +125,13 @@ object ABoxThemeResolver {
|
||||
val colorTokens = if (preferences.dynamicColorEnabled && sdkInt >= 31 && dynamicColorTokens != null) {
|
||||
dynamicColorTokens
|
||||
} else {
|
||||
brandTokens.withAccent(preferences.accentPreference, isDarkTheme)
|
||||
ABoxAccentPalette.resolve(
|
||||
base = brandTokens,
|
||||
accent = preferences.accentPreference,
|
||||
style = preferences.colorStyle,
|
||||
standard = preferences.colorStandard,
|
||||
dark = isDarkTheme,
|
||||
)
|
||||
}
|
||||
val shapeTokens = when (preferences.renderer) {
|
||||
DesignRenderer.MATERIAL3 -> ABoxThemeDefaults.material3Shapes
|
||||
@@ -139,13 +148,86 @@ object ABoxThemeResolver {
|
||||
}
|
||||
}
|
||||
|
||||
private fun ABoxColorTokens.withAccent(accent: AccentPreference, dark: Boolean): ABoxColorTokens {
|
||||
val selected = when (accent) {
|
||||
AccentPreference.BLUE -> if (dark) Color(0xFF9CCAFF) else Color(0xFF385F9D)
|
||||
AccentPreference.GREEN -> if (dark) Color(0xFF7EDFA7) else Color(0xFF247A4B)
|
||||
AccentPreference.AMBER -> if (dark) Color(0xFFFFC46B) else Color(0xFF8A5800)
|
||||
AccentPreference.ROSE -> if (dark) Color(0xFFFFB1C1) else Color(0xFFA43C59)
|
||||
AccentPreference.VIOLET -> if (dark) Color(0xFFD4BBFF) else Color(0xFF6D52A2)
|
||||
object ABoxAccentPalette {
|
||||
fun preview(
|
||||
accent: AccentPreference,
|
||||
style: ColorStyle,
|
||||
standard: ColorStandard,
|
||||
dark: Boolean,
|
||||
): Color = resolve(
|
||||
base = if (dark) ABoxThemeDefaults.darkColors else ABoxThemeDefaults.lightColors,
|
||||
accent = accent,
|
||||
style = style,
|
||||
standard = standard,
|
||||
dark = dark,
|
||||
).primary
|
||||
|
||||
fun resolve(
|
||||
base: ABoxColorTokens,
|
||||
accent: AccentPreference,
|
||||
style: ColorStyle,
|
||||
standard: ColorStandard,
|
||||
dark: Boolean,
|
||||
): ABoxColorTokens {
|
||||
val source = source(accent, dark)
|
||||
val primary = when (style) {
|
||||
ColorStyle.TONAL_SPOT -> lerp(source, base.surface, if (dark) 0.08f else 0.12f)
|
||||
ColorStyle.EXPRESSIVE -> expressive(accent, dark)
|
||||
ColorStyle.FIDELITY -> source
|
||||
}
|
||||
val deviceSensor = when (style) {
|
||||
ColorStyle.EXPRESSIVE -> expressiveSensor(accent, dark)
|
||||
ColorStyle.FIDELITY -> lerp(source, base.success, 0.38f)
|
||||
ColorStyle.TONAL_SPOT -> base.deviceSensor
|
||||
}
|
||||
val deviceInfrared = when (style) {
|
||||
ColorStyle.EXPRESSIVE -> expressiveInfrared(accent, dark)
|
||||
ColorStyle.FIDELITY -> lerp(source, base.deviceInfrared, 0.46f)
|
||||
ColorStyle.TONAL_SPOT -> base.deviceInfrared
|
||||
}
|
||||
val styled = base.copy(
|
||||
primary = primary,
|
||||
deviceSocket = primary,
|
||||
deviceSensor = deviceSensor,
|
||||
deviceInfrared = deviceInfrared,
|
||||
)
|
||||
return when (standard) {
|
||||
ColorStandard.SPEC_2021 -> styled
|
||||
ColorStandard.SPEC_2025 -> styled.copy(
|
||||
surface = if (dark) Color(0xFF0C1114) else Color(0xFFFAF9FC),
|
||||
surfaceContainer = if (dark) Color(0xFF242C30) else Color(0xFFE9E8EE),
|
||||
outline = if (dark) Color(0xFFA7B2B7) else Color(0xFF596267),
|
||||
primary = lerp(primary, if (dark) Color.White else Color.Black, 0.08f),
|
||||
deviceSocket = lerp(primary, if (dark) Color.White else Color.Black, 0.08f),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun source(accent: AccentPreference, dark: Boolean): Color = when (accent) {
|
||||
AccentPreference.BLUE -> if (dark) Color(0xFF9CCAFF) else Color(0xFF4F6FA9)
|
||||
AccentPreference.GREEN -> if (dark) Color(0xFF7EDFA7) else Color(0xFF2F7D57)
|
||||
AccentPreference.AMBER -> if (dark) Color(0xFFFFC46B) else Color(0xFFA66700)
|
||||
AccentPreference.ROSE -> if (dark) Color(0xFFFFB1C1) else Color(0xFFA94C68)
|
||||
AccentPreference.VIOLET -> if (dark) Color(0xFFD4BBFF) else Color(0xFF7659A8)
|
||||
}
|
||||
|
||||
private fun expressive(accent: AccentPreference, dark: Boolean): Color = when (accent) {
|
||||
AccentPreference.BLUE -> if (dark) Color(0xFF72D6FF) else Color(0xFF006B91)
|
||||
AccentPreference.GREEN -> if (dark) Color(0xFF69E6B1) else Color(0xFF007A54)
|
||||
AccentPreference.AMBER -> if (dark) Color(0xFFFFC24B) else Color(0xFF9A5C00)
|
||||
AccentPreference.ROSE -> if (dark) Color(0xFFFFA8C4) else Color(0xFFB12F62)
|
||||
AccentPreference.VIOLET -> if (dark) Color(0xFFC9B5FF) else Color(0xFF7048B8)
|
||||
}
|
||||
|
||||
private fun expressiveSensor(accent: AccentPreference, dark: Boolean): Color = when (accent) {
|
||||
AccentPreference.BLUE, AccentPreference.VIOLET -> if (dark) Color(0xFF78DFA2) else Color(0xFF26764A)
|
||||
AccentPreference.GREEN, AccentPreference.AMBER -> if (dark) Color(0xFF79D6F3) else Color(0xFF006985)
|
||||
AccentPreference.ROSE -> if (dark) Color(0xFFFFC45F) else Color(0xFF8B5B00)
|
||||
}
|
||||
|
||||
private fun expressiveInfrared(accent: AccentPreference, dark: Boolean): Color = when (accent) {
|
||||
AccentPreference.BLUE, AccentPreference.GREEN -> if (dark) Color(0xFFD1B8FF) else Color(0xFF7453A5)
|
||||
AccentPreference.AMBER, AccentPreference.ROSE -> if (dark) Color(0xFF9FCBFF) else Color(0xFF38639A)
|
||||
AccentPreference.VIOLET -> if (dark) Color(0xFFFFB0C7) else Color(0xFFA43E62)
|
||||
}
|
||||
return copy(primary = selected, deviceSocket = selected)
|
||||
}
|
||||
|
||||
+40
-4
@@ -26,8 +26,26 @@ class ABoxThemeResolverTest {
|
||||
)
|
||||
|
||||
assertFalse(light.isDarkTheme)
|
||||
assertEquals(ABoxThemeDefaults.lightColors, light.colorTokens)
|
||||
assertEquals(ABoxThemeDefaults.darkColors, dark.colorTokens)
|
||||
assertEquals(
|
||||
ABoxAccentPalette.resolve(
|
||||
ABoxThemeDefaults.lightColors,
|
||||
UiPreferences().accentPreference,
|
||||
UiPreferences().colorStyle,
|
||||
UiPreferences().colorStandard,
|
||||
false,
|
||||
),
|
||||
light.colorTokens,
|
||||
)
|
||||
assertEquals(
|
||||
ABoxAccentPalette.resolve(
|
||||
ABoxThemeDefaults.darkColors,
|
||||
UiPreferences().accentPreference,
|
||||
UiPreferences().colorStyle,
|
||||
UiPreferences().colorStandard,
|
||||
true,
|
||||
),
|
||||
dark.colorTokens,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -42,7 +60,16 @@ class ABoxThemeResolverTest {
|
||||
dynamicColorTokens = dynamicTokens,
|
||||
)
|
||||
|
||||
assertEquals(ABoxThemeDefaults.lightColors, resolution.colorTokens)
|
||||
assertEquals(
|
||||
ABoxAccentPalette.resolve(
|
||||
ABoxThemeDefaults.lightColors,
|
||||
UiPreferences().accentPreference,
|
||||
UiPreferences().colorStyle,
|
||||
UiPreferences().colorStandard,
|
||||
false,
|
||||
),
|
||||
resolution.colorTokens,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -57,7 +84,16 @@ class ABoxThemeResolverTest {
|
||||
dynamicColorTokens = dynamicTokens,
|
||||
)
|
||||
|
||||
assertEquals(ABoxThemeDefaults.darkColors, resolution.colorTokens)
|
||||
assertEquals(
|
||||
ABoxAccentPalette.resolve(
|
||||
ABoxThemeDefaults.darkColors,
|
||||
UiPreferences().accentPreference,
|
||||
UiPreferences().colorStyle,
|
||||
UiPreferences().colorStandard,
|
||||
true,
|
||||
),
|
||||
resolution.colorTokens,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+34
-23
@@ -50,7 +50,6 @@ import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.SwapVert
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -71,6 +70,7 @@ import androidx.compose.ui.unit.sp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.flagship.abox.manager.designsystem.ABoxSurface
|
||||
import com.flagship.abox.manager.designsystem.ABoxBackLayer
|
||||
import com.flagship.abox.manager.designsystem.ABoxIconButton
|
||||
import com.flagship.abox.manager.designsystem.ABoxDeviceDetailScaffold
|
||||
import com.flagship.abox.manager.designsystem.icon.ABoxIcon
|
||||
@@ -157,8 +157,6 @@ internal fun InfraredHubScreen(
|
||||
var showCreateDialog by remember { mutableStateOf(false) }
|
||||
var showManagement by remember { mutableStateOf(false) }
|
||||
|
||||
BackHandler(enabled = showManagement) { showManagement = false }
|
||||
|
||||
LaunchedEffect(state.message) {
|
||||
state.message?.let { msg ->
|
||||
when (msg) {
|
||||
@@ -188,18 +186,16 @@ internal fun InfraredHubScreen(
|
||||
|
||||
ABoxDeviceDetailScaffold(
|
||||
title = state.savedDeviceName.value,
|
||||
subtitle = if (showManagement) stringResource(R.string.infrared_settings_subtitle) else stringResource(R.string.infrared_center_subtitle),
|
||||
subtitle = stringResource(R.string.infrared_center_subtitle),
|
||||
icon = ABoxIconKey.INFRARED,
|
||||
onBack = if (showManagement) ({ showManagement = false }) else onBack,
|
||||
onBack = onBack,
|
||||
action = {
|
||||
if (!showManagement) {
|
||||
ABoxIconButton(
|
||||
key = ABoxIconKey.EDIT,
|
||||
contentDescription = stringResource(R.string.infrared_manage_action),
|
||||
onClick = { showManagement = true },
|
||||
modifier = Modifier.testTag("hub_settings_button"),
|
||||
)
|
||||
}
|
||||
ABoxIconButton(
|
||||
key = ABoxIconKey.EDIT,
|
||||
contentDescription = stringResource(R.string.infrared_manage_action),
|
||||
onClick = { showManagement = true },
|
||||
modifier = Modifier.testTag("hub_settings_button"),
|
||||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
ABoxSurface(
|
||||
@@ -207,16 +203,7 @@ internal fun InfraredHubScreen(
|
||||
.fillMaxSize()
|
||||
.padding(innerPadding),
|
||||
) {
|
||||
if (showManagement) {
|
||||
InfraredManagementScreen(
|
||||
state = state,
|
||||
onAddRemote = { showCreateDialog = true },
|
||||
onDeleteRemote = onDeleteRemote,
|
||||
onSaveDeviceName = onSaveDeviceName,
|
||||
onMoveRemote = onMoveRemote,
|
||||
onUpdateRemote = onUpdateRemote,
|
||||
)
|
||||
} else Column(
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 16.dp, vertical = 10.dp),
|
||||
@@ -299,6 +286,30 @@ internal fun InfraredHubScreen(
|
||||
}
|
||||
}
|
||||
|
||||
if (showManagement) {
|
||||
ABoxBackLayer(onBack = { showManagement = false }) {
|
||||
ABoxDeviceDetailScaffold(
|
||||
title = state.savedDeviceName.value,
|
||||
subtitle = stringResource(R.string.infrared_settings_subtitle),
|
||||
icon = ABoxIconKey.INFRARED,
|
||||
onBack = { showManagement = false },
|
||||
) { innerPadding ->
|
||||
ABoxSurface(
|
||||
modifier = Modifier.fillMaxSize().padding(innerPadding),
|
||||
) {
|
||||
InfraredManagementScreen(
|
||||
state = state,
|
||||
onAddRemote = { showCreateDialog = true },
|
||||
onDeleteRemote = onDeleteRemote,
|
||||
onSaveDeviceName = onSaveDeviceName,
|
||||
onMoveRemote = onMoveRemote,
|
||||
onUpdateRemote = onUpdateRemote,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (showCreateDialog) {
|
||||
CreateVirtualRemoteDialog(
|
||||
onDismiss = { showCreateDialog = false },
|
||||
|
||||
Reference in New Issue
Block a user