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
}
R.id.radio_config -> {
supportFragmentManager.navigateToRadioConfig()
supportFragmentManager.navigateToNavGraph()
return true
}
R.id.save_messages_csv -> {

Wyświetl plik

@ -1,53 +1,27 @@
package com.geeksville.mesh.ui
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
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.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TextButton
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
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.Message
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.Cloud
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.LocationOn
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.SettingsRemote
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.Wifi
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.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
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.os.bundleOf
import androidx.fragment.app.FragmentManager
@ -87,7 +53,6 @@ import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
import com.geeksville.mesh.Position
import com.geeksville.mesh.R
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.ui.components.DeviceMetricsScreen
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.AudioConfigItemList
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.DeviceConfigItemList
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.LoRaConfigItemList
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 dagger.hilt.android.AndroidEntryPoint
internal fun FragmentManager.navigateToRadioConfig(
internal fun FragmentManager.navigateToNavGraph(
destNum: Int? = null,
startDestination: String = "RadioConfig",
) {
val radioConfigFragment = DeviceSettingsFragment().apply {
val radioConfigFragment = NavGraphFragment().apply {
arguments = bundleOf("destNum" to destNum, "startDestination" to startDestination)
}
beginTransaction()
@ -143,7 +106,7 @@ internal fun FragmentManager.navigateToRadioConfig(
}
@AndroidEntryPoint
class DeviceSettingsFragment : ScreenFragment("Radio Configuration"), Logging {
class NavGraphFragment : ScreenFragment("NavGraph"), Logging {
private val model: RadioConfigViewModel by viewModels()
@ -189,7 +152,7 @@ class DeviceSettingsFragment : ScreenFragment("Radio Configuration"), Logging {
)
}
) { innerPadding ->
RadioConfigNavHost(
NavGraph(
node = node,
viewModel = model,
navController = navController,
@ -278,7 +241,7 @@ private fun MeshAppBar(
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
fun RadioConfigNavHost(
fun NavGraph(
node: NodeEntity?,
viewModel: RadioConfigViewModel = hiltViewModel(),
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) {
info("calling NodeDetails --> destNum: $nodeNum")
parentFragmentManager.navigateToRadioConfig(nodeNum, "NodeDetails")
parentFragmentManager.navigateToNavGraph(nodeNum, "NodeDetails")
}
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 = "port_num") val port_num: Int</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:MapFragment.kt$@Composable fun MapView( model: UIViewModel = viewModel(), )</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: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: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: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>
@ -222,20 +220,6 @@
<ID>MagicNumber:ContextServices.kt$33</ID>
<ID>MagicNumber:DataPacket.kt$DataPacket.CREATOR$16</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$10000</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$4</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$EnvironmentMetrics.Companion$1000</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: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: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$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>
@ -520,7 +515,7 @@
<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$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$viewModelScope.launch { radioConfigRepository.replaceAllSettings(new) }</ID>
<ID>MultiLineIfElse:RadioInterfaceService.kt$RadioInterfaceService$startInterface()</ID>