kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
feat: add `fixed_position` to config import/export
rodzic
0062d38c8b
commit
aa84d47375
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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) ->
|
||||
|
|
|
@ -136,19 +136,22 @@ class RadioConfigRepository @Inject constructor(
|
|||
* Flow representing the combined [DeviceProfile] protobuf.
|
||||
*/
|
||||
val deviceProfileFlow: Flow<DeviceProfile> = 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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<Descriptors.FieldDescriptor, Boolean>()
|
||||
.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 = {},
|
||||
)
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue