mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor: update EditDeviceProfileDialog using dynamic fields
This commit is contained in:
parent
0b7718f8d5
commit
19e0f7d8b3
3 changed files with 144 additions and 71 deletions
|
|
@ -0,0 +1,93 @@
|
|||
package com.geeksville.mesh.compose
|
||||
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.junit4.createComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.deviceProfile
|
||||
import com.geeksville.mesh.ui.components.config.EditDeviceProfileDialog
|
||||
import org.junit.Assert
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class EditDeviceProfileDialogTest {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
|
||||
private fun getString(id: Int): String =
|
||||
InstrumentationRegistry.getInstrumentation().targetContext.getString(id)
|
||||
|
||||
private val title = "Export configuration"
|
||||
private val deviceProfile = deviceProfile {
|
||||
longName = "Long name"
|
||||
shortName = "Short name"
|
||||
channelUrl = "https://meshtastic.org/e/#CgMSAQESBggBQANIAQ"
|
||||
}
|
||||
|
||||
private fun testEditDeviceProfileDialog(
|
||||
onDismissRequest: () -> Unit = {},
|
||||
onAddClick: (DeviceProfile) -> Unit = {},
|
||||
) = composeTestRule.setContent {
|
||||
EditDeviceProfileDialog(
|
||||
title = title,
|
||||
deviceProfile = deviceProfile,
|
||||
onAddClick = onAddClick,
|
||||
onDismissRequest = onDismissRequest,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEditDeviceProfileDialog_showsDialogTitle() {
|
||||
composeTestRule.apply {
|
||||
testEditDeviceProfileDialog()
|
||||
|
||||
// Verify that the dialog title is displayed
|
||||
onNodeWithText(title).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEditDeviceProfileDialog_showsCancelAndSaveButtons() {
|
||||
composeTestRule.apply {
|
||||
testEditDeviceProfileDialog()
|
||||
|
||||
// Verify the "Cancel" and "Save" buttons are displayed
|
||||
onNodeWithText(getString(R.string.cancel)).assertIsDisplayed()
|
||||
onNodeWithText(getString(R.string.save)).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEditDeviceProfileDialog_clickCancelButton() {
|
||||
var onDismissClicked = false
|
||||
composeTestRule.apply {
|
||||
testEditDeviceProfileDialog(onDismissRequest = { onDismissClicked = true })
|
||||
|
||||
// Click the "Cancel" button
|
||||
onNodeWithText(getString(R.string.cancel)).performClick()
|
||||
}
|
||||
|
||||
// Verify onDismiss is called
|
||||
Assert.assertTrue(onDismissClicked)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEditDeviceProfileDialog_addChannels() {
|
||||
var actualDeviceProfile: DeviceProfile? = null
|
||||
composeTestRule.apply {
|
||||
testEditDeviceProfileDialog(onAddClick = { actualDeviceProfile = it })
|
||||
|
||||
onNodeWithText(getString(R.string.save)).performClick()
|
||||
}
|
||||
|
||||
// Verify onConfirm is called with the correct DeviceProfile
|
||||
Assert.assertEquals(deviceProfile, actualDeviceProfile)
|
||||
}
|
||||
}
|
||||
|
|
@ -326,7 +326,8 @@ class RadioConfigViewModel @Inject constructor(
|
|||
if (hasLongName() || hasShortName()) destNode.value?.user?.let {
|
||||
val user = it.copy(
|
||||
longName = if (hasLongName()) longName else it.longName,
|
||||
shortName = if (hasShortName()) shortName else it.shortName
|
||||
shortName = if (hasShortName()) shortName else it.shortName,
|
||||
hwModel = MeshProtos.HardwareModel.UNSET,
|
||||
)
|
||||
if (it != user) setOwner(user.toProto())
|
||||
}
|
||||
|
|
@ -337,28 +338,22 @@ class RadioConfigViewModel @Inject constructor(
|
|||
setResponseStateError(ex.customMessage)
|
||||
}
|
||||
if (hasConfig()) {
|
||||
setConfig(config { device = config.device })
|
||||
setConfig(config { position = config.position })
|
||||
setConfig(config { power = config.power })
|
||||
setConfig(config { network = config.network })
|
||||
setConfig(config { display = config.display })
|
||||
setConfig(config { lora = config.lora })
|
||||
setConfig(config { bluetooth = config.bluetooth })
|
||||
val descriptor = config.descriptorForType
|
||||
config.allFields.forEach { (field, value) ->
|
||||
val newConfig = ConfigProtos.Config.newBuilder()
|
||||
.setField(descriptor.findFieldByName(field.name), value)
|
||||
.build()
|
||||
setConfig(newConfig)
|
||||
}
|
||||
}
|
||||
if (hasModuleConfig()) moduleConfig.let {
|
||||
setModuleConfig(moduleConfig { mqtt = it.mqtt })
|
||||
setModuleConfig(moduleConfig { serial = it.serial })
|
||||
setModuleConfig(moduleConfig { externalNotification = it.externalNotification })
|
||||
setModuleConfig(moduleConfig { storeForward = it.storeForward })
|
||||
setModuleConfig(moduleConfig { rangeTest = it.rangeTest })
|
||||
setModuleConfig(moduleConfig { telemetry = it.telemetry })
|
||||
setModuleConfig(moduleConfig { cannedMessage = it.cannedMessage })
|
||||
setModuleConfig(moduleConfig { audio = it.audio })
|
||||
setModuleConfig(moduleConfig { remoteHardware = it.remoteHardware })
|
||||
setModuleConfig(moduleConfig { neighborInfo = it.neighborInfo })
|
||||
setModuleConfig(moduleConfig { ambientLighting = it.ambientLighting })
|
||||
setModuleConfig(moduleConfig { detectionSensor = it.detectionSensor })
|
||||
setModuleConfig(moduleConfig { paxcounter = it.paxcounter })
|
||||
if (hasModuleConfig()) {
|
||||
val descriptor = moduleConfig.descriptorForType
|
||||
moduleConfig.allFields.forEach { (field, value) ->
|
||||
val newConfig = ModuleConfigProtos.ModuleConfig.newBuilder()
|
||||
.setField(descriptor.findFieldByName(field.name), value)
|
||||
.build()
|
||||
setModuleConfig(newConfig)
|
||||
}
|
||||
}
|
||||
meshService?.commitEditSettings()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
package com.geeksville.mesh.ui.components.config
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.mutableStateMapOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -21,8 +22,9 @@ import com.geeksville.mesh.ClientOnlyProtos
|
|||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.deviceProfile
|
||||
import com.geeksville.mesh.ui.components.SwitchPreference
|
||||
import com.google.accompanist.themeadapter.appcompat.AppCompatTheme
|
||||
import com.google.protobuf.Descriptors
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun EditDeviceProfileDialog(
|
||||
title: String,
|
||||
|
|
@ -31,72 +33,55 @@ fun EditDeviceProfileDialog(
|
|||
onDismissRequest: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var longNameInput by remember(deviceProfile) { mutableStateOf(deviceProfile.hasLongName()) }
|
||||
var shortNameInput by remember(deviceProfile) { mutableStateOf(deviceProfile.hasShortName()) }
|
||||
var channelUrlInput by remember(deviceProfile) { mutableStateOf(deviceProfile.hasChannelUrl()) }
|
||||
var configInput by remember(deviceProfile) { mutableStateOf(deviceProfile.hasConfig()) }
|
||||
var moduleConfigInput by remember(deviceProfile) { mutableStateOf(deviceProfile.hasModuleConfig()) }
|
||||
val state = remember {
|
||||
val fields = deviceProfile.descriptorForType.fields
|
||||
mutableStateMapOf<Descriptors.FieldDescriptor, Boolean>()
|
||||
.apply { putAll(fields.associateWith(deviceProfile::hasField)) }
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
title = { Text(title) },
|
||||
onDismissRequest = onDismissRequest,
|
||||
text = {
|
||||
AppCompatTheme {
|
||||
Column(modifier.fillMaxWidth()) {
|
||||
SwitchPreference(title = "longName",
|
||||
checked = longNameInput,
|
||||
enabled = deviceProfile.hasLongName(),
|
||||
onCheckedChange = { longNameInput = it }
|
||||
)
|
||||
SwitchPreference(title = "shortName",
|
||||
checked = shortNameInput,
|
||||
enabled = deviceProfile.hasShortName(),
|
||||
onCheckedChange = { shortNameInput = it }
|
||||
)
|
||||
SwitchPreference(title = "channelUrl",
|
||||
checked = channelUrlInput,
|
||||
enabled = deviceProfile.hasChannelUrl(),
|
||||
onCheckedChange = { channelUrlInput = it }
|
||||
)
|
||||
SwitchPreference(title = "config",
|
||||
checked = configInput,
|
||||
enabled = deviceProfile.hasConfig(),
|
||||
onCheckedChange = { configInput = it }
|
||||
)
|
||||
SwitchPreference(title = "moduleConfig",
|
||||
checked = moduleConfigInput,
|
||||
enabled = deviceProfile.hasModuleConfig(),
|
||||
onCheckedChange = { moduleConfigInput = it }
|
||||
Column(modifier.fillMaxWidth()) {
|
||||
state.keys.sortedBy { it.number }.forEach { field ->
|
||||
SwitchPreference(
|
||||
title = field.name,
|
||||
checked = state[field] == true,
|
||||
enabled = deviceProfile.hasField(field),
|
||||
onCheckedChange = { state[field] = it },
|
||||
padding = PaddingValues(0.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
buttons = {
|
||||
Row(
|
||||
FlowRow(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Button(
|
||||
TextButton(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 24.dp)
|
||||
.padding(horizontal = 24.dp)
|
||||
.weight(1f),
|
||||
onClick = onDismissRequest
|
||||
) { Text(stringResource(R.string.cancel)) }
|
||||
Button(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(end = 24.dp)
|
||||
.padding(horizontal = 24.dp)
|
||||
.weight(1f),
|
||||
onClick = {
|
||||
onAddClick(deviceProfile {
|
||||
if (longNameInput) longName = deviceProfile.longName
|
||||
if (shortNameInput) shortName = deviceProfile.shortName
|
||||
if (channelUrlInput) channelUrl = deviceProfile.channelUrl
|
||||
if (configInput) config = deviceProfile.config
|
||||
if (moduleConfigInput) moduleConfig = deviceProfile.moduleConfig
|
||||
})
|
||||
val builder = ClientOnlyProtos.DeviceProfile.newBuilder()
|
||||
deviceProfile.allFields.forEach { (field, value) ->
|
||||
if (state[field] == true) {
|
||||
builder.setField(field, value)
|
||||
}
|
||||
}
|
||||
onAddClick(builder.build())
|
||||
},
|
||||
enabled = state.values.any { it },
|
||||
) { Text(stringResource(R.string.save)) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue