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 baee8dd1f..768f24a48 100644 --- a/app/src/main/java/com/geeksville/mesh/model/UIState.kt +++ b/app/src/main/java/com/geeksville/mesh/model/UIState.kt @@ -177,6 +177,7 @@ data class Contact( val isMuted: Boolean, val isUnmessageable: Boolean, val nodeColors: Pair? = null, + val isDefaultPSK: Boolean? = false ) @Suppress("LongParameterList", "LargeClass") @@ -519,6 +520,15 @@ class UIViewModel @Inject constructor( } else { user.longName } + val isDefaultPSK = if (toBroadcast) { + val _channel = channelSet.getChannel(data.channel) + val isDefaultPSK = (_channel?.settings?.psk?.size() == 1 && + _channel.settings.psk.byteAt(0) == 1.toByte()) || + _channel?.settings?.psk?.toByteArray()?.isEmpty() == true + isDefaultPSK + } else { + false + } Contact( contactKey = contactKey, @@ -535,6 +545,7 @@ class UIViewModel @Inject constructor( } else { null }, + isDefaultPSK = isDefaultPSK ) } }.stateIn( diff --git a/app/src/main/java/com/geeksville/mesh/ui/contact/ContactItem.kt b/app/src/main/java/com/geeksville/mesh/ui/contact/ContactItem.kt index 99404dc4d..b82d42a7a 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/contact/ContactItem.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/contact/ContactItem.kt @@ -23,6 +23,7 @@ import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width @@ -41,6 +42,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -49,6 +51,7 @@ import androidx.compose.ui.unit.dp import com.geeksville.mesh.R import com.geeksville.mesh.model.Contact import com.geeksville.mesh.ui.common.theme.AppTheme +import androidx.compose.ui.graphics.vector.ImageVector @Suppress("LongMethod") @Composable @@ -108,10 +111,23 @@ fun ContactItem( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, ) { - Text( - text = longName, - modifier = Modifier.weight(1f), - ) + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.weight(1f)) { + Text( + text = longName, + ) + // Show unlock icon for broadcast with default PSK + val isBroadcast = contact.contactKey.getOrNull(1) == '^' || + contact.contactKey.endsWith("^all") || + contact.contactKey.endsWith("^broadcast") + + if (isBroadcast && isDefaultPSK == true) { + Spacer(modifier = Modifier.width(10.dp)) + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_lock_open_right_24), + contentDescription = "Unlocked" + ) + } + } Text( text = lastMessageTime.orEmpty(), color = MaterialTheme.colorScheme.onSurface, @@ -172,7 +188,7 @@ private fun ContactItemPreview() { unreadCount = 2, messageCount = 10, isMuted = true, - isUnmessageable = false, + isUnmessageable = false ), selected = false, ) diff --git a/app/src/main/java/com/geeksville/mesh/ui/message/Message.kt b/app/src/main/java/com/geeksville/mesh/ui/message/Message.kt index 4b3afc159..a2ee72c77 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/message/Message.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/message/Message.kt @@ -29,6 +29,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState @@ -76,6 +77,7 @@ import androidx.compose.ui.platform.ClipEntry import androidx.compose.ui.platform.LocalClipboard import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization @@ -97,6 +99,7 @@ import com.geeksville.mesh.ui.node.components.NodeKeyStatusIcon import com.geeksville.mesh.ui.node.components.NodeMenuAction import com.geeksville.mesh.ui.sharing.SharedContactDialog import kotlinx.coroutines.launch +import androidx.compose.ui.graphics.vector.ImageVector private const val MESSAGE_CHARACTER_LIMIT = 200 private const val SNIPPET_CHARACTER_LIMIT = 50 @@ -122,12 +125,20 @@ internal fun MessageScreen( val channels by viewModel.channels.collectAsStateWithLifecycle() val channelName by remember(channelIndex) { derivedStateOf { - channelIndex?.let { channels.getChannel(it)?.name } ?: "Unknown Channel" + channelIndex?.let { + val channel = channels.getChannel(it) + val name = channel?.name ?: "Unknown Channel" + // Check if PSK is the default (base64 'AQ==', i.e., single byte 0x01) + val isDefaultPSK = (channel?.settings?.psk?.size() == 1 && + channel.settings.psk.byteAt(0) == 1.toByte()) || + channel?.psk?.toByteArray()?.isEmpty() == true + Pair(name, isDefaultPSK) + } ?: Pair("Unknown Channel", false) } } - + val (channelTitle, isDefaultPsk) = channelName val title = when (nodeId) { - DataPacket.ID_BROADCAST -> channelName + DataPacket.ID_BROADCAST -> channelTitle else -> viewModel.getUser(nodeId).longName } viewModel.setTitle(title) @@ -201,7 +212,8 @@ internal fun MessageScreen( } } } else { - MessageTopBar(title, channelIndex, mismatchKey, onNavigateBack) + MessageTopBar(title, channelIndex, mismatchKey, onNavigateBack, isDefaultPsk = isDefaultPsk + ) } }, ) { padding -> @@ -442,9 +454,22 @@ private fun MessageTopBar( title: String, channelIndex: Int?, mismatchKey: Boolean = false, - onNavigateBack: () -> Unit + onNavigateBack: () -> Unit, + isDefaultPsk: Boolean = false ) = TopAppBar( - title = { Text(text = title) }, + title = { + Row(verticalAlignment = Alignment.CenterVertically) { + Text(text = title) + if (isDefaultPsk + ) { + Spacer(modifier = Modifier.width(10.dp)) + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_lock_open_right_24), + contentDescription = "Unlocked" + ) + } + } + }, navigationIcon = { IconButton(onClick = onNavigateBack) { Icon( diff --git a/app/src/main/java/com/geeksville/mesh/ui/radioconfig/components/ChannelSettingsItemList.kt b/app/src/main/java/com/geeksville/mesh/ui/radioconfig/components/ChannelSettingsItemList.kt index 354cbca3c..316e97c85 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/radioconfig/components/ChannelSettingsItemList.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/radioconfig/components/ChannelSettingsItemList.kt @@ -34,6 +34,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState @@ -61,6 +62,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.res.stringResource +import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -78,6 +80,7 @@ import com.geeksville.mesh.ui.common.components.dragContainer import com.geeksville.mesh.ui.common.components.dragDropItemsIndexed import com.geeksville.mesh.ui.common.components.rememberDragDropState import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel +import androidx.compose.ui.graphics.vector.ImageVector @Composable private fun ChannelItem( @@ -122,12 +125,20 @@ fun ChannelCard( enabled: Boolean, onEditClick: () -> Unit, onDeleteClick: () -> Unit, + isDefaultPSK: Boolean = false, ) = ChannelItem( index = index, title = title, enabled = enabled, onClick = onEditClick, ) { + if (isDefaultPSK) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_lock_open_right_24), + contentDescription = "Unlocked" + ) + Spacer(modifier = Modifier.width(10.dp)) + } IconButton(onClick = { onDeleteClick() }) { Icon( imageVector = Icons.TwoTone.Close, @@ -143,13 +154,20 @@ fun ChannelSelection( title: String, enabled: Boolean, isSelected: Boolean, - onSelected: (Boolean) -> Unit + onSelected: (Boolean) -> Unit, + isDefaultPSK: Boolean = false, ) = ChannelItem( index = index, title = title, enabled = enabled, onClick = {}, ) { + if (isDefaultPSK) { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_lock_open_right_24), + contentDescription = "Unlocked" + ) + } Checkbox( enabled = enabled, checked = isSelected, @@ -274,12 +292,15 @@ fun ChannelSettingsItemList( items = settingsListInput, dragDropState = dragDropState, ) { index, channel, isDragging -> + val isDefaultPSK = (channel.psk.size() == 1 && channel.psk.byteAt(0) == 1.toByte()) || + channel.psk.toByteArray().isEmpty() ChannelCard( index = index, title = channel.name.ifEmpty { primaryChannel.name }, enabled = enabled, onEditClick = { showEditChannelDialog = index }, - onDeleteClick = { settingsListInput.removeAt(index) } + onDeleteClick = { settingsListInput.removeAt(index) }, + isDefaultPSK = isDefaultPSK ) if (index == 0 && !isDragging) { Text( diff --git a/app/src/main/java/com/geeksville/mesh/ui/sharing/Channel.kt b/app/src/main/java/com/geeksville/mesh/ui/sharing/Channel.kt index 549b46cb4..00536b7ea 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/sharing/Channel.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/sharing/Channel.kt @@ -448,9 +448,13 @@ private fun ChannelListView( AdaptiveTwoPane( first = { channelSet.settingsList.forEachIndexed { index, channel -> + val isDefaultPSK = (channel.psk.size() == 1 && channel.psk.byteAt(0) == 1.toByte()) || + channel.psk.toByteArray().isEmpty() + val displayTitle = channel.name.ifEmpty { modemPresetName } + ChannelSelection( index = index, - title = channel.name.ifEmpty { modemPresetName }, + title = displayTitle, enabled = enabled, isSelected = channelSelections[index], onSelected = { @@ -458,6 +462,7 @@ private fun ChannelListView( channelSelections[index] = it } }, + isDefaultPSK = isDefaultPSK ) } OutlinedButton(