From 24886994a6afc1a443b92e2630903612403158f8 Mon Sep 17 00:00:00 2001 From: andrekir Date: Mon, 16 Sep 2024 18:44:18 -0300 Subject: [PATCH] =?UTF-8?q?feat:=20show=20lock=20icon=20for=20nodes=20with?= =?UTF-8?q?=20public=20keys=20=F0=9F=94=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/geeksville/mesh/model/UIState.kt | 5 ++- .../geeksville/mesh/ui/MessagesFragment.kt | 5 ++- .../java/com/geeksville/mesh/ui/NodeInfo.kt | 8 ++-- .../java/com/geeksville/mesh/ui/NodeMenu.kt | 6 +-- .../com/geeksville/mesh/ui/UsersFragment.kt | 39 +++++++++++-------- .../com/geeksville/mesh/ui/map/MapFragment.kt | 8 +++- 6 files changed, 43 insertions(+), 28 deletions(-) diff --git a/app/src/main/java/com/geeksville/mesh/model/UIState.kt b/app/src/main/java/com/geeksville/mesh/model/UIState.kt index 8b4cd4337..abdda95ed 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -21,6 +21,7 @@ import com.geeksville.mesh.android.Logging import com.geeksville.mesh.database.MeshLogRepository import com.geeksville.mesh.database.PacketRepository import com.geeksville.mesh.database.QuickChatActionRepository +import com.geeksville.mesh.database.entity.NodeEntity import com.geeksville.mesh.database.entity.Packet import com.geeksville.mesh.database.entity.QuickChatAction import com.geeksville.mesh.database.entity.toNodeInfo @@ -229,9 +230,9 @@ class UIViewModel @Inject constructor( ) @OptIn(ExperimentalCoroutinesApi::class) - val nodeList: StateFlow> = nodesUiState.flatMapLatest { state -> + val nodeList: StateFlow> = nodesUiState.flatMapLatest { state -> nodeDB.getNodes(state.sort, state.filter, state.includeUnknown) - }.mapLatest { list -> list.map { it.toNodeInfo() } }.stateIn( + }.stateIn( scope = viewModelScope, started = Eagerly, initialValue = emptyList(), diff --git a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt index 17ce1a18b..326af2b37 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/MessagesFragment.kt @@ -26,6 +26,7 @@ import com.geeksville.mesh.DataPacket import com.geeksville.mesh.android.Logging import com.geeksville.mesh.R import com.geeksville.mesh.database.entity.QuickChatAction +import com.geeksville.mesh.database.entity.toNodeInfo import com.geeksville.mesh.databinding.MessagesFragmentBinding import com.geeksville.mesh.model.Message import com.geeksville.mesh.model.UIViewModel @@ -294,9 +295,9 @@ class MessagesFragment : Fragment(), Logging { } private fun openNodeInfo(msg: Message) = lifecycleScope.launch { - model.nodeList.firstOrNull()?.find { it.user?.id == msg.user.id }?.let { node -> + model.nodeList.firstOrNull()?.find { it.user.id == msg.user.id }?.let { node -> parentFragmentManager.popBackStack() - model.focusUserNode(node) + model.focusUserNode(node.toNodeInfo()) } } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/NodeInfo.kt b/app/src/main/java/com/geeksville/mesh/ui/NodeInfo.kt index d384045bc..7d4e82cd8 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/NodeInfo.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/NodeInfo.kt @@ -75,11 +75,13 @@ fun NodeInfo( blinking: Boolean = false, expanded: Boolean = false, currentTimeMillis: Long, + hasPublicKey: Boolean = false, ) { + val isUnknownUser = thatNodeInfo.user?.hwModel == MeshProtos.HardwareModel.UNSET val unknownShortName = stringResource(id = R.string.unknown_node_short_name) - val unknownLongName = stringResource(id = R.string.unknown_username) + val longName = thatNodeInfo.user?.longName ?: stringResource(id = R.string.unknown_username) - val nodeName = thatNodeInfo.user?.longName ?: unknownLongName + val nodeName = if (hasPublicKey) "🔒 $longName" else longName val isThisNode = thisNodeInfo?.num == thatNodeInfo.num val distance = thisNodeInfo?.distanceStr(thatNodeInfo, distanceUnits) val (textColor, nodeColor) = thatNodeInfo.colors @@ -101,7 +103,7 @@ fun NodeInfo( label = "blinking node" ) - val style = if (thatNodeInfo.user?.hwModel == MeshProtos.HardwareModel.UNSET) { + val style = if (isUnknownUser) { LocalTextStyle.current.copy(fontStyle = FontStyle.Italic) } else { LocalTextStyle.current diff --git a/app/src/main/java/com/geeksville/mesh/ui/NodeMenu.kt b/app/src/main/java/com/geeksville/mesh/ui/NodeMenu.kt index 160ece914..a1d4a95d7 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/NodeMenu.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/NodeMenu.kt @@ -4,12 +4,12 @@ import android.view.Gravity import android.view.MenuItem import android.view.View import androidx.appcompat.widget.PopupMenu -import com.geeksville.mesh.NodeInfo import com.geeksville.mesh.R +import com.geeksville.mesh.database.entity.NodeEntity import com.google.android.material.dialog.MaterialAlertDialogBuilder internal fun View.nodeMenu( - node: NodeInfo, + node: NodeEntity, ignoreIncomingList: List, isOurNode: Boolean = false, isManaged: Boolean = false, @@ -43,7 +43,7 @@ internal fun View.nodeMenu( val message = if (isIgnored) R.string.ignore_remove else R.string.ignore_add MaterialAlertDialogBuilder(context) .setTitle(R.string.ignore) - .setMessage(context.getString(message, node.user?.longName)) + .setMessage(context.getString(message, node.user.longName)) .setNeutralButton(R.string.cancel) { _, _ -> } .setPositiveButton(R.string.send) { _, _ -> item.onMenuItemAction() diff --git a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt index 1139c59b0..74d3d96cf 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -22,9 +22,11 @@ import androidx.compose.ui.unit.dp import androidx.fragment.app.activityViewModels import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.geeksville.mesh.NodeInfo +import com.geeksville.mesh.DataPacket import com.geeksville.mesh.R import com.geeksville.mesh.android.Logging +import com.geeksville.mesh.database.entity.NodeEntity +import com.geeksville.mesh.database.entity.toNodeInfo import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.ui.components.NodeFilterTextField import com.geeksville.mesh.ui.components.rememberTimeTickWithLifecycle @@ -36,7 +38,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { private val model: UIViewModel by activityViewModels() - private fun popup(node: NodeInfo) { + private fun popup(node: NodeEntity) { if (!model.isConnected()) return val isOurNode = node.num == model.myNodeNum val ignoreIncomingList = model.ignoreIncomingList @@ -77,30 +79,31 @@ class UsersFragment : ScreenFragment("Users"), Logging { } R.id.remote_admin -> { - navigateToRadioConfig(node) + navigateToRadioConfig(node.num) } R.id.metrics -> { - navigateToMetrics(node) + navigateToMetrics(node.num) } } } } - private fun navigateToMessages(node: NodeInfo) = node.user?.let { user -> - val contactKey = "${node.channel}${user.id}" + private fun navigateToMessages(node: NodeEntity) = node.user.let { user -> + val channel = if (user.publicKey.isEmpty) node.channel else DataPacket.PKC_CHANNEL_INDEX + val contactKey = "$channel${user.id}" info("calling MessagesFragment filter: $contactKey") parentFragmentManager.navigateToMessages(contactKey, user.longName) } - private fun navigateToRadioConfig(node: NodeInfo) { - info("calling RadioConfig --> destNum: ${node.num}") - parentFragmentManager.navigateToRadioConfig(node.num) + private fun navigateToRadioConfig(nodeNum: Int) { + info("calling RadioConfig --> destNum: $nodeNum") + parentFragmentManager.navigateToRadioConfig(nodeNum) } - private fun navigateToMetrics(node: NodeInfo) { - info("calling Metrics --> destNum: ${node.num}") - parentFragmentManager.navigateToMetrics(node.num) + private fun navigateToMetrics(nodeNum: Int) { + info("calling Metrics --> destNum: $nodeNum") + parentFragmentManager.navigateToMetrics(nodeNum) } @@ -124,7 +127,7 @@ class UsersFragment : ScreenFragment("Users"), Logging { @Composable fun NodesScreen( model: UIViewModel = hiltViewModel(), - chipClicked: (NodeInfo) -> Unit, + chipClicked: (NodeEntity) -> Unit, ) { val focusManager = LocalFocusManager.current val state by model.nodesUiState.collectAsStateWithLifecycle() @@ -136,7 +139,7 @@ fun NodesScreen( val focusedNode by model.focusedNode.collectAsStateWithLifecycle() LaunchedEffect(focusedNode) { focusedNode?.let { node -> - val index = nodes.indexOfFirst { it == node } + val index = nodes.indexOfFirst { it.num == node.num } if (index != -1) { listState.animateScrollToItem(index) } @@ -167,9 +170,10 @@ fun NodesScreen( } items(nodes, key = { it.num }) { node -> + val nodeInfo = node.toNodeInfo() NodeInfo( thisNodeInfo = ourNodeInfo, - thatNodeInfo = node, + thatNodeInfo = nodeInfo, gpsFormat = state.gpsFormat, distanceUnits = state.distanceUnits, tempInFahrenheit = state.tempInFahrenheit, @@ -178,9 +182,10 @@ fun NodesScreen( focusManager.clearFocus() chipClicked(node) }, - blinking = node == focusedNode, + blinking = nodeInfo == focusedNode, expanded = state.showDetails, - currentTimeMillis = currentTimeMillis + currentTimeMillis = currentTimeMillis, + hasPublicKey = !node.user.publicKey.isEmpty ) } } diff --git a/app/src/main/java/com/geeksville/mesh/ui/map/MapFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/map/MapFragment.kt index 56c5968a9..f4dbeb0c9 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/map/MapFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/map/MapFragment.kt @@ -47,6 +47,7 @@ import com.geeksville.mesh.android.hasGps import com.geeksville.mesh.android.hasLocationPermission import com.geeksville.mesh.copy import com.geeksville.mesh.database.entity.Packet +import com.geeksville.mesh.database.entity.toNodeInfo import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.model.map.CustomTileSource import com.geeksville.mesh.model.map.MarkerWithLabel @@ -59,6 +60,8 @@ import com.geeksville.mesh.util.zoomIn import com.geeksville.mesh.waypoint import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.mapLatest import org.osmdroid.bonuspack.utils.BonusPackHelper.getBitmapFromVectorDrawable import org.osmdroid.config.Configuration import org.osmdroid.events.MapEventsReceiver @@ -299,7 +302,10 @@ fun MapView( if (permissions.entries.all { it.value }) map.toggleMyLocation() } - val nodes by model.nodeList.collectAsStateWithLifecycle() + @OptIn(ExperimentalCoroutinesApi::class) + val nodes by model.nodeList + .mapLatest { list -> list.map { it.toNodeInfo() } } + .collectAsStateWithLifecycle(emptyList()) val waypoints by model.waypoints.collectAsStateWithLifecycle(emptyMap()) var showDownloadButton: Boolean by remember { mutableStateOf(false) }