kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
feat: implement `PacketResponseState.Loading` (#630)
rodzic
7d1d793fb9
commit
70f7ffb5fc
|
@ -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 -> {
|
||||||
|
|
|
@ -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) },
|
||||||
|
|
|
@ -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 = { }
|
||||||
|
)
|
||||||
|
}
|
Ładowanie…
Reference in New Issue