refactor: settings screen ui updates (#1955)

pull/1956/head
James Rich 2025-05-27 20:02:53 -05:00 zatwierdzone przez GitHub
rodzic 653c9e4f26
commit 9f0765526d
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
6 zmienionych plików z 85 dodań i 26 usunięć

Wyświetl plik

@ -274,13 +274,17 @@ fun NavGraph(
) { backStackEntry ->
SettingsScreen(
uIViewModel,
) {
navController.navigate(Route.RadioConfig()) {
popUpTo(Route.Settings) {
inclusive = false
sharedTransitionScope = this@SharedTransitionLayout,
animatedContentScope = this@composable,
onNavigateToRadioConfig = {
navController.navigate(Route.RadioConfig()) {
popUpTo(Route.Settings) {
inclusive = false
}
}
}
}
},
onNavigateToNodeDetails = { navController.navigate(Route.NodeDetail(it)) }
)
}
composable<Route.DebugPanel> {
DebugScreen()

Wyświetl plik

@ -178,7 +178,7 @@ fun MainScreen(
enum class MainMenuAction(@StringRes val stringRes: Int) {
DEBUG(R.string.debug_panel),
RADIO_CONFIG(R.string.device_settings),
RADIO_CONFIG(R.string.radio_configuration),
EXPORT_MESSAGES(R.string.save_messages),
THEME(R.string.theme),
LANGUAGE(R.string.preferences_language),

Wyświetl plik

@ -25,6 +25,9 @@ import android.os.Build
import android.util.Patterns
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@ -33,6 +36,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.selection.selectableGroup
@ -72,7 +76,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.hilt.navigation.compose.hiltViewModel
import com.geeksville.mesh.BuildConfig
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.ConfigProtos
import com.geeksville.mesh.R
import com.geeksville.mesh.android.BuildUtils.debug
@ -89,9 +93,11 @@ import com.geeksville.mesh.android.permissionMissing
import com.geeksville.mesh.database.entity.MyNodeEntity
import com.geeksville.mesh.model.BTScanModel
import com.geeksville.mesh.model.BluetoothViewModel
import com.geeksville.mesh.model.Node
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.repository.network.NetworkRepository
import com.geeksville.mesh.service.MeshService
import com.geeksville.mesh.ui.components.NodeMenuAction
import kotlinx.coroutines.delay
fun String?.isIPAddress(): Boolean {
@ -103,13 +109,17 @@ fun String?.isIPAddress(): Boolean {
}
}
@OptIn(ExperimentalSharedTransitionApi::class)
@Suppress("CyclomaticComplexMethod", "LongMethod")
@Composable
fun SettingsScreen(
uiViewModel: UIViewModel = hiltViewModel(),
scanModel: BTScanModel = hiltViewModel(),
bluetoothViewModel: BluetoothViewModel = hiltViewModel(),
onSetRegion: () -> Unit,
sharedTransitionScope: SharedTransitionScope,
animatedContentScope: AnimatedContentScope,
onNavigateToRadioConfig: () -> Unit,
onNavigateToNodeDetails: (Int) -> Unit,
) {
val config by uiViewModel.localConfig.collectAsState()
val currentRegion = config.lora.region
@ -211,13 +221,10 @@ fun SettingsScreen(
MeshService.ConnectionState.DISCONNECTED -> R.string.not_connected
MeshService.ConnectionState.DEVICE_SLEEP -> R.string.connected_sleeping
else -> null
}.let {
val firmwareString =
info?.firmwareString ?: context.getString(R.string.unknown)
if (it != null) {
scanModel.setErrorText(context.getString(it, firmwareString))
}
scanModel.setErrorText(context.getString(it, firmwareString))
}
when (connectionState) {
MeshService.ConnectionState.CONNECTED -> {
@ -229,6 +236,13 @@ fun SettingsScreen(
else -> {}
}
}
var showSharedContact by remember { mutableStateOf<Node?>(null) }
if (showSharedContact != null) {
SharedContactDialog(
contact = showSharedContact,
onDismiss = { showSharedContact = null }
)
}
Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier
@ -246,18 +260,60 @@ fun SettingsScreen(
Spacer(modifier = Modifier.height(8.dp))
// Set Region Button
val isConnected = connectionState == MeshService.ConnectionState.CONNECTED
if (isConnected && regionUnset) {
val isConnected by uiViewModel.isConnected.collectAsStateWithLifecycle(false)
val ourNode by uiViewModel.ourNodeInfo.collectAsState()
if (isConnected) {
ourNode?.let { node ->
Row(
verticalAlignment = Alignment.CenterVertically
) {
NodeChip(
node = node,
isThisNode = true,
isConnected = true,
onAction = { action ->
when (action) {
is NodeMenuAction.MoreDetails -> {
onNavigateToNodeDetails(node.num)
}
is NodeMenuAction.Share -> {
showSharedContact = node
}
else -> {}
}
},
sharedTransitionScope = sharedTransitionScope,
animatedContentScope = animatedContentScope
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "${node.user.longName}",
style = MaterialTheme.typography.titleLarge
)
}
}
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
onSetRegion()
}
onClick = onNavigateToRadioConfig
) {
Text(stringResource(R.string.set_region))
Text(stringResource(R.string.radio_configuration))
}
Spacer(modifier = Modifier.height(8.dp))
if (regionUnset && selectedDevice != "m") {
Text(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp),
text = stringResource(R.string.must_set_region),
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.titleLarge,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(8.dp))
}
Spacer(modifier = Modifier.height(16.dp))
}
// Device List and Manual Input
@ -406,7 +462,7 @@ fun SettingsScreen(
// Analytics Okay Checkbox
val isGooglePlayAvailable = app.isGooglePlayAvailable() || BuildConfig.DEBUG
val isGooglePlayAvailable = app.isGooglePlayAvailable()
val isAnalyticsAllowed = app.isAnalyticsAllowed && isGooglePlayAvailable
if (isGooglePlayAvailable) {
var loading by remember { mutableStateOf(false) }
@ -461,7 +517,7 @@ fun SettingsScreen(
// If no permissions needed, trigger the scan directly (or via ViewModel)
scanModel.startScan()
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
if (
context.findActivity()
.shouldShowRequestPermissionRationale(bluetoothPermissions.first())
) {

Wyświetl plik

@ -302,7 +302,7 @@ private fun RadioConfigItemList(
modifier = modifier,
contentPadding = PaddingValues(horizontal = 16.dp),
) {
item { PreferenceCategory(stringResource(R.string.device_settings)) }
item { PreferenceCategory(stringResource(R.string.radio_configuration)) }
items(ConfigRoute.filterExcludedFrom(state.metadata)) {
NavCard(
title = stringResource(it.title),

Wyświetl plik

@ -234,7 +234,7 @@
<string name="map_start_download">Start Download</string>
<string name="exchange_position">Exchange position</string>
<string name="close">Close</string>
<string name="device_settings">Radio configuration</string>
<string name="radio_configuration">Radio configuration</string>
<string name="module_settings">Module configuration</string>
<string name="add">Add</string>
<string name="edit">Edit</string>

Wyświetl plik

@ -29,7 +29,6 @@ import coil3.util.Logger
import com.geeksville.mesh.network.BuildConfig
import com.geeksville.mesh.network.retrofit.ApiService
import com.geeksville.mesh.network.retrofit.NoOpApiService
import dagger.Binds
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn