feat: replace device shortcuts with device center
This commit is contained in:
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package com.flagship.abox.manager.ui.devicecenter
|
||||||
|
|
||||||
|
import com.flagship.abox.manager.domain.DeviceDescriptor
|
||||||
|
import com.flagship.abox.manager.domain.DeviceName
|
||||||
|
import com.flagship.abox.manager.domain.DeviceType
|
||||||
|
import com.flagship.abox.manager.domain.ProviderProfileId
|
||||||
|
|
||||||
|
data class DeviceCardKey(val profileId: ProviderProfileId, val deviceName: DeviceName)
|
||||||
|
enum class DeviceCategoryFilter { ALL, SOCKET, SENSOR, INFRARED }
|
||||||
|
data class DeviceCardUiModel(
|
||||||
|
val key: DeviceCardKey,
|
||||||
|
val device: DeviceDescriptor,
|
||||||
|
val displayName: String = device.name.value,
|
||||||
|
val typeLabel: String = when (device.type) {
|
||||||
|
DeviceType.SOCKET -> "插座"
|
||||||
|
DeviceType.TEMPERATURE_HUMIDITY -> "温湿度"
|
||||||
|
DeviceType.INFRARED -> "红外控制器"
|
||||||
|
},
|
||||||
|
)
|
||||||
|
enum class DeviceCenterContent { LOADING, CONTENT, EMPTY, ERROR }
|
||||||
|
data class DeviceCenterUiState(
|
||||||
|
val profileId: ProviderProfileId? = null,
|
||||||
|
val filter: DeviceCategoryFilter = DeviceCategoryFilter.ALL,
|
||||||
|
val cards: List<DeviceCardUiModel> = emptyList(),
|
||||||
|
val selectedCard: DeviceCardKey? = null,
|
||||||
|
val content: DeviceCenterContent = DeviceCenterContent.LOADING,
|
||||||
|
)
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
package com.flagship.abox.manager.ui.devicecenter
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.flagship.abox.manager.domain.DeviceType
|
||||||
|
import com.flagship.abox.manager.domain.ProviderProfileId
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DeviceCenterRoute(
|
||||||
|
profileId: ProviderProfileId,
|
||||||
|
onOpenDevice: (String, DeviceType) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
viewModel: DeviceCenterViewModel = hiltViewModel(),
|
||||||
|
) {
|
||||||
|
val state = viewModel.uiState.collectAsStateWithLifecycle().value
|
||||||
|
LaunchedEffect(profileId) { viewModel.initialize(profileId) }
|
||||||
|
DeviceCenterScreen(
|
||||||
|
state = state,
|
||||||
|
onFilterChanged = viewModel::setFilter,
|
||||||
|
onOpenDevice = { card -> onOpenDevice(card.device.name.value, card.device.type) },
|
||||||
|
onConfigureDevice = viewModel::addConfiguredDevice,
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
+166
@@ -0,0 +1,166 @@
|
|||||||
|
package com.flagship.abox.manager.ui.devicecenter
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.testTag
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.flagship.abox.manager.designsystem.ABoxCard
|
||||||
|
import com.flagship.abox.manager.designsystem.ABoxEmptyState
|
||||||
|
import com.flagship.abox.manager.designsystem.ABoxFilterChip
|
||||||
|
import com.flagship.abox.manager.designsystem.icon.ABoxIcon
|
||||||
|
import com.flagship.abox.manager.designsystem.icon.ABoxIconKey
|
||||||
|
import com.flagship.abox.manager.domain.DeviceType
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun DeviceCenterScreen(
|
||||||
|
state: DeviceCenterUiState,
|
||||||
|
onFilterChanged: (DeviceCategoryFilter) -> Unit,
|
||||||
|
onOpenDevice: (DeviceCardUiModel) -> Unit,
|
||||||
|
onConfigureDevice: (String, DeviceType) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
var showConfiguration by remember { mutableStateOf(false) }
|
||||||
|
Column(
|
||||||
|
modifier = modifier.fillMaxSize().padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
) {
|
||||||
|
Text("设备中心", style = MaterialTheme.typography.headlineSmall)
|
||||||
|
Text("选择设备以查看状态或打开控制页面", style = MaterialTheme.typography.bodyMedium)
|
||||||
|
FilterRow(selected = state.filter, onSelected = onFilterChanged)
|
||||||
|
when (state.content) {
|
||||||
|
DeviceCenterContent.LOADING -> Text("正在加载设备", modifier = Modifier.testTag("device_center_loading"))
|
||||||
|
DeviceCenterContent.EMPTY -> ABoxEmptyState(
|
||||||
|
title = "尚未配置可管理设备",
|
||||||
|
description = "请先配置设备名称和类型",
|
||||||
|
modifier = Modifier.testTag("device_center_empty"),
|
||||||
|
action = {
|
||||||
|
Button(onClick = { showConfiguration = true }) { Text("配置设备") }
|
||||||
|
},
|
||||||
|
)
|
||||||
|
DeviceCenterContent.ERROR -> Text("设备列表加载失败", modifier = Modifier.testTag("device_center_error"))
|
||||||
|
DeviceCenterContent.CONTENT -> LazyVerticalGrid(
|
||||||
|
columns = GridCells.Adaptive(minSize = 220.dp),
|
||||||
|
contentPadding = PaddingValues(bottom = 24.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
|
modifier = Modifier.testTag("device_center_grid"),
|
||||||
|
) {
|
||||||
|
items(state.cards, key = { it.key }) { card ->
|
||||||
|
DeviceCard(card = card, onClick = { onOpenDevice(card) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showConfiguration) {
|
||||||
|
DeviceConfigurationDialog(
|
||||||
|
onDismiss = { showConfiguration = false },
|
||||||
|
onConfirm = { name, type ->
|
||||||
|
showConfiguration = false
|
||||||
|
onConfigureDevice(name, type)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DeviceConfigurationDialog(
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
onConfirm: (String, DeviceType) -> Unit,
|
||||||
|
) {
|
||||||
|
var name by remember { mutableStateOf("") }
|
||||||
|
var type by remember { mutableStateOf(DeviceType.SOCKET) }
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = onDismiss,
|
||||||
|
title = { Text("配置设备") },
|
||||||
|
text = {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = name,
|
||||||
|
onValueChange = { name = it },
|
||||||
|
label = { Text("设备名称") },
|
||||||
|
singleLine = true,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
)
|
||||||
|
FilterRow(
|
||||||
|
selected = when (type) {
|
||||||
|
DeviceType.SOCKET -> DeviceCategoryFilter.SOCKET
|
||||||
|
DeviceType.TEMPERATURE_HUMIDITY -> DeviceCategoryFilter.SENSOR
|
||||||
|
DeviceType.INFRARED -> DeviceCategoryFilter.INFRARED
|
||||||
|
},
|
||||||
|
onSelected = { selected ->
|
||||||
|
type = when (selected) {
|
||||||
|
DeviceCategoryFilter.SOCKET -> DeviceType.SOCKET
|
||||||
|
DeviceCategoryFilter.SENSOR -> DeviceType.TEMPERATURE_HUMIDITY
|
||||||
|
DeviceCategoryFilter.INFRARED -> DeviceType.INFRARED
|
||||||
|
DeviceCategoryFilter.ALL -> type
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
Button(onClick = { onConfirm(name.trim(), type) }, enabled = name.isNotBlank()) { Text("保存") }
|
||||||
|
},
|
||||||
|
dismissButton = { Button(onClick = onDismiss) { Text("取消") } },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun FilterRow(
|
||||||
|
selected: DeviceCategoryFilter,
|
||||||
|
onSelected: (DeviceCategoryFilter) -> Unit,
|
||||||
|
) {
|
||||||
|
androidx.compose.foundation.lazy.LazyRow(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
item { FilterChipItem(DeviceCategoryFilter.ALL, "全部", selected, onSelected) }
|
||||||
|
item { FilterChipItem(DeviceCategoryFilter.SOCKET, "插座", selected, onSelected) }
|
||||||
|
item { FilterChipItem(DeviceCategoryFilter.SENSOR, "温湿度", selected, onSelected) }
|
||||||
|
item { FilterChipItem(DeviceCategoryFilter.INFRARED, "红外", selected, onSelected) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun FilterChipItem(
|
||||||
|
filter: DeviceCategoryFilter,
|
||||||
|
label: String,
|
||||||
|
selected: DeviceCategoryFilter,
|
||||||
|
onSelected: (DeviceCategoryFilter) -> Unit,
|
||||||
|
) {
|
||||||
|
ABoxFilterChip(selected = selected == filter, label = label, onClick = { onSelected(filter) })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DeviceCard(card: DeviceCardUiModel, onClick: () -> Unit) {
|
||||||
|
val icon = when (card.device.type) {
|
||||||
|
DeviceType.SOCKET -> ABoxIconKey.SOCKET
|
||||||
|
DeviceType.TEMPERATURE_HUMIDITY -> ABoxIconKey.SENSOR
|
||||||
|
DeviceType.INFRARED -> ABoxIconKey.INFRARED
|
||||||
|
}
|
||||||
|
ABoxCard(
|
||||||
|
title = card.displayName,
|
||||||
|
subtitle = card.typeLabel,
|
||||||
|
onClick = onClick,
|
||||||
|
modifier = Modifier.testTag("device_card_${card.device.id.value}"),
|
||||||
|
leadingContent = { ABoxIcon(icon, contentDescription = card.typeLabel) },
|
||||||
|
)
|
||||||
|
}
|
||||||
+81
@@ -0,0 +1,81 @@
|
|||||||
|
package com.flagship.abox.manager.ui.devicecenter
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.flagship.abox.manager.domain.DeviceCatalog
|
||||||
|
import com.flagship.abox.manager.domain.DeviceDescriptor
|
||||||
|
import com.flagship.abox.manager.domain.DeviceType
|
||||||
|
import com.flagship.abox.manager.domain.DomainResult
|
||||||
|
import com.flagship.abox.manager.domain.ProviderProfileId
|
||||||
|
import com.flagship.abox.manager.devicecatalog.data.ConfiguredDeviceRepository
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class DeviceCenterViewModel @Inject constructor(
|
||||||
|
private val catalog: DeviceCatalog,
|
||||||
|
private val configuredRepository: ConfiguredDeviceRepository,
|
||||||
|
) : ViewModel() {
|
||||||
|
private val _uiState = MutableStateFlow(DeviceCenterUiState())
|
||||||
|
val uiState: StateFlow<DeviceCenterUiState> = _uiState.asStateFlow()
|
||||||
|
private var catalogJob: Job? = null
|
||||||
|
private var allCards: List<DeviceCardUiModel> = emptyList()
|
||||||
|
|
||||||
|
fun initialize(profileId: ProviderProfileId) {
|
||||||
|
if (_uiState.value.profileId == profileId && catalogJob?.isActive == true) return
|
||||||
|
catalogJob?.cancel()
|
||||||
|
allCards = emptyList()
|
||||||
|
_uiState.value = DeviceCenterUiState(profileId = profileId)
|
||||||
|
catalogJob = viewModelScope.launch {
|
||||||
|
catalog.observeDevices(profileId).collectLatest { result ->
|
||||||
|
when (result) {
|
||||||
|
is DomainResult.Success -> {
|
||||||
|
allCards = result.value.map(::toCard)
|
||||||
|
publishCards()
|
||||||
|
}
|
||||||
|
is DomainResult.Failure -> _uiState.value = _uiState.value.copy(content = DeviceCenterContent.ERROR)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setFilter(filter: DeviceCategoryFilter) {
|
||||||
|
_uiState.value = _uiState.value.copy(filter = filter)
|
||||||
|
publishCards()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun select(device: DeviceDescriptor) {
|
||||||
|
_uiState.value = _uiState.value.copy(selectedCard = DeviceCardKey(device.profileId, device.name))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addConfiguredDevice(name: String, type: DeviceType) {
|
||||||
|
val profileId = _uiState.value.profileId ?: return
|
||||||
|
viewModelScope.launch { configuredRepository.add(profileId, com.flagship.abox.manager.domain.DeviceName(name), type) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeConfiguredDevice(device: DeviceCardUiModel) {
|
||||||
|
val profileId = _uiState.value.profileId ?: return
|
||||||
|
viewModelScope.launch { configuredRepository.remove(profileId, device.device.name) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun publishCards() {
|
||||||
|
val filter = _uiState.value.filter
|
||||||
|
val cards = allCards.filter { filter.matches(it.device.type) }
|
||||||
|
_uiState.value = _uiState.value.copy(cards = cards, content = if (allCards.isEmpty()) DeviceCenterContent.EMPTY else DeviceCenterContent.CONTENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toCard(device: DeviceDescriptor) = DeviceCardUiModel(DeviceCardKey(device.profileId, device.name), device)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DeviceCategoryFilter.matches(type: DeviceType): Boolean = when (this) {
|
||||||
|
DeviceCategoryFilter.ALL -> true
|
||||||
|
DeviceCategoryFilter.SOCKET -> type == DeviceType.SOCKET
|
||||||
|
DeviceCategoryFilter.SENSOR -> type == DeviceType.TEMPERATURE_HUMIDITY
|
||||||
|
DeviceCategoryFilter.INFRARED -> type == DeviceType.INFRARED
|
||||||
|
}
|
||||||
+5
-17
@@ -40,7 +40,7 @@ import com.flagship.abox.manager.domain.Username
|
|||||||
import com.flagship.abox.manager.infrared.hub.InfraredHubRoute
|
import com.flagship.abox.manager.infrared.hub.InfraredHubRoute
|
||||||
import com.flagship.abox.manager.infrared.panel.RemotePanelRoute
|
import com.flagship.abox.manager.infrared.panel.RemotePanelRoute
|
||||||
import com.flagship.abox.manager.infrared.ui.InfraredControlRoute
|
import com.flagship.abox.manager.infrared.ui.InfraredControlRoute
|
||||||
import com.flagship.abox.manager.ui.devicecontrollist.DeviceListScreen
|
import com.flagship.abox.manager.ui.devicecenter.DeviceCenterRoute
|
||||||
import com.flagship.abox.manager.ui.scene.SceneListScreen
|
import com.flagship.abox.manager.ui.scene.SceneListScreen
|
||||||
import com.flagship.abox.manager.ui.preferences.SettingsRoute
|
import com.flagship.abox.manager.ui.preferences.SettingsRoute
|
||||||
import com.flagship.abox.manager.ui.scene.SceneRoutes
|
import com.flagship.abox.manager.ui.scene.SceneRoutes
|
||||||
@@ -336,7 +336,10 @@ private fun AuthenticatedNavHost(
|
|||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
composable(MainTab.DEVICES.route) {
|
composable(MainTab.DEVICES.route) {
|
||||||
DeviceRoot(onSelectDevice = onSelectDevice)
|
DeviceCenterRoute(
|
||||||
|
profileId = profile.id,
|
||||||
|
onOpenDevice = { deviceName, type -> onSelectDevice(DeviceName(deviceName), type) },
|
||||||
|
)
|
||||||
}
|
}
|
||||||
composable(MainTab.SCENES.route) {
|
composable(MainTab.SCENES.route) {
|
||||||
SceneListScreen(
|
SceneListScreen(
|
||||||
@@ -407,21 +410,6 @@ private fun AuthenticatedNavHost(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun DeviceRoot(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onSelectDevice: (DeviceName, DeviceType) -> Unit,
|
|
||||||
) {
|
|
||||||
DeviceListScreen(
|
|
||||||
modifier = modifier,
|
|
||||||
onSocketClick = { onSelectDevice(DeviceName("Zigbee插座控制器"), DeviceType.SOCKET) },
|
|
||||||
onTemperatureHumidityClick = {
|
|
||||||
onSelectDevice(DeviceName("温湿度传感器"), DeviceType.TEMPERATURE_HUMIDITY)
|
|
||||||
},
|
|
||||||
onInfraredClick = { onSelectDevice(DeviceName("红外遥控器"), DeviceType.INFRARED) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DeviceDetailPane(
|
private fun DeviceDetailPane(
|
||||||
selection: DeviceSelection,
|
selection: DeviceSelection,
|
||||||
|
|||||||
+75
@@ -0,0 +1,75 @@
|
|||||||
|
package com.flagship.abox.manager.ui.devicecenter
|
||||||
|
|
||||||
|
import com.flagship.abox.manager.domain.DeviceCatalog
|
||||||
|
import com.flagship.abox.manager.domain.DeviceDescriptor
|
||||||
|
import com.flagship.abox.manager.domain.DeviceId
|
||||||
|
import com.flagship.abox.manager.domain.DeviceName
|
||||||
|
import com.flagship.abox.manager.domain.DeviceType
|
||||||
|
import com.flagship.abox.manager.domain.DomainResult
|
||||||
|
import com.flagship.abox.manager.domain.ProviderProfileId
|
||||||
|
import com.flagship.abox.manager.domain.DomainError
|
||||||
|
import com.flagship.abox.manager.devicecatalog.data.ConfiguredDeviceRepository
|
||||||
|
import com.flagship.abox.manager.testing.MainDispatcherRule
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import kotlinx.coroutines.test.UnconfinedTestDispatcher
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class DeviceCenterViewModelTest {
|
||||||
|
@get:Rule
|
||||||
|
val mainDispatcherRule = MainDispatcherRule(UnconfinedTestDispatcher())
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun catalogEmissionMapsCardsAndFilterLocally() = runTest {
|
||||||
|
val profile = ProviderProfileId("p1")
|
||||||
|
val catalog = TestCatalog(listOf(
|
||||||
|
descriptor(profile, "plug", DeviceType.SOCKET),
|
||||||
|
descriptor(profile, "sensor", DeviceType.TEMPERATURE_HUMIDITY),
|
||||||
|
))
|
||||||
|
val viewModel = DeviceCenterViewModel(catalog, TestConfiguredRepository())
|
||||||
|
viewModel.initialize(profile)
|
||||||
|
viewModel.setFilter(DeviceCategoryFilter.SOCKET)
|
||||||
|
|
||||||
|
assertEquals(listOf("plug"), viewModel.uiState.value.cards.map { it.displayName })
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun profileChangeClearsSelectionAndLoadsNewCards() = runTest {
|
||||||
|
val first = ProviderProfileId("one")
|
||||||
|
val second = ProviderProfileId("two")
|
||||||
|
val catalog = TestCatalog(listOf(descriptor(first, "old", DeviceType.SOCKET)))
|
||||||
|
val viewModel = DeviceCenterViewModel(catalog, TestConfiguredRepository())
|
||||||
|
viewModel.initialize(first)
|
||||||
|
viewModel.select(catalog.devices.single())
|
||||||
|
catalog.emit(listOf(descriptor(second, "new", DeviceType.INFRARED)))
|
||||||
|
viewModel.initialize(second)
|
||||||
|
|
||||||
|
assertEquals(second, viewModel.uiState.value.profileId)
|
||||||
|
assertEquals(null, viewModel.uiState.value.selectedCard)
|
||||||
|
assertEquals(listOf("new"), viewModel.uiState.value.cards.map { it.displayName })
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun descriptor(profile: ProviderProfileId, name: String, type: DeviceType) =
|
||||||
|
DeviceDescriptor(DeviceId("${profile.value}:$name"), profile, DeviceName(name), type)
|
||||||
|
|
||||||
|
private class TestCatalog(initial: List<DeviceDescriptor>) : DeviceCatalog {
|
||||||
|
val devices = initial.toMutableList()
|
||||||
|
private val state = MutableStateFlow<DomainResult<List<DeviceDescriptor>>>(DomainResult.Success(initial))
|
||||||
|
override fun observeDevices(profileId: ProviderProfileId): Flow<DomainResult<List<DeviceDescriptor>>> = state
|
||||||
|
fun emit(value: List<DeviceDescriptor>) { devices.clear(); devices.addAll(value); state.value = DomainResult.Success(value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestConfiguredRepository : ConfiguredDeviceRepository {
|
||||||
|
override fun observe(profileId: ProviderProfileId): Flow<DomainResult<List<DeviceDescriptor>>> =
|
||||||
|
MutableStateFlow(DomainResult.Success(emptyList()))
|
||||||
|
override suspend fun add(profileId: ProviderProfileId, name: DeviceName, type: DeviceType): DomainResult<DeviceDescriptor> =
|
||||||
|
DomainResult.Failure(DomainError.InvalidRequest())
|
||||||
|
override suspend fun remove(profileId: ProviderProfileId, deviceName: DeviceName): DomainResult<Unit> =
|
||||||
|
DomainResult.Success(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user