refactor: extract `NavGraph` from `RadioConfig`

pull/1340/head
andrekir 2024-10-20 08:48:30 -03:00 zatwierdzone przez Andre K
rodzic b748c124ab
commit 3ac0e8c28e
5 zmienionych plików z 317 dodań i 307 usunięć

Wyświetl plik

@ -660,7 +660,7 @@ class MainActivity : AppCompatActivity(), Logging {
return true return true
} }
R.id.radio_config -> { R.id.radio_config -> {
supportFragmentManager.navigateToRadioConfig() supportFragmentManager.navigateToNavGraph()
return true return true
} }
R.id.save_messages_csv -> { R.id.save_messages_csv -> {

Wyświetl plik

@ -1,53 +1,27 @@
package com.geeksville.mesh.ui package com.geeksville.mesh.ui
import android.app.Activity
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.ContentAlpha
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.IconButton import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold import androidx.compose.material.Scaffold
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.TopAppBar import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.Forward import androidx.compose.material.icons.automirrored.filled.Forward
import androidx.compose.material.icons.automirrored.twotone.KeyboardArrowRight
import androidx.compose.material.icons.filled.Bluetooth
import androidx.compose.material.icons.filled.DisplaySettings
import androidx.compose.material.icons.automirrored.filled.List import androidx.compose.material.icons.automirrored.filled.List
import androidx.compose.material.icons.automirrored.filled.Message import androidx.compose.material.icons.automirrored.filled.Message
import androidx.compose.material.icons.automirrored.filled.VolumeUp import androidx.compose.material.icons.automirrored.filled.VolumeUp
import androidx.compose.material.icons.filled.Bluetooth
import androidx.compose.material.icons.filled.CellTower import androidx.compose.material.icons.filled.CellTower
import androidx.compose.material.icons.filled.Cloud import androidx.compose.material.icons.filled.Cloud
import androidx.compose.material.icons.filled.DataUsage import androidx.compose.material.icons.filled.DataUsage
import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.DisplaySettings
import androidx.compose.material.icons.filled.LightMode import androidx.compose.material.icons.filled.LightMode
import androidx.compose.material.icons.filled.LocationOn import androidx.compose.material.icons.filled.LocationOn
import androidx.compose.material.icons.filled.Notifications import androidx.compose.material.icons.filled.Notifications
@ -60,23 +34,15 @@ import androidx.compose.material.icons.filled.Security
import androidx.compose.material.icons.filled.Sensors import androidx.compose.material.icons.filled.Sensors
import androidx.compose.material.icons.filled.SettingsRemote import androidx.compose.material.icons.filled.SettingsRemote
import androidx.compose.material.icons.filled.Speed import androidx.compose.material.icons.filled.Speed
import androidx.compose.material.icons.filled.Upload
import androidx.compose.material.icons.filled.Usb import androidx.compose.material.icons.filled.Usb
import androidx.compose.material.icons.filled.Wifi import androidx.compose.material.icons.filled.Wifi
import androidx.compose.material.icons.twotone.Warning
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
@ -87,7 +53,6 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
import com.geeksville.mesh.Position import com.geeksville.mesh.Position
import com.geeksville.mesh.R import com.geeksville.mesh.R
import com.geeksville.mesh.android.Logging import com.geeksville.mesh.android.Logging
@ -100,7 +65,6 @@ import com.geeksville.mesh.moduleConfig
import com.geeksville.mesh.service.MeshService.ConnectionState import com.geeksville.mesh.service.MeshService.ConnectionState
import com.geeksville.mesh.ui.components.DeviceMetricsScreen import com.geeksville.mesh.ui.components.DeviceMetricsScreen
import com.geeksville.mesh.ui.components.EnvironmentMetricsScreen import com.geeksville.mesh.ui.components.EnvironmentMetricsScreen
import com.geeksville.mesh.ui.components.PreferenceCategory
import com.geeksville.mesh.ui.components.config.AmbientLightingConfigItemList import com.geeksville.mesh.ui.components.config.AmbientLightingConfigItemList
import com.geeksville.mesh.ui.components.config.AudioConfigItemList import com.geeksville.mesh.ui.components.config.AudioConfigItemList
import com.geeksville.mesh.ui.components.config.BluetoothConfigItemList import com.geeksville.mesh.ui.components.config.BluetoothConfigItemList
@ -109,7 +73,6 @@ import com.geeksville.mesh.ui.components.config.ChannelSettingsItemList
import com.geeksville.mesh.ui.components.config.DetectionSensorConfigItemList import com.geeksville.mesh.ui.components.config.DetectionSensorConfigItemList
import com.geeksville.mesh.ui.components.config.DeviceConfigItemList import com.geeksville.mesh.ui.components.config.DeviceConfigItemList
import com.geeksville.mesh.ui.components.config.DisplayConfigItemList import com.geeksville.mesh.ui.components.config.DisplayConfigItemList
import com.geeksville.mesh.ui.components.config.EditDeviceProfileDialog
import com.geeksville.mesh.ui.components.config.ExternalNotificationConfigItemList import com.geeksville.mesh.ui.components.config.ExternalNotificationConfigItemList
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
@ -129,11 +92,11 @@ import com.geeksville.mesh.ui.components.config.UserConfigItemList
import com.google.accompanist.themeadapter.appcompat.AppCompatTheme import com.google.accompanist.themeadapter.appcompat.AppCompatTheme
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
internal fun FragmentManager.navigateToRadioConfig( internal fun FragmentManager.navigateToNavGraph(
destNum: Int? = null, destNum: Int? = null,
startDestination: String = "RadioConfig", startDestination: String = "RadioConfig",
) { ) {
val radioConfigFragment = DeviceSettingsFragment().apply { val radioConfigFragment = NavGraphFragment().apply {
arguments = bundleOf("destNum" to destNum, "startDestination" to startDestination) arguments = bundleOf("destNum" to destNum, "startDestination" to startDestination)
} }
beginTransaction() beginTransaction()
@ -143,7 +106,7 @@ internal fun FragmentManager.navigateToRadioConfig(
} }
@AndroidEntryPoint @AndroidEntryPoint
class DeviceSettingsFragment : ScreenFragment("Radio Configuration"), Logging { class NavGraphFragment : ScreenFragment("NavGraph"), Logging {
private val model: RadioConfigViewModel by viewModels() private val model: RadioConfigViewModel by viewModels()
@ -189,7 +152,7 @@ class DeviceSettingsFragment : ScreenFragment("Radio Configuration"), Logging {
) )
} }
) { innerPadding -> ) { innerPadding ->
RadioConfigNavHost( NavGraph(
node = node, node = node,
viewModel = model, viewModel = model,
navController = navController, navController = navController,
@ -278,7 +241,7 @@ private fun MeshAppBar(
@Suppress("LongMethod", "CyclomaticComplexMethod") @Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable @Composable
fun RadioConfigNavHost( fun NavGraph(
node: NodeEntity?, node: NodeEntity?,
viewModel: RadioConfigViewModel = hiltViewModel(), viewModel: RadioConfigViewModel = hiltViewModel(),
navController: NavHostController = rememberNavController(), navController: NavHostController = rememberNavController(),
@ -603,244 +566,3 @@ fun RadioConfigNavHost(
} }
} }
} }
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
fun RadioConfigScreen(
node: NodeEntity?,
enabled: Boolean,
viewModel: RadioConfigViewModel,
modifier: Modifier = Modifier,
) {
val isLocal = node?.num == viewModel.myNodeNum
var deviceProfile by remember { mutableStateOf<DeviceProfile?>(null) }
var showEditDeviceProfileDialog by remember { mutableStateOf(false) }
val importConfigLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
showEditDeviceProfileDialog = true
it.data?.data?.let { uri ->
viewModel.importProfile(uri) { profile -> deviceProfile = profile }
}
}
}
val exportConfigLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
it.data?.data?.let { uri -> viewModel.exportProfile(uri, deviceProfile!!) }
}
}
if (showEditDeviceProfileDialog) {
EditDeviceProfileDialog(
title = if (deviceProfile != null) "Import configuration" else "Export configuration",
deviceProfile = deviceProfile ?: viewModel.currentDeviceProfile,
onConfirm = {
showEditDeviceProfileDialog = false
if (deviceProfile != null) {
viewModel.installProfile(it)
} else {
deviceProfile = it
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/*"
putExtra(Intent.EXTRA_TITLE, "${node!!.num.toUInt()}.cfg")
}
exportConfigLauncher.launch(intent)
}
},
onDismiss = {
showEditDeviceProfileDialog = false
deviceProfile = null
}
)
}
RadioConfigItemList(
enabled = enabled,
isLocal = isLocal,
modifier = modifier,
onRouteClick = { route ->
viewModel.setResponseStateLoading(route)
},
onImport = {
viewModel.clearPacketResponse()
deviceProfile = null
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/*"
}
importConfigLauncher.launch(intent)
},
onExport = {
viewModel.clearPacketResponse()
deviceProfile = null
showEditDeviceProfileDialog = true
},
)
}
@Composable
fun NavCard(
title: String,
enabled: Boolean,
icon: ImageVector? = null,
onClick: () -> Unit
) {
val color = if (enabled) {
MaterialTheme.colors.onSurface
} else {
MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled)
}
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 2.dp)
.clickable(enabled = enabled) { onClick() },
elevation = 4.dp
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 12.dp, horizontal = 16.dp)
) {
if (icon != null) {
Icon(
imageVector = icon,
contentDescription = title,
modifier = Modifier.size(24.dp),
tint = color,
)
Spacer(modifier = Modifier.width(8.dp))
}
Text(
text = title,
style = MaterialTheme.typography.body1,
color = color,
modifier = Modifier.weight(1f)
)
Icon(
Icons.AutoMirrored.TwoTone.KeyboardArrowRight, "trailingIcon",
modifier = Modifier.wrapContentSize(),
tint = color,
)
}
}
}
@Composable
private fun NavButton(@StringRes title: Int, enabled: Boolean, onClick: () -> Unit) {
var showDialog by remember { mutableStateOf(false) }
if (showDialog) AlertDialog(
onDismissRequest = {},
shape = RoundedCornerShape(16.dp),
backgroundColor = MaterialTheme.colors.background,
title = {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
) {
Icon(
imageVector = Icons.TwoTone.Warning,
contentDescription = "warning",
modifier = Modifier.padding(end = 8.dp)
)
Text(
text = "${stringResource(title)}?\n")
Icon(
imageVector = Icons.TwoTone.Warning,
contentDescription = "warning",
modifier = Modifier.padding(start = 8.dp)
)
}
},
buttons = {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
TextButton(
modifier = Modifier.weight(1f),
onClick = { showDialog = false },
) { Text(stringResource(R.string.cancel)) }
Button(
modifier = Modifier.weight(1f),
onClick = {
showDialog = false
onClick()
},
) { Text(stringResource(R.string.send)) }
}
}
)
Column {
Spacer(modifier = Modifier.height(4.dp))
Button(
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
enabled = enabled,
onClick = { showDialog = true },
) { Text(text = stringResource(title)) }
}
}
@Composable
private fun RadioConfigItemList(
enabled: Boolean = true,
isLocal: Boolean = true,
modifier: Modifier = Modifier,
onRouteClick: (Enum<*>) -> Unit = {},
onImport: () -> Unit = {},
onExport: () -> Unit = {},
) {
LazyColumn(
modifier = modifier,
contentPadding = PaddingValues(horizontal = 16.dp),
) {
item { PreferenceCategory(stringResource(R.string.device_settings)) }
items(ConfigRoute.entries) {
NavCard(title = it.title, icon = it.icon, enabled = enabled) { onRouteClick(it) }
}
item { PreferenceCategory(stringResource(R.string.module_settings)) }
items(ModuleRoute.entries) {
NavCard(title = it.title, icon = it.icon, enabled = enabled) { onRouteClick(it) }
}
if (isLocal) {
item {
PreferenceCategory("Backup & Restore")
NavCard(
title = "Import configuration",
icon = Icons.Default.Download,
enabled = enabled,
onClick = onImport,
)
NavCard(
title = "Export configuration",
icon = Icons.Default.Upload,
enabled = enabled,
onClick = onExport,
)
}
}
items(AdminRoute.entries) { NavButton(it.title, enabled) { onRouteClick(it) } }
}
}
@Preview(showBackground = true)
@Composable
private fun RadioSettingsScreenPreview() {
RadioConfigItemList()
}

Wyświetl plik

@ -0,0 +1,293 @@
package com.geeksville.mesh.ui
import android.app.Activity
import android.content.Intent
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.StringRes
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.AlertDialog
import androidx.compose.material.Button
import androidx.compose.material.Card
import androidx.compose.material.ContentAlpha
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.twotone.KeyboardArrowRight
import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.Upload
import androidx.compose.material.icons.twotone.Warning
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
import com.geeksville.mesh.R
import com.geeksville.mesh.database.entity.NodeEntity
import com.geeksville.mesh.model.RadioConfigViewModel
import com.geeksville.mesh.ui.components.PreferenceCategory
import com.geeksville.mesh.ui.components.config.EditDeviceProfileDialog
@Suppress("LongMethod")
@Composable
fun RadioConfigScreen(
node: NodeEntity?,
enabled: Boolean,
viewModel: RadioConfigViewModel,
modifier: Modifier = Modifier,
) {
val isLocal = node?.num == viewModel.myNodeNum
var deviceProfile by remember { mutableStateOf<DeviceProfile?>(null) }
var showEditDeviceProfileDialog by remember { mutableStateOf(false) }
val importConfigLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
showEditDeviceProfileDialog = true
it.data?.data?.let { uri ->
viewModel.importProfile(uri) { profile -> deviceProfile = profile }
}
}
}
val exportConfigLauncher = rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {
if (it.resultCode == Activity.RESULT_OK) {
it.data?.data?.let { uri -> viewModel.exportProfile(uri, deviceProfile!!) }
}
}
if (showEditDeviceProfileDialog) {
EditDeviceProfileDialog(
title = if (deviceProfile != null) "Import configuration" else "Export configuration",
deviceProfile = deviceProfile ?: viewModel.currentDeviceProfile,
onConfirm = {
showEditDeviceProfileDialog = false
if (deviceProfile != null) {
viewModel.installProfile(it)
} else {
deviceProfile = it
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/*"
putExtra(Intent.EXTRA_TITLE, "${node!!.num.toUInt()}.cfg")
}
exportConfigLauncher.launch(intent)
}
},
onDismiss = {
showEditDeviceProfileDialog = false
deviceProfile = null
}
)
}
RadioConfigItemList(
enabled = enabled,
isLocal = isLocal,
modifier = modifier,
onRouteClick = { route ->
viewModel.setResponseStateLoading(route)
},
onImport = {
viewModel.clearPacketResponse()
deviceProfile = null
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/*"
}
importConfigLauncher.launch(intent)
},
onExport = {
viewModel.clearPacketResponse()
deviceProfile = null
showEditDeviceProfileDialog = true
},
)
}
@Composable
fun NavCard(
title: String,
enabled: Boolean,
icon: ImageVector? = null,
onClick: () -> Unit
) {
val color = if (enabled) {
MaterialTheme.colors.onSurface
} else {
MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled)
}
Card(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 2.dp)
.clickable(enabled = enabled) { onClick() },
elevation = 4.dp
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(vertical = 12.dp, horizontal = 16.dp)
) {
if (icon != null) {
Icon(
imageVector = icon,
contentDescription = title,
modifier = Modifier.size(24.dp),
tint = color,
)
Spacer(modifier = Modifier.width(8.dp))
}
Text(
text = title,
style = MaterialTheme.typography.body1,
color = color,
modifier = Modifier.weight(1f)
)
Icon(
Icons.AutoMirrored.TwoTone.KeyboardArrowRight, "trailingIcon",
modifier = Modifier.wrapContentSize(),
tint = color,
)
}
}
}
@Composable
private fun NavButton(@StringRes title: Int, enabled: Boolean, onClick: () -> Unit) {
var showDialog by remember { mutableStateOf(false) }
if (showDialog) AlertDialog(
onDismissRequest = {},
shape = RoundedCornerShape(16.dp),
backgroundColor = MaterialTheme.colors.background,
title = {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center,
) {
Icon(
imageVector = Icons.TwoTone.Warning,
contentDescription = "warning",
modifier = Modifier.padding(end = 8.dp)
)
Text(
text = "${stringResource(title)}?\n")
Icon(
imageVector = Icons.TwoTone.Warning,
contentDescription = "warning",
modifier = Modifier.padding(start = 8.dp)
)
}
},
buttons = {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically,
) {
TextButton(
modifier = Modifier.weight(1f),
onClick = { showDialog = false },
) { Text(stringResource(R.string.cancel)) }
Button(
modifier = Modifier.weight(1f),
onClick = {
showDialog = false
onClick()
},
) { Text(stringResource(R.string.send)) }
}
}
)
Column {
Spacer(modifier = Modifier.height(4.dp))
Button(
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
enabled = enabled,
onClick = { showDialog = true },
) { Text(text = stringResource(title)) }
}
}
@Composable
private fun RadioConfigItemList(
enabled: Boolean = true,
isLocal: Boolean = true,
modifier: Modifier = Modifier,
onRouteClick: (Enum<*>) -> Unit = {},
onImport: () -> Unit = {},
onExport: () -> Unit = {},
) {
LazyColumn(
modifier = modifier,
contentPadding = PaddingValues(horizontal = 16.dp),
) {
item { PreferenceCategory(stringResource(R.string.device_settings)) }
items(ConfigRoute.entries) {
NavCard(title = it.title, icon = it.icon, enabled = enabled) { onRouteClick(it) }
}
item { PreferenceCategory(stringResource(R.string.module_settings)) }
items(ModuleRoute.entries) {
NavCard(title = it.title, icon = it.icon, enabled = enabled) { onRouteClick(it) }
}
if (isLocal) {
item {
PreferenceCategory("Backup & Restore")
NavCard(
title = "Import configuration",
icon = Icons.Default.Download,
enabled = enabled,
onClick = onImport,
)
NavCard(
title = "Export configuration",
icon = Icons.Default.Upload,
enabled = enabled,
onClick = onExport,
)
}
}
items(AdminRoute.entries) { NavButton(it.title, enabled) { onRouteClick(it) } }
}
}
@Preview(showBackground = true)
@Composable
private fun RadioSettingsScreenPreview() {
RadioConfigItemList()
}

Wyświetl plik

@ -97,7 +97,7 @@ class UsersFragment : ScreenFragment("Users"), Logging {
private fun navigateToRadioConfig(nodeNum: Int) { private fun navigateToRadioConfig(nodeNum: Int) {
info("calling NodeDetails --> destNum: $nodeNum") info("calling NodeDetails --> destNum: $nodeNum")
parentFragmentManager.navigateToRadioConfig(nodeNum, "NodeDetails") parentFragmentManager.navigateToNavGraph(nodeNum, "NodeDetails")
} }
override fun onCreateView( override fun onCreateView(

Wyświetl plik

@ -71,7 +71,6 @@
<ID>ConstructorParameterNaming:Packet.kt$Packet$@ColumnInfo(name = "contact_key") val contact_key: String</ID> <ID>ConstructorParameterNaming:Packet.kt$Packet$@ColumnInfo(name = "contact_key") val contact_key: String</ID>
<ID>ConstructorParameterNaming:Packet.kt$Packet$@ColumnInfo(name = "port_num") val port_num: Int</ID> <ID>ConstructorParameterNaming:Packet.kt$Packet$@ColumnInfo(name = "port_num") val port_num: Int</ID>
<ID>ConstructorParameterNaming:Packet.kt$Packet$@ColumnInfo(name = "received_time") val received_time: Long</ID> <ID>ConstructorParameterNaming:Packet.kt$Packet$@ColumnInfo(name = "received_time") val received_time: Long</ID>
<ID>CyclomaticComplexMethod:ChannelFragment.kt$@Composable fun ChannelScreen( viewModel: UIViewModel = viewModel(), showSnackbar: (String) -&gt; Unit = {}, )</ID>
<ID>CyclomaticComplexMethod:MainActivity.kt$MainActivity$override fun onOptionsItemSelected(item: MenuItem): Boolean</ID> <ID>CyclomaticComplexMethod:MainActivity.kt$MainActivity$override fun onOptionsItemSelected(item: MenuItem): Boolean</ID>
<ID>CyclomaticComplexMethod:MapFragment.kt$@Composable fun MapView( model: UIViewModel = viewModel(), )</ID> <ID>CyclomaticComplexMethod:MapFragment.kt$@Composable fun MapView( model: UIViewModel = viewModel(), )</ID>
<ID>CyclomaticComplexMethod:MeshService.kt$MeshService$private fun handleReceivedData(packet: MeshPacket)</ID> <ID>CyclomaticComplexMethod:MeshService.kt$MeshService$private fun handleReceivedData(packet: MeshPacket)</ID>
@ -151,7 +150,6 @@
<ID>LongMethod:AmbientLightingConfigItemList.kt$@Composable fun AmbientLightingConfigItemList( ambientLightingConfig: ModuleConfigProtos.ModuleConfig.AmbientLightingConfig, enabled: Boolean, onSaveClicked: (ModuleConfigProtos.ModuleConfig.AmbientLightingConfig) -&gt; Unit, )</ID> <ID>LongMethod:AmbientLightingConfigItemList.kt$@Composable fun AmbientLightingConfigItemList( ambientLightingConfig: ModuleConfigProtos.ModuleConfig.AmbientLightingConfig, enabled: Boolean, onSaveClicked: (ModuleConfigProtos.ModuleConfig.AmbientLightingConfig) -&gt; Unit, )</ID>
<ID>LongMethod:AudioConfigItemList.kt$@Composable fun AudioConfigItemList( audioConfig: AudioConfig, enabled: Boolean, onSaveClicked: (AudioConfig) -&gt; Unit, )</ID> <ID>LongMethod:AudioConfigItemList.kt$@Composable fun AudioConfigItemList( audioConfig: AudioConfig, enabled: Boolean, onSaveClicked: (AudioConfig) -&gt; Unit, )</ID>
<ID>LongMethod:CannedMessageConfigItemList.kt$@Composable fun CannedMessageConfigItemList( messages: String, cannedMessageConfig: CannedMessageConfig, enabled: Boolean, onSaveClicked: (messages: String, config: CannedMessageConfig) -&gt; Unit, )</ID> <ID>LongMethod:CannedMessageConfigItemList.kt$@Composable fun CannedMessageConfigItemList( messages: String, cannedMessageConfig: CannedMessageConfig, enabled: Boolean, onSaveClicked: (messages: String, config: CannedMessageConfig) -&gt; Unit, )</ID>
<ID>LongMethod:ChannelFragment.kt$@Composable fun ChannelScreen( viewModel: UIViewModel = viewModel(), showSnackbar: (String) -&gt; Unit = {}, )</ID>
<ID>LongMethod:ChannelSettingsItemList.kt$@Composable fun ChannelSettingsItemList( settingsList: List&lt;ChannelSettings&gt;, modemPresetName: String = "Default", maxChannels: Int = 8, enabled: Boolean, onNegativeClicked: () -&gt; Unit = { }, onPositiveClicked: (List&lt;ChannelSettings&gt;) -&gt; Unit, )</ID> <ID>LongMethod:ChannelSettingsItemList.kt$@Composable fun ChannelSettingsItemList( settingsList: List&lt;ChannelSettings&gt;, modemPresetName: String = "Default", maxChannels: Int = 8, enabled: Boolean, onNegativeClicked: () -&gt; Unit = { }, onPositiveClicked: (List&lt;ChannelSettings&gt;) -&gt; Unit, )</ID>
<ID>LongMethod:ContactsFragment.kt$ContactsFragment.ActionModeCallback$override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean</ID> <ID>LongMethod:ContactsFragment.kt$ContactsFragment.ActionModeCallback$override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean</ID>
<ID>LongMethod:DeviceConfigItemList.kt$@Composable fun DeviceConfigItemList( deviceConfig: DeviceConfig, enabled: Boolean, onSaveClicked: (DeviceConfig) -&gt; Unit, )</ID> <ID>LongMethod:DeviceConfigItemList.kt$@Composable fun DeviceConfigItemList( deviceConfig: DeviceConfig, enabled: Boolean, onSaveClicked: (DeviceConfig) -&gt; Unit, )</ID>
@ -222,20 +220,6 @@
<ID>MagicNumber:ContextServices.kt$33</ID> <ID>MagicNumber:ContextServices.kt$33</ID>
<ID>MagicNumber:DataPacket.kt$DataPacket.CREATOR$16</ID> <ID>MagicNumber:DataPacket.kt$DataPacket.CREATOR$16</ID>
<ID>MagicNumber:DebugFragment.kt$DebugFragment$3</ID> <ID>MagicNumber:DebugFragment.kt$DebugFragment$3</ID>
<ID>MagicNumber:DeviceSettingsFragment.kt$ConfigRoute.BLUETOOTH$6</ID>
<ID>MagicNumber:DeviceSettingsFragment.kt$ConfigRoute.DISPLAY$4</ID>
<ID>MagicNumber:DeviceSettingsFragment.kt$ConfigRoute.LORA$5</ID>
<ID>MagicNumber:DeviceSettingsFragment.kt$ConfigRoute.NETWORK$3</ID>
<ID>MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.AMBIENT_LIGHTING$10</ID>
<ID>MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.AUDIO$7</ID>
<ID>MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.CANNED_MESSAGE$6</ID>
<ID>MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.DETECTION_SENSOR$11</ID>
<ID>MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.NEIGHBOR_INFO$9</ID>
<ID>MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.PAXCOUNTER$12</ID>
<ID>MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.RANGE_TEST$4</ID>
<ID>MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.REMOTE_HARDWARE$8</ID>
<ID>MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.STORE_FORWARD$3</ID>
<ID>MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.TELEMETRY$5</ID>
<ID>MagicNumber:DeviceVersion.kt$DeviceVersion$100</ID> <ID>MagicNumber:DeviceVersion.kt$DeviceVersion$100</ID>
<ID>MagicNumber:DeviceVersion.kt$DeviceVersion$10000</ID> <ID>MagicNumber:DeviceVersion.kt$DeviceVersion$10000</ID>
<ID>MagicNumber:DownloadButton.kt$1.25f</ID> <ID>MagicNumber:DownloadButton.kt$1.25f</ID>
@ -315,6 +299,20 @@
<ID>MagicNumber:NOAAWmsTileSource.kt$NOAAWmsTileSource$360.0</ID> <ID>MagicNumber:NOAAWmsTileSource.kt$NOAAWmsTileSource$360.0</ID>
<ID>MagicNumber:NOAAWmsTileSource.kt$NOAAWmsTileSource$4</ID> <ID>MagicNumber:NOAAWmsTileSource.kt$NOAAWmsTileSource$4</ID>
<ID>MagicNumber:NOAAWmsTileSource.kt$NOAAWmsTileSource$5</ID> <ID>MagicNumber:NOAAWmsTileSource.kt$NOAAWmsTileSource$5</ID>
<ID>MagicNumber:NavGraph.kt$ConfigRoute.BLUETOOTH$6</ID>
<ID>MagicNumber:NavGraph.kt$ConfigRoute.DISPLAY$4</ID>
<ID>MagicNumber:NavGraph.kt$ConfigRoute.LORA$5</ID>
<ID>MagicNumber:NavGraph.kt$ConfigRoute.NETWORK$3</ID>
<ID>MagicNumber:NavGraph.kt$ModuleRoute.AMBIENT_LIGHTING$10</ID>
<ID>MagicNumber:NavGraph.kt$ModuleRoute.AUDIO$7</ID>
<ID>MagicNumber:NavGraph.kt$ModuleRoute.CANNED_MESSAGE$6</ID>
<ID>MagicNumber:NavGraph.kt$ModuleRoute.DETECTION_SENSOR$11</ID>
<ID>MagicNumber:NavGraph.kt$ModuleRoute.NEIGHBOR_INFO$9</ID>
<ID>MagicNumber:NavGraph.kt$ModuleRoute.PAXCOUNTER$12</ID>
<ID>MagicNumber:NavGraph.kt$ModuleRoute.RANGE_TEST$4</ID>
<ID>MagicNumber:NavGraph.kt$ModuleRoute.REMOTE_HARDWARE$8</ID>
<ID>MagicNumber:NavGraph.kt$ModuleRoute.STORE_FORWARD$3</ID>
<ID>MagicNumber:NavGraph.kt$ModuleRoute.TELEMETRY$5</ID>
<ID>MagicNumber:NodeInfo.kt$DeviceMetrics.Companion$1000</ID> <ID>MagicNumber:NodeInfo.kt$DeviceMetrics.Companion$1000</ID>
<ID>MagicNumber:NodeInfo.kt$EnvironmentMetrics.Companion$1000</ID> <ID>MagicNumber:NodeInfo.kt$EnvironmentMetrics.Companion$1000</ID>
<ID>MagicNumber:NodeInfo.kt$NodeInfo$0.114</ID> <ID>MagicNumber:NodeInfo.kt$NodeInfo$0.114</ID>
@ -489,9 +487,6 @@
<ID>MultiLineIfElse:ChannelOption.kt$when (bandwidth) { 31 -&gt; .03125f 62 -&gt; .0625f 200 -&gt; .203125f 400 -&gt; .40625f 800 -&gt; .8125f 1600 -&gt; 1.6250f else -&gt; bandwidth / 1000f }</ID> <ID>MultiLineIfElse:ChannelOption.kt$when (bandwidth) { 31 -&gt; .03125f 62 -&gt; .0625f 200 -&gt; .203125f 400 -&gt; .40625f 800 -&gt; .8125f 1600 -&gt; 1.6250f else -&gt; bandwidth / 1000f }</ID>
<ID>MultiLineIfElse:ContextServices.kt$MaterialAlertDialogBuilder(this) .setTitle(title) .setMessage(rationale) .setNeutralButton(R.string.cancel) { _, _ -&gt; } .setPositiveButton(R.string.accept) { _, _ -&gt; invokeFun() } .show()</ID> <ID>MultiLineIfElse:ContextServices.kt$MaterialAlertDialogBuilder(this) .setTitle(title) .setMessage(rationale) .setNeutralButton(R.string.cancel) { _, _ -&gt; } .setPositiveButton(R.string.accept) { _, _ -&gt; invokeFun() } .show()</ID>
<ID>MultiLineIfElse:ContextServices.kt$invokeFun()</ID> <ID>MultiLineIfElse:ContextServices.kt$invokeFun()</ID>
<ID>MultiLineIfElse:DeviceSettingsFragment.kt$AlertDialog( onDismissRequest = {}, shape = RoundedCornerShape(16.dp), backgroundColor = MaterialTheme.colors.background, title = { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, ) { Icon( imageVector = Icons.TwoTone.Warning, contentDescription = "warning", modifier = Modifier.padding(end = 8.dp) ) Text( text = "${stringResource(title)}?\n") Icon( imageVector = Icons.TwoTone.Warning, contentDescription = "warning", modifier = Modifier.padding(start = 8.dp) ) } }, buttons = { Row( modifier = Modifier .fillMaxWidth() .padding(start = 16.dp, end = 16.dp, bottom = 16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically, ) { TextButton( modifier = Modifier.weight(1f), onClick = { showDialog = false }, ) { Text(stringResource(R.string.cancel)) } Button( modifier = Modifier.weight(1f), onClick = { showDialog = false onClick() }, ) { Text(stringResource(R.string.send)) } } } )</ID>
<ID>MultiLineIfElse:DeviceSettingsFragment.kt$EditDeviceProfileDialog( title = if (deviceProfile != null) "Import configuration" else "Export configuration", deviceProfile = deviceProfile ?: viewModel.currentDeviceProfile, onConfirm = { showEditDeviceProfileDialog = false if (deviceProfile != null) { viewModel.installProfile(it) } else { viewModel.setDeviceProfile(it) val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "application/*" putExtra(Intent.EXTRA_TITLE, "${destNum.toUInt()}.cfg") } exportConfigLauncher.launch(intent) } }, onDismiss = { showEditDeviceProfileDialog = false viewModel.setDeviceProfile(null) } )</ID>
<ID>MultiLineIfElse:DeviceSettingsFragment.kt$PacketResponseStateDialog( radioConfigState.responseState, onDismiss = { showEditDeviceProfileDialog = false viewModel.clearPacketResponse() }, onComplete = { val route = radioConfigState.route if (ConfigRoute.entries.any { it.name == route } || ModuleRoute.entries.any { it.name == route }) { navController.navigate(route) viewModel.clearPacketResponse() } } )</ID>
<ID>MultiLineIfElse:EditListPreference.kt$EditBase64Preference( title = "${index + 1}/$maxCount", value = value, enabled = enabled, keyboardActions = keyboardActions, onValueChange = { listState[index] = it as T onValuesChanged(listState) }, modifier = modifier.fillMaxWidth(), trailingIcon = trailingIcon, )</ID> <ID>MultiLineIfElse:EditListPreference.kt$EditBase64Preference( title = "${index + 1}/$maxCount", value = value, enabled = enabled, keyboardActions = keyboardActions, onValueChange = { listState[index] = it as T onValuesChanged(listState) }, modifier = modifier.fillMaxWidth(), trailingIcon = trailingIcon, )</ID>
<ID>MultiLineIfElse:EditListPreference.kt$EditTextPreference( title = "${index + 1}/$maxCount", value = value, enabled = enabled, keyboardActions = keyboardActions, onValueChanged = { listState[index] = it as T onValuesChanged(listState) }, modifier = modifier.fillMaxWidth(), trailingIcon = trailingIcon, )</ID> <ID>MultiLineIfElse:EditListPreference.kt$EditTextPreference( title = "${index + 1}/$maxCount", value = value, enabled = enabled, keyboardActions = keyboardActions, onValueChanged = { listState[index] = it as T onValuesChanged(listState) }, modifier = modifier.fillMaxWidth(), trailingIcon = trailingIcon, )</ID>
<ID>MultiLineIfElse:EditPasswordPreference.kt$painterResource(R.drawable.ic_twotone_visibility_24)</ID> <ID>MultiLineIfElse:EditPasswordPreference.kt$painterResource(R.drawable.ic_twotone_visibility_24)</ID>
@ -520,7 +515,7 @@
<ID>MultiLineIfElse:NOAAWmsTileSource.kt$NOAAWmsTileSource$sb.append("service=WMS")</ID> <ID>MultiLineIfElse:NOAAWmsTileSource.kt$NOAAWmsTileSource$sb.append("service=WMS")</ID>
<ID>MultiLineIfElse:NodeInfo.kt$MeshUser$hwModel.name.replace('_', '-').replace('p', '.').lowercase()</ID> <ID>MultiLineIfElse:NodeInfo.kt$MeshUser$hwModel.name.replace('_', '-').replace('p', '.').lowercase()</ID>
<ID>MultiLineIfElse:NodeInfo.kt$MeshUser$null</ID> <ID>MultiLineIfElse:NodeInfo.kt$MeshUser$null</ID>
<ID>MultiLineIfElse:RadioConfigViewModel.kt$RadioConfigViewModel$destNode.value?.user?.let { val user = MeshProtos.User.newBuilder() .setLongName(if (hasLongName()) longName else it.longName) .setShortName(if (hasShortName()) shortName else it.shortName) .setIsLicensed(it.isLicensed) .build() setOwner(user) }</ID> <ID>MultiLineIfElse:RadioConfigScreen.kt$AlertDialog( onDismissRequest = {}, shape = RoundedCornerShape(16.dp), backgroundColor = MaterialTheme.colors.background, title = { Row( modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center, ) { Icon( imageVector = Icons.TwoTone.Warning, contentDescription = "warning", modifier = Modifier.padding(end = 8.dp) ) Text( text = "${stringResource(title)}?\n") Icon( imageVector = Icons.TwoTone.Warning, contentDescription = "warning", modifier = Modifier.padding(start = 8.dp) ) } }, buttons = { Row( modifier = Modifier .fillMaxWidth() .padding(start = 16.dp, end = 16.dp, bottom = 16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically, ) { TextButton( modifier = Modifier.weight(1f), onClick = { showDialog = false }, ) { Text(stringResource(R.string.cancel)) } Button( modifier = Modifier.weight(1f), onClick = { showDialog = false onClick() }, ) { Text(stringResource(R.string.send)) } } } )</ID>
<ID>MultiLineIfElse:RadioConfigViewModel.kt$RadioConfigViewModel$try { setChannels(channelUrl) } catch (ex: Exception) { errormsg("DeviceProfile channel import error", ex) setResponseStateError(ex.customMessage) }</ID> <ID>MultiLineIfElse:RadioConfigViewModel.kt$RadioConfigViewModel$try { setChannels(channelUrl) } catch (ex: Exception) { errormsg("DeviceProfile channel import error", ex) setResponseStateError(ex.customMessage) }</ID>
<ID>MultiLineIfElse:RadioConfigViewModel.kt$RadioConfigViewModel$viewModelScope.launch { radioConfigRepository.replaceAllSettings(new) }</ID> <ID>MultiLineIfElse:RadioConfigViewModel.kt$RadioConfigViewModel$viewModelScope.launch { radioConfigRepository.replaceAllSettings(new) }</ID>
<ID>MultiLineIfElse:RadioInterfaceService.kt$RadioInterfaceService$startInterface()</ID> <ID>MultiLineIfElse:RadioInterfaceService.kt$RadioInterfaceService$startInterface()</ID>