feat: implement `PacketResponseState.Loading` (#630)

pull/633/head
Andre K 2023-05-08 17:31:07 -03:00 zatwierdzone przez GitHub
rodzic 7d1d793fb9
commit 70f7ffb5fc
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
3 zmienionych plików z 133 dodań i 39 usunięć

Wyświetl plik

@ -79,6 +79,7 @@ import com.geeksville.mesh.ui.components.config.ExternalNotificationConfigItemLi
import com.geeksville.mesh.ui.components.config.LoRaConfigItemList import com.geeksville.mesh.ui.components.config.LoRaConfigItemList
import com.geeksville.mesh.ui.components.config.MQTTConfigItemList import com.geeksville.mesh.ui.components.config.MQTTConfigItemList
import com.geeksville.mesh.ui.components.config.NetworkConfigItemList import com.geeksville.mesh.ui.components.config.NetworkConfigItemList
import com.geeksville.mesh.ui.components.config.PacketResponseStateDialog
import com.geeksville.mesh.ui.components.config.PositionConfigItemList import com.geeksville.mesh.ui.components.config.PositionConfigItemList
import com.geeksville.mesh.ui.components.config.PowerConfigItemList import com.geeksville.mesh.ui.components.config.PowerConfigItemList
import com.geeksville.mesh.ui.components.config.RangeTestConfigItemList import com.geeksville.mesh.ui.components.config.RangeTestConfigItemList
@ -134,6 +135,20 @@ enum class ModuleDest(val title: String, val route: String, val config: ModuleCo
REMOTE_HARDWARE("Remote Hardware", "remote_hardware", ModuleConfigType.REMOTEHARDWARE_CONFIG); REMOTE_HARDWARE("Remote Hardware", "remote_hardware", ModuleConfigType.REMOTEHARDWARE_CONFIG);
} }
/**
* This sealed class defines each possible state of a packet response.
*/
sealed class PacketResponseState {
object Loading : PacketResponseState() {
var total: Int = 0
var completed: Int = 0
}
data class Success(val packets: List<String>) : PacketResponseState()
object Empty : PacketResponseState()
data class Error(val error: String) : PacketResponseState()
}
@Composable @Composable
fun RadioConfigNavHost(node: NodeInfo, viewModel: UIViewModel = viewModel()) { fun RadioConfigNavHost(node: NodeInfo, viewModel: UIViewModel = viewModel()) {
val navController = rememberNavController() val navController = rememberNavController()
@ -156,12 +171,15 @@ fun RadioConfigNavHost(node: NodeInfo, viewModel: UIViewModel = viewModel()) {
val configResponse by viewModel.packetResponse.collectAsStateWithLifecycle() val configResponse by viewModel.packetResponse.collectAsStateWithLifecycle()
val deviceProfile by viewModel.deviceProfile.collectAsStateWithLifecycle() val deviceProfile by viewModel.deviceProfile.collectAsStateWithLifecycle()
var isWaiting by remember { mutableStateOf(false) } var packetResponseState by remember { mutableStateOf<PacketResponseState>(PacketResponseState.Empty) }
val isWaiting = packetResponseState is PacketResponseState.Loading
var showEditDeviceProfileDialog by remember { mutableStateOf(false) }
val importConfigLauncher = rememberLauncherForActivityResult( val importConfigLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult() ActivityResultContracts.StartActivityForResult()
) { ) {
if (it.resultCode == Activity.RESULT_OK) { if (it.resultCode == Activity.RESULT_OK) {
showEditDeviceProfileDialog = true
it.data?.data?.let { file_uri -> viewModel.importProfile(file_uri) } it.data?.data?.let { file_uri -> viewModel.importProfile(file_uri) }
} }
} }
@ -174,10 +192,9 @@ fun RadioConfigNavHost(node: NodeInfo, viewModel: UIViewModel = viewModel()) {
} }
} }
var showEditDeviceProfileDialog by remember { mutableStateOf(false) }
if (showEditDeviceProfileDialog) EditDeviceProfileDialog( if (showEditDeviceProfileDialog) EditDeviceProfileDialog(
title = "Export configuration", title = if (deviceProfile != null) "Import configuration" else "Export configuration",
deviceProfile = with(viewModel) { deviceProfile = deviceProfile ?: with(viewModel) {
deviceProfile { deviceProfile {
ourNodeInfo.value?.user?.let { ourNodeInfo.value?.user?.let {
longName = it.longName longName = it.longName
@ -189,7 +206,10 @@ fun RadioConfigNavHost(node: NodeInfo, viewModel: UIViewModel = viewModel()) {
} }
}, },
onAddClick = { onAddClick = {
isWaiting = false showEditDeviceProfileDialog = false
if (deviceProfile != null) {
viewModel.installProfile(it)
} else {
viewModel.setDeviceProfile(it) viewModel.setDeviceProfile(it)
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
@ -197,29 +217,21 @@ fun RadioConfigNavHost(node: NodeInfo, viewModel: UIViewModel = viewModel()) {
putExtra(Intent.EXTRA_TITLE, "${destNum.toUInt()}.cfg") putExtra(Intent.EXTRA_TITLE, "${destNum.toUInt()}.cfg")
} }
exportConfigLauncher.launch(intent) exportConfigLauncher.launch(intent)
showEditDeviceProfileDialog = false }
}, },
onDismissRequest = { onDismissRequest = {
isWaiting = false
showEditDeviceProfileDialog = false showEditDeviceProfileDialog = false
viewModel.setDeviceProfile(null) viewModel.setDeviceProfile(null)
} }
) )
if (isWaiting && deviceProfile != null) { if (isWaiting) PacketResponseStateDialog(
EditDeviceProfileDialog( packetResponseState as PacketResponseState.Loading,
title = "Import configuration", onDismiss = {
deviceProfile = deviceProfile ?: return, packetResponseState = PacketResponseState.Empty
onAddClick = { viewModel.clearPacketResponse()
isWaiting = false
viewModel.installProfile(it)
},
onDismissRequest = {
isWaiting = false
viewModel.setDeviceProfile(null)
} }
) )
}
if (isWaiting) LaunchedEffect(configResponse) { if (isWaiting) LaunchedEffect(configResponse) {
val data = configResponse?.meshPacket?.decoded val data = configResponse?.meshPacket?.decoded
@ -233,28 +245,29 @@ fun RadioConfigNavHost(node: NodeInfo, viewModel: UIViewModel = viewModel()) {
// Stop once we get to the first disabled entry // Stop once we get to the first disabled entry
if (response.role != ChannelProtos.Channel.Role.DISABLED) { if (response.role != ChannelProtos.Channel.Role.DISABLED) {
// Not done yet, request next channel // Not done yet, request next channel
(packetResponseState as PacketResponseState.Loading).completed++
channelList.add(response.index, response.settings) channelList.add(response.index, response.settings)
viewModel.getChannel(destNum, response.index + 1) viewModel.getChannel(destNum, response.index + 1)
} else { } else {
// Received the last channel, start channel editor // Received the last channel, start channel editor
isWaiting = false packetResponseState = PacketResponseState.Success(emptyList())
navController.navigate("channels") navController.navigate("channels")
} }
} else { } else {
// Received max channels, start channel editor // Received max channels, start channel editor
isWaiting = false packetResponseState = PacketResponseState.Success(emptyList())
navController.navigate("channels") navController.navigate("channels")
} }
} }
AdminProtos.AdminMessage.PayloadVariantCase.GET_OWNER_RESPONSE -> { AdminProtos.AdminMessage.PayloadVariantCase.GET_OWNER_RESPONSE -> {
isWaiting = false packetResponseState = PacketResponseState.Success(emptyList())
userConfig = parsed.getOwnerResponse userConfig = parsed.getOwnerResponse
navController.navigate("user") navController.navigate("user")
} }
AdminProtos.AdminMessage.PayloadVariantCase.GET_CONFIG_RESPONSE -> { AdminProtos.AdminMessage.PayloadVariantCase.GET_CONFIG_RESPONSE -> {
isWaiting = false packetResponseState = PacketResponseState.Success(emptyList())
val response = parsed.getConfigResponse val response = parsed.getConfigResponse
radioConfig = response radioConfig = response
enumValues<ConfigDest>().find { it.name == "${response.payloadVariantCase}" } enumValues<ConfigDest>().find { it.name == "${response.payloadVariantCase}" }
@ -262,7 +275,7 @@ fun RadioConfigNavHost(node: NodeInfo, viewModel: UIViewModel = viewModel()) {
} }
AdminProtos.AdminMessage.PayloadVariantCase.GET_MODULE_CONFIG_RESPONSE -> { AdminProtos.AdminMessage.PayloadVariantCase.GET_MODULE_CONFIG_RESPONSE -> {
isWaiting = false packetResponseState = PacketResponseState.Success(emptyList())
val response = parsed.getModuleConfigResponse val response = parsed.getModuleConfigResponse
moduleConfig = response moduleConfig = response
enumValues<ModuleDest>().find { it.name == "${response.payloadVariantCase}" } enumValues<ModuleDest>().find { it.name == "${response.payloadVariantCase}" }
@ -271,11 +284,13 @@ fun RadioConfigNavHost(node: NodeInfo, viewModel: UIViewModel = viewModel()) {
AdminProtos.AdminMessage.PayloadVariantCase.GET_CANNED_MESSAGE_MODULE_MESSAGES_RESPONSE -> { AdminProtos.AdminMessage.PayloadVariantCase.GET_CANNED_MESSAGE_MODULE_MESSAGES_RESPONSE -> {
cannedMessageMessages = parsed.getCannedMessageModuleMessagesResponse cannedMessageMessages = parsed.getCannedMessageModuleMessagesResponse
(packetResponseState as PacketResponseState.Loading).completed++
viewModel.getModuleConfig(destNum, ModuleConfigType.CANNEDMSG_CONFIG_VALUE) viewModel.getModuleConfig(destNum, ModuleConfigType.CANNEDMSG_CONFIG_VALUE)
} }
AdminProtos.AdminMessage.PayloadVariantCase.GET_RINGTONE_RESPONSE -> { AdminProtos.AdminMessage.PayloadVariantCase.GET_RINGTONE_RESPONSE -> {
ringtone = parsed.getRingtoneResponse ringtone = parsed.getRingtoneResponse
(packetResponseState as PacketResponseState.Loading).completed++
viewModel.getModuleConfig(destNum, ModuleConfigType.EXTNOTIF_CONFIG_VALUE) viewModel.getModuleConfig(destNum, ModuleConfigType.EXTNOTIF_CONFIG_VALUE)
} }
@ -291,15 +306,20 @@ fun RadioConfigNavHost(node: NodeInfo, viewModel: UIViewModel = viewModel()) {
isLocal = destNum == viewModel.myNodeNum, isLocal = destNum == viewModel.myNodeNum,
headerText = node.user?.longName ?: stringResource(R.string.unknown_username), headerText = node.user?.longName ?: stringResource(R.string.unknown_username),
onRouteClick = { configType -> onRouteClick = { configType ->
isWaiting = true packetResponseState = PacketResponseState.Loading.apply {
total = 1
completed = 0
}
// clearAllConfigs() ? // clearAllConfigs() ?
when (configType) { when (configType) {
"USER" -> { viewModel.getOwner(destNum) } "USER" -> { viewModel.getOwner(destNum) }
"CHANNELS" -> { "CHANNELS" -> {
(packetResponseState as PacketResponseState.Loading).total = maxChannels
channelList.clear() channelList.clear()
viewModel.getChannel(destNum, 0) viewModel.getChannel(destNum, 0)
} }
"IMPORT" -> { "IMPORT" -> {
packetResponseState = PacketResponseState.Empty
viewModel.setDeviceProfile(null) viewModel.setDeviceProfile(null)
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE) addCategory(Intent.CATEGORY_OPENABLE)
@ -307,14 +327,19 @@ fun RadioConfigNavHost(node: NodeInfo, viewModel: UIViewModel = viewModel()) {
} }
importConfigLauncher.launch(intent) importConfigLauncher.launch(intent)
} }
"EXPORT" -> { showEditDeviceProfileDialog = true } "EXPORT" -> {
packetResponseState = PacketResponseState.Empty
showEditDeviceProfileDialog = true
}
is ConfigType -> { is ConfigType -> {
viewModel.getConfig(destNum, configType.number) viewModel.getConfig(destNum, configType.number)
} }
ModuleConfigType.CANNEDMSG_CONFIG -> { ModuleConfigType.CANNEDMSG_CONFIG -> {
(packetResponseState as PacketResponseState.Loading).total = 2
viewModel.getCannedMessages(destNum) viewModel.getCannedMessages(destNum)
} }
ModuleConfigType.EXTNOTIF_CONFIG -> { ModuleConfigType.EXTNOTIF_CONFIG -> {
(packetResponseState as PacketResponseState.Loading).total = 2
viewModel.getRingtone(destNum) viewModel.getRingtone(destNum)
} }
is ModuleConfigType -> { is ModuleConfigType -> {

Wyświetl plik

@ -31,11 +31,11 @@ fun EditDeviceProfileDialog(
onDismissRequest: () -> Unit, onDismissRequest: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
var longNameInput by remember { mutableStateOf(deviceProfile.hasLongName()) } var longNameInput by remember(deviceProfile) { mutableStateOf(deviceProfile.hasLongName()) }
var shortNameInput by remember { mutableStateOf(deviceProfile.hasShortName()) } var shortNameInput by remember(deviceProfile) { mutableStateOf(deviceProfile.hasShortName()) }
var channelUrlInput by remember { mutableStateOf(deviceProfile.hasChannelUrl()) } var channelUrlInput by remember(deviceProfile) { mutableStateOf(deviceProfile.hasChannelUrl()) }
var configInput by remember { mutableStateOf(deviceProfile.hasConfig()) } var configInput by remember(deviceProfile) { mutableStateOf(deviceProfile.hasConfig()) }
var moduleConfigInput by remember { mutableStateOf(deviceProfile.hasModuleConfig()) } var moduleConfigInput by remember(deviceProfile) { mutableStateOf(deviceProfile.hasModuleConfig()) }
AlertDialog( AlertDialog(
title = { Text(title) }, title = { Text(title) },

Wyświetl plik

@ -0,0 +1,69 @@
package com.geeksville.mesh.ui.components.config
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.LinearProgressIndicator
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.geeksville.mesh.R
import com.geeksville.mesh.ui.PacketResponseState
@Composable
fun PacketResponseStateDialog(
state: PacketResponseState.Loading,
onDismiss: () -> Unit
) {
val progress = state.completed.toFloat() / state.total.toFloat()
AlertDialog(
onDismissRequest = { },
text = {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text("%.0f%%".format(progress * 100))
LinearProgressIndicator(
progress = progress,
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
color = MaterialTheme.colors.onSurface,
)
}
},
buttons = {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
Button(
onClick = onDismiss,
modifier = Modifier.padding(bottom = 16.dp)
) { Text(stringResource(R.string.cancel)) }
}
}
)
}
@Preview(showBackground = true)
@Composable
fun PacketResponseStateDialogPreview() {
PacketResponseStateDialog(
state = PacketResponseState.Loading.apply {
total = 17
completed = 5
},
onDismiss = { }
)
}