Clean up string access (#3629)

This commit is contained in:
Phil Oliver 2025-11-05 20:00:09 -05:00 committed by GitHub
parent 4e033d422d
commit a2da943ed7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 106 additions and 127 deletions

View file

@ -25,7 +25,6 @@ import android.hardware.usb.UsbManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
@ -40,13 +39,16 @@ import androidx.compose.ui.platform.LocalView
import androidx.core.net.toUri
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import com.geeksville.mesh.model.UIViewModel
import com.geeksville.mesh.ui.MainScreen
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.meshtastic.core.datastore.UiPreferencesDataSource
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.core.ui.theme.MODE_DYNAMIC
import org.meshtastic.core.ui.util.showToast
import org.meshtastic.feature.intro.AppIntroductionScreen
import timber.log.Timber
import javax.inject.Inject
@ -119,17 +121,13 @@ class MainActivity : AppCompatActivity() {
Timber.d("App link data is a channel set")
model.requestChannelUrl(
url = it,
onFailure = {
Toast.makeText(this, getString(Res.string.channel_invalid), Toast.LENGTH_SHORT).show()
},
onFailure = { lifecycleScope.launch { showToast(Res.string.channel_invalid) } },
)
} else if (it.path?.startsWith("/v/") == true || it.path?.startsWith("/V/") == true) {
Timber.d("App link data is a shared contact")
model.setSharedContactRequested(
url = it,
onFailure = {
Toast.makeText(this, getString(Res.string.contact_invalid), Toast.LENGTH_SHORT).show()
},
onFailure = { lifecycleScope.launch { showToast(Res.string.contact_invalid) } },
)
} else {
Timber.d("App link data is not a channel set")

View file

@ -21,7 +21,6 @@ import android.Manifest
import android.content.ClipData
import android.net.Uri
import android.os.RemoteException
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
@ -78,7 +77,6 @@ import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
@ -107,6 +105,7 @@ import org.meshtastic.core.ui.component.ChannelSelection
import org.meshtastic.core.ui.component.MainAppBar
import org.meshtastic.core.ui.component.PreferenceFooter
import org.meshtastic.core.ui.qr.ScannedQrCodeDialog
import org.meshtastic.core.ui.util.showToast
import org.meshtastic.feature.settings.navigation.ConfigRoute
import org.meshtastic.feature.settings.navigation.getNavRouteFrom
import org.meshtastic.feature.settings.radio.RadioConfigViewModel
@ -181,13 +180,13 @@ fun ChannelScreen(
settings.addAll(result)
}
val scope = rememberCoroutineScope()
val context = LocalContext.current
val resources = LocalResources.current
val barcodeLauncher =
rememberLauncherForActivityResult(ScanContract()) { result ->
if (result.contents != null) {
viewModel.requestChannelUrl(result.contents.toUri()) {
Toast.makeText(context, resources.getString(Res.string.channel_invalid), Toast.LENGTH_SHORT).show()
scope.launch { context.showToast(Res.string.channel_invalid) }
}
}
}
@ -225,7 +224,7 @@ fun ChannelScreen(
channelSet = channels // Throw away user edits
// Tell the user to try again
Toast.makeText(context, resources.getString(Res.string.cant_change_no_radio), Toast.LENGTH_SHORT).show()
scope.launch { context.showToast(Res.string.cant_change_no_radio) }
}
}
@ -314,8 +313,7 @@ fun ChannelScreen(
onTrackShare = viewModel::trackShare,
onConfirm = {
viewModel.requestChannelUrl(it) {
Toast.makeText(context, resources.getString(Res.string.channel_invalid), Toast.LENGTH_SHORT)
.show()
scope.launch { context.showToast(Res.string.channel_invalid) }
}
},
)

View file

@ -0,0 +1,34 @@
/*
* 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 org.meshtastic.core.ui.util
import android.content.Context
import android.widget.Toast
import androidx.annotation.StringRes
suspend fun Context.showToast(@StringRes resId: Int) {
showToast(getString(resId))
}
suspend fun Context.showToast(text: CharSequence) {
Toast.makeText(this, text, Toast.LENGTH_SHORT).show()
}
suspend fun Context.showToast(@StringRes resId: Int, vararg formatArgs: Any) {
Toast.makeText(this, getString(resId, formatArgs), Toast.LENGTH_SHORT).show()
}

View file

@ -19,7 +19,6 @@ package org.meshtastic.feature.map
import android.Manifest // Added for Accompanist
import android.content.Context
import android.widget.Toast
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
@ -50,6 +49,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableDoubleStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -66,12 +66,14 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.permissions.ExperimentalPermissionsApi // Added for Accompanist
import com.google.accompanist.permissions.rememberMultiplePermissionsState // Added for Accompanist
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import org.meshtastic.core.common.gpsDisabled
import org.meshtastic.core.common.hasGps
import org.meshtastic.core.database.entity.Packet
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.util.formatAgo
import org.meshtastic.core.ui.util.showToast
import org.meshtastic.feature.map.cluster.RadiusMarkerClusterer
import org.meshtastic.feature.map.component.CacheLayout
import org.meshtastic.feature.map.component.DownloadButton
@ -223,6 +225,7 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
var showEditWaypointDialog by remember { mutableStateOf<Waypoint?>(null) }
var showCurrentCacheInfo by remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
val context = LocalContext.current
val resources = LocalResources.current
val density = LocalDensity.current
@ -264,7 +267,7 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
fun MapView.toggleMyLocation() {
if (context.gpsDisabled()) {
Timber.d("Telling user we need location turned on for MyLocationNewOverlay")
Toast.makeText(context, resources.getString(Res.string.location_disabled), Toast.LENGTH_SHORT).show()
scope.launch { context.showToast(Res.string.location_disabled) }
return
}
Timber.d("user clicked MyLocationNewOverlay ${myLocationOverlay == null}")
@ -451,7 +454,7 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
LaunchedEffect(showCurrentCacheInfo) {
if (!showCurrentCacheInfo) return@LaunchedEffect
Toast.makeText(context, resources.getString(Res.string.calculating), Toast.LENGTH_SHORT).show()
context.showToast(Res.string.calculating)
val cacheManager = CacheManager(map)
val cacheCapacity = cacheManager.cacheCapacity()
val currentCacheUsage = cacheManager.currentCacheUsage()
@ -560,21 +563,11 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
zoomLevelMax.toInt(),
cacheManagerCallback(
onTaskComplete = {
Toast.makeText(
context,
resources.getString(Res.string.map_download_complete),
Toast.LENGTH_SHORT,
)
.show()
scope.launch { context.showToast(Res.string.map_download_complete) }
writer.onDetach()
},
onTaskFailed = { errors ->
Toast.makeText(
context,
resources.getString(Res.string.map_download_errors, errors),
Toast.LENGTH_SHORT,
)
.show()
scope.launch { context.showToast(Res.string.map_download_errors, errors) }
writer.onDetach()
},
),
@ -619,7 +612,7 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
dialog.dismiss()
}
2 -> purgeTileSource { Toast.makeText(this, it, Toast.LENGTH_SHORT).show() }
2 -> purgeTileSource { scope.launch { context.showToast(it) } }
else -> dialog.dismiss()
}
}

View file

@ -17,7 +17,6 @@
package org.meshtastic.feature.map.component
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@ -53,6 +52,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.flow.collectLatest
import org.meshtastic.core.data.model.CustomTileProviderConfig
import org.meshtastic.core.ui.util.showToast
import org.meshtastic.feature.map.MapViewModel
import org.meshtastic.core.strings.R as Res
@ -64,11 +64,7 @@ fun CustomTileProviderManagerSheet(mapViewModel: MapViewModel) {
var showEditDialog by remember { mutableStateOf(false) }
val context = LocalContext.current
LaunchedEffect(Unit) {
mapViewModel.errorFlow.collectLatest { errorMessage ->
Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show()
}
}
LaunchedEffect(Unit) { mapViewModel.errorFlow.collectLatest { errorMessage -> context.showToast(errorMessage) } }
if (showEditDialog) {
AddEditCustomTileProviderDialog(

View file

@ -17,13 +17,15 @@
package org.meshtastic.feature.map.component
import android.widget.Toast
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.compose.Marker
import com.google.maps.android.compose.rememberUpdatedMarkerState
import kotlinx.coroutines.launch
import org.meshtastic.core.ui.util.showToast
import org.meshtastic.feature.map.BaseMapViewModel
import org.meshtastic.proto.MeshProtos
import org.meshtastic.core.strings.R as Res
@ -39,6 +41,7 @@ fun WaypointMarkers(
unicodeEmojiToBitmapProvider: (Int) -> BitmapDescriptor,
onEditWaypointRequest: (MeshProtos.Waypoint) -> Unit,
) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
if (mapFilterState.showWaypoints) {
displayableWaypoints.forEach { waypoint ->
@ -60,7 +63,7 @@ fun WaypointMarkers(
if (waypoint.lockedTo == 0 || waypoint.lockedTo == myNodeNum || !isConnected) {
onEditWaypointRequest(waypoint)
} else {
Toast.makeText(context, context.getString(Res.string.locked), Toast.LENGTH_SHORT).show()
scope.launch { context.showToast(Res.string.locked) }
}
},
)

View file

@ -19,7 +19,6 @@ package org.meshtastic.feature.node.component
import android.content.ActivityNotFoundException
import android.content.Intent
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
@ -37,21 +36,24 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import com.mikepenz.markdown.m3.Markdown
import kotlinx.coroutines.launch
import org.meshtastic.core.database.entity.FirmwareRelease
import org.meshtastic.core.ui.util.showToast
import timber.log.Timber
import org.meshtastic.core.strings.R as Res
@Composable
fun FirmwareReleaseSheetContent(firmwareRelease: FirmwareRelease, modifier: Modifier = Modifier) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
val resources = LocalResources.current
Column(
modifier = modifier.verticalScroll(rememberScrollState()).padding(16.dp).fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(8.dp),
@ -66,12 +68,7 @@ fun FirmwareReleaseSheetContent(firmwareRelease: FirmwareRelease, modifier: Modi
val intent = Intent(Intent.ACTION_VIEW, firmwareRelease.pageUrl.toUri())
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(
context,
resources.getString(Res.string.error_no_app_to_handle_link),
Toast.LENGTH_LONG,
)
.show()
scope.launch { context.showToast(Res.string.error_no_app_to_handle_link) }
Timber.e(e)
}
},
@ -87,12 +84,7 @@ fun FirmwareReleaseSheetContent(firmwareRelease: FirmwareRelease, modifier: Modi
val intent = Intent(Intent.ACTION_VIEW, firmwareRelease.zipUrl.toUri())
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(
context,
resources.getString(Res.string.error_no_app_to_handle_link),
Toast.LENGTH_LONG,
)
.show()
scope.launch { context.showToast(Res.string.error_no_app_to_handle_link) }
Timber.e(e)
}
},

View file

@ -20,7 +20,6 @@ package org.meshtastic.feature.node.component
import android.content.ActivityNotFoundException
import android.content.ClipData
import android.content.Intent
import android.widget.Toast
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
@ -41,6 +40,7 @@ import org.meshtastic.core.model.util.formatAgo
import org.meshtastic.core.ui.component.BasicListItem
import org.meshtastic.core.ui.component.icon
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.core.ui.util.showToast
import timber.log.Timber
import java.net.URLEncoder
import org.meshtastic.core.strings.R as Res
@ -69,7 +69,7 @@ fun LinkedCoordinatesItem(node: Node) {
if (intent.resolveActivity(context.packageManager) != null) {
context.startActivity(intent)
} else {
Toast.makeText(context, "No application available to open this location!", Toast.LENGTH_LONG).show()
coroutineScope.launch { context.showToast("No application available to open this location!") }
}
} catch (ex: ActivityNotFoundException) {
Timber.d("Failed to open geo intent: $ex")

View file

@ -24,7 +24,6 @@ import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.provider.Settings.ACTION_APP_LOCALE_SETTINGS
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity.RESULT_OK
@ -50,6 +49,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@ -62,6 +62,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.meshtastic.core.common.gpsDisabled
import org.meshtastic.core.navigation.Route
import org.meshtastic.core.ui.component.ListItem
@ -70,6 +71,7 @@ import org.meshtastic.core.ui.component.MultipleChoiceAlertDialog
import org.meshtastic.core.ui.component.SwitchListItem
import org.meshtastic.core.ui.component.TitledCard
import org.meshtastic.core.ui.theme.MODE_DYNAMIC
import org.meshtastic.core.ui.util.showToast
import org.meshtastic.feature.settings.navigation.getNavRouteFrom
import org.meshtastic.feature.settings.radio.RadioConfigItemList
import org.meshtastic.feature.settings.radio.RadioConfigViewModel
@ -230,8 +232,8 @@ fun SettingsScreen(
onNavigate = onNavigate,
)
val scope = rememberCoroutineScope()
val context = LocalContext.current
val resources = LocalResources.current
TitledCard(title = stringResource(Res.string.app_settings), modifier = Modifier.padding(top = 16.dp)) {
if (state.analyticsAvailable) {
@ -255,12 +257,7 @@ fun SettingsScreen(
if (!isGpsDisabled) {
settingsViewModel.meshService?.startProvideLocation()
} else {
Toast.makeText(
context,
resources.getString(Res.string.location_disabled),
Toast.LENGTH_LONG,
)
.show()
context.showToast(Res.string.location_disabled)
}
} else {
// Request permissions if not granted and user wants to provide location
@ -393,8 +390,8 @@ private fun AppVersionButton(
appVersionName: String,
onUnlockExcludedModules: () -> Unit,
) {
val scope = rememberCoroutineScope()
val context = LocalContext.current
val resources = LocalResources.current
var clickCount by remember { mutableIntStateOf(0) }
LaunchedEffect(clickCount) {
@ -415,14 +412,13 @@ private fun AppVersionButton(
when {
clickCount == UNLOCKED_CLICK_COUNT && excludedModulesUnlocked -> {
clickCount = 0
Toast.makeText(context, resources.getString(Res.string.modules_already_unlocked), Toast.LENGTH_LONG)
.show()
scope.launch { context.showToast(Res.string.modules_already_unlocked) }
}
clickCount == UNLOCK_CLICK_COUNT -> {
clickCount = 0
onUnlockExcludedModules()
Toast.makeText(context, resources.getString(Res.string.modules_unlocked), Toast.LENGTH_LONG).show()
scope.launch { context.showToast(Res.string.modules_unlocked) }
}
}
}

View file

@ -19,7 +19,6 @@ package org.meshtastic.feature.settings.debugging
import android.content.Context
import android.net.Uri
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.core.animateFloatAsState
@ -86,6 +85,7 @@ import org.meshtastic.core.ui.component.MainAppBar
import org.meshtastic.core.ui.component.SimpleAlertDialog
import org.meshtastic.core.ui.theme.AnnotationColor
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.core.ui.util.showToast
import org.meshtastic.feature.settings.debugging.DebugViewModel.UiMeshLog
import timber.log.Timber
import java.io.IOException
@ -396,23 +396,9 @@ private suspend fun exportAllLogsToUri(context: Context, targetUri: Uri, logs: L
}
} ?: run { throw IOException("Unable to open output stream for URI: $targetUri") }
withContext(Dispatchers.Main) {
Toast.makeText(
context,
context.getString(Res.string.debug_export_success, logs.size),
Toast.LENGTH_LONG,
)
.show()
}
withContext(Dispatchers.Main) { context.showToast(Res.string.debug_export_success, logs.size) }
} catch (e: IOException) {
withContext(Dispatchers.Main) {
Toast.makeText(
context,
context.getString(Res.string.debug_export_failed, e.message ?: ""),
Toast.LENGTH_LONG,
)
.show()
}
withContext(Dispatchers.Main) { context.showToast(Res.string.debug_export_failed, e.message ?: "") }
Timber.w(e, "Error:IOException ")
}
}

View file

@ -24,7 +24,6 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
@ -50,7 +49,6 @@ fun DetectionSensorConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(
val detectionSensorConfig = state.moduleConfig.detectionSensor
val formState = rememberConfigState(initialValue = detectionSensorConfig)
val focusManager = LocalFocusManager.current
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(Res.string.detection_sensor),
@ -81,7 +79,7 @@ fun DetectionSensorConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(
title = stringResource(Res.string.minimum_broadcast_seconds),
selectedItem = formState.value.minimumBroadcastSecs.toLong(),
enabled = state.connected,
items = minimumBroadcastIntervals.map { it.value to it.toDisplayString(context = context) },
items = minimumBroadcastIntervals.map { it.value to it.toDisplayString() },
onItemSelected = { formState.value = formState.value.copy { minimumBroadcastSecs = it.toInt() } },
)
@ -90,7 +88,7 @@ fun DetectionSensorConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(
title = stringResource(Res.string.state_broadcast_seconds),
selectedItem = formState.value.stateBroadcastSecs.toLong(),
enabled = state.connected,
items = stateBroadcastIntervals.map { it.value to it.toDisplayString(context = context) },
items = stateBroadcastIntervals.map { it.value to it.toDisplayString() },
onItemSelected = { formState.value = formState.value.copy { stateBroadcastSecs = it.toInt() } },
)
HorizontalDivider()

View file

@ -130,7 +130,6 @@ fun DeviceConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack
}
}
val focusManager = LocalFocusManager.current
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(Res.string.device),
onBack = onBack,
@ -170,7 +169,7 @@ fun DeviceConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack
title = stringResource(Res.string.nodeinfo_broadcast_interval),
selectedItem = formState.value.nodeInfoBroadcastSecs.toLong(),
enabled = state.connected,
items = nodeInfoBroadcastIntervals.map { it.value to it.toDisplayString(context = context) },
items = nodeInfoBroadcastIntervals.map { it.value to it.toDisplayString() },
onItemSelected = { formState.value = formState.value.copy { nodeInfoBroadcastSecs = it.toInt() } },
)
}

View file

@ -22,7 +22,6 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@ -42,7 +41,6 @@ fun DisplayConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBac
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val displayConfig = state.radioConfig.display
val formState = rememberConfigState(initialValue = displayConfig)
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(Res.string.display),
@ -106,7 +104,7 @@ fun DisplayConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBac
title = stringResource(Res.string.screen_on_for),
summary = stringResource(Res.string.config_display_screen_on_secs_summary),
enabled = state.connected,
items = screenOnIntervals.map { it to it.toDisplayString(context = context) },
items = screenOnIntervals.map { it to it.toDisplayString() },
selectedItem =
screenOnIntervals.find { it.value == formState.value.screenOnSecs.toLong() }
?: screenOnIntervals.first(),
@ -117,7 +115,7 @@ fun DisplayConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBac
title = stringResource(Res.string.carousel_interval),
summary = stringResource(Res.string.config_display_auto_screen_carousel_secs_summary),
enabled = state.connected,
items = carouselIntervals.map { it to it.toDisplayString(context = context) },
items = carouselIntervals.map { it to it.toDisplayString() },
selectedItem =
carouselIntervals.find { it.value == formState.value.autoScreenCarouselSecs.toLong() }
?: carouselIntervals.first(),

View file

@ -27,7 +27,6 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction
@ -54,7 +53,6 @@ fun ExternalNotificationConfigScreen(viewModel: RadioConfigViewModel = hiltViewM
val formState = rememberConfigState(initialValue = extNotificationConfig)
var ringtoneInput by rememberSaveable(ringtone) { mutableStateOf(ringtone) }
val focusManager = LocalFocusManager.current
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(Res.string.external_notification),
@ -191,7 +189,7 @@ fun ExternalNotificationConfigScreen(viewModel: RadioConfigViewModel = hiltViewM
val outputItems = remember { IntervalConfiguration.OUTPUT.allowedIntervals }
DropDownPreference(
title = stringResource(Res.string.output_duration_milliseconds),
items = outputItems.map { it.value to it.toDisplayString(context = context) },
items = outputItems.map { it.value to it.toDisplayString() },
selectedItem = formState.value.outputMs,
enabled = state.connected,
onItemSelected = { formState.value = formState.value.copy { outputMs = it.toInt() } },
@ -200,7 +198,7 @@ fun ExternalNotificationConfigScreen(viewModel: RadioConfigViewModel = hiltViewM
val nagItems = remember { IntervalConfiguration.NAG_TIMEOUT.allowedIntervals }
DropDownPreference(
title = stringResource(Res.string.nag_timeout_seconds),
items = nagItems.map { it.value to it.toDisplayString(context = context) },
items = nagItems.map { it.value to it.toDisplayString() },
selectedItem = formState.value.nagTimeout,
enabled = state.connected,
onItemSelected = { formState.value = formState.value.copy { nagTimeout = it.toInt() } },

View file

@ -34,7 +34,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
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.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
@ -65,7 +64,6 @@ fun MapReportingPreference(
onPublishIntervalSecsChanged: (Int) -> Unit = {},
enabled: Boolean,
) {
val context = LocalContext.current
Column {
var showMapReportingWarning by rememberSaveable { mutableStateOf(mapReportingEnabled) }
LaunchedEffect(mapReportingEnabled) { showMapReportingWarning = mapReportingEnabled }
@ -126,7 +124,7 @@ fun MapReportingPreference(
DropDownPreference(
modifier = Modifier.padding(bottom = 16.dp),
title = stringResource(Res.string.map_reporting_interval_seconds),
items = publishItems.map { it.value to it.toDisplayString(context = context) },
items = publishItems.map { it.value to it.toDisplayString() },
selectedItem = publishIntervalSecs,
enabled = enabled,
onItemSelected = { onPublishIntervalSecsChanged(it.toInt()) },

View file

@ -23,7 +23,6 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
@ -45,7 +44,6 @@ fun PaxcounterConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), on
val paxcounterConfig = state.moduleConfig.paxcounter
val formState = rememberConfigState(initialValue = paxcounterConfig)
val focusManager = LocalFocusManager.current
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(Res.string.paxcounter),
@ -74,7 +72,7 @@ fun PaxcounterConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), on
title = stringResource(Res.string.update_interval_seconds),
selectedItem = formState.value.paxcounterUpdateInterval.toLong(),
enabled = state.connected,
items = items.map { it.value to it.toDisplayString(context = context) },
items = items.map { it.value to it.toDisplayString() },
onItemSelected = {
formState.value = formState.value.copy { paxcounterUpdateInterval = it.toInt() }
},

View file

@ -34,7 +34,6 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.core.location.LocationCompat
@ -119,7 +118,6 @@ fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBa
}
}
val focusManager = LocalFocusManager.current
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(Res.string.position),
onBack = onBack,
@ -149,7 +147,7 @@ fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBa
title = stringResource(Res.string.broadcast_interval),
summary = stringResource(Res.string.config_position_broadcast_secs_summary),
enabled = state.connected,
items = items.map { it to it.toDisplayString(context = context) },
items = items.map { it to it.toDisplayString() },
selectedItem =
FixedUpdateIntervals.fromValue(formState.value.positionBroadcastSecs.toLong()) ?: items.first(),
onItemSelected = {
@ -172,7 +170,7 @@ fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBa
summary =
stringResource(Res.string.config_position_broadcast_smart_minimum_interval_secs_summary),
enabled = state.connected,
items = smartItems.map { it to it.toDisplayString(context = context) },
items = smartItems.map { it to it.toDisplayString() },
selectedItem =
FixedUpdateIntervals.fromValue(formState.value.broadcastSmartMinimumIntervalSecs.toLong())
?: smartItems.first(),
@ -262,7 +260,7 @@ fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBa
title = stringResource(Res.string.update_interval),
summary = stringResource(Res.string.config_position_gps_update_interval_summary),
enabled = state.connected,
items = items.map { it to it.toDisplayString(context = context) },
items = items.map { it to it.toDisplayString() },
selectedItem =
FixedUpdateIntervals.fromValue(formState.value.gpsUpdateInterval.toLong()) ?: items.first(),
onItemSelected = {

View file

@ -23,7 +23,6 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
@ -45,7 +44,6 @@ fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack:
val powerConfig = state.radioConfig.power
val formState = rememberConfigState(initialValue = powerConfig)
val focusManager = LocalFocusManager.current
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(Res.string.power),
@ -75,7 +73,7 @@ fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack:
title = stringResource(Res.string.shutdown_on_power_loss),
selectedItem = formState.value.onBatteryShutdownAfterSecs.toLong(),
enabled = state.connected,
items = items.map { it.value to it.toDisplayString(context = context) },
items = items.map { it.value to it.toDisplayString() },
onItemSelected = {
formState.value = formState.value.copy { onBatteryShutdownAfterSecs = it.toInt() }
},
@ -106,7 +104,7 @@ fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack:
title = stringResource(Res.string.wait_for_bluetooth_duration_seconds),
selectedItem = formState.value.waitBluetoothSecs.toLong(),
enabled = state.connected,
items = waitBluetoothItems.map { it.value to it.toDisplayString(context = context) },
items = waitBluetoothItems.map { it.value to it.toDisplayString() },
onItemSelected = { formState.value = formState.value.copy { waitBluetoothSecs = it.toInt() } },
)
HorizontalDivider()
@ -116,7 +114,7 @@ fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack:
selectedItem = formState.value.sdsSecs.toLong(),
onItemSelected = { formState.value = formState.value.copy { sdsSecs = it.toInt() } },
enabled = state.connected,
items = sdsSecsItems.map { it.value to it.toDisplayString(context = context) },
items = sdsSecsItems.map { it.value to it.toDisplayString() },
)
HorizontalDivider()
val minWakeItems = remember { IntervalConfiguration.NAG_TIMEOUT.allowedIntervals }
@ -124,7 +122,7 @@ fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack:
title = stringResource(Res.string.minimum_wake_time_seconds),
selectedItem = formState.value.minWakeSecs.toLong(),
enabled = state.connected,
items = minWakeItems.map { it.value to it.toDisplayString(context = context) },
items = minWakeItems.map { it.value to it.toDisplayString() },
onItemSelected = { formState.value = formState.value.copy { minWakeSecs = it.toInt() } },
)
HorizontalDivider()

View file

@ -22,7 +22,6 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@ -41,7 +40,6 @@ fun RangeTestConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onB
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()
val rangeTestConfig = state.moduleConfig.rangeTest
val formState = rememberConfigState(initialValue = rangeTestConfig)
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(Res.string.range_test),
@ -70,7 +68,7 @@ fun RangeTestConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onB
title = stringResource(Res.string.sender_message_interval_seconds),
selectedItem = formState.value.sender.toLong(),
enabled = state.connected,
items = rangeItems.map { it.value to it.toDisplayString(context = context) },
items = rangeItems.map { it.value to it.toDisplayString() },
onItemSelected = { formState.value = formState.value.copy { sender = it.toInt() } },
)
HorizontalDivider()

View file

@ -22,7 +22,6 @@ import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@ -46,7 +45,6 @@ fun TelemetryConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onB
val formState = rememberConfigState(initialValue = telemetryConfig)
val firmwareVersion = state.metadata?.firmwareVersion ?: "1"
val context = LocalContext.current
RadioConfigScreenList(
title = stringResource(Res.string.telemetry),
@ -78,7 +76,7 @@ fun TelemetryConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onB
title = stringResource(Res.string.device_metrics_update_interval_seconds),
selectedItem = formState.value.deviceUpdateInterval.toLong(),
enabled = state.connected,
items = items.map { it.value to it.toDisplayString(context = context) },
items = items.map { it.value to it.toDisplayString() },
onItemSelected = { formState.value = formState.value.copy { deviceUpdateInterval = it.toInt() } },
)
HorizontalDivider()
@ -95,7 +93,7 @@ fun TelemetryConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onB
title = stringResource(Res.string.environment_metrics_update_interval_seconds),
selectedItem = formState.value.environmentUpdateInterval.toLong(),
enabled = state.connected,
items = envItems.map { it.value to it.toDisplayString(context = context) },
items = envItems.map { it.value to it.toDisplayString() },
onItemSelected = {
formState.value = formState.value.copy { environmentUpdateInterval = it.toInt() }
},
@ -130,7 +128,7 @@ fun TelemetryConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onB
title = stringResource(Res.string.air_quality_metrics_update_interval_seconds),
selectedItem = formState.value.airQualityInterval.toLong(),
enabled = state.connected,
items = airItems.map { it.value to it.toDisplayString(context = context) },
items = airItems.map { it.value to it.toDisplayString() },
onItemSelected = { formState.value = formState.value.copy { airQualityInterval = it.toInt() } },
)
HorizontalDivider()
@ -147,7 +145,7 @@ fun TelemetryConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onB
title = stringResource(Res.string.power_metrics_update_interval_seconds),
selectedItem = formState.value.powerUpdateInterval.toLong(),
enabled = state.connected,
items = powerItems.map { it.value to it.toDisplayString(context = context) },
items = powerItems.map { it.value to it.toDisplayString() },
onItemSelected = { formState.value = formState.value.copy { powerUpdateInterval = it.toInt() } },
)
HorizontalDivider()

View file

@ -17,11 +17,13 @@
package org.meshtastic.feature.settings.util
import android.content.Context
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import org.meshtastic.core.strings.R as Res
fun FixedUpdateIntervals.toDisplayString(context: Context): String = if (this == FixedUpdateIntervals.UNSET) {
context.getString(Res.string.unset)
@Composable
fun FixedUpdateIntervals.toDisplayString(): String = if (this == FixedUpdateIntervals.UNSET) {
stringResource(Res.string.unset)
} else {
name.split('_').joinToString(" ") { word -> word.lowercase().replaceFirstChar { it.uppercase() } }
}