mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
More migration to top-level Settings (#2903)
This commit is contained in:
parent
b08691bda6
commit
177138ac8f
7 changed files with 279 additions and 275 deletions
|
|
@ -227,42 +227,7 @@ class MainActivity :
|
|||
createRangetestLauncher.launch(intent)
|
||||
}
|
||||
|
||||
MainMenuAction.THEME -> {
|
||||
chooseThemeDialog()
|
||||
}
|
||||
|
||||
MainMenuAction.LANGUAGE -> {
|
||||
chooseLangDialog()
|
||||
}
|
||||
|
||||
else -> warn("Unexpected action: $action")
|
||||
}
|
||||
}
|
||||
|
||||
private fun chooseThemeDialog() {
|
||||
val styles =
|
||||
mapOf(
|
||||
getString(R.string.dynamic) to MODE_DYNAMIC,
|
||||
getString(R.string.theme_light) to AppCompatDelegate.MODE_NIGHT_NO,
|
||||
getString(R.string.theme_dark) to AppCompatDelegate.MODE_NIGHT_YES,
|
||||
getString(R.string.theme_system) to AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM,
|
||||
)
|
||||
|
||||
val theme = uiPrefs.theme
|
||||
debug("Theme from prefs: $theme")
|
||||
model.showAlert(
|
||||
title = getString(R.string.choose_theme),
|
||||
message = "",
|
||||
choices = styles.mapValues { (_, value) -> { model.setTheme(value) } },
|
||||
)
|
||||
}
|
||||
|
||||
private fun chooseLangDialog() {
|
||||
val languageTags = LanguageUtils.getLanguageTags(this)
|
||||
val lang = LanguageUtils.getLocale()
|
||||
debug("Lang from prefs: $lang")
|
||||
val langMap = languageTags.mapValues { (_, value) -> { LanguageUtils.setLocale(value) } }
|
||||
|
||||
model.showAlert(title = getString(R.string.preferences_language), message = "", choices = langMap)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,8 +56,8 @@ 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.settings.SettingsScreen
|
||||
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
|
||||
|
|
@ -161,7 +161,7 @@ fun NavGraphBuilder.settingsGraph(navController: NavHostController, uiViewModel:
|
|||
) { backStackEntry ->
|
||||
val parentEntry =
|
||||
remember(backStackEntry) { navController.getBackStackEntry(SettingsRoutes.SettingsGraph::class) }
|
||||
RadioConfigScreen(uiViewModel = uiViewModel, viewModel = hiltViewModel(parentEntry)) {
|
||||
SettingsScreen(uiViewModel = uiViewModel, viewModel = hiltViewModel(parentEntry)) {
|
||||
navController.navigate(it) { popUpTo(SettingsRoutes.Settings()) { inclusive = false } }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,6 @@ import com.geeksville.mesh.model.BluetoothViewModel
|
|||
import com.geeksville.mesh.model.DeviceVersion
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.navigation.ChannelsRoutes
|
||||
import com.geeksville.mesh.navigation.ConnectionsRoutes
|
||||
import com.geeksville.mesh.navigation.ContactsRoutes
|
||||
import com.geeksville.mesh.navigation.MapRoutes
|
||||
|
|
@ -115,6 +114,7 @@ import com.google.accompanist.permissions.isGranted
|
|||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
enum class TopLevelDestination(@StringRes val label: Int, val icon: ImageVector, val route: Route) {
|
||||
Conversations(R.string.conversations, MeshtasticIcons.Conversations, ContactsRoutes.ContactsGraph),
|
||||
|
|
@ -125,14 +125,14 @@ enum class TopLevelDestination(@StringRes val label: Int, val icon: ImageVector,
|
|||
;
|
||||
|
||||
companion object {
|
||||
fun NavDestination.isTopLevel(): Boolean = listOf<Route>(
|
||||
ContactsRoutes.Contacts,
|
||||
NodesRoutes.Nodes,
|
||||
MapRoutes.Map,
|
||||
ChannelsRoutes.Channels,
|
||||
ConnectionsRoutes.Connections,
|
||||
fun NavDestination.isTopLevel(): Boolean = listOf<KClass<out Route>>(
|
||||
ContactsRoutes.Contacts::class,
|
||||
NodesRoutes.Nodes::class,
|
||||
MapRoutes.Map::class,
|
||||
ConnectionsRoutes.Connections::class,
|
||||
SettingsRoutes.Settings::class,
|
||||
)
|
||||
.any { this.hasRoute(it::class) }
|
||||
.any { this.hasRoute(it) }
|
||||
|
||||
fun fromNavDestination(destination: NavDestination?): TopLevelDestination? =
|
||||
entries.find { dest -> destination?.hierarchy?.any { it.hasRoute(dest.route::class) } == true }
|
||||
|
|
|
|||
|
|
@ -208,8 +208,6 @@ private fun TopBarActions(
|
|||
enum class MainMenuAction(@StringRes val stringRes: Int) {
|
||||
DEBUG(R.string.debug_panel),
|
||||
EXPORT_RANGETEST(R.string.save_rangetest),
|
||||
THEME(R.string.theme),
|
||||
LANGUAGE(R.string.preferences_language),
|
||||
SHOW_INTRO(R.string.intro_show),
|
||||
QUICK_CHAT(R.string.quick_chat),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.BugReport
|
||||
import androidx.compose.material.icons.rounded.FormatPaint
|
||||
import androidx.compose.material.icons.rounded.Language
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
|
||||
import com.geeksville.mesh.DeviceUIProtos.Language
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.android.BuildUtils.debug
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.navigation.Route
|
||||
import com.geeksville.mesh.navigation.getNavRouteFrom
|
||||
import com.geeksville.mesh.ui.common.components.TitledCard
|
||||
import com.geeksville.mesh.ui.common.theme.MODE_DYNAMIC
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItem
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItemSwitch
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigItemList
|
||||
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.settings.radio.components.EditDeviceProfileDialog
|
||||
import com.geeksville.mesh.ui.settings.radio.components.PacketResponseStateDialog
|
||||
import com.geeksville.mesh.util.LanguageUtils
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
fun SettingsScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
uiViewModel: UIViewModel = hiltViewModel(),
|
||||
onNavigate: (Route) -> Unit = {},
|
||||
) {
|
||||
uiViewModel.setTitle(stringResource(R.string.bottom_nav_settings))
|
||||
|
||||
val excludedModulesUnlocked by uiViewModel.excludedModulesUnlocked.collectAsStateWithLifecycle()
|
||||
val localConfig by uiViewModel.localConfig.collectAsStateWithLifecycle()
|
||||
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
var isWaiting by remember { mutableStateOf(false) }
|
||||
if (isWaiting) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = {
|
||||
isWaiting = false
|
||||
viewModel.clearPacketResponse()
|
||||
},
|
||||
onComplete = {
|
||||
getNavRouteFrom(state.route)?.let { route ->
|
||||
isWaiting = false
|
||||
viewModel.clearPacketResponse()
|
||||
onNavigate(route)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
var deviceProfile by remember { mutableStateOf<DeviceProfile?>(null) }
|
||||
var showEditDeviceProfileDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val importConfigLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
showEditDeviceProfileDialog = true
|
||||
it.data?.data?.let { uri -> viewModel.importProfile(uri) { profile -> deviceProfile = profile } }
|
||||
}
|
||||
}
|
||||
|
||||
val exportConfigLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
it.data?.data?.let { uri -> viewModel.exportProfile(uri, deviceProfile!!) }
|
||||
}
|
||||
}
|
||||
|
||||
if (showEditDeviceProfileDialog) {
|
||||
EditDeviceProfileDialog(
|
||||
title =
|
||||
if (deviceProfile != null) {
|
||||
stringResource(R.string.import_configuration)
|
||||
} else {
|
||||
stringResource(R.string.export_configuration)
|
||||
},
|
||||
deviceProfile = deviceProfile ?: viewModel.currentDeviceProfile,
|
||||
onConfirm = {
|
||||
showEditDeviceProfileDialog = false
|
||||
if (deviceProfile != null) {
|
||||
viewModel.installProfile(it)
|
||||
} else {
|
||||
deviceProfile = it
|
||||
val intent =
|
||||
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/*"
|
||||
putExtra(Intent.EXTRA_TITLE, "device_profile.cfg")
|
||||
}
|
||||
exportConfigLauncher.launch(intent)
|
||||
}
|
||||
},
|
||||
onDismiss = {
|
||||
showEditDeviceProfileDialog = false
|
||||
deviceProfile = null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState()).padding(16.dp)) {
|
||||
RadioConfigItemList(
|
||||
state = state,
|
||||
isManaged = localConfig.security.isManaged,
|
||||
excludedModulesUnlocked = excludedModulesUnlocked,
|
||||
onRouteClick = { route ->
|
||||
isWaiting = true
|
||||
viewModel.setResponseStateLoading(route)
|
||||
},
|
||||
onImport = {
|
||||
viewModel.clearPacketResponse()
|
||||
deviceProfile = null
|
||||
val intent =
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/*"
|
||||
}
|
||||
importConfigLauncher.launch(intent)
|
||||
},
|
||||
onExport = {
|
||||
viewModel.clearPacketResponse()
|
||||
deviceProfile = null
|
||||
showEditDeviceProfileDialog = true
|
||||
},
|
||||
onNavigate = onNavigate,
|
||||
)
|
||||
|
||||
TitledCard(title = stringResource(R.string.phone_settings), modifier = Modifier.padding(top = 16.dp)) {
|
||||
if (state.analyticsAvailable) {
|
||||
SettingsItemSwitch(
|
||||
text = stringResource(R.string.analytics_okay),
|
||||
checked = state.analyticsEnabled,
|
||||
leadingIcon = Icons.Default.BugReport,
|
||||
onClick = { viewModel.toggleAnalytics() },
|
||||
)
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
val languageTags = remember { LanguageUtils.getLanguageTags(context) }
|
||||
SettingsItem(
|
||||
text = stringResource(R.string.preferences_language),
|
||||
leadingIcon = Icons.Rounded.Language,
|
||||
trailingIcon = null,
|
||||
) {
|
||||
val lang = LanguageUtils.getLocale()
|
||||
debug("Lang from prefs: $lang")
|
||||
val langMap = languageTags.mapValues { (_, value) -> { LanguageUtils.setLocale(value) } }
|
||||
|
||||
uiViewModel.showAlert(
|
||||
title = context.getString(R.string.preferences_language),
|
||||
message = "",
|
||||
choices = langMap,
|
||||
)
|
||||
}
|
||||
|
||||
val themeMap = remember {
|
||||
mapOf(
|
||||
context.getString(R.string.dynamic) to MODE_DYNAMIC,
|
||||
context.getString(R.string.theme_light) to AppCompatDelegate.MODE_NIGHT_NO,
|
||||
context.getString(R.string.theme_dark) to AppCompatDelegate.MODE_NIGHT_YES,
|
||||
context.getString(R.string.theme_system) to AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM,
|
||||
)
|
||||
}
|
||||
SettingsItem(
|
||||
text = stringResource(R.string.theme),
|
||||
leadingIcon = Icons.Rounded.FormatPaint,
|
||||
trailingIcon = null,
|
||||
) {
|
||||
uiViewModel.showAlert(
|
||||
title = context.getString(R.string.choose_theme),
|
||||
message = "",
|
||||
choices = themeMap.mapValues { (_, value) -> { uiViewModel.setTheme(value) } },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -17,35 +17,22 @@
|
|||
|
||||
package com.geeksville.mesh.ui.settings.radio
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.twotone.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.BugReport
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.icons.filled.Upload
|
||||
import androidx.compose.material.icons.twotone.Warning
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
|
@ -60,14 +47,11 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.navigation.AdminRoute
|
||||
|
|
@ -75,156 +59,13 @@ import com.geeksville.mesh.navigation.ConfigRoute
|
|||
import com.geeksville.mesh.navigation.ModuleRoute
|
||||
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.TitledCard
|
||||
import com.geeksville.mesh.ui.common.theme.AppTheme
|
||||
import com.geeksville.mesh.ui.common.theme.StatusColors.StatusRed
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItem
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItemSwitch
|
||||
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
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
fun RadioConfigScreen(
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
uiViewModel: UIViewModel = hiltViewModel(),
|
||||
onNavigate: (Route) -> Unit = {},
|
||||
) {
|
||||
val node by viewModel.destNode.collectAsStateWithLifecycle()
|
||||
val ourNode by uiViewModel.ourNodeInfo.collectAsStateWithLifecycle()
|
||||
val isLocal = node?.num == ourNode?.num
|
||||
val nodeName: String? =
|
||||
node?.user?.longName?.let {
|
||||
if (!isLocal) {
|
||||
"$it (" + stringResource(R.string.remote) + ")"
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
|
||||
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) }
|
||||
if (isWaiting) {
|
||||
PacketResponseStateDialog(
|
||||
state = state.responseState,
|
||||
onDismiss = {
|
||||
isWaiting = false
|
||||
viewModel.clearPacketResponse()
|
||||
},
|
||||
onComplete = {
|
||||
getNavRouteFrom(state.route)?.let { route ->
|
||||
isWaiting = false
|
||||
viewModel.clearPacketResponse()
|
||||
onNavigate(route)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
var deviceProfile by remember { mutableStateOf<DeviceProfile?>(null) }
|
||||
var showEditDeviceProfileDialog by remember { mutableStateOf(false) }
|
||||
|
||||
val importConfigLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
showEditDeviceProfileDialog = true
|
||||
it.data?.data?.let { uri -> viewModel.importProfile(uri) { profile -> deviceProfile = profile } }
|
||||
}
|
||||
}
|
||||
|
||||
val exportConfigLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == Activity.RESULT_OK) {
|
||||
it.data?.data?.let { uri -> viewModel.exportProfile(uri, deviceProfile!!) }
|
||||
}
|
||||
}
|
||||
|
||||
if (showEditDeviceProfileDialog) {
|
||||
EditDeviceProfileDialog(
|
||||
title =
|
||||
if (deviceProfile != null) {
|
||||
stringResource(R.string.import_configuration)
|
||||
} else {
|
||||
stringResource(R.string.export_configuration)
|
||||
},
|
||||
deviceProfile = deviceProfile ?: viewModel.currentDeviceProfile,
|
||||
onConfirm = {
|
||||
showEditDeviceProfileDialog = false
|
||||
if (deviceProfile != null) {
|
||||
viewModel.installProfile(it)
|
||||
} else {
|
||||
deviceProfile = it
|
||||
val intent =
|
||||
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/*"
|
||||
putExtra(Intent.EXTRA_TITLE, "device_profile.cfg")
|
||||
}
|
||||
exportConfigLauncher.launch(intent)
|
||||
}
|
||||
},
|
||||
onDismiss = {
|
||||
showEditDeviceProfileDialog = false
|
||||
deviceProfile = null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
RadioConfigItemList(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
isManaged = localConfig.security.isManaged,
|
||||
excludedModulesUnlocked = excludedModulesUnlocked,
|
||||
onRouteClick = { route ->
|
||||
isWaiting = true
|
||||
viewModel.setResponseStateLoading(route)
|
||||
},
|
||||
onImport = {
|
||||
viewModel.clearPacketResponse()
|
||||
deviceProfile = null
|
||||
val intent =
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "application/*"
|
||||
}
|
||||
importConfigLauncher.launch(intent)
|
||||
},
|
||||
onExport = {
|
||||
viewModel.clearPacketResponse()
|
||||
deviceProfile = null
|
||||
showEditDeviceProfileDialog = true
|
||||
},
|
||||
onToggleAnalytics = { viewModel.toggleAnalytics() },
|
||||
onNavigate = onNavigate,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NavCard(title: String, enabled: Boolean, icon: ImageVector? = null, onClick: () -> Unit) {
|
||||
Card(onClick = onClick, enabled = enabled, modifier = Modifier.fillMaxWidth().padding(vertical = 2.dp)) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier.padding(vertical = 12.dp, horizontal = 16.dp),
|
||||
) {
|
||||
if (icon != null) {
|
||||
Icon(imageVector = icon, contentDescription = title, modifier = Modifier.size(24.dp))
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
Text(text = title, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.weight(1f))
|
||||
Icon(Icons.AutoMirrored.TwoTone.KeyboardArrowRight, "trailingIcon", modifier = Modifier.wrapContentSize())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun NavButton(@StringRes title: Int, enabled: Boolean, onClick: () -> Unit) {
|
||||
|
|
@ -281,19 +122,18 @@ private fun NavButton(@StringRes title: Int, enabled: Boolean, onClick: () -> Un
|
|||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
private fun RadioConfigItemList(
|
||||
fun RadioConfigItemList(
|
||||
state: RadioConfigState,
|
||||
isManaged: Boolean,
|
||||
excludedModulesUnlocked: Boolean = false,
|
||||
modifier: Modifier = Modifier,
|
||||
onRouteClick: (Enum<*>) -> Unit = {},
|
||||
onImport: () -> Unit = {},
|
||||
onExport: () -> Unit = {},
|
||||
onToggleAnalytics: () -> Unit = {},
|
||||
onNavigate: (Route) -> Unit,
|
||||
) {
|
||||
val enabled = state.connected && !state.responseState.isWaiting() && !isManaged
|
||||
var modules by remember { mutableStateOf(ModuleRoute.filterExcludedFrom(state.metadata)) }
|
||||
|
||||
LaunchedEffect(excludedModulesUnlocked) {
|
||||
if (excludedModulesUnlocked) {
|
||||
modules = ModuleRoute.entries
|
||||
|
|
@ -301,85 +141,68 @@ private fun RadioConfigItemList(
|
|||
modules = ModuleRoute.filterExcludedFrom(state.metadata)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
Column {
|
||||
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 {
|
||||
TitledCard(title = stringResource(R.string.module_settings), modifier = Modifier.padding(top = 16.dp)) {
|
||||
if (isManaged) {
|
||||
ManagedMessage()
|
||||
}
|
||||
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)
|
||||
}
|
||||
modules.forEach {
|
||||
SettingsItem(text = stringResource(it.title), leadingIcon = it.icon, enabled = enabled) {
|
||||
onRouteClick(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.isLocal) {
|
||||
item {
|
||||
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,
|
||||
)
|
||||
}
|
||||
if (state.isLocal) {
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.padding(top = 16.dp)) {
|
||||
AdminRoute.entries.forEach { NavButton(it.title, enabled) { onRouteClick(it) } }
|
||||
}
|
||||
|
||||
TitledCard(title = stringResource(R.string.advanced_title), modifier = Modifier.padding(top = 16.dp)) {
|
||||
if (isManaged) {
|
||||
ManagedMessage()
|
||||
}
|
||||
|
||||
items(AdminRoute.entries) { NavButton(it.title, enabled) { onRouteClick(it) } }
|
||||
|
||||
item {
|
||||
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) },
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
if (state.analyticsAvailable) {
|
||||
TitledCard(title = stringResource(R.string.phone_settings), modifier = Modifier.padding(top = 16.dp)) {
|
||||
SettingsItemSwitch(
|
||||
text = stringResource(R.string.analytics_okay),
|
||||
checked = state.analyticsEnabled,
|
||||
leadingIcon = Icons.Default.BugReport,
|
||||
onClick = onToggleAnalytics,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
SettingsItem(
|
||||
text = stringResource(R.string.clean_node_database_title),
|
||||
enabled = enabled,
|
||||
onClick = { onNavigate(SettingsRoutes.CleanNodeDb) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -673,6 +673,7 @@
|
|||
<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="advanced_title">Advanced</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