From 71628cac8434c8565ec0ea13ee12c099ff46ca42 Mon Sep 17 00:00:00 2001 From: James Rich <2199651+jamesarich@users.noreply.github.com> Date: Sat, 24 Jan 2026 21:11:14 -0600 Subject: [PATCH] Refactor: Only show advanced and app settings for local node (#4290) Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com> --- .../feature/settings/SettingsScreen.kt | 293 +++++++++--------- .../feature/settings/radio/RadioConfig.kt | 46 +-- 2 files changed, 172 insertions(+), 167 deletions(-) diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt index 86c9bdde6..e9116e1af 100644 --- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt +++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt @@ -276,177 +276,180 @@ fun SettingsScreen( val context = LocalContext.current - TitledCard(title = stringResource(Res.string.app_settings), modifier = Modifier.padding(top = 16.dp)) { - if (state.analyticsAvailable) { - val allowed by viewModel.analyticsAllowedFlow.collectAsStateWithLifecycle(false) - SwitchListItem( - text = stringResource(Res.string.analytics_okay), - checked = allowed, - leadingIcon = Icons.Default.BugReport, - onClick = { viewModel.toggleAnalyticsAllowed() }, - ) - } + if (state.isLocal) { + TitledCard(title = stringResource(Res.string.app_settings), modifier = Modifier.padding(top = 16.dp)) { + if (state.analyticsAvailable) { + val allowed by viewModel.analyticsAllowedFlow.collectAsStateWithLifecycle(false) + SwitchListItem( + text = stringResource(Res.string.analytics_okay), + checked = allowed, + leadingIcon = Icons.Default.BugReport, + onClick = { viewModel.toggleAnalyticsAllowed() }, + ) + } - val locationPermissionsState = - rememberMultiplePermissionsState(permissions = listOf(Manifest.permission.ACCESS_FINE_LOCATION)) - val isGpsDisabled = context.gpsDisabled() - val provideLocation by settingsViewModel.provideLocation.collectAsStateWithLifecycle() + val locationPermissionsState = + rememberMultiplePermissionsState(permissions = listOf(Manifest.permission.ACCESS_FINE_LOCATION)) + val isGpsDisabled = context.gpsDisabled() + val provideLocation by settingsViewModel.provideLocation.collectAsStateWithLifecycle() - LaunchedEffect(provideLocation, locationPermissionsState.allPermissionsGranted, isGpsDisabled) { - if (provideLocation) { - if (locationPermissionsState.allPermissionsGranted) { - if (!isGpsDisabled) { - settingsViewModel.meshService?.startProvideLocation() + LaunchedEffect(provideLocation, locationPermissionsState.allPermissionsGranted, isGpsDisabled) { + if (provideLocation) { + if (locationPermissionsState.allPermissionsGranted) { + if (!isGpsDisabled) { + settingsViewModel.meshService?.startProvideLocation() + } else { + context.showToast(Res.string.location_disabled) + } } else { - context.showToast(Res.string.location_disabled) + // Request permissions if not granted and user wants to provide location + locationPermissionsState.launchMultiplePermissionRequest() } } else { - // Request permissions if not granted and user wants to provide location - locationPermissionsState.launchMultiplePermissionRequest() + settingsViewModel.meshService?.stopProvideLocation() } - } else { - settingsViewModel.meshService?.stopProvideLocation() } - } - SwitchListItem( - text = stringResource(Res.string.provide_location_to_mesh), - leadingIcon = Icons.Rounded.LocationOn, - enabled = !isGpsDisabled, - checked = provideLocation, - onClick = { settingsViewModel.setProvideLocation(!provideLocation) }, - ) + SwitchListItem( + text = stringResource(Res.string.provide_location_to_mesh), + leadingIcon = Icons.Rounded.LocationOn, + enabled = !isGpsDisabled, + checked = provideLocation, + onClick = { settingsViewModel.setProvideLocation(!provideLocation) }, + ) - val settingsLauncher = - rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) {} + val settingsLauncher = + rememberLauncherForActivityResult( + contract = ActivityResultContracts.StartActivityForResult(), + ) {} - // On Android 12 and below, system app settings for language are not available. Use the in-app language - // picker for these devices. - val useInAppLangPicker = Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU - ListItem( - text = stringResource(Res.string.preferences_language), - leadingIcon = Icons.Rounded.Language, - trailingIcon = if (useInAppLangPicker) null else Icons.AutoMirrored.Rounded.KeyboardArrowRight, - ) { - if (useInAppLangPicker) { - showLanguagePickerDialog = true - } else { - val intent = Intent(ACTION_APP_LOCALE_SETTINGS, "package:${context.packageName}".toUri()) - if (intent.resolveActivity(context.packageManager) != null) { - settingsLauncher.launch(intent) - } else { - // Fall back to the in-app picker + // On Android 12 and below, system app settings for language are not available. Use the in-app + // language + // picker for these devices. + val useInAppLangPicker = Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU + ListItem( + text = stringResource(Res.string.preferences_language), + leadingIcon = Icons.Rounded.Language, + trailingIcon = if (useInAppLangPicker) null else Icons.AutoMirrored.Rounded.KeyboardArrowRight, + ) { + if (useInAppLangPicker) { showLanguagePickerDialog = true + } else { + val intent = Intent(ACTION_APP_LOCALE_SETTINGS, "package:${context.packageName}".toUri()) + if (intent.resolveActivity(context.packageManager) != null) { + settingsLauncher.launch(intent) + } else { + // Fall back to the in-app picker + showLanguagePickerDialog = true + } } } - } - ListItem( - text = stringResource(Res.string.theme), - leadingIcon = Icons.Rounded.FormatPaint, - trailingIcon = null, - ) { - showThemePickerDialog = true - } - - ListItem( - text = "Message Filter", - leadingIcon = Icons.Rounded.FilterList, - trailingIcon = Icons.AutoMirrored.Rounded.KeyboardArrowRight, - ) { - onNavigate(SettingsRoutes.FilterSettings) - } - - // Node DB cache limit (App setting) - val cacheLimit = settingsViewModel.dbCacheLimit.collectAsStateWithLifecycle().value - val cacheItems = remember { - (DatabaseConstants.MIN_CACHE_LIMIT..DatabaseConstants.MAX_CACHE_LIMIT).map { - it.toLong() to it.toString() + ListItem( + text = stringResource(Res.string.theme), + leadingIcon = Icons.Rounded.FormatPaint, + trailingIcon = null, + ) { + showThemePickerDialog = true } - } - DropDownPreference( - title = stringResource(Res.string.device_db_cache_limit), - enabled = true, - items = cacheItems, - selectedItem = cacheLimit.toLong(), - onItemSelected = { selected -> settingsViewModel.setDbCacheLimit(selected.toInt()) }, - summary = stringResource(Res.string.device_db_cache_limit_summary), - ) - val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) - val nodeName = ourNode?.user?.shortName ?: "" - - val exportRangeTestLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (it.resultCode == RESULT_OK) { - it.data?.data?.let { uri -> settingsViewModel.saveDataCsv(uri) } + ListItem( + text = "Message Filter", + leadingIcon = Icons.Rounded.FilterList, + trailingIcon = Icons.AutoMirrored.Rounded.KeyboardArrowRight, + ) { + onNavigate(SettingsRoutes.FilterSettings) + } // Node DB cache limit (App setting) + val cacheLimit = settingsViewModel.dbCacheLimit.collectAsStateWithLifecycle().value + val cacheItems = remember { + (DatabaseConstants.MIN_CACHE_LIMIT..DatabaseConstants.MAX_CACHE_LIMIT).map { + it.toLong() to it.toString() } } - ListItem( - text = stringResource(Res.string.save_rangetest), - leadingIcon = Icons.Rounded.Output, - trailingIcon = null, - ) { - val intent = - Intent(Intent.ACTION_CREATE_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - type = "application/csv" - putExtra(Intent.EXTRA_TITLE, "Meshtastic_rangetest_${nodeName}_$timestamp.csv") - } - exportRangeTestLauncher.launch(intent) - } + DropDownPreference( + title = stringResource(Res.string.device_db_cache_limit), + enabled = true, + items = cacheItems, + selectedItem = cacheLimit.toLong(), + onItemSelected = { selected -> settingsViewModel.setDbCacheLimit(selected.toInt()) }, + summary = stringResource(Res.string.device_db_cache_limit_summary), + ) - val exportDataLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (it.resultCode == RESULT_OK) { - it.data?.data?.let { uri -> settingsViewModel.saveDataCsv(uri) } + val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) + val nodeName = ourNode?.user?.shortName ?: "" + + val exportRangeTestLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == RESULT_OK) { + it.data?.data?.let { uri -> settingsViewModel.saveDataCsv(uri) } + } } + ListItem( + text = stringResource(Res.string.save_rangetest), + leadingIcon = Icons.Rounded.Output, + trailingIcon = null, + ) { + val intent = + Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/csv" + putExtra(Intent.EXTRA_TITLE, "Meshtastic_rangetest_${nodeName}_$timestamp.csv") + } + exportRangeTestLauncher.launch(intent) } - ListItem( - text = stringResource(Res.string.export_data_csv), - leadingIcon = Icons.Rounded.Output, - trailingIcon = null, - ) { - val intent = - Intent(Intent.ACTION_CREATE_DOCUMENT).apply { - addCategory(Intent.CATEGORY_OPENABLE) - type = "application/csv" - putExtra(Intent.EXTRA_TITLE, "Meshtastic_datalog_${nodeName}_$timestamp.csv") + + val exportDataLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (it.resultCode == RESULT_OK) { + it.data?.data?.let { uri -> settingsViewModel.saveDataCsv(uri) } + } } - exportDataLauncher.launch(intent) - } + ListItem( + text = stringResource(Res.string.export_data_csv), + leadingIcon = Icons.Rounded.Output, + trailingIcon = null, + ) { + val intent = + Intent(Intent.ACTION_CREATE_DOCUMENT).apply { + addCategory(Intent.CATEGORY_OPENABLE) + type = "application/csv" + putExtra(Intent.EXTRA_TITLE, "Meshtastic_datalog_${nodeName}_$timestamp.csv") + } + exportDataLauncher.launch(intent) + } - ListItem( - text = stringResource(Res.string.intro_show), - leadingIcon = Icons.Rounded.WavingHand, - trailingIcon = null, - ) { - settingsViewModel.showAppIntro() - } + ListItem( + text = stringResource(Res.string.intro_show), + leadingIcon = Icons.Rounded.WavingHand, + trailingIcon = null, + ) { + settingsViewModel.showAppIntro() + } - ListItem( - text = stringResource(Res.string.system_settings), - leadingIcon = Icons.Rounded.AppSettingsAlt, - trailingIcon = null, - ) { - val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - intent.data = Uri.fromParts("package", context.packageName, null) - settingsLauncher.launch(intent) - } + ListItem( + text = stringResource(Res.string.system_settings), + leadingIcon = Icons.Rounded.AppSettingsAlt, + trailingIcon = null, + ) { + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + intent.data = Uri.fromParts("package", context.packageName, null) + settingsLauncher.launch(intent) + } - ListItem( - text = stringResource(Res.string.acknowledgements), - leadingIcon = Icons.Rounded.Info, - trailingIcon = null, - ) { - onNavigate(SettingsRoutes.About) - } + ListItem( + text = stringResource(Res.string.acknowledgements), + leadingIcon = Icons.Rounded.Info, + trailingIcon = null, + ) { + onNavigate(SettingsRoutes.About) + } - AppVersionButton( - excludedModulesUnlocked = excludedModulesUnlocked, - appVersionName = settingsViewModel.appVersionName, - ) { - settingsViewModel.unlockExcludedModules() + AppVersionButton( + excludedModulesUnlocked = excludedModulesUnlocked, + appVersionName = settingsViewModel.appVersionName, + ) { + settingsViewModel.unlockExcludedModules() + } } } } diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfig.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfig.kt index 5d13a76a4..3ead7a6a8 100644 --- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfig.kt +++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfig.kt @@ -206,33 +206,35 @@ fun RadioConfigItemList( } } - TitledCard(title = stringResource(Res.string.advanced_title), modifier = Modifier.padding(top = 16.dp)) { - if (isManaged) { - ManagedMessage() - } + if (state.isLocal) { + TitledCard(title = stringResource(Res.string.advanced_title), modifier = Modifier.padding(top = 16.dp)) { + if (isManaged) { + ManagedMessage() + } + + if (isOtaCapable) { + ListItem( + text = stringResource(Res.string.firmware_update_title), + leadingIcon = Icons.Rounded.SystemUpdate, + enabled = enabled, + onClick = { onNavigate(FirmwareRoutes.FirmwareUpdate) }, + ) + } - if (isOtaCapable && state.isLocal) { ListItem( - text = stringResource(Res.string.firmware_update_title), - leadingIcon = Icons.Rounded.SystemUpdate, + text = stringResource(Res.string.clean_node_database_title), + leadingIcon = Icons.Rounded.CleaningServices, enabled = enabled, - onClick = { onNavigate(FirmwareRoutes.FirmwareUpdate) }, + onClick = { onNavigate(SettingsRoutes.CleanNodeDb) }, + ) + + ListItem( + text = stringResource(Res.string.debug_panel), + leadingIcon = Icons.Rounded.BugReport, + enabled = enabled, + onClick = { onNavigate(SettingsRoutes.DebugPanel) }, ) } - - ListItem( - text = stringResource(Res.string.clean_node_database_title), - leadingIcon = Icons.Rounded.CleaningServices, - enabled = enabled, - onClick = { onNavigate(SettingsRoutes.CleanNodeDb) }, - ) - - ListItem( - text = stringResource(Res.string.debug_panel), - leadingIcon = Icons.Rounded.BugReport, - enabled = enabled, - onClick = { onNavigate(SettingsRoutes.DebugPanel) }, - ) } }