diff --git a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt index 78cb84d1f..9aa28bd2b 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/ChannelFragment.kt @@ -11,16 +11,22 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.ButtonDefaults +import androidx.compose.material.Card +import androidx.compose.material.Checkbox +import androidx.compose.material.Chip import androidx.compose.material.ContentAlpha +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.LocalContentAlpha @@ -35,16 +41,20 @@ import androidx.compose.material.Text import androidx.compose.material.icons.Icons import androidx.compose.material.icons.twotone.Check import androidx.compose.material.icons.twotone.Close -import androidx.compose.material.icons.twotone.Edit import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.saveable.listSaver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.runtime.toMutableStateList +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter @@ -83,11 +93,9 @@ import com.geeksville.mesh.model.Channel import com.geeksville.mesh.model.ChannelOption import com.geeksville.mesh.model.UIViewModel import com.geeksville.mesh.model.getChannelUrl -import com.geeksville.mesh.model.primaryChannel import com.geeksville.mesh.model.qrCode import com.geeksville.mesh.model.toChannelSet import com.geeksville.mesh.service.MeshService -import com.geeksville.mesh.ui.components.ClickableTextField import com.geeksville.mesh.ui.components.DropDownPreference import com.geeksville.mesh.ui.components.PreferenceFooter import com.geeksville.mesh.ui.components.config.ChannelCard @@ -155,7 +163,20 @@ fun ChannelScreen( var showChannelEditor by rememberSaveable { mutableStateOf(false) } val isEditing = channelSet != channels || showChannelEditor - val primaryChannel = channelSet.primaryChannel + /* Holds selections made by the user for QR generation. */ + val channelSelections = rememberSaveable( + saver = listSaver( + save = { stateList -> stateList.toList() }, + restore = { it.toMutableStateList() } + ) + ) { mutableStateListOf(elements = Array(size = 8, init = { true })) } + + val selectedChannelSet = channelSet.copy { + val result = settings.filterIndexed { i, _ -> channelSelections.getOrNull(i) == true } + settings.clear() + settings.addAll(result) + } + val channelUrl = channelSet.getChannelUrl() val modemPresetName = Channel(loraConfig = channelSet.loraConfig).name @@ -305,15 +326,25 @@ fun ChannelScreen( state = listState, contentPadding = PaddingValues(horizontal = 24.dp, vertical = 16.dp), ) { - if (!showChannelEditor) item { - ClickableTextField( - label = R.string.channel_name, - value = primaryChannel?.name.orEmpty(), - onClick = { showChannelEditor = true }, - enabled = enabled, - trailingIcon = Icons.TwoTone.Edit, - modifier = Modifier.fillMaxWidth(), - ) + if (!showChannelEditor) { + itemsIndexed(channelSet.settingsList) { index, channel -> + ChannelSelection( + index = index, + title = channel.name.ifEmpty { modemPresetName }, + enabled = enabled, + isSelected = channelSelections[index], + onSelected = { + channelSelections[index] = it + } + ) + } + item { + OutlinedButton( + modifier = Modifier.fillMaxWidth(), + onClick = { showChannelEditor = true }, + enabled = enabled, + ) { Text(text = stringResource(R.string.edit)) } + } } else { dragDropItemsIndexed( items = channelSet.settingsList, @@ -338,7 +369,7 @@ fun ChannelScreen( } showEditChannelDialog = channelSet.settingsList.lastIndex }, - enabled = viewModel.maxChannels > channelSet.settingsCount, + enabled = enabled && viewModel.maxChannels > channelSet.settingsCount, colors = ButtonDefaults.buttonColors( disabledContentColor = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) ) @@ -348,7 +379,7 @@ fun ChannelScreen( if (!isEditing) item { Image( - painter = channelSet.qrCode?.let { BitmapPainter(it.asImageBitmap()) } + painter = selectedChannelSet.qrCode?.let { BitmapPainter(it.asImageBitmap()) } ?: painterResource(id = R.drawable.qrcode), contentDescription = stringResource(R.string.qr_code), contentScale = ContentScale.Inside, @@ -464,6 +495,48 @@ fun ChannelScreen( } } +/** + * Enables the user to select what channels are used for QR generation. + */ +@OptIn(ExperimentalMaterialApi::class) +@Composable +private fun ChannelSelection( + index: Int, + title: String, + enabled: Boolean, + isSelected: Boolean, + onSelected: (Boolean) -> Unit +) { + var checked by remember { mutableStateOf(isSelected) } + Card( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 2.dp), + elevation = 4.dp + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(vertical = 4.dp, horizontal = 4.dp) + ) { + Chip(onClick = { }, enabled = enabled) { Text("$index") } + Text( + text = title, + style = MaterialTheme.typography.body1, + color = if (!enabled) MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled) else Color.Unspecified, + modifier = Modifier.weight(1f) + ) + Checkbox( + enabled = enabled, + checked = checked, + onCheckedChange = { + onSelected.invoke(it) + checked = it + } + ) + } + } +} + @Preview(showBackground = true) @Composable private fun ChannelScreenPreview() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 05ce44e25..b5855ff72 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -180,6 +180,7 @@ Radio configuration Module configuration Add + Edit Calculating… Offline Manager Current Cache size