From 3ac0e8c28e06267449dd97345e0f5b9cf5e2ee1f Mon Sep 17 00:00:00 2001 From: andrekir Date: Sun, 20 Oct 2024 08:48:30 -0300 Subject: [PATCH] refactor: extract `NavGraph` from `RadioConfig` --- .../java/com/geeksville/mesh/MainActivity.kt | 2 +- ...{DeviceSettingsFragment.kt => NavGraph.kt} | 292 +---------------- .../geeksville/mesh/ui/RadioConfigScreen.kt | 293 ++++++++++++++++++ .../com/geeksville/mesh/ui/UsersFragment.kt | 2 +- config/detekt/detekt-baseline.xml | 35 +-- 5 files changed, 317 insertions(+), 307 deletions(-) rename app/src/main/java/com/geeksville/mesh/ui/{DeviceSettingsFragment.kt => NavGraph.kt} (71%) create mode 100644 app/src/main/java/com/geeksville/mesh/ui/RadioConfigScreen.kt diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt index b5f62c0be..5b9248fa9 100644 --- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt +++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt @@ -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 -> { diff --git a/app/src/main/java/com/geeksville/mesh/ui/DeviceSettingsFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/NavGraph.kt similarity index 71% rename from app/src/main/java/com/geeksville/mesh/ui/DeviceSettingsFragment.kt rename to app/src/main/java/com/geeksville/mesh/ui/NavGraph.kt index 216465ac0..87f2629ad 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/DeviceSettingsFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/NavGraph.kt @@ -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(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() -} diff --git a/app/src/main/java/com/geeksville/mesh/ui/RadioConfigScreen.kt b/app/src/main/java/com/geeksville/mesh/ui/RadioConfigScreen.kt new file mode 100644 index 000000000..672875511 --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/RadioConfigScreen.kt @@ -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(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() +} diff --git a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt index fc647062e..5e6935a19 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -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( diff --git a/config/detekt/detekt-baseline.xml b/config/detekt/detekt-baseline.xml index 5848f9221..98a7c0212 100644 --- a/config/detekt/detekt-baseline.xml +++ b/config/detekt/detekt-baseline.xml @@ -71,7 +71,6 @@ ConstructorParameterNaming:Packet.kt$Packet$@ColumnInfo(name = "contact_key") val contact_key: String ConstructorParameterNaming:Packet.kt$Packet$@ColumnInfo(name = "port_num") val port_num: Int ConstructorParameterNaming:Packet.kt$Packet$@ColumnInfo(name = "received_time") val received_time: Long - CyclomaticComplexMethod:ChannelFragment.kt$@Composable fun ChannelScreen( viewModel: UIViewModel = viewModel(), showSnackbar: (String) -> Unit = {}, ) CyclomaticComplexMethod:MainActivity.kt$MainActivity$override fun onOptionsItemSelected(item: MenuItem): Boolean CyclomaticComplexMethod:MapFragment.kt$@Composable fun MapView( model: UIViewModel = viewModel(), ) CyclomaticComplexMethod:MeshService.kt$MeshService$private fun handleReceivedData(packet: MeshPacket) @@ -151,7 +150,6 @@ LongMethod:AmbientLightingConfigItemList.kt$@Composable fun AmbientLightingConfigItemList( ambientLightingConfig: ModuleConfigProtos.ModuleConfig.AmbientLightingConfig, enabled: Boolean, onSaveClicked: (ModuleConfigProtos.ModuleConfig.AmbientLightingConfig) -> Unit, ) LongMethod:AudioConfigItemList.kt$@Composable fun AudioConfigItemList( audioConfig: AudioConfig, enabled: Boolean, onSaveClicked: (AudioConfig) -> Unit, ) LongMethod:CannedMessageConfigItemList.kt$@Composable fun CannedMessageConfigItemList( messages: String, cannedMessageConfig: CannedMessageConfig, enabled: Boolean, onSaveClicked: (messages: String, config: CannedMessageConfig) -> Unit, ) - LongMethod:ChannelFragment.kt$@Composable fun ChannelScreen( viewModel: UIViewModel = viewModel(), showSnackbar: (String) -> Unit = {}, ) LongMethod:ChannelSettingsItemList.kt$@Composable fun ChannelSettingsItemList( settingsList: List<ChannelSettings>, modemPresetName: String = "Default", maxChannels: Int = 8, enabled: Boolean, onNegativeClicked: () -> Unit = { }, onPositiveClicked: (List<ChannelSettings>) -> Unit, ) LongMethod:ContactsFragment.kt$ContactsFragment.ActionModeCallback$override fun onActionItemClicked(mode: ActionMode, item: MenuItem): Boolean LongMethod:DeviceConfigItemList.kt$@Composable fun DeviceConfigItemList( deviceConfig: DeviceConfig, enabled: Boolean, onSaveClicked: (DeviceConfig) -> Unit, ) @@ -222,20 +220,6 @@ MagicNumber:ContextServices.kt$33 MagicNumber:DataPacket.kt$DataPacket.CREATOR$16 MagicNumber:DebugFragment.kt$DebugFragment$3 - MagicNumber:DeviceSettingsFragment.kt$ConfigRoute.BLUETOOTH$6 - MagicNumber:DeviceSettingsFragment.kt$ConfigRoute.DISPLAY$4 - MagicNumber:DeviceSettingsFragment.kt$ConfigRoute.LORA$5 - MagicNumber:DeviceSettingsFragment.kt$ConfigRoute.NETWORK$3 - MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.AMBIENT_LIGHTING$10 - MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.AUDIO$7 - MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.CANNED_MESSAGE$6 - MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.DETECTION_SENSOR$11 - MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.NEIGHBOR_INFO$9 - MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.PAXCOUNTER$12 - MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.RANGE_TEST$4 - MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.REMOTE_HARDWARE$8 - MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.STORE_FORWARD$3 - MagicNumber:DeviceSettingsFragment.kt$ModuleRoute.TELEMETRY$5 MagicNumber:DeviceVersion.kt$DeviceVersion$100 MagicNumber:DeviceVersion.kt$DeviceVersion$10000 MagicNumber:DownloadButton.kt$1.25f @@ -315,6 +299,20 @@ MagicNumber:NOAAWmsTileSource.kt$NOAAWmsTileSource$360.0 MagicNumber:NOAAWmsTileSource.kt$NOAAWmsTileSource$4 MagicNumber:NOAAWmsTileSource.kt$NOAAWmsTileSource$5 + MagicNumber:NavGraph.kt$ConfigRoute.BLUETOOTH$6 + MagicNumber:NavGraph.kt$ConfigRoute.DISPLAY$4 + MagicNumber:NavGraph.kt$ConfigRoute.LORA$5 + MagicNumber:NavGraph.kt$ConfigRoute.NETWORK$3 + MagicNumber:NavGraph.kt$ModuleRoute.AMBIENT_LIGHTING$10 + MagicNumber:NavGraph.kt$ModuleRoute.AUDIO$7 + MagicNumber:NavGraph.kt$ModuleRoute.CANNED_MESSAGE$6 + MagicNumber:NavGraph.kt$ModuleRoute.DETECTION_SENSOR$11 + MagicNumber:NavGraph.kt$ModuleRoute.NEIGHBOR_INFO$9 + MagicNumber:NavGraph.kt$ModuleRoute.PAXCOUNTER$12 + MagicNumber:NavGraph.kt$ModuleRoute.RANGE_TEST$4 + MagicNumber:NavGraph.kt$ModuleRoute.REMOTE_HARDWARE$8 + MagicNumber:NavGraph.kt$ModuleRoute.STORE_FORWARD$3 + MagicNumber:NavGraph.kt$ModuleRoute.TELEMETRY$5 MagicNumber:NodeInfo.kt$DeviceMetrics.Companion$1000 MagicNumber:NodeInfo.kt$EnvironmentMetrics.Companion$1000 MagicNumber:NodeInfo.kt$NodeInfo$0.114 @@ -489,9 +487,6 @@ MultiLineIfElse:ChannelOption.kt$when (bandwidth) { 31 -> .03125f 62 -> .0625f 200 -> .203125f 400 -> .40625f 800 -> .8125f 1600 -> 1.6250f else -> bandwidth / 1000f } MultiLineIfElse:ContextServices.kt$MaterialAlertDialogBuilder(this) .setTitle(title) .setMessage(rationale) .setNeutralButton(R.string.cancel) { _, _ -> } .setPositiveButton(R.string.accept) { _, _ -> invokeFun() } .show() MultiLineIfElse:ContextServices.kt$invokeFun() - 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)) } } } ) - 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) } ) - 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() } } ) 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, ) 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, ) MultiLineIfElse:EditPasswordPreference.kt$painterResource(R.drawable.ic_twotone_visibility_24) @@ -520,7 +515,7 @@ MultiLineIfElse:NOAAWmsTileSource.kt$NOAAWmsTileSource$sb.append("service=WMS") MultiLineIfElse:NodeInfo.kt$MeshUser$hwModel.name.replace('_', '-').replace('p', '.').lowercase() MultiLineIfElse:NodeInfo.kt$MeshUser$null - 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) } + 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)) } } } ) MultiLineIfElse:RadioConfigViewModel.kt$RadioConfigViewModel$try { setChannels(channelUrl) } catch (ex: Exception) { errormsg("DeviceProfile channel import error", ex) setResponseStateError(ex.customMessage) } MultiLineIfElse:RadioConfigViewModel.kt$RadioConfigViewModel$viewModelScope.launch { radioConfigRepository.replaceAllSettings(new) } MultiLineIfElse:RadioInterfaceService.kt$RadioInterfaceService$startInterface()