mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Refactor: Improve shared contact import dialog (#1888)
This commit is contained in:
parent
5a52239721
commit
990cd103d7
6 changed files with 198 additions and 70 deletions
|
|
@ -309,6 +309,12 @@ class UIViewModel @Inject constructor(
|
|||
initialValue = NodesUiState.Empty,
|
||||
)
|
||||
|
||||
val unfilteredNodeList: StateFlow<List<Node>> = nodeDB.getNodes().stateIn(
|
||||
scope = viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(5_000),
|
||||
initialValue = emptyList(),
|
||||
)
|
||||
|
||||
val nodeList: StateFlow<List<Node>> = nodesUiState.flatMapLatest { state ->
|
||||
nodeDB.getNodes(state.sort, state.filter, state.includeUnknown, state.includeUnmessageable)
|
||||
}.stateIn(
|
||||
|
|
|
|||
|
|
@ -33,9 +33,12 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.twotone.QrCodeScanner
|
||||
import androidx.compose.material3.FloatingActionButton
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
|
|
@ -50,15 +53,19 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import com.geeksville.mesh.AdminProtos
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.android.BuildUtils.debug
|
||||
import com.geeksville.mesh.android.BuildUtils.errormsg
|
||||
import com.geeksville.mesh.android.getCameraPermissions
|
||||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.components.CopyIconButton
|
||||
import com.geeksville.mesh.ui.components.SimpleAlertDialog
|
||||
import com.google.protobuf.Descriptors
|
||||
import com.google.zxing.BarcodeFormat
|
||||
import com.google.zxing.MultiFormatWriter
|
||||
import com.google.zxing.WriterException
|
||||
|
|
@ -72,6 +79,7 @@ import java.net.MalformedURLException
|
|||
@Composable
|
||||
fun AddContactFAB(
|
||||
modifier: Modifier = Modifier,
|
||||
model: UIViewModel = hiltViewModel(),
|
||||
onSharedContactImport: (AdminProtos.SharedContact) -> Unit = {},
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
|
@ -93,14 +101,43 @@ fun AddContactFAB(
|
|||
}
|
||||
|
||||
if (contactToImport != null) {
|
||||
val nodeNum = contactToImport?.nodeNum
|
||||
val nodes by model.unfilteredNodeList.collectAsState()
|
||||
val node = nodes.find { it.num == nodeNum }
|
||||
SimpleAlertDialog(
|
||||
title = R.string.import_shared_contact,
|
||||
text = {
|
||||
Text("$contactToImport")
|
||||
Column {
|
||||
if (node != null) {
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.import_known_shared_contact_text
|
||||
)
|
||||
)
|
||||
if (node.user.publicKey.size() > 0 && node.user.publicKey != contactToImport?.user?.publicKey) {
|
||||
Text(
|
||||
text = stringResource(
|
||||
R.string.public_key_changed
|
||||
),
|
||||
color = MaterialTheme.colorScheme.error
|
||||
)
|
||||
}
|
||||
HorizontalDivider()
|
||||
Text(
|
||||
text = compareUsers(node.user, contactToImport!!.user)
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
text = userFieldsToString(contactToImport!!.user)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
dismissText = stringResource(R.string.cancel),
|
||||
onDismiss = {
|
||||
contactToImport = null
|
||||
},
|
||||
confirmText = stringResource(R.string.import_label),
|
||||
onConfirm = {
|
||||
onSharedContactImport(contactToImport!!)
|
||||
contactToImport = null
|
||||
|
|
@ -282,3 +319,80 @@ fun AdminProtos.SharedContact.getSharedContactUrl(): Uri {
|
|||
val enc = Base64.encodeToString(bytes, BASE64FLAGS)
|
||||
return "$URL_PREFIX$enc".toUri()
|
||||
}
|
||||
|
||||
fun compareUsers(oldUser: MeshProtos.User, newUser: MeshProtos.User): String {
|
||||
val changes = mutableListOf<String>()
|
||||
|
||||
// Iterate over all fields in the User message descriptor
|
||||
for (fieldDescriptor: Descriptors.FieldDescriptor in MeshProtos.User.getDescriptor().fields) {
|
||||
val fieldName = fieldDescriptor.name
|
||||
val oldValue =
|
||||
if (oldUser.hasField(fieldDescriptor)) oldUser.getField(fieldDescriptor) else null
|
||||
val newValue =
|
||||
if (newUser.hasField(fieldDescriptor)) newUser.getField(fieldDescriptor) else null
|
||||
|
||||
if (oldValue != newValue) {
|
||||
val oldValueString = valueToString(oldValue, fieldDescriptor)
|
||||
val newValueString = valueToString(newValue, fieldDescriptor)
|
||||
changes.add("$fieldName: $oldValueString -> $newValueString")
|
||||
}
|
||||
}
|
||||
|
||||
return if (changes.isEmpty()) {
|
||||
"No changes detected."
|
||||
} else {
|
||||
"Changes:\n" + changes.joinToString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
fun userFieldsToString(user: MeshProtos.User): String {
|
||||
val fieldLines = mutableListOf<String>()
|
||||
|
||||
for (fieldDescriptor: Descriptors.FieldDescriptor in MeshProtos.User.getDescriptor().fields) {
|
||||
val fieldName = fieldDescriptor.name
|
||||
if (user.hasField(fieldDescriptor)) {
|
||||
val value = user.getField(fieldDescriptor)
|
||||
val valueString =
|
||||
valueToString(value, fieldDescriptor) // Using the helper from previous example
|
||||
fieldLines.add("$fieldName: $valueString")
|
||||
} else if (fieldDescriptor.isRepeated || fieldDescriptor.hasDefaultValue() || fieldDescriptor.isOptional) {
|
||||
val defaultValue = fieldDescriptor.defaultValue
|
||||
val valueString = if (fieldDescriptor.isRepeated) {
|
||||
"[]" // Empty list
|
||||
} else if (user.hasField(fieldDescriptor)) {
|
||||
valueToString(
|
||||
user.getField(fieldDescriptor),
|
||||
fieldDescriptor
|
||||
)
|
||||
} else {
|
||||
valueToString(defaultValue, fieldDescriptor)
|
||||
}
|
||||
|
||||
fieldLines.add("$fieldName: $valueString")
|
||||
}
|
||||
}
|
||||
return if (fieldLines.isEmpty()) {
|
||||
"User object has no fields set."
|
||||
} else {
|
||||
fieldLines.joinToString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
private fun valueToString(value: Any?, fieldDescriptor: Descriptors.FieldDescriptor): String {
|
||||
if (value == null) {
|
||||
return "null"
|
||||
}
|
||||
return when (fieldDescriptor.type) {
|
||||
Descriptors.FieldDescriptor.Type.BYTES -> {
|
||||
// For ByteString, you might want to display it as hex or Base64
|
||||
// For simplicity, here we'll just show its size.
|
||||
if (value is com.google.protobuf.ByteString) {
|
||||
Base64.encodeToString(value.toByteArray(), Base64.DEFAULT).trim()
|
||||
} else {
|
||||
value.toString().trim()
|
||||
}
|
||||
}
|
||||
// Add more custom formatting for other types if needed
|
||||
else -> value.toString().trim()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ fun NodeScreen(
|
|||
) {
|
||||
@Suppress("NewApi")
|
||||
AddContactFAB(
|
||||
model = model,
|
||||
onSharedContactImport = { contact ->
|
||||
model.addSharedContact(contact)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,9 @@ import com.geeksville.mesh.ui.theme.AppTheme
|
|||
fun SimpleAlertDialog(
|
||||
@StringRes title: Int,
|
||||
text: @Composable (() -> Unit)? = null,
|
||||
confirmText: String? = null,
|
||||
onConfirm: (() -> Unit)? = null,
|
||||
dismissText: String? = null,
|
||||
onDismiss: () -> Unit = {},
|
||||
) = AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
|
|
@ -51,7 +53,7 @@ fun SimpleAlertDialog(
|
|||
colors = ButtonDefaults.textButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
),
|
||||
) { Text(text = stringResource(id = R.string.close)) }
|
||||
) { Text(text = dismissText ?: stringResource(id = R.string.cancel)) }
|
||||
},
|
||||
confirmButton = {
|
||||
onConfirm?.let {
|
||||
|
|
@ -62,7 +64,7 @@ fun SimpleAlertDialog(
|
|||
colors = ButtonDefaults.textButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||
),
|
||||
) { Text(text = stringResource(id = R.string.okay)) }
|
||||
) { Text(text = confirmText ?: stringResource(id = R.string.okay)) }
|
||||
}
|
||||
},
|
||||
title = {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue