mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Relocate radio config to new top-level Settings screen (#2834)
This commit is contained in:
parent
af6066d788
commit
f1bb020203
52 changed files with 1077 additions and 1189 deletions
|
|
@ -27,7 +27,7 @@ import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
|
|||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.deviceProfile
|
||||
import com.geeksville.mesh.position
|
||||
import com.geeksville.mesh.ui.radioconfig.components.EditDeviceProfileDialog
|
||||
import com.geeksville.mesh.ui.settings.radio.components.EditDeviceProfileDialog
|
||||
import org.junit.Assert
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
|
@ -36,11 +36,9 @@ import org.junit.runner.RunWith
|
|||
@RunWith(AndroidJUnit4::class)
|
||||
class EditDeviceProfileDialogTest {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
@get:Rule val composeTestRule = createComposeRule()
|
||||
|
||||
private fun getString(id: Int): String =
|
||||
InstrumentationRegistry.getInstrumentation().targetContext.getString(id)
|
||||
private fun getString(id: Int): String = InstrumentationRegistry.getInstrumentation().targetContext.getString(id)
|
||||
|
||||
private val title = "Export configuration"
|
||||
private val deviceProfile = deviceProfile {
|
||||
|
|
@ -54,17 +52,15 @@ class EditDeviceProfileDialogTest {
|
|||
}
|
||||
}
|
||||
|
||||
private fun testEditDeviceProfileDialog(
|
||||
onDismiss: () -> Unit = {},
|
||||
onConfirm: (DeviceProfile) -> Unit = {},
|
||||
) = composeTestRule.setContent {
|
||||
EditDeviceProfileDialog(
|
||||
title = title,
|
||||
deviceProfile = deviceProfile,
|
||||
onConfirm = onConfirm,
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
private fun testEditDeviceProfileDialog(onDismiss: () -> Unit = {}, onConfirm: (DeviceProfile) -> Unit = {}) =
|
||||
composeTestRule.setContent {
|
||||
EditDeviceProfileDialog(
|
||||
title = title,
|
||||
deviceProfile = deviceProfile,
|
||||
onConfirm = onConfirm,
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEditDeviceProfileDialog_showsDialogTitle() {
|
||||
|
|
@ -113,4 +109,4 @@ class EditDeviceProfileDialogTest {
|
|||
// Verify onConfirm is called with the correct DeviceProfile
|
||||
Assert.assertEquals(deviceProfile, actualDeviceProfile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import androidx.compose.ui.test.performClick
|
|||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.ui.radioconfig.components.MapReportingPreference
|
||||
import com.geeksville.mesh.ui.settings.radio.components.MapReportingPreference
|
||||
import org.junit.Assert
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
|
@ -36,30 +36,19 @@ import org.junit.runner.RunWith
|
|||
@RunWith(AndroidJUnit4::class)
|
||||
class MapReportingPreferenceTest {
|
||||
|
||||
@get:Rule
|
||||
val composeTestRule = createComposeRule()
|
||||
@get:Rule val composeTestRule = createComposeRule()
|
||||
|
||||
private fun getString(id: Int): String =
|
||||
InstrumentationRegistry.getInstrumentation().targetContext.getString(id)
|
||||
private fun getString(id: Int): String = InstrumentationRegistry.getInstrumentation().targetContext.getString(id)
|
||||
|
||||
var mapReportingEnabled = false
|
||||
var shouldReportLocation = false
|
||||
var positionPrecision = 5
|
||||
var positionReportingInterval = 60
|
||||
|
||||
var mapReportingEnabledChanged = { enabled: Boolean ->
|
||||
mapReportingEnabled = enabled
|
||||
}
|
||||
var shouldReportLocationChanged = { enabled: Boolean ->
|
||||
shouldReportLocation = enabled
|
||||
}
|
||||
var positionPrecisionChanged = { precision: Int ->
|
||||
positionPrecision = precision
|
||||
}
|
||||
var positionReportingIntervalChanged = { interval: Int ->
|
||||
positionReportingInterval = interval
|
||||
}
|
||||
|
||||
var mapReportingEnabledChanged = { enabled: Boolean -> mapReportingEnabled = enabled }
|
||||
var shouldReportLocationChanged = { enabled: Boolean -> shouldReportLocation = enabled }
|
||||
var positionPrecisionChanged = { precision: Int -> positionPrecision = precision }
|
||||
var positionReportingIntervalChanged = { interval: Int -> positionReportingInterval = interval }
|
||||
|
||||
private fun testMapReportingPreference() = composeTestRule.setContent {
|
||||
Column {
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ import androidx.navigation.compose.composable
|
|||
import androidx.navigation.navDeepLink
|
||||
import androidx.navigation.navigation
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.radioconfig.components.ChannelConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.LoRaConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.ChannelConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.LoRaConfigScreen
|
||||
import com.geeksville.mesh.ui.sharing.ChannelScreen
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import androidx.navigation.navigation
|
|||
import com.geeksville.mesh.model.BluetoothViewModel
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.connections.ConnectionsScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.LoRaConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.LoRaConfigScreen
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
|
|
@ -56,7 +56,7 @@ fun NavGraphBuilder.connectionsGraph(
|
|||
uiViewModel = uiViewModel,
|
||||
bluetoothViewModel = bluetoothViewModel,
|
||||
radioConfigViewModel = hiltViewModel(parentEntry),
|
||||
onNavigateToRadioConfig = { navController.navigate(RadioConfigRoutes.RadioConfig()) },
|
||||
onNavigateToSettings = { navController.navigate(SettingsRoutes.Settings()) },
|
||||
onNavigateToNodeDetails = { navController.navigate(NodesRoutes.NodeDetailGraph(it)) },
|
||||
onConfigNavigate = { route -> navController.navigate(route) },
|
||||
)
|
||||
|
|
@ -66,7 +66,7 @@ fun NavGraphBuilder.connectionsGraph(
|
|||
}
|
||||
|
||||
private fun NavGraphBuilder.configRoutes(navController: NavHostController) {
|
||||
composable<RadioConfigRoutes.LoRa> { backStackEntry ->
|
||||
composable<SettingsRoutes.LoRa> { backStackEntry ->
|
||||
val parentEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(ConnectionsRoutes.ConnectionsGraph) }
|
||||
LoRaConfigScreen(hiltViewModel(parentEntry))
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ fun NavDestination.isNodeDetailRoute(): Boolean = NodeDetailRoute.entries.any {
|
|||
|
||||
fun NavDestination.showLongNameTitle(): Boolean = !this.isTopLevel() &&
|
||||
(
|
||||
this.hasRoute<RadioConfigRoutes.RadioConfig>() ||
|
||||
this.hasRoute<SettingsRoutes.Settings>() ||
|
||||
this.hasRoute<NodesRoutes.NodeDetail>() ||
|
||||
this.isConfigRoute() ||
|
||||
this.isNodeDetailRoute()
|
||||
|
|
@ -85,6 +85,6 @@ fun NavGraph(
|
|||
) {
|
||||
DebugScreen()
|
||||
}
|
||||
radioConfigGraph(navController, uIViewModel)
|
||||
settingsGraph(navController, uIViewModel)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,38 +56,40 @@ import com.geeksville.mesh.AdminProtos
|
|||
import com.geeksville.mesh.MeshProtos.DeviceMetadata
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.radioconfig.CleanNodeDatabaseScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.radioconfig.components.AmbientLightingConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.AudioConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.BluetoothConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.CannedMessageConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.ChannelConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.DetectionSensorConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.DeviceConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.DisplayConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.ExternalNotificationConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.LoRaConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.MQTTConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.NeighborInfoConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.NetworkConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.PaxcounterConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.PositionConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.PowerConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.RangeTestConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.RemoteHardwareConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.SecurityConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.SerialConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.StoreForwardConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.TelemetryConfigScreen
|
||||
import com.geeksville.mesh.ui.radioconfig.components.UserConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.CleanNodeDatabaseScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.components.AmbientLightingConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.AudioConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.BluetoothConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.CannedMessageConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.ChannelConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.DetectionSensorConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.DeviceConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.DisplayConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.ExternalNotificationConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.LoRaConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.MQTTConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.NeighborInfoConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.NetworkConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.PaxcounterConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.PositionConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.PowerConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.RangeTestConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.RemoteHardwareConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.SecurityConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.SerialConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.StoreForwardConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.TelemetryConfigScreen
|
||||
import com.geeksville.mesh.ui.settings.radio.components.UserConfigScreen
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
sealed class RadioConfigRoutes {
|
||||
@Serializable data class RadioConfigGraph(val destNum: Int? = null) : Graph
|
||||
sealed class SettingsRoutes {
|
||||
@Serializable data class SettingsGraph(val destNum: Int? = null) : Graph
|
||||
|
||||
@Serializable data class RadioConfig(val destNum: Int? = null) : Route
|
||||
@Serializable data class Settings(val destNum: Int? = null) : Route
|
||||
|
||||
// region radio Config Routes
|
||||
|
||||
@Serializable data object User : Route
|
||||
|
||||
|
|
@ -109,6 +111,10 @@ sealed class RadioConfigRoutes {
|
|||
|
||||
@Serializable data object Security : Route
|
||||
|
||||
// endregion
|
||||
|
||||
// region module config routes
|
||||
|
||||
@Serializable data object MQTT : Route
|
||||
|
||||
@Serializable data object Serial : Route
|
||||
|
|
@ -135,30 +141,35 @@ sealed class RadioConfigRoutes {
|
|||
|
||||
@Serializable data object Paxcounter : Route
|
||||
|
||||
// endregion
|
||||
|
||||
// region advanced config routes
|
||||
|
||||
@Serializable data object CleanNodeDb : Route
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
||||
fun getNavRouteFrom(routeName: String): Route? =
|
||||
ConfigRoute.entries.find { it.name == routeName }?.route ?: ModuleRoute.entries.find { it.name == routeName }?.route
|
||||
|
||||
@Suppress("LongMethod")
|
||||
fun NavGraphBuilder.radioConfigGraph(navController: NavHostController, uiViewModel: UIViewModel) {
|
||||
navigation<RadioConfigRoutes.RadioConfigGraph>(startDestination = RadioConfigRoutes.RadioConfig()) {
|
||||
composable<RadioConfigRoutes.RadioConfig>(
|
||||
deepLinks =
|
||||
listOf(navDeepLink<RadioConfigRoutes.RadioConfig>(basePath = "$DEEP_LINK_BASE_URI/radio_config")),
|
||||
fun NavGraphBuilder.settingsGraph(navController: NavHostController, uiViewModel: UIViewModel) {
|
||||
navigation<SettingsRoutes.SettingsGraph>(startDestination = SettingsRoutes.Settings()) {
|
||||
composable<SettingsRoutes.Settings>(
|
||||
deepLinks = listOf(navDeepLink<SettingsRoutes.Settings>(basePath = "$DEEP_LINK_BASE_URI/settings")),
|
||||
) { backStackEntry ->
|
||||
val parentEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(RadioConfigRoutes.RadioConfigGraph::class) }
|
||||
remember(backStackEntry) { navController.getBackStackEntry(SettingsRoutes.SettingsGraph::class) }
|
||||
RadioConfigScreen(uiViewModel = uiViewModel, viewModel = hiltViewModel(parentEntry)) {
|
||||
navController.navigate(it) { popUpTo(RadioConfigRoutes.RadioConfig()) { inclusive = false } }
|
||||
navController.navigate(it) { popUpTo(SettingsRoutes.Settings()) { inclusive = false } }
|
||||
}
|
||||
}
|
||||
composable<RadioConfigRoutes.CleanNodeDb>(
|
||||
composable<SettingsRoutes.CleanNodeDb>(
|
||||
deepLinks =
|
||||
listOf(
|
||||
navDeepLink<RadioConfigRoutes.CleanNodeDb>(
|
||||
basePath = "$DEEP_LINK_BASE_URI/radio_config/clean_node_db",
|
||||
navDeepLink<SettingsRoutes.CleanNodeDb>(
|
||||
basePath = "$DEEP_LINK_BASE_URI/settings/radio/clean_node_db",
|
||||
),
|
||||
),
|
||||
) {
|
||||
|
|
@ -174,7 +185,7 @@ fun NavGraphBuilder.radioConfigGraph(navController: NavHostController, uiViewMod
|
|||
*
|
||||
* This function simplifies adding screens by handling common tasks like:
|
||||
* - Setting up deep links based on the route's name.
|
||||
* - Retrieving the parent [NavBackStackEntry] for the [RadioConfigRoutes.RadioConfigGraph].
|
||||
* - Retrieving the parent [NavBackStackEntry] for the [SettingsRoutes.SettingsGraph].
|
||||
* - Providing the [RadioConfigViewModel] scoped to the parent graph, which the [screenContent] will use.
|
||||
*
|
||||
* @param R The type of the [Route] object, must be serializable.
|
||||
|
|
@ -191,12 +202,14 @@ private inline fun <reified R : Route> NavGraphBuilder.addRadioConfigScreenCompo
|
|||
composable<R>(
|
||||
deepLinks =
|
||||
listOf(
|
||||
navDeepLink<R>(basePath = "$DEEP_LINK_BASE_URI/radio_config/{destNum}/${routeNameString.lowercase()}"),
|
||||
navDeepLink<R>(basePath = "$DEEP_LINK_BASE_URI/radio_config/${routeNameString.lowercase()}"),
|
||||
navDeepLink<R>(
|
||||
basePath = "$DEEP_LINK_BASE_URI/settings/radio/{destNum}/${routeNameString.lowercase()}",
|
||||
),
|
||||
navDeepLink<R>(basePath = "$DEEP_LINK_BASE_URI/settings/radio/${routeNameString.lowercase()}"),
|
||||
),
|
||||
) { backStackEntry ->
|
||||
val parentEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(RadioConfigRoutes.RadioConfigGraph::class) }
|
||||
remember(backStackEntry) { navController.getBackStackEntry(SettingsRoutes.SettingsGraph::class) }
|
||||
val viewModel = hiltViewModel<RadioConfigViewModel>(parentEntry)
|
||||
screenContent(viewModel)
|
||||
}
|
||||
|
|
@ -206,62 +219,46 @@ private inline fun <reified R : Route> NavGraphBuilder.addRadioConfigScreenCompo
|
|||
private fun NavGraphBuilder.configRoutesScreens(navController: NavHostController) {
|
||||
ConfigRoute.entries.forEach { entry ->
|
||||
when (entry.route) {
|
||||
is RadioConfigRoutes.User ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.User>(
|
||||
is SettingsRoutes.User ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.User>(navController, entry.name, entry.screenComposable)
|
||||
is SettingsRoutes.ChannelConfig ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.ChannelConfig>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.ChannelConfig ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.ChannelConfig>(
|
||||
is SettingsRoutes.Device ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.Device>(navController, entry.name, entry.screenComposable)
|
||||
is SettingsRoutes.Position ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.Position>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Device ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Device>(
|
||||
is SettingsRoutes.Power ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.Power>(navController, entry.name, entry.screenComposable)
|
||||
is SettingsRoutes.Network ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.Network>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Position ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Position>(
|
||||
is SettingsRoutes.Display ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.Display>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Power ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Power>(
|
||||
is SettingsRoutes.LoRa ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.LoRa>(navController, entry.name, entry.screenComposable)
|
||||
is SettingsRoutes.Bluetooth ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.Bluetooth>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Network ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Network>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Display ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Display>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.LoRa ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.LoRa>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Bluetooth ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Bluetooth>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Security ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Security>(
|
||||
is SettingsRoutes.Security ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.Security>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
|
|
@ -275,80 +272,68 @@ private fun NavGraphBuilder.configRoutesScreens(navController: NavHostController
|
|||
private fun NavGraphBuilder.moduleRoutesScreens(navController: NavHostController) {
|
||||
ModuleRoute.entries.forEach { entry ->
|
||||
when (entry.route) {
|
||||
is RadioConfigRoutes.MQTT ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.MQTT>(
|
||||
is SettingsRoutes.MQTT ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.MQTT>(navController, entry.name, entry.screenComposable)
|
||||
is SettingsRoutes.Serial ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.Serial>(navController, entry.name, entry.screenComposable)
|
||||
is SettingsRoutes.ExtNotification ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.ExtNotification>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Serial ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Serial>(
|
||||
is SettingsRoutes.StoreForward ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.StoreForward>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.ExtNotification ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.ExtNotification>(
|
||||
is SettingsRoutes.RangeTest ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.RangeTest>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.StoreForward ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.StoreForward>(
|
||||
is SettingsRoutes.Telemetry ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.Telemetry>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.RangeTest ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.RangeTest>(
|
||||
is SettingsRoutes.CannedMessage ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.CannedMessage>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Telemetry ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Telemetry>(
|
||||
is SettingsRoutes.Audio ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.Audio>(navController, entry.name, entry.screenComposable)
|
||||
is SettingsRoutes.RemoteHardware ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.RemoteHardware>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.CannedMessage ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.CannedMessage>(
|
||||
is SettingsRoutes.NeighborInfo ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.NeighborInfo>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Audio ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Audio>(
|
||||
is SettingsRoutes.AmbientLighting ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.AmbientLighting>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.RemoteHardware ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.RemoteHardware>(
|
||||
is SettingsRoutes.DetectionSensor ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.DetectionSensor>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.NeighborInfo ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.NeighborInfo>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.AmbientLighting ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.AmbientLighting>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.DetectionSensor ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.DetectionSensor>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
)
|
||||
is RadioConfigRoutes.Paxcounter ->
|
||||
addRadioConfigScreenComposable<RadioConfigRoutes.Paxcounter>(
|
||||
is SettingsRoutes.Paxcounter ->
|
||||
addRadioConfigScreenComposable<SettingsRoutes.Paxcounter>(
|
||||
navController,
|
||||
entry.name,
|
||||
entry.screenComposable,
|
||||
|
|
@ -366,66 +351,66 @@ enum class ConfigRoute(
|
|||
val type: Int = 0,
|
||||
val screenComposable: @Composable (viewModel: RadioConfigViewModel) -> Unit,
|
||||
) {
|
||||
USER(R.string.user, RadioConfigRoutes.User, Icons.Default.Person, 0, { vm -> UserConfigScreen(vm) }),
|
||||
USER(R.string.user, SettingsRoutes.User, Icons.Default.Person, 0, { vm -> UserConfigScreen(vm) }),
|
||||
CHANNELS(
|
||||
R.string.channels,
|
||||
RadioConfigRoutes.ChannelConfig,
|
||||
SettingsRoutes.ChannelConfig,
|
||||
Icons.AutoMirrored.Default.List,
|
||||
0,
|
||||
{ vm -> ChannelConfigScreen(vm) },
|
||||
),
|
||||
DEVICE(
|
||||
R.string.device,
|
||||
RadioConfigRoutes.Device,
|
||||
SettingsRoutes.Device,
|
||||
Icons.Default.Router,
|
||||
AdminProtos.AdminMessage.ConfigType.DEVICE_CONFIG_VALUE,
|
||||
{ vm -> DeviceConfigScreen(vm) },
|
||||
),
|
||||
POSITION(
|
||||
R.string.position,
|
||||
RadioConfigRoutes.Position,
|
||||
SettingsRoutes.Position,
|
||||
Icons.Default.LocationOn,
|
||||
AdminProtos.AdminMessage.ConfigType.POSITION_CONFIG_VALUE,
|
||||
{ vm -> PositionConfigScreen(vm) },
|
||||
),
|
||||
POWER(
|
||||
R.string.power,
|
||||
RadioConfigRoutes.Power,
|
||||
SettingsRoutes.Power,
|
||||
Icons.Default.Power,
|
||||
AdminProtos.AdminMessage.ConfigType.POWER_CONFIG_VALUE,
|
||||
{ vm -> PowerConfigScreen(vm) },
|
||||
),
|
||||
NETWORK(
|
||||
R.string.network,
|
||||
RadioConfigRoutes.Network,
|
||||
SettingsRoutes.Network,
|
||||
Icons.Default.Wifi,
|
||||
AdminProtos.AdminMessage.ConfigType.NETWORK_CONFIG_VALUE,
|
||||
{ vm -> NetworkConfigScreen(vm) },
|
||||
),
|
||||
DISPLAY(
|
||||
R.string.display,
|
||||
RadioConfigRoutes.Display,
|
||||
SettingsRoutes.Display,
|
||||
Icons.Default.DisplaySettings,
|
||||
AdminProtos.AdminMessage.ConfigType.DISPLAY_CONFIG_VALUE,
|
||||
{ vm -> DisplayConfigScreen(vm) },
|
||||
),
|
||||
LORA(
|
||||
R.string.lora,
|
||||
RadioConfigRoutes.LoRa,
|
||||
SettingsRoutes.LoRa,
|
||||
Icons.Default.CellTower,
|
||||
AdminProtos.AdminMessage.ConfigType.LORA_CONFIG_VALUE,
|
||||
{ vm -> LoRaConfigScreen(vm) },
|
||||
),
|
||||
BLUETOOTH(
|
||||
R.string.bluetooth,
|
||||
RadioConfigRoutes.Bluetooth,
|
||||
SettingsRoutes.Bluetooth,
|
||||
Icons.Default.Bluetooth,
|
||||
AdminProtos.AdminMessage.ConfigType.BLUETOOTH_CONFIG_VALUE,
|
||||
{ vm -> BluetoothConfigScreen(vm) },
|
||||
),
|
||||
SECURITY(
|
||||
R.string.security,
|
||||
RadioConfigRoutes.Security,
|
||||
SettingsRoutes.Security,
|
||||
Icons.Default.Security,
|
||||
AdminProtos.AdminMessage.ConfigType.SECURITY_CONFIG_VALUE,
|
||||
{ vm -> SecurityConfigScreen(vm) },
|
||||
|
|
@ -454,91 +439,91 @@ enum class ModuleRoute(
|
|||
) {
|
||||
MQTT(
|
||||
R.string.mqtt,
|
||||
RadioConfigRoutes.MQTT,
|
||||
SettingsRoutes.MQTT,
|
||||
Icons.Default.Cloud,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.MQTT_CONFIG_VALUE,
|
||||
{ vm -> MQTTConfigScreen(vm) },
|
||||
),
|
||||
SERIAL(
|
||||
R.string.serial,
|
||||
RadioConfigRoutes.Serial,
|
||||
SettingsRoutes.Serial,
|
||||
Icons.Default.Usb,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.SERIAL_CONFIG_VALUE,
|
||||
{ vm -> SerialConfigScreen(vm) },
|
||||
),
|
||||
EXT_NOTIFICATION(
|
||||
R.string.external_notification,
|
||||
RadioConfigRoutes.ExtNotification,
|
||||
SettingsRoutes.ExtNotification,
|
||||
Icons.Default.Notifications,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.EXTNOTIF_CONFIG_VALUE,
|
||||
{ vm -> ExternalNotificationConfigScreen(vm) },
|
||||
),
|
||||
STORE_FORWARD(
|
||||
R.string.store_forward,
|
||||
RadioConfigRoutes.StoreForward,
|
||||
SettingsRoutes.StoreForward,
|
||||
Icons.AutoMirrored.Default.Forward,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.STOREFORWARD_CONFIG_VALUE,
|
||||
{ vm -> StoreForwardConfigScreen(vm) },
|
||||
),
|
||||
RANGE_TEST(
|
||||
R.string.range_test,
|
||||
RadioConfigRoutes.RangeTest,
|
||||
SettingsRoutes.RangeTest,
|
||||
Icons.Default.Speed,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.RANGETEST_CONFIG_VALUE,
|
||||
{ vm -> RangeTestConfigScreen(vm) },
|
||||
),
|
||||
TELEMETRY(
|
||||
R.string.telemetry,
|
||||
RadioConfigRoutes.Telemetry,
|
||||
SettingsRoutes.Telemetry,
|
||||
Icons.Default.DataUsage,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.TELEMETRY_CONFIG_VALUE,
|
||||
{ vm -> TelemetryConfigScreen(vm) },
|
||||
),
|
||||
CANNED_MESSAGE(
|
||||
R.string.canned_message,
|
||||
RadioConfigRoutes.CannedMessage,
|
||||
SettingsRoutes.CannedMessage,
|
||||
Icons.AutoMirrored.Default.Message,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.CANNEDMSG_CONFIG_VALUE,
|
||||
{ vm -> CannedMessageConfigScreen(vm) },
|
||||
),
|
||||
AUDIO(
|
||||
R.string.audio,
|
||||
RadioConfigRoutes.Audio,
|
||||
SettingsRoutes.Audio,
|
||||
Icons.AutoMirrored.Default.VolumeUp,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.AUDIO_CONFIG_VALUE,
|
||||
{ vm -> AudioConfigScreen(vm) },
|
||||
),
|
||||
REMOTE_HARDWARE(
|
||||
R.string.remote_hardware,
|
||||
RadioConfigRoutes.RemoteHardware,
|
||||
SettingsRoutes.RemoteHardware,
|
||||
Icons.Default.SettingsRemote,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.REMOTEHARDWARE_CONFIG_VALUE,
|
||||
{ vm -> RemoteHardwareConfigScreen(vm) },
|
||||
),
|
||||
NEIGHBOR_INFO(
|
||||
R.string.neighbor_info,
|
||||
RadioConfigRoutes.NeighborInfo,
|
||||
SettingsRoutes.NeighborInfo,
|
||||
Icons.Default.People,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.NEIGHBORINFO_CONFIG_VALUE,
|
||||
{ vm -> NeighborInfoConfigScreen(vm) },
|
||||
),
|
||||
AMBIENT_LIGHTING(
|
||||
R.string.ambient_lighting,
|
||||
RadioConfigRoutes.AmbientLighting,
|
||||
SettingsRoutes.AmbientLighting,
|
||||
Icons.Default.LightMode,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.AMBIENTLIGHTING_CONFIG_VALUE,
|
||||
{ vm -> AmbientLightingConfigScreen(vm) },
|
||||
),
|
||||
DETECTION_SENSOR(
|
||||
R.string.detection_sensor,
|
||||
RadioConfigRoutes.DetectionSensor,
|
||||
SettingsRoutes.DetectionSensor,
|
||||
Icons.Default.Sensors,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.DETECTIONSENSOR_CONFIG_VALUE,
|
||||
{ vm -> DetectionSensorConfigScreen(vm) },
|
||||
),
|
||||
PAXCOUNTER(
|
||||
R.string.paxcounter,
|
||||
RadioConfigRoutes.Paxcounter,
|
||||
SettingsRoutes.Paxcounter,
|
||||
Icons.Default.PermScanWifi,
|
||||
AdminProtos.AdminMessage.ModuleConfigType.PAXCOUNTER_CONFIG_VALUE,
|
||||
{ vm -> PaxcounterConfigScreen(vm) },
|
||||
|
|
@ -34,7 +34,6 @@ import androidx.compose.foundation.layout.safeDrawingPadding
|
|||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.QrCode2
|
||||
import androidx.compose.material.icons.rounded.Wifi
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme.colorScheme
|
||||
|
|
@ -90,8 +89,8 @@ import com.geeksville.mesh.navigation.ContactsRoutes
|
|||
import com.geeksville.mesh.navigation.MapRoutes
|
||||
import com.geeksville.mesh.navigation.NavGraph
|
||||
import com.geeksville.mesh.navigation.NodesRoutes
|
||||
import com.geeksville.mesh.navigation.RadioConfigRoutes
|
||||
import com.geeksville.mesh.navigation.Route
|
||||
import com.geeksville.mesh.navigation.SettingsRoutes
|
||||
import com.geeksville.mesh.repository.radio.MeshActivity
|
||||
import com.geeksville.mesh.service.ConnectionState
|
||||
import com.geeksville.mesh.service.MeshService
|
||||
|
|
@ -104,6 +103,7 @@ import com.geeksville.mesh.ui.common.icons.Conversations
|
|||
import com.geeksville.mesh.ui.common.icons.Map
|
||||
import com.geeksville.mesh.ui.common.icons.MeshtasticIcons
|
||||
import com.geeksville.mesh.ui.common.icons.Nodes
|
||||
import com.geeksville.mesh.ui.common.icons.Settings
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusBlue
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusGreen
|
||||
import com.geeksville.mesh.ui.connections.DeviceType
|
||||
|
|
@ -120,7 +120,7 @@ enum class TopLevelDestination(@StringRes val label: Int, val icon: ImageVector,
|
|||
Conversations(R.string.conversations, MeshtasticIcons.Conversations, ContactsRoutes.ContactsGraph),
|
||||
Nodes(R.string.nodes, MeshtasticIcons.Nodes, NodesRoutes.NodesGraph),
|
||||
Map(R.string.map, MeshtasticIcons.Map, MapRoutes.Map),
|
||||
Share(R.string.bottom_nav_share, Icons.Rounded.QrCode2, ChannelsRoutes.ChannelsGraph),
|
||||
Settings(R.string.bottom_nav_settings, MeshtasticIcons.Settings, SettingsRoutes.SettingsGraph()),
|
||||
Connections(R.string.connections, Icons.Rounded.Wifi, ConnectionsRoutes.ConnectionsGraph),
|
||||
;
|
||||
|
||||
|
|
@ -150,7 +150,6 @@ fun MainScreen(
|
|||
) {
|
||||
val navController = rememberNavController()
|
||||
val connectionState by uIViewModel.connectionState.collectAsStateWithLifecycle()
|
||||
val localConfig by uIViewModel.localConfig.collectAsStateWithLifecycle()
|
||||
val requestChannelSet by uIViewModel.requestChannelSet.collectAsStateWithLifecycle()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
|
|
@ -205,7 +204,7 @@ fun MainScreen(
|
|||
text = { Text(text = message) },
|
||||
onConfirm = {
|
||||
if (compromisedKeys) {
|
||||
navController.navigate(RadioConfigRoutes.Security)
|
||||
navController.navigate(SettingsRoutes.Security)
|
||||
}
|
||||
uIViewModel.clearClientNotification(notification)
|
||||
},
|
||||
|
|
@ -348,13 +347,11 @@ fun MainScreen(
|
|||
}
|
||||
MainAppBar(
|
||||
viewModel = uIViewModel,
|
||||
isManaged = localConfig.security.isManaged,
|
||||
navController = navController,
|
||||
onAction = { action ->
|
||||
if (action is MainMenuAction) {
|
||||
when (action) {
|
||||
MainMenuAction.DEBUG -> navController.navigate(Route.DebugPanel)
|
||||
MainMenuAction.RADIO_CONFIG -> navController.navigate(RadioConfigRoutes.RadioConfig())
|
||||
MainMenuAction.QUICK_CHAT -> navController.navigate(ContactsRoutes.QuickChat)
|
||||
MainMenuAction.SHOW_INTRO -> uIViewModel.onMainMenuAction(action)
|
||||
else -> onAction(action)
|
||||
|
|
|
|||
|
|
@ -55,14 +55,14 @@ import com.geeksville.mesh.model.Node
|
|||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.navigation.ContactsRoutes
|
||||
import com.geeksville.mesh.navigation.NodesRoutes
|
||||
import com.geeksville.mesh.navigation.RadioConfigRoutes
|
||||
import com.geeksville.mesh.navigation.Route
|
||||
import com.geeksville.mesh.navigation.SettingsRoutes
|
||||
import com.geeksville.mesh.navigation.showLongNameTitle
|
||||
import com.geeksville.mesh.ui.TopLevelDestination.Companion.isTopLevel
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.debug.DebugMenuActions
|
||||
import com.geeksville.mesh.ui.node.components.NodeChip
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigMenuActions
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigMenuActions
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
@Composable
|
||||
|
|
@ -70,7 +70,6 @@ fun MainAppBar(
|
|||
modifier: Modifier = Modifier,
|
||||
viewModel: UIViewModel = hiltViewModel(),
|
||||
navController: NavHostController,
|
||||
isManaged: Boolean,
|
||||
onAction: (Any?) -> Unit,
|
||||
) {
|
||||
val backStackEntry by navController.currentBackStackEntryAsState()
|
||||
|
|
@ -119,11 +118,11 @@ fun MainAppBar(
|
|||
actions = {
|
||||
currentDestination?.let {
|
||||
when {
|
||||
it.isTopLevel() -> MainMenuActions(isManaged, onAction)
|
||||
it.isTopLevel() -> MainMenuActions(onAction)
|
||||
|
||||
currentDestination.hasRoute<Route.DebugPanel>() -> DebugMenuActions()
|
||||
|
||||
currentDestination.hasRoute<RadioConfigRoutes.RadioConfig>() ->
|
||||
currentDestination.hasRoute<SettingsRoutes.Settings>() ->
|
||||
RadioConfigMenuActions(viewModel = viewModel)
|
||||
|
||||
else -> {}
|
||||
|
|
@ -208,7 +207,6 @@ private fun TopBarActions(
|
|||
|
||||
enum class MainMenuAction(@StringRes val stringRes: Int) {
|
||||
DEBUG(R.string.debug_panel),
|
||||
RADIO_CONFIG(R.string.radio_configuration),
|
||||
EXPORT_RANGETEST(R.string.save_rangetest),
|
||||
THEME(R.string.theme),
|
||||
LANGUAGE(R.string.preferences_language),
|
||||
|
|
@ -217,7 +215,7 @@ enum class MainMenuAction(@StringRes val stringRes: Int) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun MainMenuActions(isManaged: Boolean, onAction: (MainMenuAction) -> Unit) {
|
||||
private fun MainMenuActions(onAction: (MainMenuAction) -> Unit) {
|
||||
var showMenu by remember { mutableStateOf(false) }
|
||||
IconButton(onClick = { showMenu = true }) {
|
||||
Icon(imageVector = Icons.Default.MoreVert, contentDescription = stringResource(R.string.overflow_menu))
|
||||
|
|
@ -235,11 +233,7 @@ private fun MainMenuActions(isManaged: Boolean, onAction: (MainMenuAction) -> Un
|
|||
onAction(action)
|
||||
showMenu = false
|
||||
},
|
||||
enabled =
|
||||
when (action) {
|
||||
MainMenuAction.RADIO_CONFIG -> !isManaged
|
||||
else -> true
|
||||
},
|
||||
enabled = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -257,7 +251,7 @@ private fun MainAppBarPreview(@PreviewParameter(BooleanProvider::class) canNavig
|
|||
showNodeChip = true,
|
||||
canNavigateUp = canNavigateUp,
|
||||
onNavigateUp = {},
|
||||
actions = { MainMenuActions(isManaged = false, onAction = {}) },
|
||||
actions = { MainMenuActions(onAction = {}) },
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ import com.geeksville.mesh.channelSet
|
|||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.model.Channel
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.radioconfig.components.ChannelSelection
|
||||
import com.geeksville.mesh.ui.settings.radio.components.ChannelSelection
|
||||
|
||||
@Composable
|
||||
fun ScannedQrCodeDialog(viewModel: UIViewModel, incoming: ChannelSet) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.connections.components
|
||||
package com.geeksville.mesh.ui.common.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -35,8 +35,8 @@ import androidx.compose.ui.unit.dp
|
|||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
fun TitledCard(title: String, content: @Composable ColumnScope.() -> Unit) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
fun TitledCard(title: String, modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit) {
|
||||
Column(modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text(
|
||||
title,
|
||||
modifier = Modifier.padding(horizontal = 16.dp).fillMaxWidth(),
|
||||
|
|
@ -49,6 +49,6 @@ fun TitledCard(title: String, content: @Composable ColumnScope.() -> Unit) {
|
|||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
fun TitledCardPreview() {
|
||||
private fun TitledCardPreview() {
|
||||
AppTheme { Surface { TitledCard(title = "Title") { Box(modifier = Modifier.fillMaxWidth().height(100.dp)) {} } } }
|
||||
}
|
||||
|
|
@ -0,0 +1,160 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.common.icons
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* This is from Material Symbols.
|
||||
*
|
||||
* @see
|
||||
* [settings](https://fonts.google.com/icons?selected=Material+Symbols+Rounded:settings:FILL@0;wght@400;GRAD@0;opsz@24&icon.style=Rounded&icon.query=settings&icon.set=Material+Symbols&icon.size=24&icon.color=%23e3e3e3&icon.platform=android)
|
||||
*/
|
||||
val MeshtasticIcons.Settings: ImageVector
|
||||
get() {
|
||||
if (settings != null) {
|
||||
return settings!!
|
||||
}
|
||||
settings =
|
||||
ImageVector.Builder(
|
||||
name = "Settings",
|
||||
defaultWidth = 24.dp,
|
||||
defaultHeight = 24.dp,
|
||||
viewportWidth = 960f,
|
||||
viewportHeight = 960f,
|
||||
)
|
||||
.apply {
|
||||
path(fill = SolidColor(Color(0xFFE3E3E3))) {
|
||||
moveTo(433f, 880f)
|
||||
quadToRelative(-27f, 0f, -46.5f, -18f)
|
||||
reflectiveQuadTo(363f, 818f)
|
||||
lineToRelative(-9f, -66f)
|
||||
quadToRelative(-13f, -5f, -24.5f, -12f)
|
||||
reflectiveQuadTo(307f, 725f)
|
||||
lineToRelative(-62f, 26f)
|
||||
quadToRelative(-25f, 11f, -50f, 2f)
|
||||
reflectiveQuadToRelative(-39f, -32f)
|
||||
lineToRelative(-47f, -82f)
|
||||
quadToRelative(-14f, -23f, -8f, -49f)
|
||||
reflectiveQuadToRelative(27f, -43f)
|
||||
lineToRelative(53f, -40f)
|
||||
quadToRelative(-1f, -7f, -1f, -13.5f)
|
||||
verticalLineToRelative(-27f)
|
||||
quadToRelative(0f, -6.5f, 1f, -13.5f)
|
||||
lineToRelative(-53f, -40f)
|
||||
quadToRelative(-21f, -17f, -27f, -43f)
|
||||
reflectiveQuadToRelative(8f, -49f)
|
||||
lineToRelative(47f, -82f)
|
||||
quadToRelative(14f, -23f, 39f, -32f)
|
||||
reflectiveQuadToRelative(50f, 2f)
|
||||
lineToRelative(62f, 26f)
|
||||
quadToRelative(11f, -8f, 23f, -15f)
|
||||
reflectiveQuadToRelative(24f, -12f)
|
||||
lineToRelative(9f, -66f)
|
||||
quadToRelative(4f, -26f, 23.5f, -44f)
|
||||
reflectiveQuadToRelative(46.5f, -18f)
|
||||
horizontalLineToRelative(94f)
|
||||
quadToRelative(27f, 0f, 46.5f, 18f)
|
||||
reflectiveQuadToRelative(23.5f, 44f)
|
||||
lineToRelative(9f, 66f)
|
||||
quadToRelative(13f, 5f, 24.5f, 12f)
|
||||
reflectiveQuadToRelative(22.5f, 15f)
|
||||
lineToRelative(62f, -26f)
|
||||
quadToRelative(25f, -11f, 50f, -2f)
|
||||
reflectiveQuadToRelative(39f, 32f)
|
||||
lineToRelative(47f, 82f)
|
||||
quadToRelative(14f, 23f, 8f, 49f)
|
||||
reflectiveQuadToRelative(-27f, 43f)
|
||||
lineToRelative(-53f, 40f)
|
||||
quadToRelative(1f, 7f, 1f, 13.5f)
|
||||
verticalLineToRelative(27f)
|
||||
quadToRelative(0f, 6.5f, -2f, 13.5f)
|
||||
lineToRelative(53f, 40f)
|
||||
quadToRelative(21f, 17f, 27f, 43f)
|
||||
reflectiveQuadToRelative(-8f, 49f)
|
||||
lineToRelative(-48f, 82f)
|
||||
quadToRelative(-14f, 23f, -39f, 32f)
|
||||
reflectiveQuadToRelative(-50f, -2f)
|
||||
lineToRelative(-60f, -26f)
|
||||
quadToRelative(-11f, 8f, -23f, 15f)
|
||||
reflectiveQuadToRelative(-24f, 12f)
|
||||
lineToRelative(-9f, 66f)
|
||||
quadToRelative(-4f, 26f, -23.5f, 44f)
|
||||
reflectiveQuadTo(527f, 880f)
|
||||
horizontalLineToRelative(-94f)
|
||||
close()
|
||||
moveTo(440f, 800f)
|
||||
horizontalLineToRelative(79f)
|
||||
lineToRelative(14f, -106f)
|
||||
quadToRelative(31f, -8f, 57.5f, -23.5f)
|
||||
reflectiveQuadTo(639f, 633f)
|
||||
lineToRelative(99f, 41f)
|
||||
lineToRelative(39f, -68f)
|
||||
lineToRelative(-86f, -65f)
|
||||
quadToRelative(5f, -14f, 7f, -29.5f)
|
||||
reflectiveQuadToRelative(2f, -31.5f)
|
||||
quadToRelative(0f, -16f, -2f, -31.5f)
|
||||
reflectiveQuadToRelative(-7f, -29.5f)
|
||||
lineToRelative(86f, -65f)
|
||||
lineToRelative(-39f, -68f)
|
||||
lineToRelative(-99f, 42f)
|
||||
quadToRelative(-22f, -23f, -48.5f, -38.5f)
|
||||
reflectiveQuadTo(533f, 266f)
|
||||
lineToRelative(-13f, -106f)
|
||||
horizontalLineToRelative(-79f)
|
||||
lineToRelative(-14f, 106f)
|
||||
quadToRelative(-31f, 8f, -57.5f, 23.5f)
|
||||
reflectiveQuadTo(321f, 327f)
|
||||
lineToRelative(-99f, -41f)
|
||||
lineToRelative(-39f, 68f)
|
||||
lineToRelative(86f, 64f)
|
||||
quadToRelative(-5f, 15f, -7f, 30f)
|
||||
reflectiveQuadToRelative(-2f, 32f)
|
||||
quadToRelative(0f, 16f, 2f, 31f)
|
||||
reflectiveQuadToRelative(7f, 30f)
|
||||
lineToRelative(-86f, 65f)
|
||||
lineToRelative(39f, 68f)
|
||||
lineToRelative(99f, -42f)
|
||||
quadToRelative(22f, 23f, 48.5f, 38.5f)
|
||||
reflectiveQuadTo(427f, 694f)
|
||||
lineToRelative(13f, 106f)
|
||||
close()
|
||||
moveTo(482f, 620f)
|
||||
quadToRelative(58f, 0f, 99f, -41f)
|
||||
reflectiveQuadToRelative(41f, -99f)
|
||||
quadToRelative(0f, -58f, -41f, -99f)
|
||||
reflectiveQuadToRelative(-99f, -41f)
|
||||
quadToRelative(-59f, 0f, -99.5f, 41f)
|
||||
reflectiveQuadTo(342f, 480f)
|
||||
quadToRelative(0f, 58f, 40.5f, 99f)
|
||||
reflectiveQuadToRelative(99.5f, 41f)
|
||||
close()
|
||||
moveTo(480f, 480f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
||||
return settings!!
|
||||
}
|
||||
|
||||
private var settings: ImageVector? = null
|
||||
|
|
@ -94,8 +94,8 @@ import com.geeksville.mesh.model.NO_DEVICE_SELECTED
|
|||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.navigation.ConfigRoute
|
||||
import com.geeksville.mesh.navigation.RadioConfigRoutes
|
||||
import com.geeksville.mesh.navigation.Route
|
||||
import com.geeksville.mesh.navigation.SettingsRoutes
|
||||
import com.geeksville.mesh.navigation.getNavRouteFrom
|
||||
import com.geeksville.mesh.service.ConnectionState
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
|
|
@ -103,8 +103,8 @@ import com.geeksville.mesh.ui.connections.components.BLEDevices
|
|||
import com.geeksville.mesh.ui.connections.components.CurrentlyConnectedCard
|
||||
import com.geeksville.mesh.ui.connections.components.NetworkDevices
|
||||
import com.geeksville.mesh.ui.connections.components.UsbDevices
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.radioconfig.components.PacketResponseStateDialog
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.components.PacketResponseStateDialog
|
||||
import com.geeksville.mesh.ui.sharing.SharedContactDialog
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||
|
|
@ -129,7 +129,7 @@ fun ConnectionsScreen(
|
|||
scanModel: BTScanModel = hiltViewModel(),
|
||||
bluetoothViewModel: BluetoothViewModel = hiltViewModel(),
|
||||
radioConfigViewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
onNavigateToRadioConfig: () -> Unit,
|
||||
onNavigateToSettings: () -> Unit,
|
||||
onNavigateToNodeDetails: (Int) -> Unit,
|
||||
onConfigNavigate: (Route) -> Unit,
|
||||
) {
|
||||
|
|
@ -167,8 +167,8 @@ fun ConnectionsScreen(
|
|||
getNavRouteFrom(radioConfigState.route)?.let { route ->
|
||||
isWaiting = false
|
||||
radioConfigViewModel.clearPacketResponse()
|
||||
if (route == RadioConfigRoutes.LoRa) {
|
||||
onConfigNavigate(RadioConfigRoutes.LoRa)
|
||||
if (route == SettingsRoutes.LoRa) {
|
||||
onConfigNavigate(SettingsRoutes.LoRa)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -260,7 +260,7 @@ fun ConnectionsScreen(
|
|||
node = node,
|
||||
onNavigateToNodeDetails = onNavigateToNodeDetails,
|
||||
onSetShowSharedContact = { showSharedContact = it },
|
||||
onNavigateToRadioConfig = onNavigateToRadioConfig,
|
||||
onNavigateToSettings = onNavigateToSettings,
|
||||
onClickDisconnect = { scanModel.disconnect() },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ import com.geeksville.mesh.R
|
|||
import com.geeksville.mesh.model.BTScanModel
|
||||
import com.geeksville.mesh.model.DeviceListEntry
|
||||
import com.geeksville.mesh.service.ConnectionState
|
||||
import com.geeksville.mesh.ui.common.components.TitledCard
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.MultiplePermissionsState
|
||||
import com.google.accompanist.permissions.rememberMultiplePermissionsState
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ fun CurrentlyConnectedCard(
|
|||
modifier: Modifier = Modifier,
|
||||
onNavigateToNodeDetails: (Int) -> Unit,
|
||||
onSetShowSharedContact: (Node) -> Unit,
|
||||
onNavigateToRadioConfig: () -> Unit,
|
||||
onNavigateToSettings: () -> Unit,
|
||||
onClickDisconnect: () -> Unit,
|
||||
) {
|
||||
Card(modifier = modifier) {
|
||||
|
|
@ -98,7 +98,7 @@ fun CurrentlyConnectedCard(
|
|||
}
|
||||
}
|
||||
|
||||
IconButton(enabled = true, onClick = onNavigateToRadioConfig) {
|
||||
IconButton(enabled = true, onClick = onNavigateToSettings) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Settings,
|
||||
contentDescription = stringResource(id = R.string.radio_configuration),
|
||||
|
|
@ -142,7 +142,7 @@ private fun CurrentlyConnectedCardPreview() {
|
|||
),
|
||||
onNavigateToNodeDetails = {},
|
||||
onSetShowSharedContact = {},
|
||||
onNavigateToRadioConfig = {},
|
||||
onNavigateToSettings = {},
|
||||
onClickDisconnect = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ import com.geeksville.mesh.model.BTScanModel
|
|||
import com.geeksville.mesh.model.DeviceListEntry
|
||||
import com.geeksville.mesh.repository.network.NetworkRepository
|
||||
import com.geeksville.mesh.service.ConnectionState
|
||||
import com.geeksville.mesh.ui.common.components.TitledCard
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.connections.isIPAddress
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import com.geeksville.mesh.R
|
|||
import com.geeksville.mesh.model.BTScanModel
|
||||
import com.geeksville.mesh.model.DeviceListEntry
|
||||
import com.geeksville.mesh.service.ConnectionState
|
||||
import com.geeksville.mesh.ui.common.components.TitledCard
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -141,8 +141,8 @@ import com.geeksville.mesh.model.Node
|
|||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.model.isUnmessageableRole
|
||||
import com.geeksville.mesh.navigation.NodeDetailRoutes
|
||||
import com.geeksville.mesh.navigation.RadioConfigRoutes
|
||||
import com.geeksville.mesh.navigation.Route
|
||||
import com.geeksville.mesh.navigation.SettingsRoutes
|
||||
import com.geeksville.mesh.service.ServiceAction
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.preview.NodePreviewParameterProvider
|
||||
|
|
@ -153,7 +153,7 @@ import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
|
|||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusYellow
|
||||
import com.geeksville.mesh.ui.node.components.NodeActionDialogs
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import com.geeksville.mesh.ui.radioconfig.NavCard
|
||||
import com.geeksville.mesh.ui.settings.radio.NavCard
|
||||
import com.geeksville.mesh.ui.sharing.SharedContactDialog
|
||||
import com.geeksville.mesh.util.UnitConversions
|
||||
import com.geeksville.mesh.util.UnitConversions.toTempString
|
||||
|
|
@ -440,7 +440,7 @@ private fun AdministrationSection(
|
|||
icon = Icons.Default.Settings,
|
||||
enabled = metricsState.isLocal || node.metadata != null,
|
||||
) {
|
||||
onAction(NodeDetailAction.Navigate(RadioConfigRoutes.RadioConfig(node.num)))
|
||||
onAction(NodeDetailAction.Navigate(SettingsRoutes.Settings(node.num)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Meshtastic LLC
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.settings.components
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
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.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.rounded.Android
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
|
||||
@Composable
|
||||
fun SettingsItem(
|
||||
text: String,
|
||||
enabled: Boolean,
|
||||
leadingIcon: ImageVector? = null,
|
||||
trailingIcon: ImageVector? = Icons.AutoMirrored.Rounded.KeyboardArrowRight,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Card(
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = Color.Transparent,
|
||||
disabledContainerColor = Color.Transparent,
|
||||
),
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 12.dp, horizontal = 16.dp),
|
||||
) {
|
||||
leadingIcon?.let {
|
||||
Icon(imageVector = it, contentDescription = text, modifier = Modifier.size(24.dp))
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
}
|
||||
Text(text = text, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.weight(1f))
|
||||
|
||||
trailingIcon?.let {
|
||||
Icon(imageVector = it, contentDescription = null, modifier = Modifier.wrapContentSize())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SettingsItemPreview() {
|
||||
AppTheme { SettingsItem(text = "Text", leadingIcon = Icons.Rounded.Android, enabled = true) {} }
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SettingsItemDisabledPreview() {
|
||||
AppTheme { SettingsItem(text = "Text", leadingIcon = Icons.Rounded.Android, enabled = false) {} }
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig
|
||||
package com.geeksville.mesh.ui.settings.radio
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig
|
||||
package com.geeksville.mesh.ui.settings.radio
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig
|
||||
package com.geeksville.mesh.ui.settings.radio
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
|
|
@ -72,13 +72,15 @@ import com.geeksville.mesh.model.UIViewModel
|
|||
import com.geeksville.mesh.navigation.AdminRoute
|
||||
import com.geeksville.mesh.navigation.ConfigRoute
|
||||
import com.geeksville.mesh.navigation.ModuleRoute
|
||||
import com.geeksville.mesh.navigation.RadioConfigRoutes
|
||||
import com.geeksville.mesh.navigation.Route
|
||||
import com.geeksville.mesh.navigation.SettingsRoutes
|
||||
import com.geeksville.mesh.navigation.getNavRouteFrom
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.TitledCard
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.radioconfig.components.EditDeviceProfileDialog
|
||||
import com.geeksville.mesh.ui.radioconfig.components.PacketResponseStateDialog
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItem
|
||||
import com.geeksville.mesh.ui.settings.radio.components.EditDeviceProfileDialog
|
||||
import com.geeksville.mesh.ui.settings.radio.components.PacketResponseStateDialog
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
|
|
@ -105,6 +107,7 @@ fun RadioConfigScreen(
|
|||
nodeName?.let { uiViewModel.setTitle(it) }
|
||||
|
||||
val excludedModulesUnlocked by uiViewModel.excludedModulesUnlocked.collectAsStateWithLifecycle()
|
||||
val localConfig by uiViewModel.localConfig.collectAsStateWithLifecycle()
|
||||
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
var isWaiting by remember { mutableStateOf(false) }
|
||||
|
|
@ -177,6 +180,7 @@ fun RadioConfigScreen(
|
|||
RadioConfigItemList(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
isManaged = localConfig.security.isManaged,
|
||||
excludedModulesUnlocked = excludedModulesUnlocked,
|
||||
onRouteClick = { route ->
|
||||
isWaiting = true
|
||||
|
|
@ -272,9 +276,11 @@ private fun NavButton(@StringRes title: Int, enabled: Boolean, onClick: () -> Un
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun RadioConfigItemList(
|
||||
state: RadioConfigState,
|
||||
isManaged: Boolean,
|
||||
excludedModulesUnlocked: Boolean = false,
|
||||
modifier: Modifier = Modifier,
|
||||
onRouteClick: (Enum<*>) -> Unit = {},
|
||||
|
|
@ -282,7 +288,7 @@ private fun RadioConfigItemList(
|
|||
onExport: () -> Unit = {},
|
||||
onNavigate: (Route) -> Unit,
|
||||
) {
|
||||
val enabled = state.connected && !state.responseState.isWaiting()
|
||||
val enabled = state.connected && !state.responseState.isWaiting() && !isManaged
|
||||
var modules by remember { mutableStateOf(ModuleRoute.filterExcludedFrom(state.metadata)) }
|
||||
LaunchedEffect(excludedModulesUnlocked) {
|
||||
if (excludedModulesUnlocked) {
|
||||
|
|
@ -291,44 +297,72 @@ private fun RadioConfigItemList(
|
|||
modules = ModuleRoute.filterExcludedFrom(state.metadata)
|
||||
}
|
||||
}
|
||||
LazyColumn(modifier = modifier, contentPadding = PaddingValues(horizontal = 16.dp)) {
|
||||
item { PreferenceCategory(stringResource(R.string.radio_configuration)) }
|
||||
items(ConfigRoute.filterExcludedFrom(state.metadata)) {
|
||||
NavCard(title = stringResource(it.title), icon = it.icon, enabled = enabled) { onRouteClick(it) }
|
||||
LazyColumn(modifier = modifier, contentPadding = PaddingValues(16.dp)) {
|
||||
item {
|
||||
TitledCard(title = stringResource(R.string.radio_configuration)) {
|
||||
if (isManaged) {
|
||||
ManagedMessage()
|
||||
}
|
||||
|
||||
ConfigRoute.filterExcludedFrom(state.metadata).forEach {
|
||||
SettingsItem(text = stringResource(it.title), leadingIcon = it.icon, enabled = enabled) {
|
||||
onRouteClick(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item { PreferenceCategory(stringResource(R.string.module_settings)) }
|
||||
items(modules) {
|
||||
NavCard(title = stringResource(it.title), icon = it.icon, enabled = enabled) { onRouteClick(it) }
|
||||
item {
|
||||
TitledCard(title = stringResource(R.string.module_settings), modifier = Modifier.padding(top = 16.dp)) {
|
||||
if (isManaged) {
|
||||
ManagedMessage()
|
||||
}
|
||||
|
||||
modules.forEach {
|
||||
SettingsItem(text = stringResource(it.title), leadingIcon = it.icon, enabled = enabled) {
|
||||
onRouteClick(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.isLocal) {
|
||||
item {
|
||||
PreferenceCategory(stringResource(R.string.backup_restore))
|
||||
NavCard(
|
||||
title = stringResource(R.string.import_configuration),
|
||||
icon = Icons.Default.Download,
|
||||
enabled = enabled,
|
||||
onClick = onImport,
|
||||
)
|
||||
NavCard(
|
||||
title = stringResource(R.string.export_configuration),
|
||||
icon = Icons.Default.Upload,
|
||||
enabled = enabled,
|
||||
onClick = onExport,
|
||||
)
|
||||
TitledCard(title = stringResource(R.string.backup_restore), modifier = Modifier.padding(top = 16.dp)) {
|
||||
if (isManaged) {
|
||||
ManagedMessage()
|
||||
}
|
||||
|
||||
SettingsItem(
|
||||
text = stringResource(R.string.import_configuration),
|
||||
leadingIcon = Icons.Default.Download,
|
||||
enabled = enabled,
|
||||
onClick = onImport,
|
||||
)
|
||||
SettingsItem(
|
||||
text = stringResource(R.string.export_configuration),
|
||||
leadingIcon = Icons.Default.Upload,
|
||||
enabled = enabled,
|
||||
onClick = onExport,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items(AdminRoute.entries) { NavButton(it.title, enabled) { onRouteClick(it) } }
|
||||
|
||||
item {
|
||||
PreferenceCategory("Advanced")
|
||||
NavCard(
|
||||
title = stringResource(R.string.clean_node_database_title),
|
||||
enabled = enabled,
|
||||
onClick = { onNavigate(RadioConfigRoutes.CleanNodeDb) },
|
||||
)
|
||||
TitledCard(title = "Advanced", modifier = Modifier.padding(top = 16.dp)) {
|
||||
if (isManaged) {
|
||||
ManagedMessage()
|
||||
}
|
||||
|
||||
SettingsItem(
|
||||
text = stringResource(R.string.clean_node_database_title),
|
||||
enabled = enabled,
|
||||
onClick = { onNavigate(SettingsRoutes.CleanNodeDb) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -362,5 +396,28 @@ fun RadioConfigMenuActions(modifier: Modifier = Modifier, viewModel: UIViewModel
|
|||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun RadioSettingsScreenPreview() = AppTheme {
|
||||
RadioConfigItemList(state = RadioConfigState(isLocal = true, connected = true), onNavigate = { _ -> })
|
||||
RadioConfigItemList(
|
||||
state = RadioConfigState(isLocal = true, connected = true),
|
||||
isManaged = false,
|
||||
onNavigate = { _ -> },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ManagedMessage() {
|
||||
Text(
|
||||
text = stringResource(R.string.message_device_managed),
|
||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
color = MaterialTheme.colorScheme.StatusRed,
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun RadioSettingsScreenManagedPreview() = AppTheme {
|
||||
RadioConfigItemList(
|
||||
state = RadioConfigState(isLocal = true, connected = true),
|
||||
isManaged = true,
|
||||
onNavigate = { _ -> },
|
||||
)
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig
|
||||
package com.geeksville.mesh.ui.settings.radio
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Application
|
||||
|
|
@ -56,7 +56,7 @@ import com.geeksville.mesh.moduleConfig
|
|||
import com.geeksville.mesh.navigation.AdminRoute
|
||||
import com.geeksville.mesh.navigation.ConfigRoute
|
||||
import com.geeksville.mesh.navigation.ModuleRoute
|
||||
import com.geeksville.mesh.navigation.RadioConfigRoutes
|
||||
import com.geeksville.mesh.navigation.SettingsRoutes
|
||||
import com.geeksville.mesh.repository.datastore.RadioConfigRepository
|
||||
import com.geeksville.mesh.repository.location.LocationRepository
|
||||
import com.geeksville.mesh.service.ConnectionState
|
||||
|
|
@ -108,7 +108,7 @@ constructor(
|
|||
private val meshService: IMeshService?
|
||||
get() = radioConfigRepository.meshService
|
||||
|
||||
private val destNum = savedStateHandle.toRoute<RadioConfigRoutes.RadioConfig>().destNum
|
||||
private val destNum = savedStateHandle.toRoute<SettingsRoutes.Settings>().destNum
|
||||
private val _destNode = MutableStateFlow<Node?>(null)
|
||||
val destNode: StateFlow<Node?>
|
||||
get() = _destNode
|
||||
|
|
@ -15,17 +15,18 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig
|
||||
package com.geeksville.mesh.ui.settings.radio
|
||||
|
||||
import com.geeksville.mesh.util.UiText
|
||||
|
||||
/**
|
||||
* Generic sealed class defines each possible state of a response.
|
||||
*/
|
||||
/** Generic sealed class defines each possible state of a response. */
|
||||
sealed class ResponseState<out T> {
|
||||
data object Empty : ResponseState<Nothing>()
|
||||
|
||||
data class Loading(var total: Int = 1, var completed: Int = 0) : ResponseState<Nothing>()
|
||||
|
||||
data class Success<T>(val result: T) : ResponseState<T>()
|
||||
|
||||
data class Error(val error: UiText) : ResponseState<Nothing>()
|
||||
|
||||
fun isWaiting() = this !is Empty
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -40,19 +40,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
fun AmbientLightingConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun AmbientLightingConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
AmbientLightingConfigItemList(
|
||||
|
|
@ -61,7 +56,7 @@ fun AmbientLightingConfigScreen(
|
|||
onSaveClicked = { ambientLightingInput ->
|
||||
val config = moduleConfig { ambientLighting = ambientLightingInput }
|
||||
viewModel.setModuleConfig(config)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -74,9 +69,7 @@ fun AmbientLightingConfigItemList(
|
|||
val focusManager = LocalFocusManager.current
|
||||
var ambientLightingInput by rememberSaveable { mutableStateOf(ambientLightingConfig) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.ambient_lighting_config)) }
|
||||
|
||||
item {
|
||||
|
|
@ -84,9 +77,7 @@ fun AmbientLightingConfigItemList(
|
|||
title = stringResource(R.string.led_state),
|
||||
checked = ambientLightingInput.ledState,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
ambientLightingInput = ambientLightingInput.copy { ledState = it }
|
||||
}
|
||||
onCheckedChange = { ambientLightingInput = ambientLightingInput.copy { ledState = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -97,9 +88,7 @@ fun AmbientLightingConfigItemList(
|
|||
value = ambientLightingInput.current,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
ambientLightingInput = ambientLightingInput.copy { current = it }
|
||||
}
|
||||
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { current = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -109,7 +98,7 @@ fun AmbientLightingConfigItemList(
|
|||
value = ambientLightingInput.red,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { red = it } }
|
||||
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { red = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +108,7 @@ fun AmbientLightingConfigItemList(
|
|||
value = ambientLightingInput.green,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { green = it } }
|
||||
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { green = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +118,7 @@ fun AmbientLightingConfigItemList(
|
|||
value = ambientLightingInput.blue,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { blue = it } }
|
||||
onValueChanged = { ambientLightingInput = ambientLightingInput.copy { blue = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -143,7 +132,7 @@ fun AmbientLightingConfigItemList(
|
|||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(ambientLightingInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -155,6 +144,6 @@ private fun AmbientLightingConfigPreview() {
|
|||
AmbientLightingConfigItemList(
|
||||
ambientLightingConfig = ModuleConfigProtos.ModuleConfig.AmbientLightingConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { },
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -42,19 +42,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
fun AudioConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun AudioConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
AudioConfigItemList(
|
||||
|
|
@ -63,22 +58,17 @@ fun AudioConfigScreen(
|
|||
onSaveClicked = { audioInput ->
|
||||
val config = moduleConfig { audio = audioInput }
|
||||
viewModel.setModuleConfig(config)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun AudioConfigItemList(
|
||||
audioConfig: AudioConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (AudioConfig) -> Unit,
|
||||
) {
|
||||
fun AudioConfigItemList(audioConfig: AudioConfig, enabled: Boolean, onSaveClicked: (AudioConfig) -> Unit) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var audioInput by rememberSaveable { mutableStateOf(audioConfig) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.audio_config)) }
|
||||
|
||||
item {
|
||||
|
|
@ -86,7 +76,7 @@ fun AudioConfigItemList(
|
|||
title = stringResource(R.string.codec_2_enabled),
|
||||
checked = audioInput.codec2Enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { audioInput = audioInput.copy { codec2Enabled = it } }
|
||||
onCheckedChange = { audioInput = audioInput.copy { codec2Enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -97,7 +87,7 @@ fun AudioConfigItemList(
|
|||
value = audioInput.pttPin,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { pttPin = it } }
|
||||
onValueChanged = { audioInput = audioInput.copy { pttPin = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -105,11 +95,12 @@ fun AudioConfigItemList(
|
|||
DropDownPreference(
|
||||
title = stringResource(R.string.codec2_sample_rate),
|
||||
enabled = enabled,
|
||||
items = AudioConfig.Audio_Baud.entries
|
||||
items =
|
||||
AudioConfig.Audio_Baud.entries
|
||||
.filter { it != AudioConfig.Audio_Baud.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = audioInput.bitrate,
|
||||
onItemSelected = { audioInput = audioInput.copy { bitrate = it } }
|
||||
onItemSelected = { audioInput = audioInput.copy { bitrate = it } },
|
||||
)
|
||||
}
|
||||
item { Divider() }
|
||||
|
|
@ -120,7 +111,7 @@ fun AudioConfigItemList(
|
|||
value = audioInput.i2SWs,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { i2SWs = it } }
|
||||
onValueChanged = { audioInput = audioInput.copy { i2SWs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -130,7 +121,7 @@ fun AudioConfigItemList(
|
|||
value = audioInput.i2SSd,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { i2SSd = it } }
|
||||
onValueChanged = { audioInput = audioInput.copy { i2SSd = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +131,7 @@ fun AudioConfigItemList(
|
|||
value = audioInput.i2SDin,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { i2SDin = it } }
|
||||
onValueChanged = { audioInput = audioInput.copy { i2SDin = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -150,7 +141,7 @@ fun AudioConfigItemList(
|
|||
value = audioInput.i2SSck,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { audioInput = audioInput.copy { i2SSck = it } }
|
||||
onValueChanged = { audioInput = audioInput.copy { i2SSck = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -164,7 +155,7 @@ fun AudioConfigItemList(
|
|||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(audioInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -173,9 +164,5 @@ fun AudioConfigItemList(
|
|||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun AudioConfigPreview() {
|
||||
AudioConfigItemList(
|
||||
audioConfig = AudioConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { },
|
||||
)
|
||||
AudioConfigItemList(audioConfig = AudioConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -41,19 +41,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
fun BluetoothConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun BluetoothConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
BluetoothConfigItemList(
|
||||
|
|
@ -62,7 +57,7 @@ fun BluetoothConfigScreen(
|
|||
onSaveClicked = { bluetoothInput ->
|
||||
val config = config { bluetooth = bluetoothInput }
|
||||
viewModel.setConfig(config)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -75,9 +70,7 @@ fun BluetoothConfigItemList(
|
|||
val focusManager = LocalFocusManager.current
|
||||
var bluetoothInput by rememberSaveable { mutableStateOf(bluetoothConfig) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.bluetooth_config)) }
|
||||
|
||||
item {
|
||||
|
|
@ -85,7 +78,7 @@ fun BluetoothConfigItemList(
|
|||
title = stringResource(R.string.bluetooth_enabled),
|
||||
checked = bluetoothInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { bluetoothInput = bluetoothInput.copy { this.enabled = it } }
|
||||
onCheckedChange = { bluetoothInput = bluetoothInput.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -94,11 +87,12 @@ fun BluetoothConfigItemList(
|
|||
DropDownPreference(
|
||||
title = stringResource(R.string.pairing_mode),
|
||||
enabled = enabled,
|
||||
items = BluetoothConfig.PairingMode.entries
|
||||
items =
|
||||
BluetoothConfig.PairingMode.entries
|
||||
.filter { it != BluetoothConfig.PairingMode.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = bluetoothInput.mode,
|
||||
onItemSelected = { bluetoothInput = bluetoothInput.copy { mode = it } }
|
||||
onItemSelected = { bluetoothInput = bluetoothInput.copy { mode = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -113,7 +107,7 @@ fun BluetoothConfigItemList(
|
|||
if (it.toString().length == 6) { // ensure 6 digits
|
||||
bluetoothInput = bluetoothInput.copy { fixedPin = it }
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +121,7 @@ fun BluetoothConfigItemList(
|
|||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(bluetoothInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -136,9 +130,5 @@ fun BluetoothConfigItemList(
|
|||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun BluetoothConfigPreview() {
|
||||
BluetoothConfigItemList(
|
||||
bluetoothConfig = BluetoothConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { },
|
||||
)
|
||||
BluetoothConfigItemList(bluetoothConfig = BluetoothConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -44,19 +44,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
fun CannedMessageConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun CannedMessageConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
CannedMessageConfigItemList(
|
||||
|
|
@ -71,7 +66,7 @@ fun CannedMessageConfigScreen(
|
|||
val config = moduleConfig { cannedMessage = cannedMessageInput }
|
||||
viewModel.setModuleConfig(config)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -86,9 +81,7 @@ fun CannedMessageConfigItemList(
|
|||
var messagesInput by rememberSaveable { mutableStateOf(messages) }
|
||||
var cannedMessageInput by rememberSaveable { mutableStateOf(cannedMessageConfig) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.canned_message_config)) }
|
||||
|
||||
item {
|
||||
|
|
@ -96,9 +89,7 @@ fun CannedMessageConfigItemList(
|
|||
title = stringResource(R.string.canned_message_enabled),
|
||||
checked = cannedMessageInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
cannedMessageInput = cannedMessageInput.copy { this.enabled = it }
|
||||
}
|
||||
onCheckedChange = { cannedMessageInput = cannedMessageInput.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -108,9 +99,7 @@ fun CannedMessageConfigItemList(
|
|||
title = stringResource(R.string.rotary_encoder_1_enabled),
|
||||
checked = cannedMessageInput.rotary1Enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
cannedMessageInput = cannedMessageInput.copy { rotary1Enabled = it }
|
||||
}
|
||||
onCheckedChange = { cannedMessageInput = cannedMessageInput.copy { rotary1Enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -121,9 +110,7 @@ fun CannedMessageConfigItemList(
|
|||
value = cannedMessageInput.inputbrokerPinA,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
cannedMessageInput = cannedMessageInput.copy { inputbrokerPinA = it }
|
||||
}
|
||||
onValueChanged = { cannedMessageInput = cannedMessageInput.copy { inputbrokerPinA = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -133,9 +120,7 @@ fun CannedMessageConfigItemList(
|
|||
value = cannedMessageInput.inputbrokerPinB,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
cannedMessageInput = cannedMessageInput.copy { inputbrokerPinB = it }
|
||||
}
|
||||
onValueChanged = { cannedMessageInput = cannedMessageInput.copy { inputbrokerPinB = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -145,9 +130,7 @@ fun CannedMessageConfigItemList(
|
|||
value = cannedMessageInput.inputbrokerPinPress,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
cannedMessageInput = cannedMessageInput.copy { inputbrokerPinPress = it }
|
||||
}
|
||||
onValueChanged = { cannedMessageInput = cannedMessageInput.copy { inputbrokerPinPress = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -155,13 +138,12 @@ fun CannedMessageConfigItemList(
|
|||
DropDownPreference(
|
||||
title = stringResource(R.string.generate_input_event_on_press),
|
||||
enabled = enabled,
|
||||
items = CannedMessageConfig.InputEventChar.entries
|
||||
items =
|
||||
CannedMessageConfig.InputEventChar.entries
|
||||
.filter { it != CannedMessageConfig.InputEventChar.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = cannedMessageInput.inputbrokerEventPress,
|
||||
onItemSelected = {
|
||||
cannedMessageInput = cannedMessageInput.copy { inputbrokerEventPress = it }
|
||||
}
|
||||
onItemSelected = { cannedMessageInput = cannedMessageInput.copy { inputbrokerEventPress = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -170,13 +152,12 @@ fun CannedMessageConfigItemList(
|
|||
DropDownPreference(
|
||||
title = stringResource(R.string.generate_input_event_on_cw),
|
||||
enabled = enabled,
|
||||
items = CannedMessageConfig.InputEventChar.entries
|
||||
items =
|
||||
CannedMessageConfig.InputEventChar.entries
|
||||
.filter { it != CannedMessageConfig.InputEventChar.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = cannedMessageInput.inputbrokerEventCw,
|
||||
onItemSelected = {
|
||||
cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCw = it }
|
||||
}
|
||||
onItemSelected = { cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCw = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -185,13 +166,12 @@ fun CannedMessageConfigItemList(
|
|||
DropDownPreference(
|
||||
title = stringResource(R.string.generate_input_event_on_ccw),
|
||||
enabled = enabled,
|
||||
items = CannedMessageConfig.InputEventChar.entries
|
||||
items =
|
||||
CannedMessageConfig.InputEventChar.entries
|
||||
.filter { it != CannedMessageConfig.InputEventChar.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = cannedMessageInput.inputbrokerEventCcw,
|
||||
onItemSelected = {
|
||||
cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCcw = it }
|
||||
}
|
||||
onItemSelected = { cannedMessageInput = cannedMessageInput.copy { inputbrokerEventCcw = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -201,9 +181,7 @@ fun CannedMessageConfigItemList(
|
|||
title = stringResource(R.string.up_down_select_input_enabled),
|
||||
checked = cannedMessageInput.updown1Enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
cannedMessageInput = cannedMessageInput.copy { updown1Enabled = it }
|
||||
}
|
||||
onCheckedChange = { cannedMessageInput = cannedMessageInput.copy { updown1Enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -215,13 +193,10 @@ fun CannedMessageConfigItemList(
|
|||
maxSize = 63, // allow_input_source max_size:16
|
||||
enabled = enabled,
|
||||
isError = false,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
cannedMessageInput = cannedMessageInput.copy { allowInputSource = it }
|
||||
}
|
||||
onValueChanged = { cannedMessageInput = cannedMessageInput.copy { allowInputSource = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -230,9 +205,7 @@ fun CannedMessageConfigItemList(
|
|||
title = stringResource(R.string.send_bell),
|
||||
checked = cannedMessageInput.sendBell,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
cannedMessageInput = cannedMessageInput.copy { sendBell = it }
|
||||
}
|
||||
onCheckedChange = { cannedMessageInput = cannedMessageInput.copy { sendBell = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -244,11 +217,10 @@ fun CannedMessageConfigItemList(
|
|||
maxSize = 200, // messages max_size:201
|
||||
enabled = enabled,
|
||||
isError = false,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { messagesInput = it }
|
||||
onValueChanged = { messagesInput = it },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -263,7 +235,7 @@ fun CannedMessageConfigItemList(
|
|||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(messagesInput, cannedMessageInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
|
|
@ -79,7 +79,7 @@ import com.geeksville.mesh.ui.common.components.SecurityIcon
|
|||
import com.geeksville.mesh.ui.common.components.dragContainer
|
||||
import com.geeksville.mesh.ui.common.components.dragDropItemsIndexed
|
||||
import com.geeksville.mesh.ui.common.components.rememberDragDropState
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
private fun ChannelItem(
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -44,19 +44,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
fun DetectionSensorConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun DetectionSensorConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
DetectionSensorConfigItemList(
|
||||
|
|
@ -65,7 +60,7 @@ fun DetectionSensorConfigScreen(
|
|||
onSaveClicked = { detectionSensorInput ->
|
||||
val config = moduleConfig { detectionSensor = detectionSensorInput }
|
||||
viewModel.setModuleConfig(config)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -79,9 +74,7 @@ fun DetectionSensorConfigItemList(
|
|||
val focusManager = LocalFocusManager.current
|
||||
var detectionSensorInput by rememberSaveable { mutableStateOf(detectionSensorConfig) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.detection_sensor_config)) }
|
||||
|
||||
item {
|
||||
|
|
@ -89,9 +82,7 @@ fun DetectionSensorConfigItemList(
|
|||
title = stringResource(R.string.detection_sensor_enabled),
|
||||
checked = detectionSensorInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
detectionSensorInput = detectionSensorInput.copy { this.enabled = it }
|
||||
}
|
||||
onCheckedChange = { detectionSensorInput = detectionSensorInput.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -102,9 +93,7 @@ fun DetectionSensorConfigItemList(
|
|||
value = detectionSensorInput.minimumBroadcastSecs,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
detectionSensorInput = detectionSensorInput.copy { minimumBroadcastSecs = it }
|
||||
}
|
||||
onValueChanged = { detectionSensorInput = detectionSensorInput.copy { minimumBroadcastSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -114,9 +103,7 @@ fun DetectionSensorConfigItemList(
|
|||
value = detectionSensorInput.stateBroadcastSecs,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
detectionSensorInput = detectionSensorInput.copy { stateBroadcastSecs = it }
|
||||
}
|
||||
onValueChanged = { detectionSensorInput = detectionSensorInput.copy { stateBroadcastSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -125,9 +112,7 @@ fun DetectionSensorConfigItemList(
|
|||
title = stringResource(R.string.send_bell_with_alert_message),
|
||||
checked = detectionSensorInput.sendBell,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
detectionSensorInput = detectionSensorInput.copy { sendBell = it }
|
||||
}
|
||||
onCheckedChange = { detectionSensorInput = detectionSensorInput.copy { sendBell = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -139,13 +124,10 @@ fun DetectionSensorConfigItemList(
|
|||
maxSize = 19, // name max_size:20
|
||||
enabled = enabled,
|
||||
isError = false,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
detectionSensorInput = detectionSensorInput.copy { name = it }
|
||||
}
|
||||
onValueChanged = { detectionSensorInput = detectionSensorInput.copy { name = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -155,9 +137,7 @@ fun DetectionSensorConfigItemList(
|
|||
value = detectionSensorInput.monitorPin,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
detectionSensorInput = detectionSensorInput.copy { monitorPin = it }
|
||||
}
|
||||
onValueChanged = { detectionSensorInput = detectionSensorInput.copy { monitorPin = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -165,13 +145,12 @@ fun DetectionSensorConfigItemList(
|
|||
DropDownPreference(
|
||||
title = stringResource(R.string.detection_trigger_type),
|
||||
enabled = enabled,
|
||||
items = ModuleConfig.DetectionSensorConfig.TriggerType.entries
|
||||
items =
|
||||
ModuleConfig.DetectionSensorConfig.TriggerType.entries
|
||||
.filter { it != ModuleConfig.DetectionSensorConfig.TriggerType.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = detectionSensorInput.detectionTriggerType,
|
||||
onItemSelected = {
|
||||
detectionSensorInput = detectionSensorInput.copy { detectionTriggerType = it }
|
||||
}
|
||||
onItemSelected = { detectionSensorInput = detectionSensorInput.copy { detectionTriggerType = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -181,9 +160,7 @@ fun DetectionSensorConfigItemList(
|
|||
title = stringResource(R.string.use_input_pullup_mode),
|
||||
checked = detectionSensorInput.usePullup,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
detectionSensorInput = detectionSensorInput.copy { usePullup = it }
|
||||
}
|
||||
onCheckedChange = { detectionSensorInput = detectionSensorInput.copy { usePullup = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -198,7 +175,7 @@ fun DetectionSensorConfigItemList(
|
|||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(detectionSensorInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -210,6 +187,6 @@ private fun DetectionSensorConfigPreview() {
|
|||
DetectionSensorConfigItemList(
|
||||
detectionSensorConfig = ModuleConfig.DetectionSensorConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { },
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -58,47 +58,44 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
private val DeviceConfig.Role.stringRes: Int
|
||||
get() = when (this) {
|
||||
DeviceConfig.Role.CLIENT -> R.string.role_client
|
||||
DeviceConfig.Role.CLIENT_MUTE -> R.string.role_client_mute
|
||||
DeviceConfig.Role.ROUTER -> R.string.role_router
|
||||
DeviceConfig.Role.ROUTER_CLIENT -> R.string.role_router_client
|
||||
DeviceConfig.Role.REPEATER -> R.string.role_repeater
|
||||
DeviceConfig.Role.TRACKER -> R.string.role_tracker
|
||||
DeviceConfig.Role.SENSOR -> R.string.role_sensor
|
||||
DeviceConfig.Role.TAK -> R.string.role_tak
|
||||
DeviceConfig.Role.CLIENT_HIDDEN -> R.string.role_client_hidden
|
||||
DeviceConfig.Role.LOST_AND_FOUND -> R.string.role_lost_and_found
|
||||
DeviceConfig.Role.TAK_TRACKER -> R.string.role_tak_tracker
|
||||
DeviceConfig.Role.ROUTER_LATE -> R.string.role_router_late
|
||||
else -> R.string.unrecognized
|
||||
}
|
||||
get() =
|
||||
when (this) {
|
||||
DeviceConfig.Role.CLIENT -> R.string.role_client
|
||||
DeviceConfig.Role.CLIENT_MUTE -> R.string.role_client_mute
|
||||
DeviceConfig.Role.ROUTER -> R.string.role_router
|
||||
DeviceConfig.Role.ROUTER_CLIENT -> R.string.role_router_client
|
||||
DeviceConfig.Role.REPEATER -> R.string.role_repeater
|
||||
DeviceConfig.Role.TRACKER -> R.string.role_tracker
|
||||
DeviceConfig.Role.SENSOR -> R.string.role_sensor
|
||||
DeviceConfig.Role.TAK -> R.string.role_tak
|
||||
DeviceConfig.Role.CLIENT_HIDDEN -> R.string.role_client_hidden
|
||||
DeviceConfig.Role.LOST_AND_FOUND -> R.string.role_lost_and_found
|
||||
DeviceConfig.Role.TAK_TRACKER -> R.string.role_tak_tracker
|
||||
DeviceConfig.Role.ROUTER_LATE -> R.string.role_router_late
|
||||
else -> R.string.unrecognized
|
||||
}
|
||||
|
||||
private val DeviceConfig.RebroadcastMode.stringRes: Int
|
||||
get() = when (this) {
|
||||
DeviceConfig.RebroadcastMode.ALL -> R.string.rebroadcast_mode_all
|
||||
DeviceConfig.RebroadcastMode.ALL_SKIP_DECODING -> R.string.rebroadcast_mode_all_skip_decoding
|
||||
DeviceConfig.RebroadcastMode.LOCAL_ONLY -> R.string.rebroadcast_mode_local_only
|
||||
DeviceConfig.RebroadcastMode.KNOWN_ONLY -> R.string.rebroadcast_mode_known_only
|
||||
DeviceConfig.RebroadcastMode.NONE -> R.string.rebroadcast_mode_none
|
||||
DeviceConfig.RebroadcastMode.CORE_PORTNUMS_ONLY -> R.string.rebroadcast_mode_core_portnums_only
|
||||
else -> R.string.unrecognized
|
||||
}
|
||||
get() =
|
||||
when (this) {
|
||||
DeviceConfig.RebroadcastMode.ALL -> R.string.rebroadcast_mode_all
|
||||
DeviceConfig.RebroadcastMode.ALL_SKIP_DECODING -> R.string.rebroadcast_mode_all_skip_decoding
|
||||
DeviceConfig.RebroadcastMode.LOCAL_ONLY -> R.string.rebroadcast_mode_local_only
|
||||
DeviceConfig.RebroadcastMode.KNOWN_ONLY -> R.string.rebroadcast_mode_known_only
|
||||
DeviceConfig.RebroadcastMode.NONE -> R.string.rebroadcast_mode_none
|
||||
DeviceConfig.RebroadcastMode.CORE_PORTNUMS_ONLY -> R.string.rebroadcast_mode_core_portnums_only
|
||||
else -> R.string.unrecognized
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DeviceConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun DeviceConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
DeviceConfigItemList(
|
||||
|
|
@ -107,94 +104,62 @@ fun DeviceConfigScreen(
|
|||
onSaveClicked = { deviceInput ->
|
||||
val config = config { device = deviceInput }
|
||||
viewModel.setConfig(config)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun RouterRoleConfirmationDialog(
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: () -> Unit,
|
||||
) {
|
||||
fun RouterRoleConfirmationDialog(onDismiss: () -> Unit, onConfirm: () -> Unit) {
|
||||
val dialogTitle = stringResource(R.string.are_you_sure)
|
||||
val annotatedDialogText = AnnotatedString.fromHtml(
|
||||
htmlString = stringResource(R.string.router_role_confirmation_text),
|
||||
linkStyles = TextLinkStyles(style = SpanStyle(color = Color.Blue))
|
||||
)
|
||||
val annotatedDialogText =
|
||||
AnnotatedString.fromHtml(
|
||||
htmlString = stringResource(R.string.router_role_confirmation_text),
|
||||
linkStyles = TextLinkStyles(style = SpanStyle(color = Color.Blue)),
|
||||
)
|
||||
|
||||
var confirmed by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
AlertDialog(
|
||||
title = {
|
||||
Text(text = dialogTitle)
|
||||
},
|
||||
title = { Text(text = dialogTitle) },
|
||||
text = {
|
||||
Column {
|
||||
Text(text = annotatedDialogText)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(true) {
|
||||
confirmed = !confirmed
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
modifier = Modifier.fillMaxWidth().clickable(true) { confirmed = !confirmed },
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Checkbox(
|
||||
checked = confirmed,
|
||||
onCheckedChange = { confirmed = it }
|
||||
)
|
||||
Checkbox(checked = confirmed, onCheckedChange = { confirmed = it })
|
||||
Text(stringResource(R.string.i_know_what_i_m_doing))
|
||||
}
|
||||
}
|
||||
},
|
||||
onDismissRequest = onDismiss,
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = onConfirm,
|
||||
enabled = confirmed
|
||||
) {
|
||||
Text(stringResource(R.string.accept))
|
||||
}
|
||||
TextButton(onClick = onConfirm, enabled = confirmed) { Text(stringResource(R.string.accept)) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = onDismiss
|
||||
) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
dismissButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.cancel)) } },
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun DeviceConfigItemList(
|
||||
deviceConfig: DeviceConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (DeviceConfig) -> Unit,
|
||||
) {
|
||||
fun DeviceConfigItemList(deviceConfig: DeviceConfig, enabled: Boolean, onSaveClicked: (DeviceConfig) -> Unit) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var deviceInput by rememberSaveable { mutableStateOf(deviceConfig) }
|
||||
var selectedRole by rememberSaveable { mutableStateOf(deviceInput.role) }
|
||||
val infrastructureRoles = listOf(
|
||||
DeviceConfig.Role.ROUTER,
|
||||
DeviceConfig.Role.REPEATER,
|
||||
)
|
||||
val infrastructureRoles = listOf(DeviceConfig.Role.ROUTER, DeviceConfig.Role.REPEATER)
|
||||
if (selectedRole != deviceInput.role) {
|
||||
if (selectedRole in infrastructureRoles) {
|
||||
RouterRoleConfirmationDialog(
|
||||
onDismiss = { selectedRole = deviceInput.role },
|
||||
onConfirm = {
|
||||
deviceInput = deviceInput.copy { role = selectedRole }
|
||||
}
|
||||
onConfirm = { deviceInput = deviceInput.copy { role = selectedRole } },
|
||||
)
|
||||
} else {
|
||||
deviceInput = deviceInput.copy { role = selectedRole }
|
||||
}
|
||||
}
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.device_config)) }
|
||||
|
||||
item {
|
||||
|
|
@ -202,9 +167,7 @@ fun DeviceConfigItemList(
|
|||
title = stringResource(R.string.role),
|
||||
enabled = enabled,
|
||||
selectedItem = deviceInput.role,
|
||||
onItemSelected = {
|
||||
selectedRole = it
|
||||
},
|
||||
onItemSelected = { selectedRole = it },
|
||||
summary = stringResource(id = deviceInput.role.stringRes),
|
||||
)
|
||||
HorizontalDivider()
|
||||
|
|
@ -216,9 +179,7 @@ fun DeviceConfigItemList(
|
|||
value = deviceInput.buttonGpio,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
deviceInput = deviceInput.copy { buttonGpio = it }
|
||||
}
|
||||
onValueChanged = { deviceInput = deviceInput.copy { buttonGpio = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -228,9 +189,7 @@ fun DeviceConfigItemList(
|
|||
value = deviceInput.buzzerGpio,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
deviceInput = deviceInput.copy { buzzerGpio = it }
|
||||
}
|
||||
onValueChanged = { deviceInput = deviceInput.copy { buzzerGpio = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -251,9 +210,7 @@ fun DeviceConfigItemList(
|
|||
value = deviceInput.nodeInfoBroadcastSecs,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
deviceInput = deviceInput.copy { nodeInfoBroadcastSecs = it }
|
||||
}
|
||||
onValueChanged = { deviceInput = deviceInput.copy { nodeInfoBroadcastSecs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -263,7 +220,7 @@ fun DeviceConfigItemList(
|
|||
summary = stringResource(id = R.string.config_device_doubleTapAsButtonPress_summary),
|
||||
checked = deviceInput.doubleTapAsButtonPress,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { doubleTapAsButtonPress = it } }
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { doubleTapAsButtonPress = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
|
@ -274,7 +231,7 @@ fun DeviceConfigItemList(
|
|||
summary = stringResource(id = R.string.config_device_disableTripleClick_summary),
|
||||
checked = deviceInput.disableTripleClick,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { disableTripleClick = it } }
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { disableTripleClick = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
|
@ -286,13 +243,10 @@ fun DeviceConfigItemList(
|
|||
maxSize = 64, // tzdef max_size:65
|
||||
enabled = enabled,
|
||||
isError = false,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
deviceInput = deviceInput.copy { tzdef = it }
|
||||
},
|
||||
onValueChanged = { deviceInput = deviceInput.copy { tzdef = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -302,7 +256,7 @@ fun DeviceConfigItemList(
|
|||
summary = stringResource(id = R.string.config_device_ledHeartbeatDisabled_summary),
|
||||
checked = deviceInput.ledHeartbeatDisabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { ledHeartbeatDisabled = it } }
|
||||
onCheckedChange = { deviceInput = deviceInput.copy { ledHeartbeatDisabled = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
|
@ -317,7 +271,7 @@ fun DeviceConfigItemList(
|
|||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(deviceInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -326,9 +280,5 @@ fun DeviceConfigItemList(
|
|||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun DeviceConfigPreview() {
|
||||
DeviceConfigItemList(
|
||||
deviceConfig = DeviceConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { },
|
||||
)
|
||||
DeviceConfigItemList(deviceConfig = DeviceConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -41,7 +41,7 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
fun DisplayConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -45,7 +45,7 @@ import com.geeksville.mesh.R
|
|||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.google.protobuf.Descriptors
|
||||
|
||||
private const val SupportedFields = 7
|
||||
private const val SUPPORTED_FIELDS = 7
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
|
|
@ -58,10 +58,13 @@ fun EditDeviceProfileDialog(
|
|||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val state = remember {
|
||||
val fields = deviceProfile.descriptorForType.fields
|
||||
.filter { it.number < SupportedFields } // TODO add ringtone & canned messages
|
||||
mutableStateMapOf<Descriptors.FieldDescriptor, Boolean>()
|
||||
.apply { putAll(fields.associateWith(deviceProfile::hasField)) }
|
||||
val fields =
|
||||
deviceProfile.descriptorForType.fields.filter {
|
||||
it.number < SUPPORTED_FIELDS
|
||||
} // TODO add ringtone & canned messages
|
||||
mutableStateMapOf<Descriptors.FieldDescriptor, Boolean>().apply {
|
||||
putAll(fields.associateWith(deviceProfile::hasField))
|
||||
}
|
||||
}
|
||||
|
||||
AlertDialog(
|
||||
|
|
@ -71,38 +74,36 @@ fun EditDeviceProfileDialog(
|
|||
Column(modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.titleLarge.copy(
|
||||
style =
|
||||
MaterialTheme.typography.titleLarge.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.Center,
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = 16.dp),
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp),
|
||||
)
|
||||
HorizontalDivider()
|
||||
state.keys.sortedBy { it.number }.forEach { field ->
|
||||
SwitchPreference(
|
||||
title = field.name,
|
||||
checked = state[field] == true,
|
||||
enabled = deviceProfile.hasField(field),
|
||||
onCheckedChange = { state[field] = it },
|
||||
padding = PaddingValues(0.dp)
|
||||
)
|
||||
}
|
||||
state.keys
|
||||
.sortedBy { it.number }
|
||||
.forEach { field ->
|
||||
SwitchPreference(
|
||||
title = field.name,
|
||||
checked = state[field] == true,
|
||||
enabled = deviceProfile.hasField(field),
|
||||
onCheckedChange = { state[field] = it },
|
||||
padding = PaddingValues(0.dp),
|
||||
)
|
||||
}
|
||||
HorizontalDivider()
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
FlowRow(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 24.dp, end = 24.dp, bottom = 16.dp),
|
||||
modifier = modifier.fillMaxWidth().padding(start = 24.dp, end = 24.dp, bottom = 16.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
TextButton(
|
||||
modifier = modifier.weight(1f),
|
||||
onClick = onDismiss
|
||||
) { Text(stringResource(R.string.cancel)) }
|
||||
TextButton(modifier = modifier.weight(1f), onClick = onDismiss) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
Button(
|
||||
modifier = modifier.weight(1f),
|
||||
onClick = {
|
||||
|
|
@ -115,9 +116,11 @@ fun EditDeviceProfileDialog(
|
|||
onConfirm(builder.build())
|
||||
},
|
||||
enabled = state.values.any { it },
|
||||
) { Text(stringResource(R.string.save)) }
|
||||
) {
|
||||
Text(stringResource(R.string.save))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -44,19 +44,14 @@ import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.common.components.TextDividerPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
fun ExternalNotificationConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun ExternalNotificationConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
ExternalNotificationConfigItemList(
|
||||
|
|
@ -71,7 +66,7 @@ fun ExternalNotificationConfigScreen(
|
|||
val config = moduleConfig { externalNotification = extNotificationInput }
|
||||
viewModel.setModuleConfig(config)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -86,9 +81,7 @@ fun ExternalNotificationConfigItemList(
|
|||
var ringtoneInput by rememberSaveable { mutableStateOf(ringtone) }
|
||||
var externalNotificationInput by rememberSaveable { mutableStateOf(extNotificationConfig) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.external_notification_config)) }
|
||||
|
||||
item {
|
||||
|
|
@ -96,9 +89,7 @@ fun ExternalNotificationConfigItemList(
|
|||
title = stringResource(R.string.external_notification_enabled),
|
||||
checked = externalNotificationInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { this.enabled = it }
|
||||
}
|
||||
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -109,9 +100,7 @@ fun ExternalNotificationConfigItemList(
|
|||
title = stringResource(R.string.alert_message_led),
|
||||
checked = externalNotificationInput.alertMessage,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { alertMessage = it }
|
||||
}
|
||||
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { alertMessage = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -122,9 +111,8 @@ fun ExternalNotificationConfigItemList(
|
|||
checked = externalNotificationInput.alertMessageBuzzer,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput =
|
||||
externalNotificationInput.copy { alertMessageBuzzer = it }
|
||||
}
|
||||
externalNotificationInput = externalNotificationInput.copy { alertMessageBuzzer = it }
|
||||
},
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -135,9 +123,8 @@ fun ExternalNotificationConfigItemList(
|
|||
checked = externalNotificationInput.alertMessageVibra,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput =
|
||||
externalNotificationInput.copy { alertMessageVibra = it }
|
||||
}
|
||||
externalNotificationInput = externalNotificationInput.copy { alertMessageVibra = it }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -148,9 +135,7 @@ fun ExternalNotificationConfigItemList(
|
|||
title = stringResource(R.string.alert_bell_led),
|
||||
checked = externalNotificationInput.alertBell,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { alertBell = it }
|
||||
}
|
||||
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { alertBell = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -161,9 +146,8 @@ fun ExternalNotificationConfigItemList(
|
|||
checked = externalNotificationInput.alertBellBuzzer,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput =
|
||||
externalNotificationInput.copy { alertBellBuzzer = it }
|
||||
}
|
||||
externalNotificationInput = externalNotificationInput.copy { alertBellBuzzer = it }
|
||||
},
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -174,9 +158,8 @@ fun ExternalNotificationConfigItemList(
|
|||
checked = externalNotificationInput.alertBellVibra,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput =
|
||||
externalNotificationInput.copy { alertBellVibra = it }
|
||||
}
|
||||
externalNotificationInput = externalNotificationInput.copy { alertBellVibra = it }
|
||||
},
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -187,23 +170,19 @@ fun ExternalNotificationConfigItemList(
|
|||
value = externalNotificationInput.output,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
externalNotificationInput = externalNotificationInput.copy { output = it }
|
||||
}
|
||||
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { output = it } },
|
||||
)
|
||||
}
|
||||
|
||||
if (externalNotificationInput.output != 0) {
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.output_led_active_high),
|
||||
checked = externalNotificationInput.active,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { active = it }
|
||||
}
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.output_led_active_high),
|
||||
checked = externalNotificationInput.active,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { active = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
|
|
@ -213,23 +192,19 @@ fun ExternalNotificationConfigItemList(
|
|||
value = externalNotificationInput.outputBuzzer,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
externalNotificationInput = externalNotificationInput.copy { outputBuzzer = it }
|
||||
}
|
||||
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { outputBuzzer = it } },
|
||||
)
|
||||
}
|
||||
|
||||
if (externalNotificationInput.outputBuzzer != 0) {
|
||||
item {
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.use_pwm_buzzer),
|
||||
checked = externalNotificationInput.usePwm,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { usePwm = it }
|
||||
}
|
||||
)
|
||||
}
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.use_pwm_buzzer),
|
||||
checked = externalNotificationInput.usePwm,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { externalNotificationInput = externalNotificationInput.copy { usePwm = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
||||
|
|
@ -239,9 +214,7 @@ fun ExternalNotificationConfigItemList(
|
|||
value = externalNotificationInput.outputVibra,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
externalNotificationInput = externalNotificationInput.copy { outputVibra = it }
|
||||
}
|
||||
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { outputVibra = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -251,9 +224,7 @@ fun ExternalNotificationConfigItemList(
|
|||
value = externalNotificationInput.outputMs,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
externalNotificationInput = externalNotificationInput.copy { outputMs = it }
|
||||
}
|
||||
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { outputMs = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -263,9 +234,7 @@ fun ExternalNotificationConfigItemList(
|
|||
value = externalNotificationInput.nagTimeout,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
externalNotificationInput = externalNotificationInput.copy { nagTimeout = it }
|
||||
}
|
||||
onValueChanged = { externalNotificationInput = externalNotificationInput.copy { nagTimeout = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -276,11 +245,10 @@ fun ExternalNotificationConfigItemList(
|
|||
maxSize = 230, // ringtone max_size:231
|
||||
enabled = enabled,
|
||||
isError = false,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { ringtoneInput = it }
|
||||
onValueChanged = { ringtoneInput = it },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -291,7 +259,7 @@ fun ExternalNotificationConfigItemList(
|
|||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
externalNotificationInput = externalNotificationInput.copy { useI2SAsBuzzer = it }
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -307,7 +275,7 @@ fun ExternalNotificationConfigItemList(
|
|||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(ringtoneInput, externalNotificationInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -48,19 +48,14 @@ import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SignedIntegerEditTextPreference
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
fun LoRaConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun LoRaConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
LoRaConfigItemList(
|
||||
|
|
@ -86,13 +81,9 @@ fun LoRaConfigItemList(
|
|||
) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var loraInput by rememberSaveable { mutableStateOf(loraConfig) }
|
||||
val primaryChannel by remember(loraInput) {
|
||||
mutableStateOf(Channel(primarySettings, loraInput))
|
||||
}
|
||||
val primaryChannel by remember(loraInput) { mutableStateOf(Channel(primarySettings, loraInput)) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.lora_config)) }
|
||||
|
||||
item {
|
||||
|
|
@ -100,7 +91,7 @@ fun LoRaConfigItemList(
|
|||
title = stringResource(R.string.use_modem_preset),
|
||||
checked = loraInput.usePreset,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { usePreset = it } }
|
||||
onCheckedChange = { loraInput = loraInput.copy { usePreset = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -110,11 +101,12 @@ fun LoRaConfigItemList(
|
|||
DropDownPreference(
|
||||
title = stringResource(R.string.modem_preset),
|
||||
enabled = enabled && loraInput.usePreset,
|
||||
items = LoRaConfig.ModemPreset.entries
|
||||
items =
|
||||
LoRaConfig.ModemPreset.entries
|
||||
.filter { it != LoRaConfig.ModemPreset.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = loraInput.modemPreset,
|
||||
onItemSelected = { loraInput = loraInput.copy { modemPreset = it } }
|
||||
onItemSelected = { loraInput = loraInput.copy { modemPreset = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -125,7 +117,7 @@ fun LoRaConfigItemList(
|
|||
value = loraInput.bandwidth,
|
||||
enabled = enabled && !loraInput.usePreset,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { bandwidth = it } }
|
||||
onValueChanged = { loraInput = loraInput.copy { bandwidth = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +127,7 @@ fun LoRaConfigItemList(
|
|||
value = loraInput.spreadFactor,
|
||||
enabled = enabled && !loraInput.usePreset,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { spreadFactor = it } }
|
||||
onValueChanged = { loraInput = loraInput.copy { spreadFactor = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -145,7 +137,7 @@ fun LoRaConfigItemList(
|
|||
value = loraInput.codingRate,
|
||||
enabled = enabled && !loraInput.usePreset,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { codingRate = it } }
|
||||
onValueChanged = { loraInput = loraInput.copy { codingRate = it } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -156,7 +148,7 @@ fun LoRaConfigItemList(
|
|||
value = loraInput.frequencyOffset,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { frequencyOffset = it } }
|
||||
onValueChanged = { loraInput = loraInput.copy { frequencyOffset = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -166,7 +158,7 @@ fun LoRaConfigItemList(
|
|||
enabled = enabled,
|
||||
items = RegionInfo.entries.map { it.regionCode to it.description },
|
||||
selectedItem = loraInput.region,
|
||||
onItemSelected = { loraInput = loraInput.copy { region = it } }
|
||||
onItemSelected = { loraInput = loraInput.copy { region = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -177,7 +169,7 @@ fun LoRaConfigItemList(
|
|||
value = loraInput.hopLimit,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { loraInput = loraInput.copy { hopLimit = it } }
|
||||
onValueChanged = { loraInput = loraInput.copy { hopLimit = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -186,7 +178,7 @@ fun LoRaConfigItemList(
|
|||
title = stringResource(R.string.tx_enabled),
|
||||
checked = loraInput.txEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { txEnabled = it } }
|
||||
onCheckedChange = { loraInput = loraInput.copy { txEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -213,7 +205,7 @@ fun LoRaConfigItemList(
|
|||
if (it <= loraInput.numChannels) { // total num of LoRa channels
|
||||
loraInput = loraInput.copy { channelNum = it }
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -222,7 +214,7 @@ fun LoRaConfigItemList(
|
|||
title = stringResource(R.string.override_duty_cycle),
|
||||
checked = loraInput.overrideDutyCycle,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { overrideDutyCycle = it } }
|
||||
onCheckedChange = { loraInput = loraInput.copy { overrideDutyCycle = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -235,11 +227,12 @@ fun LoRaConfigItemList(
|
|||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValuesChanged = { list ->
|
||||
loraInput = loraInput.copy {
|
||||
ignoreIncoming.clear()
|
||||
ignoreIncoming.addAll(list.filter { it != 0 })
|
||||
}
|
||||
}
|
||||
loraInput =
|
||||
loraInput.copy {
|
||||
ignoreIncoming.clear()
|
||||
ignoreIncoming.addAll(list.filter { it != 0 })
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -248,7 +241,7 @@ fun LoRaConfigItemList(
|
|||
title = stringResource(R.string.sx126x_rx_boosted_gain),
|
||||
checked = loraInput.sx126XRxBoostedGain,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { sx126XRxBoostedGain = it } }
|
||||
onCheckedChange = { loraInput = loraInput.copy { sx126XRxBoostedGain = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -257,11 +250,16 @@ fun LoRaConfigItemList(
|
|||
var isFocused by remember { mutableStateOf(false) }
|
||||
EditTextPreference(
|
||||
title = stringResource(R.string.override_frequency_mhz),
|
||||
value = if (isFocused || loraInput.overrideFrequency != 0f) loraInput.overrideFrequency else primaryChannel.radioFreq,
|
||||
value =
|
||||
if (isFocused || loraInput.overrideFrequency != 0f) {
|
||||
loraInput.overrideFrequency
|
||||
} else {
|
||||
primaryChannel.radioFreq
|
||||
},
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onFocusChanged = { isFocused = it.isFocused },
|
||||
onValueChanged = { loraInput = loraInput.copy { overrideFrequency = it } }
|
||||
onValueChanged = { loraInput = loraInput.copy { overrideFrequency = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -271,7 +269,7 @@ fun LoRaConfigItemList(
|
|||
title = stringResource(R.string.pa_fan_disabled),
|
||||
checked = loraInput.paFanDisabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { paFanDisabled = it } }
|
||||
onCheckedChange = { loraInput = loraInput.copy { paFanDisabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -282,7 +280,7 @@ fun LoRaConfigItemList(
|
|||
title = stringResource(R.string.ignore_mqtt),
|
||||
checked = loraInput.ignoreMqtt,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { ignoreMqtt = it } }
|
||||
onCheckedChange = { loraInput = loraInput.copy { ignoreMqtt = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -292,7 +290,7 @@ fun LoRaConfigItemList(
|
|||
title = stringResource(R.string.ok_to_mqtt),
|
||||
checked = loraInput.configOkToMqtt,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { loraInput = loraInput.copy { configOkToMqtt = it } }
|
||||
onCheckedChange = { loraInput = loraInput.copy { configOkToMqtt = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -307,7 +305,7 @@ fun LoRaConfigItemList(
|
|||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(loraInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -320,6 +318,6 @@ private fun LoRaConfigPreview() {
|
|||
loraConfig = Channel.default.loraConfig,
|
||||
primarySettings = Channel.default.settings,
|
||||
enabled = true,
|
||||
onSaveClicked = { },
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
@file:Suppress("LongMethod")
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -46,7 +46,7 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
fun MQTTConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Column
|
||||
|
|
@ -48,8 +48,9 @@ import com.geeksville.mesh.util.DistanceUnit
|
|||
import com.geeksville.mesh.util.toDistanceString
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
private const val PositionPrecisionMin = 12
|
||||
private const val PositionPrecisionMax = 15
|
||||
private const val POSITION_PRECISION_MIN = 12
|
||||
private const val POSITION_PRECISION_MAX = 15
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun MapReportingPreference(
|
||||
|
|
@ -62,13 +63,11 @@ fun MapReportingPreference(
|
|||
publishIntervalSecs: Int = 3600,
|
||||
onPublishIntervalSecsChanged: (Int) -> Unit = {},
|
||||
enabled: Boolean,
|
||||
focusManager: FocusManager
|
||||
focusManager: FocusManager,
|
||||
) {
|
||||
Column {
|
||||
var showMapReportingWarning by rememberSaveable { mutableStateOf(mapReportingEnabled) }
|
||||
LaunchedEffect(mapReportingEnabled) {
|
||||
showMapReportingWarning = mapReportingEnabled
|
||||
}
|
||||
LaunchedEffect(mapReportingEnabled) { showMapReportingWarning = mapReportingEnabled }
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.map_reporting),
|
||||
summary = stringResource(R.string.map_reporting_summary),
|
||||
|
|
@ -81,21 +80,13 @@ fun MapReportingPreference(
|
|||
} else if (!checked) {
|
||||
onMapReportingEnabledChanged(false)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
AnimatedVisibility(showMapReportingWarning) {
|
||||
Card(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.map_reporting_consent_header),
|
||||
modifier = Modifier.padding(16.dp),
|
||||
)
|
||||
Card(modifier = Modifier.padding(16.dp)) {
|
||||
Text(text = stringResource(R.string.map_reporting_consent_header), modifier = Modifier.padding(16.dp))
|
||||
HorizontalDivider()
|
||||
Text(
|
||||
stringResource(R.string.map_reporting_consent_text),
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
Text(stringResource(R.string.map_reporting_consent_text), modifier = Modifier.padding(16.dp))
|
||||
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.i_agree),
|
||||
|
|
@ -118,18 +109,14 @@ fun MapReportingPreference(
|
|||
value = positionPrecision.toFloat(),
|
||||
onValueChange = { onPositionPrecisionChanged(it.roundToInt()) },
|
||||
enabled = enabled,
|
||||
valueRange = PositionPrecisionMin.toFloat()..PositionPrecisionMax.toFloat(),
|
||||
steps = PositionPrecisionMax - PositionPrecisionMin - 1,
|
||||
valueRange = POSITION_PRECISION_MIN.toFloat()..POSITION_PRECISION_MAX.toFloat(),
|
||||
steps = POSITION_PRECISION_MAX - POSITION_PRECISION_MIN - 1,
|
||||
)
|
||||
val precisionMeters = precisionBitsToMeters(positionPrecision).toInt()
|
||||
val unit = DistanceUnit.Companion.getFromLocale()
|
||||
Text(
|
||||
text = precisionMeters.toDistanceString(unit),
|
||||
modifier = Modifier.Companion.padding(
|
||||
start = 16.dp,
|
||||
end = 16.dp,
|
||||
bottom = 16.dp
|
||||
),
|
||||
modifier = Modifier.Companion.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
||||
fontSize = MaterialTheme.typography.bodyLarge.fontSize,
|
||||
overflow = TextOverflow.Companion.Ellipsis,
|
||||
maxLines = 1,
|
||||
|
|
@ -161,6 +148,6 @@ fun MapReportingPreview() {
|
|||
positionPrecision = 5,
|
||||
onPositionPrecisionChanged = {},
|
||||
enabled = true,
|
||||
focusManager = focusManager
|
||||
focusManager = focusManager,
|
||||
)
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -40,19 +40,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
fun NeighborInfoConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun NeighborInfoConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
NeighborInfoConfigItemList(
|
||||
|
|
@ -61,7 +56,7 @@ fun NeighborInfoConfigScreen(
|
|||
onSaveClicked = { neighborInfoInput ->
|
||||
val config = moduleConfig { neighborInfo = neighborInfoInput }
|
||||
viewModel.setModuleConfig(config)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -74,9 +69,7 @@ fun NeighborInfoConfigItemList(
|
|||
val focusManager = LocalFocusManager.current
|
||||
var neighborInfoInput by rememberSaveable { mutableStateOf(neighborInfoConfig) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.neighbor_info_config)) }
|
||||
|
||||
item {
|
||||
|
|
@ -84,9 +77,7 @@ fun NeighborInfoConfigItemList(
|
|||
title = stringResource(R.string.neighbor_info_enabled),
|
||||
checked = neighborInfoInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
neighborInfoInput = neighborInfoInput.copy { this.enabled = it }
|
||||
}
|
||||
onCheckedChange = { neighborInfoInput = neighborInfoInput.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -97,9 +88,7 @@ fun NeighborInfoConfigItemList(
|
|||
value = neighborInfoInput.updateInterval,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
neighborInfoInput = neighborInfoInput.copy { updateInterval = it }
|
||||
}
|
||||
onValueChanged = { neighborInfoInput = neighborInfoInput.copy { updateInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -109,9 +98,7 @@ fun NeighborInfoConfigItemList(
|
|||
summary = stringResource(id = R.string.config_device_transmitOverLora_summary),
|
||||
checked = neighborInfoInput.transmitOverLora,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
neighborInfoInput = neighborInfoInput.copy { transmitOverLora = it }
|
||||
}
|
||||
onCheckedChange = { neighborInfoInput = neighborInfoInput.copy { transmitOverLora = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
|
@ -126,7 +113,7 @@ fun NeighborInfoConfigItemList(
|
|||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(neighborInfoInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -138,6 +125,6 @@ private fun NeighborInfoConfigPreview() {
|
|||
NeighborInfoConfigItemList(
|
||||
neighborInfoConfig = ModuleConfigProtos.ModuleConfig.NeighborInfoConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { },
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
|
|
@ -54,30 +54,20 @@ import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SimpleAlertDialog
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import com.journeyapps.barcodescanner.ScanContract
|
||||
import com.journeyapps.barcodescanner.ScanOptions
|
||||
|
||||
@Composable
|
||||
private fun ScanErrorDialog(
|
||||
onDismiss: () -> Unit = {}
|
||||
) = SimpleAlertDialog(
|
||||
title = R.string.error,
|
||||
text = R.string.wifi_qr_code_error,
|
||||
onDismiss = onDismiss,
|
||||
)
|
||||
private fun ScanErrorDialog(onDismiss: () -> Unit = {}) =
|
||||
SimpleAlertDialog(title = R.string.error, text = R.string.wifi_qr_code_error, onDismiss = onDismiss)
|
||||
|
||||
@Composable
|
||||
fun NetworkConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun NetworkConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
NetworkConfigItemList(
|
||||
|
|
@ -88,13 +78,13 @@ fun NetworkConfigScreen(
|
|||
onSaveClicked = { networkInput ->
|
||||
val config = config { network = networkInput }
|
||||
viewModel.setConfig(config)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun extractWifiCredentials(qrCode: String) = Regex("""WIFI:S:(.*?);.*?P:(.*?);""")
|
||||
.find(qrCode)?.destructured
|
||||
?.let { (ssid, password) -> ssid to password } ?: (null to null)
|
||||
private fun extractWifiCredentials(qrCode: String) =
|
||||
Regex("""WIFI:S:(.*?);.*?P:(.*?);""").find(qrCode)?.destructured?.let { (ssid, password) -> ssid to password }
|
||||
?: (null to null)
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
|
|
@ -113,33 +103,34 @@ fun NetworkConfigItemList(
|
|||
ScanErrorDialog { showScanErrorDialog = false }
|
||||
}
|
||||
|
||||
val barcodeLauncher = rememberLauncherForActivityResult(ScanContract()) { result ->
|
||||
if (result.contents != null) {
|
||||
val (ssid, psk) = extractWifiCredentials(result.contents)
|
||||
if (ssid != null && psk != null) {
|
||||
networkInput = networkInput.copy {
|
||||
wifiSsid = ssid
|
||||
wifiPsk = psk
|
||||
val barcodeLauncher =
|
||||
rememberLauncherForActivityResult(ScanContract()) { result ->
|
||||
if (result.contents != null) {
|
||||
val (ssid, psk) = extractWifiCredentials(result.contents)
|
||||
if (ssid != null && psk != null) {
|
||||
networkInput =
|
||||
networkInput.copy {
|
||||
wifiSsid = ssid
|
||||
wifiPsk = psk
|
||||
}
|
||||
} else {
|
||||
showScanErrorDialog = true
|
||||
}
|
||||
} else {
|
||||
showScanErrorDialog = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun zxingScan() {
|
||||
val zxingScan = ScanOptions().apply {
|
||||
setCameraId(0)
|
||||
setPrompt("")
|
||||
setBeepEnabled(false)
|
||||
setDesiredBarcodeFormats(ScanOptions.QR_CODE)
|
||||
}
|
||||
val zxingScan =
|
||||
ScanOptions().apply {
|
||||
setCameraId(0)
|
||||
setPrompt("")
|
||||
setBeepEnabled(false)
|
||||
setDesiredBarcodeFormats(ScanOptions.QR_CODE)
|
||||
}
|
||||
barcodeLauncher.launch(zxingScan)
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.network_config)) }
|
||||
|
||||
item {
|
||||
|
|
@ -147,7 +138,7 @@ fun NetworkConfigItemList(
|
|||
title = stringResource(R.string.wifi_enabled),
|
||||
checked = networkInput.wifiEnabled,
|
||||
enabled = enabled && hasWifi,
|
||||
onCheckedChange = { networkInput = networkInput.copy { wifiEnabled = it } }
|
||||
onCheckedChange = { networkInput = networkInput.copy { wifiEnabled = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
|
@ -159,13 +150,10 @@ fun NetworkConfigItemList(
|
|||
maxSize = 32, // wifi_ssid max_size:33
|
||||
enabled = enabled && hasWifi,
|
||||
isError = false,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
networkInput = networkInput.copy { wifiSsid = it }
|
||||
}
|
||||
onValueChanged = { networkInput = networkInput.copy { wifiSsid = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -176,17 +164,14 @@ fun NetworkConfigItemList(
|
|||
maxSize = 64, // wifi_psk max_size:65
|
||||
enabled = enabled && hasWifi,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { networkInput = networkInput.copy { wifiPsk = it } }
|
||||
onValueChanged = { networkInput = networkInput.copy { wifiPsk = it } },
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Button(
|
||||
onClick = { zxingScan() },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp)
|
||||
.height(48.dp),
|
||||
modifier = Modifier.fillMaxWidth().padding(vertical = 8.dp).height(48.dp),
|
||||
enabled = enabled && hasWifi,
|
||||
) {
|
||||
Text(text = stringResource(R.string.wifi_qr_code_scan))
|
||||
|
|
@ -198,7 +183,7 @@ fun NetworkConfigItemList(
|
|||
title = stringResource(R.string.ethernet_enabled),
|
||||
checked = networkInput.ethEnabled,
|
||||
enabled = enabled && hasEthernet,
|
||||
onCheckedChange = { networkInput = networkInput.copy { ethEnabled = it } }
|
||||
onCheckedChange = { networkInput = networkInput.copy { ethEnabled = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
|
@ -210,13 +195,10 @@ fun NetworkConfigItemList(
|
|||
maxSize = 32, // ntp_server max_size:33
|
||||
enabled = enabled,
|
||||
isError = networkInput.ntpServer.isEmpty(),
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
networkInput = networkInput.copy { ntpServer = it }
|
||||
}
|
||||
onValueChanged = { networkInput = networkInput.copy { ntpServer = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -227,13 +209,10 @@ fun NetworkConfigItemList(
|
|||
maxSize = 32, // rsyslog_server max_size:33
|
||||
enabled = enabled,
|
||||
isError = false,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Uri, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
networkInput = networkInput.copy { rsyslogServer = it }
|
||||
}
|
||||
onValueChanged = { networkInput = networkInput.copy { rsyslogServer = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -241,11 +220,12 @@ fun NetworkConfigItemList(
|
|||
DropDownPreference(
|
||||
title = stringResource(R.string.ipv4_mode),
|
||||
enabled = enabled,
|
||||
items = NetworkConfig.AddressMode.entries
|
||||
items =
|
||||
NetworkConfig.AddressMode.entries
|
||||
.filter { it != NetworkConfig.AddressMode.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = networkInput.addressMode,
|
||||
onItemSelected = { networkInput = networkInput.copy { addressMode = it } }
|
||||
onItemSelected = { networkInput = networkInput.copy { addressMode = it } },
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
|
|
@ -259,7 +239,7 @@ fun NetworkConfigItemList(
|
|||
onValueChanged = {
|
||||
val ipv4 = networkInput.ipv4Config.copy { ip = it }
|
||||
networkInput = networkInput.copy { ipv4Config = ipv4 }
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -272,7 +252,7 @@ fun NetworkConfigItemList(
|
|||
onValueChanged = {
|
||||
val ipv4 = networkInput.ipv4Config.copy { gateway = it }
|
||||
networkInput = networkInput.copy { ipv4Config = ipv4 }
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -285,7 +265,7 @@ fun NetworkConfigItemList(
|
|||
onValueChanged = {
|
||||
val ipv4 = networkInput.ipv4Config.copy { subnet = it }
|
||||
networkInput = networkInput.copy { ipv4Config = ipv4 }
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -298,14 +278,12 @@ fun NetworkConfigItemList(
|
|||
onValueChanged = {
|
||||
val ipv4 = networkInput.ipv4Config.copy { dns = it }
|
||||
networkInput = networkInput.copy { ipv4Config = ipv4 }
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
if (hasEthernet || hasWifi) {
|
||||
item {
|
||||
PreferenceCategory(text = stringResource(R.string.udp_config))
|
||||
}
|
||||
item { PreferenceCategory(text = stringResource(R.string.udp_config)) }
|
||||
|
||||
item {
|
||||
SwitchPreference(
|
||||
|
|
@ -313,11 +291,8 @@ fun NetworkConfigItemList(
|
|||
checked = networkInput.enabledProtocols == 1,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
networkInput =
|
||||
networkInput.copy {
|
||||
if (it) enabledProtocols = 1 else enabledProtocols = 0
|
||||
}
|
||||
}
|
||||
networkInput = networkInput.copy { if (it) enabledProtocols = 1 else enabledProtocols = 0 }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -333,7 +308,7 @@ fun NetworkConfigItemList(
|
|||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(networkInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -347,7 +322,7 @@ private fun NetworkConfigPreview() {
|
|||
hasEthernet = true,
|
||||
networkConfig = NetworkConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { },
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
|
|
@ -37,35 +37,24 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.ui.radioconfig.ResponseState
|
||||
import com.geeksville.mesh.ui.settings.radio.ResponseState
|
||||
|
||||
@Composable
|
||||
fun <T> PacketResponseStateDialog(
|
||||
state: ResponseState<T>,
|
||||
onDismiss: () -> Unit = {},
|
||||
onComplete: () -> Unit = {},
|
||||
) {
|
||||
fun <T> PacketResponseStateDialog(state: ResponseState<T>, onDismiss: () -> Unit = {}, onComplete: () -> Unit = {}) {
|
||||
val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
|
||||
AlertDialog(
|
||||
onDismissRequest = {},
|
||||
shape = RoundedCornerShape(16.dp),
|
||||
title = {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
if (state is ResponseState.Loading) {
|
||||
val progress by animateFloatAsState(
|
||||
targetValue = state.completed.toFloat() / state.total.toFloat(),
|
||||
label = "progress",
|
||||
)
|
||||
val progress by
|
||||
animateFloatAsState(
|
||||
targetValue = state.completed.toFloat() / state.total.toFloat(),
|
||||
label = "progress",
|
||||
)
|
||||
Text("%.0f%%".format(progress * 100))
|
||||
LinearProgressIndicator(
|
||||
progress = progress,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 8.dp),
|
||||
)
|
||||
LinearProgressIndicator(progress = progress, modifier = Modifier.fillMaxWidth().padding(top = 8.dp))
|
||||
if (state.total == state.completed) onComplete()
|
||||
}
|
||||
if (state is ResponseState.Success) {
|
||||
|
|
@ -79,10 +68,8 @@ fun <T> PacketResponseStateDialog(
|
|||
},
|
||||
confirmButton = {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 24.dp, end = 24.dp, bottom = 16.dp),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
modifier = Modifier.fillMaxWidth().padding(start = 24.dp, end = 24.dp, bottom = 16.dp),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
|
|
@ -91,20 +78,17 @@ fun <T> PacketResponseStateDialog(
|
|||
backDispatcher?.onBackPressed()
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(top = 16.dp)
|
||||
) { Text(stringResource(R.string.close)) }
|
||||
modifier = Modifier.padding(top = 16.dp),
|
||||
) {
|
||||
Text(stringResource(R.string.close))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PacketResponseStateDialogPreview() {
|
||||
PacketResponseStateDialog(
|
||||
state = ResponseState.Loading(
|
||||
total = 17,
|
||||
completed = 5,
|
||||
),
|
||||
)
|
||||
PacketResponseStateDialog(state = ResponseState.Loading(total = 17, completed = 5))
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -40,19 +40,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
fun PaxcounterConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun PaxcounterConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
PaxcounterConfigItemList(
|
||||
|
|
@ -61,7 +56,7 @@ fun PaxcounterConfigScreen(
|
|||
onSaveClicked = { paxcounterConfigInput ->
|
||||
val config = moduleConfig { paxcounter = paxcounterConfigInput }
|
||||
viewModel.setModuleConfig(config)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -75,9 +70,7 @@ fun PaxcounterConfigItemList(
|
|||
val focusManager = LocalFocusManager.current
|
||||
var paxcounterInput by rememberSaveable { mutableStateOf(paxcounterConfig) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.paxcounter_config)) }
|
||||
|
||||
item {
|
||||
|
|
@ -85,9 +78,7 @@ fun PaxcounterConfigItemList(
|
|||
title = stringResource(R.string.paxcounter_enabled),
|
||||
checked = paxcounterInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
paxcounterInput = paxcounterInput.copy { this.enabled = it }
|
||||
}
|
||||
onCheckedChange = { paxcounterInput = paxcounterInput.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -98,9 +89,7 @@ fun PaxcounterConfigItemList(
|
|||
value = paxcounterInput.paxcounterUpdateInterval,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
paxcounterInput = paxcounterInput.copy { paxcounterUpdateInterval = it }
|
||||
}
|
||||
onValueChanged = { paxcounterInput = paxcounterInput.copy { paxcounterUpdateInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -110,9 +99,7 @@ fun PaxcounterConfigItemList(
|
|||
value = paxcounterInput.wifiThreshold,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
paxcounterInput = paxcounterInput.copy { wifiThreshold = it }
|
||||
}
|
||||
onValueChanged = { paxcounterInput = paxcounterInput.copy { wifiThreshold = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -122,9 +109,7 @@ fun PaxcounterConfigItemList(
|
|||
value = paxcounterInput.bleThreshold,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
paxcounterInput = paxcounterInput.copy { bleThreshold = it }
|
||||
}
|
||||
onValueChanged = { paxcounterInput = paxcounterInput.copy { bleThreshold = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +123,7 @@ fun PaxcounterConfigItemList(
|
|||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(paxcounterInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -150,6 +135,6 @@ private fun PaxcounterConfigPreview() {
|
|||
PaxcounterConfigItemList(
|
||||
paxcounterConfig = ModuleConfigProtos.ModuleConfig.PaxcounterConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { },
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
|
|
@ -54,7 +54,7 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -40,7 +40,7 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -40,19 +40,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
fun RangeTestConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun RangeTestConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
RangeTestConfigItemList(
|
||||
|
|
@ -61,7 +56,7 @@ fun RangeTestConfigScreen(
|
|||
onSaveClicked = { rangeTestInput ->
|
||||
val config = moduleConfig { rangeTest = rangeTestInput }
|
||||
viewModel.setModuleConfig(config)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -74,9 +69,7 @@ fun RangeTestConfigItemList(
|
|||
val focusManager = LocalFocusManager.current
|
||||
var rangeTestInput by rememberSaveable { mutableStateOf(rangeTestConfig) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.range_test_config)) }
|
||||
|
||||
item {
|
||||
|
|
@ -84,7 +77,7 @@ fun RangeTestConfigItemList(
|
|||
title = stringResource(R.string.range_test_enabled),
|
||||
checked = rangeTestInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { rangeTestInput = rangeTestInput.copy { this.enabled = it } }
|
||||
onCheckedChange = { rangeTestInput = rangeTestInput.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -95,7 +88,7 @@ fun RangeTestConfigItemList(
|
|||
value = rangeTestInput.sender,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { rangeTestInput = rangeTestInput.copy { sender = it } }
|
||||
onValueChanged = { rangeTestInput = rangeTestInput.copy { sender = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -104,7 +97,7 @@ fun RangeTestConfigItemList(
|
|||
title = stringResource(R.string.save_csv_in_storage_esp32_only),
|
||||
checked = rangeTestInput.save,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { rangeTestInput = rangeTestInput.copy { save = it } }
|
||||
onCheckedChange = { rangeTestInput = rangeTestInput.copy { save = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -119,7 +112,7 @@ fun RangeTestConfigItemList(
|
|||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(rangeTestInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -128,9 +121,5 @@ fun RangeTestConfigItemList(
|
|||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun RangeTestConfig() {
|
||||
RangeTestConfigItemList(
|
||||
rangeTestConfig = RangeTestConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { },
|
||||
)
|
||||
RangeTestConfigItemList(rangeTestConfig = RangeTestConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -40,19 +40,14 @@ import com.geeksville.mesh.ui.common.components.EditListPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
fun RemoteHardwareConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun RemoteHardwareConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
RemoteHardwareConfigItemList(
|
||||
|
|
@ -61,7 +56,7 @@ fun RemoteHardwareConfigScreen(
|
|||
onSaveClicked = { remoteHardwareInput ->
|
||||
val config = moduleConfig { remoteHardware = remoteHardwareInput }
|
||||
viewModel.setModuleConfig(config)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -74,9 +69,7 @@ fun RemoteHardwareConfigItemList(
|
|||
val focusManager = LocalFocusManager.current
|
||||
var remoteHardwareInput by rememberSaveable { mutableStateOf(remoteHardwareConfig) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.remote_hardware_config)) }
|
||||
|
||||
item {
|
||||
|
|
@ -84,9 +77,7 @@ fun RemoteHardwareConfigItemList(
|
|||
title = stringResource(R.string.remote_hardware_enabled),
|
||||
checked = remoteHardwareInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
remoteHardwareInput = remoteHardwareInput.copy { this.enabled = it }
|
||||
}
|
||||
onCheckedChange = { remoteHardwareInput = remoteHardwareInput.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -96,9 +87,7 @@ fun RemoteHardwareConfigItemList(
|
|||
title = stringResource(R.string.allow_undefined_pin_access),
|
||||
checked = remoteHardwareInput.allowUndefinedPinAccess,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
remoteHardwareInput = remoteHardwareInput.copy { allowUndefinedPinAccess = it }
|
||||
}
|
||||
onCheckedChange = { remoteHardwareInput = remoteHardwareInput.copy { allowUndefinedPinAccess = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -111,11 +100,12 @@ fun RemoteHardwareConfigItemList(
|
|||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValuesChanged = { list ->
|
||||
remoteHardwareInput = remoteHardwareInput.copy {
|
||||
availablePins.clear()
|
||||
availablePins.addAll(list)
|
||||
}
|
||||
}
|
||||
remoteHardwareInput =
|
||||
remoteHardwareInput.copy {
|
||||
availablePins.clear()
|
||||
availablePins.addAll(list)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -129,7 +119,7 @@ fun RemoteHardwareConfigItemList(
|
|||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(remoteHardwareInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -141,6 +131,6 @@ private fun RemoteHardwareConfigPreview() {
|
|||
RemoteHardwareConfigItemList(
|
||||
remoteHardwareConfig = RemoteHardwareConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { },
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
|
|
@ -58,24 +58,19 @@ import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.node.NodeActionButton
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import com.geeksville.mesh.util.encodeToString
|
||||
import com.geeksville.mesh.util.toByteString
|
||||
import com.google.protobuf.ByteString
|
||||
import java.security.SecureRandom
|
||||
|
||||
@Composable
|
||||
fun SecurityConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun SecurityConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
val node by viewModel.destNode.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
SecurityConfigItemList(
|
||||
|
|
@ -86,9 +81,7 @@ fun SecurityConfigScreen(
|
|||
val config = config { security = securityInput }
|
||||
viewModel.setConfig(config)
|
||||
},
|
||||
onExport = { uri, securityConfig ->
|
||||
viewModel.exportSecurityConfig(uri, securityConfig)
|
||||
},
|
||||
onExport = { uri, securityConfig -> viewModel.exportSecurityConfig(uri, securityConfig) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -114,13 +107,12 @@ fun SecurityConfigItemList(
|
|||
}
|
||||
}
|
||||
|
||||
val exportConfigLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
it.data?.data?.let { uri -> onExport(uri, securityConfig) }
|
||||
val exportConfigLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
it.data?.data?.let { uri -> onExport(uri, securityConfig) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var showKeyGenerationDialog by rememberSaveable { mutableStateOf(false) }
|
||||
PrivateKeyRegenerateDialog(
|
||||
|
|
@ -131,7 +123,7 @@ fun SecurityConfigItemList(
|
|||
showKeyGenerationDialog = false
|
||||
onConfirm(securityInput)
|
||||
},
|
||||
onDismiss = { showKeyGenerationDialog = false }
|
||||
onDismiss = { showKeyGenerationDialog = false },
|
||||
)
|
||||
var showEditSecurityConfigDialog by rememberSaveable { mutableStateOf(false) }
|
||||
if (showEditSecurityConfigDialog) {
|
||||
|
|
@ -143,14 +135,15 @@ fun SecurityConfigItemList(
|
|||
TextButton(
|
||||
onClick = {
|
||||
showEditSecurityConfigDialog = false
|
||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/*"
|
||||
putExtra(
|
||||
Intent.EXTRA_TITLE,
|
||||
"${user?.shortName}_keys_${System.currentTimeMillis()}.json"
|
||||
)
|
||||
}
|
||||
val intent =
|
||||
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/*"
|
||||
putExtra(
|
||||
Intent.EXTRA_TITLE,
|
||||
"${user?.shortName}_keys_${System.currentTimeMillis()}.json",
|
||||
)
|
||||
}
|
||||
exportConfigLauncher.launch(intent)
|
||||
},
|
||||
) {
|
||||
|
|
@ -160,9 +153,7 @@ fun SecurityConfigItemList(
|
|||
)
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.security_config)) }
|
||||
|
||||
item {
|
||||
|
|
@ -177,11 +168,7 @@ fun SecurityConfigItemList(
|
|||
securityInput = securityInput.copy { publicKey = it }
|
||||
}
|
||||
},
|
||||
trailingIcon = {
|
||||
CopyIconButton(
|
||||
valueToCopy = securityInput.publicKey.encodeToString(),
|
||||
)
|
||||
}
|
||||
trailingIcon = { CopyIconButton(valueToCopy = securityInput.publicKey.encodeToString()) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -196,11 +183,7 @@ fun SecurityConfigItemList(
|
|||
securityInput = securityInput.copy { privateKey = it }
|
||||
}
|
||||
},
|
||||
trailingIcon = {
|
||||
CopyIconButton(
|
||||
valueToCopy = securityInput.privateKey.encodeToString(),
|
||||
)
|
||||
}
|
||||
trailingIcon = { CopyIconButton(valueToCopy = securityInput.privateKey.encodeToString()) },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -210,9 +193,7 @@ fun SecurityConfigItemList(
|
|||
title = stringResource(R.string.regenerate_private_key),
|
||||
enabled = enabled,
|
||||
icon = Icons.TwoTone.Warning,
|
||||
onClick = {
|
||||
showKeyGenerationDialog = true
|
||||
}
|
||||
onClick = { showKeyGenerationDialog = true },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -222,9 +203,7 @@ fun SecurityConfigItemList(
|
|||
title = stringResource(R.string.export_keys),
|
||||
enabled = enabled,
|
||||
icon = Icons.TwoTone.Warning,
|
||||
onClick = {
|
||||
showEditSecurityConfigDialog = true
|
||||
}
|
||||
onClick = { showEditSecurityConfigDialog = true },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -236,10 +215,11 @@ fun SecurityConfigItemList(
|
|||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValuesChanged = {
|
||||
securityInput = securityInput.copy {
|
||||
adminKey.clear()
|
||||
adminKey.addAll(it)
|
||||
}
|
||||
securityInput =
|
||||
securityInput.copy {
|
||||
adminKey.clear()
|
||||
adminKey.addAll(it)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -249,9 +229,7 @@ fun SecurityConfigItemList(
|
|||
title = stringResource(R.string.managed_mode),
|
||||
checked = securityInput.isManaged,
|
||||
enabled = enabled && securityInput.adminKeyCount > 0,
|
||||
onCheckedChange = {
|
||||
securityInput = securityInput.copy { isManaged = it }
|
||||
}
|
||||
onCheckedChange = { securityInput = securityInput.copy { isManaged = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -261,7 +239,7 @@ fun SecurityConfigItemList(
|
|||
title = stringResource(R.string.serial_console),
|
||||
checked = securityInput.serialEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { securityInput = securityInput.copy { serialEnabled = it } }
|
||||
onCheckedChange = { securityInput = securityInput.copy { serialEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -271,9 +249,7 @@ fun SecurityConfigItemList(
|
|||
title = stringResource(R.string.debug_log_api_enabled),
|
||||
checked = securityInput.debugLogApiEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
securityInput = securityInput.copy { debugLogApiEnabled = it }
|
||||
}
|
||||
onCheckedChange = { securityInput = securityInput.copy { debugLogApiEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -283,9 +259,7 @@ fun SecurityConfigItemList(
|
|||
title = stringResource(R.string.legacy_admin_channel),
|
||||
checked = securityInput.adminChannelEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
securityInput = securityInput.copy { adminChannelEnabled = it }
|
||||
}
|
||||
onCheckedChange = { securityInput = securityInput.copy { adminChannelEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -300,7 +274,7 @@ fun SecurityConfigItemList(
|
|||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onConfirm(securityInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -323,34 +297,27 @@ fun PrivateKeyRegenerateDialog(
|
|||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
securityInput = securityInput.copy {
|
||||
clearPrivateKey()
|
||||
clearPublicKey()
|
||||
// Generate a random "f" value
|
||||
val f = ByteArray(32).apply {
|
||||
SecureRandom().nextBytes(this)
|
||||
securityInput =
|
||||
securityInput.copy {
|
||||
clearPrivateKey()
|
||||
clearPublicKey()
|
||||
// Generate a random "f" value
|
||||
val f = ByteArray(32).apply { SecureRandom().nextBytes(this) }
|
||||
// Adjust the value to make it valid as an "s" value for eval().
|
||||
// According to the specification we need to mask off the 3
|
||||
// right-most bits of f[0], mask off the left-most bit of f[31],
|
||||
// and set the second to left-most bit of f[31].
|
||||
f[0] = (f[0].toInt() and 0xF8).toByte()
|
||||
f[31] = ((f[31].toInt() and 0x7F) or 0x40).toByte()
|
||||
privateKey = ByteString.copyFrom(f)
|
||||
}
|
||||
// Adjust the value to make it valid as an "s" value for eval().
|
||||
// According to the specification we need to mask off the 3
|
||||
// right-most bits of f[0], mask off the left-most bit of f[31],
|
||||
// and set the second to left-most bit of f[31].
|
||||
f[0] = (f[0].toInt() and 0xF8).toByte()
|
||||
f[31] = ((f[31].toInt() and 0x7F) or 0x40).toByte()
|
||||
privateKey = ByteString.copyFrom(f)
|
||||
}
|
||||
onConfirm(securityInput)
|
||||
},
|
||||
) {
|
||||
Text(stringResource(R.string.okay))
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = onDismiss,
|
||||
) {
|
||||
Text(stringResource(R.string.cancel))
|
||||
}
|
||||
}
|
||||
dismissButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.cancel)) } },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -358,9 +325,5 @@ fun PrivateKeyRegenerateDialog(
|
|||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SecurityConfigPreview() {
|
||||
SecurityConfigItemList(
|
||||
securityConfig = SecurityConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onConfirm = {},
|
||||
)
|
||||
SecurityConfigItemList(securityConfig = SecurityConfig.getDefaultInstance(), enabled = true, onConfirm = {})
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -41,19 +41,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
fun SerialConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun SerialConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
SerialConfigItemList(
|
||||
|
|
@ -62,22 +57,17 @@ fun SerialConfigScreen(
|
|||
onSaveClicked = { serialInput ->
|
||||
val config = moduleConfig { serial = serialInput }
|
||||
viewModel.setModuleConfig(config)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun SerialConfigItemList(
|
||||
serialConfig: SerialConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (SerialConfig) -> Unit,
|
||||
) {
|
||||
fun SerialConfigItemList(serialConfig: SerialConfig, enabled: Boolean, onSaveClicked: (SerialConfig) -> Unit) {
|
||||
val focusManager = LocalFocusManager.current
|
||||
var serialInput by rememberSaveable { mutableStateOf(serialConfig) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.serial_config)) }
|
||||
|
||||
item {
|
||||
|
|
@ -85,7 +75,7 @@ fun SerialConfigItemList(
|
|||
title = stringResource(R.string.serial_enabled),
|
||||
checked = serialInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { serialInput = serialInput.copy { this.enabled = it } }
|
||||
onCheckedChange = { serialInput = serialInput.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -95,7 +85,7 @@ fun SerialConfigItemList(
|
|||
title = stringResource(R.string.echo_enabled),
|
||||
checked = serialInput.echo,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { serialInput = serialInput.copy { echo = it } }
|
||||
onCheckedChange = { serialInput = serialInput.copy { echo = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -106,7 +96,7 @@ fun SerialConfigItemList(
|
|||
value = serialInput.rxd,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { serialInput = serialInput.copy { rxd = it } }
|
||||
onValueChanged = { serialInput = serialInput.copy { rxd = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -116,7 +106,7 @@ fun SerialConfigItemList(
|
|||
value = serialInput.txd,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { serialInput = serialInput.copy { txd = it } }
|
||||
onValueChanged = { serialInput = serialInput.copy { txd = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -124,11 +114,12 @@ fun SerialConfigItemList(
|
|||
DropDownPreference(
|
||||
title = stringResource(R.string.serial_baud_rate),
|
||||
enabled = enabled,
|
||||
items = SerialConfig.Serial_Baud.entries
|
||||
items =
|
||||
SerialConfig.Serial_Baud.entries
|
||||
.filter { it != SerialConfig.Serial_Baud.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = serialInput.baud,
|
||||
onItemSelected = { serialInput = serialInput.copy { baud = it } }
|
||||
onItemSelected = { serialInput = serialInput.copy { baud = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -139,7 +130,7 @@ fun SerialConfigItemList(
|
|||
value = serialInput.timeout,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { serialInput = serialInput.copy { timeout = it } }
|
||||
onValueChanged = { serialInput = serialInput.copy { timeout = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -147,11 +138,12 @@ fun SerialConfigItemList(
|
|||
DropDownPreference(
|
||||
title = stringResource(R.string.serial_mode),
|
||||
enabled = enabled,
|
||||
items = SerialConfig.Serial_Mode.entries
|
||||
items =
|
||||
SerialConfig.Serial_Mode.entries
|
||||
.filter { it != SerialConfig.Serial_Mode.UNRECOGNIZED }
|
||||
.map { it to it.name },
|
||||
selectedItem = serialInput.mode,
|
||||
onItemSelected = { serialInput = serialInput.copy { mode = it } }
|
||||
onItemSelected = { serialInput = serialInput.copy { mode = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -161,9 +153,7 @@ fun SerialConfigItemList(
|
|||
title = stringResource(R.string.override_console_serial_port),
|
||||
checked = serialInput.overrideConsoleSerialPort,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
serialInput = serialInput.copy { overrideConsoleSerialPort = it }
|
||||
}
|
||||
onCheckedChange = { serialInput = serialInput.copy { overrideConsoleSerialPort = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -178,7 +168,7 @@ fun SerialConfigItemList(
|
|||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(serialInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -187,9 +177,5 @@ fun SerialConfigItemList(
|
|||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun SerialConfigPreview() {
|
||||
SerialConfigItemList(
|
||||
serialConfig = SerialConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { },
|
||||
)
|
||||
SerialConfigItemList(serialConfig = SerialConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -40,19 +40,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
fun StoreForwardConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun StoreForwardConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
StoreForwardConfigItemList(
|
||||
|
|
@ -61,7 +56,7 @@ fun StoreForwardConfigScreen(
|
|||
onSaveClicked = { storeForwardInput ->
|
||||
val config = moduleConfig { storeForward = storeForwardInput }
|
||||
viewModel.setModuleConfig(config)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -74,9 +69,7 @@ fun StoreForwardConfigItemList(
|
|||
val focusManager = LocalFocusManager.current
|
||||
var storeForwardInput by rememberSaveable { mutableStateOf(storeForwardConfig) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.store_forward_config)) }
|
||||
|
||||
item {
|
||||
|
|
@ -84,9 +77,7 @@ fun StoreForwardConfigItemList(
|
|||
title = stringResource(R.string.store_forward_enabled),
|
||||
checked = storeForwardInput.enabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
storeForwardInput = storeForwardInput.copy { this.enabled = it }
|
||||
}
|
||||
onCheckedChange = { storeForwardInput = storeForwardInput.copy { this.enabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -96,7 +87,7 @@ fun StoreForwardConfigItemList(
|
|||
title = stringResource(R.string.heartbeat),
|
||||
checked = storeForwardInput.heartbeat,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { storeForwardInput = storeForwardInput.copy { heartbeat = it } }
|
||||
onCheckedChange = { storeForwardInput = storeForwardInput.copy { heartbeat = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -107,7 +98,7 @@ fun StoreForwardConfigItemList(
|
|||
value = storeForwardInput.records,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { storeForwardInput = storeForwardInput.copy { records = it } }
|
||||
onValueChanged = { storeForwardInput = storeForwardInput.copy { records = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -117,9 +108,7 @@ fun StoreForwardConfigItemList(
|
|||
value = storeForwardInput.historyReturnMax,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
storeForwardInput = storeForwardInput.copy { historyReturnMax = it }
|
||||
}
|
||||
onValueChanged = { storeForwardInput = storeForwardInput.copy { historyReturnMax = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -129,9 +118,7 @@ fun StoreForwardConfigItemList(
|
|||
value = storeForwardInput.historyReturnWindow,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
storeForwardInput = storeForwardInput.copy { historyReturnWindow = it }
|
||||
}
|
||||
onValueChanged = { storeForwardInput = storeForwardInput.copy { historyReturnWindow = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +127,7 @@ fun StoreForwardConfigItemList(
|
|||
title = stringResource(R.string.server),
|
||||
checked = storeForwardInput.isServer,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { storeForwardInput = storeForwardInput.copy { isServer = it } }
|
||||
onCheckedChange = { storeForwardInput = storeForwardInput.copy { isServer = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -155,7 +142,7 @@ fun StoreForwardConfigItemList(
|
|||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(storeForwardInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -167,6 +154,6 @@ private fun StoreForwardConfigPreview() {
|
|||
StoreForwardConfigItemList(
|
||||
storeForwardConfig = StoreForwardConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { },
|
||||
onSaveClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -40,19 +40,14 @@ import com.geeksville.mesh.ui.common.components.EditTextPreference
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
|
||||
@Composable
|
||||
fun TelemetryConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun TelemetryConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
TelemetryConfigItemList(
|
||||
|
|
@ -61,7 +56,7 @@ fun TelemetryConfigScreen(
|
|||
onSaveClicked = { telemetryInput ->
|
||||
val config = moduleConfig { telemetry = telemetryInput }
|
||||
viewModel.setModuleConfig(config)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -74,9 +69,7 @@ fun TelemetryConfigItemList(
|
|||
val focusManager = LocalFocusManager.current
|
||||
var telemetryInput by rememberSaveable { mutableStateOf(telemetryConfig) }
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.telemetry_config)) }
|
||||
|
||||
item {
|
||||
|
|
@ -85,9 +78,7 @@ fun TelemetryConfigItemList(
|
|||
value = telemetryInput.deviceUpdateInterval,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
telemetryInput = telemetryInput.copy { deviceUpdateInterval = it }
|
||||
}
|
||||
onValueChanged = { telemetryInput = telemetryInput.copy { deviceUpdateInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -97,9 +88,7 @@ fun TelemetryConfigItemList(
|
|||
value = telemetryInput.environmentUpdateInterval,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
telemetryInput = telemetryInput.copy { environmentUpdateInterval = it }
|
||||
}
|
||||
onValueChanged = { telemetryInput = telemetryInput.copy { environmentUpdateInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -108,9 +97,7 @@ fun TelemetryConfigItemList(
|
|||
title = stringResource(R.string.environment_metrics_module_enabled),
|
||||
checked = telemetryInput.environmentMeasurementEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
telemetryInput = telemetryInput.copy { environmentMeasurementEnabled = it }
|
||||
}
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { environmentMeasurementEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -120,9 +107,7 @@ fun TelemetryConfigItemList(
|
|||
title = stringResource(R.string.environment_metrics_on_screen_enabled),
|
||||
checked = telemetryInput.environmentScreenEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
telemetryInput = telemetryInput.copy { environmentScreenEnabled = it }
|
||||
}
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { environmentScreenEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -132,9 +117,7 @@ fun TelemetryConfigItemList(
|
|||
title = stringResource(R.string.environment_metrics_use_fahrenheit),
|
||||
checked = telemetryInput.environmentDisplayFahrenheit,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
telemetryInput = telemetryInput.copy { environmentDisplayFahrenheit = it }
|
||||
}
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { environmentDisplayFahrenheit = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -144,9 +127,7 @@ fun TelemetryConfigItemList(
|
|||
title = stringResource(R.string.air_quality_metrics_module_enabled),
|
||||
checked = telemetryInput.airQualityEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
telemetryInput = telemetryInput.copy { airQualityEnabled = it }
|
||||
}
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { airQualityEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -157,9 +138,7 @@ fun TelemetryConfigItemList(
|
|||
value = telemetryInput.airQualityInterval,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
telemetryInput = telemetryInput.copy { airQualityInterval = it }
|
||||
}
|
||||
onValueChanged = { telemetryInput = telemetryInput.copy { airQualityInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -168,9 +147,7 @@ fun TelemetryConfigItemList(
|
|||
title = stringResource(R.string.power_metrics_module_enabled),
|
||||
checked = telemetryInput.powerMeasurementEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
telemetryInput = telemetryInput.copy { powerMeasurementEnabled = it }
|
||||
}
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { powerMeasurementEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -181,9 +158,7 @@ fun TelemetryConfigItemList(
|
|||
value = telemetryInput.powerUpdateInterval,
|
||||
enabled = enabled,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
telemetryInput = telemetryInput.copy { powerUpdateInterval = it }
|
||||
}
|
||||
onValueChanged = { telemetryInput = telemetryInput.copy { powerUpdateInterval = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -192,9 +167,7 @@ fun TelemetryConfigItemList(
|
|||
title = stringResource(R.string.power_metrics_on_screen_enabled),
|
||||
checked = telemetryInput.powerScreenEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
telemetryInput = telemetryInput.copy { powerScreenEnabled = it }
|
||||
}
|
||||
onCheckedChange = { telemetryInput = telemetryInput.copy { powerScreenEnabled = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -209,7 +182,7 @@ fun TelemetryConfigItemList(
|
|||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(telemetryInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -218,9 +191,5 @@ fun TelemetryConfigItemList(
|
|||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun TelemetryConfigPreview() {
|
||||
TelemetryConfigItemList(
|
||||
telemetryConfig = TelemetryConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { },
|
||||
)
|
||||
TelemetryConfigItemList(telemetryConfig = TelemetryConfig.getDefaultInstance(), enabled = true, onSaveClicked = {})
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.geeksville.mesh.ui.radioconfig.components
|
||||
package com.geeksville.mesh.ui.settings.radio.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
|
|
@ -46,20 +46,15 @@ import com.geeksville.mesh.ui.common.components.PreferenceCategory
|
|||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.common.components.RegularPreference
|
||||
import com.geeksville.mesh.ui.common.components.SwitchPreference
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import com.geeksville.mesh.user
|
||||
|
||||
@Composable
|
||||
fun UserConfigScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
) {
|
||||
fun UserConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel()) {
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
|
||||
if (state.responseState.isWaiting()) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = viewModel::clearPacketResponse,
|
||||
)
|
||||
PacketResponseStateDialog(state = state.responseState, onDismiss = viewModel::clearPacketResponse)
|
||||
}
|
||||
|
||||
UserConfigItemList(
|
||||
|
|
@ -85,18 +80,10 @@ fun UserConfigItemList(
|
|||
val validLongName = userInput.longName.isNotBlank()
|
||||
val validShortName = userInput.shortName.isNotBlank()
|
||||
val validNames = validLongName && validShortName
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item { PreferenceCategory(text = stringResource(R.string.user_config)) }
|
||||
|
||||
item {
|
||||
RegularPreference(
|
||||
title = stringResource(R.string.node_id),
|
||||
subtitle = userInput.id,
|
||||
onClick = {}
|
||||
)
|
||||
}
|
||||
item { RegularPreference(title = stringResource(R.string.node_id), subtitle = userInput.id, onClick = {}) }
|
||||
item { HorizontalDivider() }
|
||||
|
||||
item {
|
||||
|
|
@ -106,13 +93,10 @@ fun UserConfigItemList(
|
|||
maxSize = 39, // long_name max_size:40
|
||||
enabled = enabled,
|
||||
isError = !validLongName,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = {
|
||||
userInput = userInput.copy { longName = it }
|
||||
}
|
||||
onValueChanged = { userInput = userInput.copy { longName = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -123,11 +107,10 @@ fun UserConfigItemList(
|
|||
maxSize = 4, // short_name max_size:5
|
||||
enabled = enabled,
|
||||
isError = !validShortName,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
|
||||
),
|
||||
keyboardOptions =
|
||||
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Text, imeAction = ImeAction.Done),
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { userInput = userInput.copy { shortName = it } }
|
||||
onValueChanged = { userInput = userInput.copy { shortName = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +118,7 @@ fun UserConfigItemList(
|
|||
RegularPreference(
|
||||
title = stringResource(R.string.hardware_model),
|
||||
subtitle = userInput.hwModel.name,
|
||||
onClick = {}
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -144,12 +127,11 @@ fun UserConfigItemList(
|
|||
SwitchPreference(
|
||||
title = stringResource(R.string.unmessageable),
|
||||
summary = stringResource(R.string.unmonitored_or_infrastructure),
|
||||
checked = userInput.isUnmessagable || (
|
||||
firmwareVersion < DeviceVersion("2.6.9") &&
|
||||
userInput.role.isUnmessageableRole()
|
||||
),
|
||||
checked =
|
||||
userInput.isUnmessagable ||
|
||||
(firmwareVersion < DeviceVersion("2.6.9") && userInput.role.isUnmessageableRole()),
|
||||
enabled = userInput.hasIsUnmessagable() || firmwareVersion >= DeviceVersion("2.6.9"),
|
||||
onCheckedChange = { userInput = userInput.copy { isUnmessagable = it } }
|
||||
onCheckedChange = { userInput = userInput.copy { isUnmessagable = it } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +143,7 @@ fun UserConfigItemList(
|
|||
summary = stringResource(R.string.licensed_amateur_radio_text),
|
||||
checked = userInput.isLicensed,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { userInput = userInput.copy { isLicensed = it } }
|
||||
onCheckedChange = { userInput = userInput.copy { isLicensed = it } },
|
||||
)
|
||||
}
|
||||
item { HorizontalDivider() }
|
||||
|
|
@ -172,10 +154,11 @@ fun UserConfigItemList(
|
|||
onCancelClicked = {
|
||||
focusManager.clearFocus()
|
||||
userInput = userConfig
|
||||
}, onSaveClicked = {
|
||||
},
|
||||
onSaveClicked = {
|
||||
focusManager.clearFocus()
|
||||
onSaveClicked(userInput)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -185,7 +168,8 @@ fun UserConfigItemList(
|
|||
@Composable
|
||||
private fun UserConfigPreview() {
|
||||
UserConfigItemList(
|
||||
userConfig = user {
|
||||
userConfig =
|
||||
user {
|
||||
id = "!a280d9c8"
|
||||
longName = "Meshtastic d9c8"
|
||||
shortName = "d9c8"
|
||||
|
|
@ -193,9 +177,7 @@ private fun UserConfigPreview() {
|
|||
isLicensed = false
|
||||
},
|
||||
enabled = true,
|
||||
onSaveClicked = { },
|
||||
metadata = deviceMetadata {
|
||||
firmwareVersion = "2.8.0"
|
||||
}
|
||||
onSaveClicked = {},
|
||||
metadata = deviceMetadata { firmwareVersion = "2.8.0" },
|
||||
)
|
||||
}
|
||||
|
|
@ -104,9 +104,9 @@ import com.geeksville.mesh.navigation.getNavRouteFrom
|
|||
import com.geeksville.mesh.service.ConnectionState
|
||||
import com.geeksville.mesh.ui.common.components.AdaptiveTwoPane
|
||||
import com.geeksville.mesh.ui.common.components.PreferenceFooter
|
||||
import com.geeksville.mesh.ui.radioconfig.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.radioconfig.components.ChannelSelection
|
||||
import com.geeksville.mesh.ui.radioconfig.components.PacketResponseStateDialog
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.components.ChannelSelection
|
||||
import com.geeksville.mesh.ui.settings.radio.components.PacketResponseStateDialog
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
|
|
|
|||
|
|
@ -628,7 +628,7 @@
|
|||
<string name="map">Mesh Map</string>
|
||||
<string name="conversations">Conversations</string>
|
||||
<string name="nodes">Nodes</string>
|
||||
<string name="bottom_nav_share">Share</string>
|
||||
<string name="bottom_nav_settings">Settings</string>
|
||||
<string name="set_your_region">Set your region</string>
|
||||
<string name="reply">Reply</string>
|
||||
<string name="map_reporting_summary">Your node will periodically send an unencrypted map report packet to the configured MQTT server, this includes id, long and short name, approximate location, hardware model, role, firmware version, LoRa region, modem preset and primary channel name.</string>
|
||||
|
|
@ -672,6 +672,7 @@
|
|||
<string name="uv_lux">UV Lux</string>
|
||||
<string name="unknown">Unknown</string>
|
||||
|
||||
<string name="message_device_managed">This radio is managed and can only be changed by a remote admin.</string>
|
||||
<string name="clean_node_database_title">Clean Node Database</string>
|
||||
<string name="clean_nodes_older_than">Clean up nodes last seen older than %1$d days</string>
|
||||
<string name="clean_unknown_nodes">Clean up only unknown nodes</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue