From aa84d47375d3660213b02e1b122c27c5cfa9787f Mon Sep 17 00:00:00 2001 From: andrekir Date: Wed, 9 Oct 2024 19:41:05 -0300 Subject: [PATCH] feat: add `fixed_position` to config import/export --- .../compose/EditDeviceProfileDialogTest.kt | 18 ++++-- .../mesh/model/RadioConfigViewModel.kt | 5 +- .../datastore/RadioConfigRepository.kt | 9 ++- .../mesh/ui/DeviceSettingsFragment.kt | 4 +- .../config/EditDeviceProfileDialog.kt | 63 ++++++++++++------- 5 files changed, 64 insertions(+), 35 deletions(-) diff --git a/app/src/androidTest/java/com/geeksville/mesh/compose/EditDeviceProfileDialogTest.kt b/app/src/androidTest/java/com/geeksville/mesh/compose/EditDeviceProfileDialogTest.kt index c687dd58..6fdf7787 100644 --- a/app/src/androidTest/java/com/geeksville/mesh/compose/EditDeviceProfileDialogTest.kt +++ b/app/src/androidTest/java/com/geeksville/mesh/compose/EditDeviceProfileDialogTest.kt @@ -9,6 +9,7 @@ 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.position import com.geeksville.mesh.ui.components.config.EditDeviceProfileDialog import org.junit.Assert import org.junit.Rule @@ -29,17 +30,22 @@ class EditDeviceProfileDialogTest { longName = "Long name" shortName = "Short name" channelUrl = "https://meshtastic.org/e/#CgMSAQESBggBQANIAQ" + fixedPosition = position { + latitudeI = 327766650 + longitudeI = -967969890 + altitude = 138 + } } private fun testEditDeviceProfileDialog( - onDismissRequest: () -> Unit = {}, - onAddClick: (DeviceProfile) -> Unit = {}, + onDismiss: () -> Unit = {}, + onConfirm: (DeviceProfile) -> Unit = {}, ) = composeTestRule.setContent { EditDeviceProfileDialog( title = title, deviceProfile = deviceProfile, - onAddClick = onAddClick, - onDismissRequest = onDismissRequest, + onConfirm = onConfirm, + onDismiss = onDismiss, ) } @@ -68,7 +74,7 @@ class EditDeviceProfileDialogTest { fun testEditDeviceProfileDialog_clickCancelButton() { var onDismissClicked = false composeTestRule.apply { - testEditDeviceProfileDialog(onDismissRequest = { onDismissClicked = true }) + testEditDeviceProfileDialog(onDismiss = { onDismissClicked = true }) // Click the "Cancel" button onNodeWithText(getString(R.string.cancel)).performClick() @@ -82,7 +88,7 @@ class EditDeviceProfileDialogTest { fun testEditDeviceProfileDialog_addChannels() { var actualDeviceProfile: DeviceProfile? = null composeTestRule.apply { - testEditDeviceProfileDialog(onAddClick = { actualDeviceProfile = it }) + testEditDeviceProfileDialog(onConfirm = { actualDeviceProfile = it }) onNodeWithText(getString(R.string.save)).performClick() } diff --git a/app/src/main/java/com/geeksville/mesh/model/RadioConfigViewModel.kt b/app/src/main/java/com/geeksville/mesh/model/RadioConfigViewModel.kt index 140e76ee..64e1b83c 100644 --- a/app/src/main/java/com/geeksville/mesh/model/RadioConfigViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/RadioConfigViewModel.kt @@ -372,7 +372,7 @@ class RadioConfigViewModel @Inject constructor( .setShortName(if (hasShortName()) shortName else it.shortName) .setIsLicensed(it.isLicensed) .build() - if (it != user) setOwner(user) + setOwner(user) } if (hasChannelUrl()) try { setChannels(channelUrl) @@ -389,6 +389,9 @@ class RadioConfigViewModel @Inject constructor( setConfig(newConfig) } } + if (hasFixedPosition()) { + setFixedPosition(myNodeNum!!, Position(fixedPosition)) + } if (hasModuleConfig()) { val descriptor = ModuleConfigProtos.ModuleConfig.getDescriptor() moduleConfig.allFields.forEach { (field, value) -> diff --git a/app/src/main/java/com/geeksville/mesh/repository/datastore/RadioConfigRepository.kt b/app/src/main/java/com/geeksville/mesh/repository/datastore/RadioConfigRepository.kt index 0ed2ed12..ef8e071f 100644 --- a/app/src/main/java/com/geeksville/mesh/repository/datastore/RadioConfigRepository.kt +++ b/app/src/main/java/com/geeksville/mesh/repository/datastore/RadioConfigRepository.kt @@ -136,19 +136,22 @@ class RadioConfigRepository @Inject constructor( * Flow representing the combined [DeviceProfile] protobuf. */ val deviceProfileFlow: Flow = combine( - nodeDBbyNum, + nodeDB.ourNodeInfo, channelSetFlow, localConfigFlow, moduleConfigFlow, - ) { nodes, channels, localConfig, localModuleConfig -> + ) { node, channels, localConfig, localModuleConfig -> deviceProfile { - nodes.values.firstOrNull()?.user?.let { + node?.user?.let { longName = it.longName shortName = it.shortName } channelUrl = channels.getChannelUrl().toString() config = localConfig moduleConfig = localModuleConfig + if (node != null && localConfig.position.fixedPosition) { + fixedPosition = node.position + } } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/DeviceSettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/DeviceSettingsFragment.kt index bc495ad4..a6ed83a1 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/DeviceSettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/DeviceSettingsFragment.kt @@ -288,7 +288,7 @@ fun RadioConfigNavHost( if (showEditDeviceProfileDialog) EditDeviceProfileDialog( title = if (deviceProfile != null) "Import configuration" else "Export configuration", deviceProfile = deviceProfile ?: viewModel.currentDeviceProfile, - onAddClick = { + onConfirm = { showEditDeviceProfileDialog = false if (deviceProfile != null) { viewModel.installProfile(it) @@ -302,7 +302,7 @@ fun RadioConfigNavHost( exportConfigLauncher.launch(intent) } }, - onDismissRequest = { + onDismiss = { showEditDeviceProfileDialog = false viewModel.setDeviceProfile(null) } diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/EditDeviceProfileDialog.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/EditDeviceProfileDialog.kt index 88854728..d75b4871 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/config/EditDeviceProfileDialog.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/EditDeviceProfileDialog.kt @@ -7,8 +7,11 @@ 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.foundation.shape.RoundedCornerShape import androidx.compose.material.AlertDialog import androidx.compose.material.Button +import androidx.compose.material.Divider +import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.material.TextButton import androidx.compose.runtime.Composable @@ -16,34 +19,51 @@ import androidx.compose.runtime.mutableStateMapOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.geeksville.mesh.ClientOnlyProtos +import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile import com.geeksville.mesh.R -import com.geeksville.mesh.deviceProfile import com.geeksville.mesh.ui.components.SwitchPreference import com.google.protobuf.Descriptors +private const val SupportedFields = 7 + +@Suppress("LongMethod") @OptIn(ExperimentalLayoutApi::class) @Composable fun EditDeviceProfileDialog( title: String, - deviceProfile: ClientOnlyProtos.DeviceProfile, - onAddClick: (ClientOnlyProtos.DeviceProfile) -> Unit, - onDismissRequest: () -> Unit, + deviceProfile: DeviceProfile, + onConfirm: (DeviceProfile) -> Unit, + onDismiss: () -> Unit, modifier: Modifier = Modifier, ) { val state = remember { val fields = deviceProfile.descriptorForType.fields + .filter { it.number < SupportedFields } // TODO add ringtone & canned messages mutableStateMapOf() .apply { putAll(fields.associateWith(deviceProfile::hasField)) } } AlertDialog( - title = { Text(title) }, - onDismissRequest = onDismissRequest, + onDismissRequest = onDismiss, + shape = RoundedCornerShape(16.dp), + backgroundColor = MaterialTheme.colors.background, text = { Column(modifier.fillMaxWidth()) { + Text( + text = title, + style = MaterialTheme.typography.h6.copy( + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + ), + modifier = Modifier + .fillMaxWidth() + .padding(bottom = 16.dp), + ) + Divider() state.keys.sortedBy { it.number }.forEach { field -> SwitchPreference( title = field.name, @@ -53,33 +73,30 @@ fun EditDeviceProfileDialog( padding = PaddingValues(0.dp) ) } + Divider() } }, buttons = { FlowRow( - modifier = modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, + modifier = modifier + .fillMaxWidth() + .padding(start = 24.dp, end = 24.dp, bottom = 16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), ) { TextButton( - modifier = modifier - .fillMaxWidth() - .padding(horizontal = 24.dp) - .weight(1f), - onClick = onDismissRequest + modifier = modifier.weight(1f), + onClick = onDismiss ) { Text(stringResource(R.string.cancel)) } Button( - modifier = modifier - .fillMaxWidth() - .padding(horizontal = 24.dp) - .weight(1f), + modifier = modifier.weight(1f), onClick = { - val builder = ClientOnlyProtos.DeviceProfile.newBuilder() + val builder = DeviceProfile.newBuilder() deviceProfile.allFields.forEach { (field, value) -> if (state[field] == true) { builder.setField(field, value) } } - onAddClick(builder.build()) + onConfirm(builder.build()) }, enabled = state.values.any { it }, ) { Text(stringResource(R.string.save)) } @@ -93,8 +110,8 @@ fun EditDeviceProfileDialog( private fun EditDeviceProfileDialogPreview() { EditDeviceProfileDialog( title = "Export configuration", - deviceProfile = deviceProfile { }, - onAddClick = { }, - onDismissRequest = { }, + deviceProfile = DeviceProfile.getDefaultInstance(), + onConfirm = {}, + onDismiss = {}, ) }