mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Start migration away from global top app bar (#3132)
This commit is contained in:
parent
e4bfce0989
commit
fed3ebbd36
17 changed files with 685 additions and 466 deletions
|
|
@ -37,6 +37,7 @@ import androidx.compose.material.icons.rounded.LocationOn
|
|||
import androidx.compose.material.icons.rounded.Memory
|
||||
import androidx.compose.material.icons.rounded.Output
|
||||
import androidx.compose.material.icons.rounded.WavingHand
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
|
|
@ -59,8 +60,10 @@ import com.geeksville.mesh.android.gpsDisabled
|
|||
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.MainAppBar
|
||||
import com.geeksville.mesh.ui.common.components.TitledCard
|
||||
import com.geeksville.mesh.ui.common.theme.MODE_DYNAMIC
|
||||
import com.geeksville.mesh.ui.node.components.NodeMenuAction
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItem
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItemDetail
|
||||
import com.geeksville.mesh.ui.settings.components.SettingsItemSwitch
|
||||
|
|
@ -83,12 +86,13 @@ import kotlin.time.Duration.Companion.seconds
|
|||
fun SettingsScreen(
|
||||
viewModel: RadioConfigViewModel = hiltViewModel(),
|
||||
uiViewModel: UIViewModel = hiltViewModel(),
|
||||
onClickNodeChip: (Int) -> Unit = {},
|
||||
onNavigate: (Route) -> Unit = {},
|
||||
) {
|
||||
uiViewModel.setTitle(stringResource(R.string.bottom_nav_settings))
|
||||
|
||||
val excludedModulesUnlocked by uiViewModel.excludedModulesUnlocked.collectAsStateWithLifecycle()
|
||||
val localConfig by uiViewModel.localConfig.collectAsStateWithLifecycle()
|
||||
val ourNode by uiViewModel.ourNodeInfo.collectAsStateWithLifecycle()
|
||||
val isConnected by uiViewModel.isConnectedStateFlow.collectAsStateWithLifecycle(false)
|
||||
|
||||
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
|
||||
var isWaiting by remember { mutableStateOf(false) }
|
||||
|
|
@ -162,163 +166,183 @@ fun SettingsScreen(
|
|||
)
|
||||
}
|
||||
|
||||
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/*"
|
||||
Scaffold(
|
||||
topBar = {
|
||||
MainAppBar(
|
||||
title = stringResource(R.string.bottom_nav_settings),
|
||||
ourNode = ourNode,
|
||||
isConnected = isConnected,
|
||||
showNodeChip = ourNode != null && isConnected,
|
||||
canNavigateUp = false,
|
||||
onNavigateUp = {},
|
||||
actions = {},
|
||||
onAction = { action ->
|
||||
when (action) {
|
||||
is NodeMenuAction.MoreDetails -> onClickNodeChip(action.node.num)
|
||||
else -> {}
|
||||
}
|
||||
importConfigLauncher.launch(intent)
|
||||
},
|
||||
onExport = {
|
||||
viewModel.clearPacketResponse()
|
||||
deviceProfile = null
|
||||
showEditDeviceProfileDialog = true
|
||||
},
|
||||
onNavigate = onNavigate,
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
) { paddingValues ->
|
||||
Column(modifier = Modifier.verticalScroll(rememberScrollState()).padding(paddingValues).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,
|
||||
)
|
||||
|
||||
val context = LocalContext.current
|
||||
val context = LocalContext.current
|
||||
|
||||
TitledCard(title = stringResource(R.string.app_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() },
|
||||
)
|
||||
}
|
||||
TitledCard(title = stringResource(R.string.app_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 locationPermissionsState =
|
||||
rememberMultiplePermissionsState(permissions = listOf(Manifest.permission.ACCESS_FINE_LOCATION))
|
||||
val isGpsDisabled = context.gpsDisabled()
|
||||
val provideLocation by uiViewModel.provideLocation.collectAsState(false)
|
||||
val locationPermissionsState =
|
||||
rememberMultiplePermissionsState(permissions = listOf(Manifest.permission.ACCESS_FINE_LOCATION))
|
||||
val isGpsDisabled = context.gpsDisabled()
|
||||
val provideLocation by uiViewModel.provideLocation.collectAsState(false)
|
||||
|
||||
LaunchedEffect(provideLocation, locationPermissionsState.allPermissionsGranted, isGpsDisabled) {
|
||||
if (provideLocation) {
|
||||
if (locationPermissionsState.allPermissionsGranted) {
|
||||
if (!isGpsDisabled) {
|
||||
uiViewModel.meshService?.startProvideLocation()
|
||||
LaunchedEffect(provideLocation, locationPermissionsState.allPermissionsGranted, isGpsDisabled) {
|
||||
if (provideLocation) {
|
||||
if (locationPermissionsState.allPermissionsGranted) {
|
||||
if (!isGpsDisabled) {
|
||||
uiViewModel.meshService?.startProvideLocation()
|
||||
} else {
|
||||
uiViewModel.showSnackBar(context.getString(R.string.location_disabled))
|
||||
}
|
||||
} else {
|
||||
uiViewModel.showSnackBar(context.getString(R.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()
|
||||
}
|
||||
} else {
|
||||
uiViewModel.meshService?.stopProvideLocation()
|
||||
}
|
||||
}
|
||||
|
||||
SettingsItemSwitch(
|
||||
text = stringResource(R.string.provide_location_to_mesh),
|
||||
leadingIcon = Icons.Rounded.LocationOn,
|
||||
enabled = !isGpsDisabled,
|
||||
checked = provideLocation,
|
||||
) {
|
||||
uiViewModel.setProvideLocation(!provideLocation)
|
||||
}
|
||||
|
||||
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) } },
|
||||
)
|
||||
}
|
||||
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
|
||||
|
||||
val exportRangeTestLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
it.data?.data?.let { uri -> uiViewModel.saveDataCsv(uri) }
|
||||
uiViewModel.meshService?.stopProvideLocation()
|
||||
}
|
||||
}
|
||||
SettingsItem(
|
||||
text = stringResource(R.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_$timestamp.csv")
|
||||
}
|
||||
exportRangeTestLauncher.launch(intent)
|
||||
}
|
||||
|
||||
val exportDataLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
it.data?.data?.let { uri -> uiViewModel.saveDataCsv(uri) }
|
||||
}
|
||||
SettingsItemSwitch(
|
||||
text = stringResource(R.string.provide_location_to_mesh),
|
||||
leadingIcon = Icons.Rounded.LocationOn,
|
||||
enabled = !isGpsDisabled,
|
||||
checked = provideLocation,
|
||||
) {
|
||||
uiViewModel.setProvideLocation(!provideLocation)
|
||||
}
|
||||
SettingsItem(
|
||||
text = stringResource(R.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_$timestamp.csv")
|
||||
|
||||
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) } },
|
||||
)
|
||||
}
|
||||
val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date())
|
||||
|
||||
val exportRangeTestLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
it.data?.data?.let { uri -> uiViewModel.saveDataCsv(uri) }
|
||||
}
|
||||
}
|
||||
exportDataLauncher.launch(intent)
|
||||
}
|
||||
SettingsItem(
|
||||
text = stringResource(R.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_$timestamp.csv")
|
||||
}
|
||||
exportRangeTestLauncher.launch(intent)
|
||||
}
|
||||
|
||||
SettingsItem(
|
||||
text = stringResource(R.string.intro_show),
|
||||
leadingIcon = Icons.Rounded.WavingHand,
|
||||
trailingIcon = null,
|
||||
) {
|
||||
uiViewModel.showAppIntro()
|
||||
}
|
||||
val exportDataLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (it.resultCode == RESULT_OK) {
|
||||
it.data?.data?.let { uri -> uiViewModel.saveDataCsv(uri) }
|
||||
}
|
||||
}
|
||||
SettingsItem(
|
||||
text = stringResource(R.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_$timestamp.csv")
|
||||
}
|
||||
exportDataLauncher.launch(intent)
|
||||
}
|
||||
|
||||
AppVersionButton(excludedModulesUnlocked) { uiViewModel.unlockExcludedModules() }
|
||||
SettingsItem(
|
||||
text = stringResource(R.string.intro_show),
|
||||
leadingIcon = Icons.Rounded.WavingHand,
|
||||
trailingIcon = null,
|
||||
) {
|
||||
uiViewModel.showAppIntro()
|
||||
}
|
||||
|
||||
AppVersionButton(excludedModulesUnlocked) { uiViewModel.unlockExcludedModules() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue