mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
chore: KMP audit — commonize code, centralize utilities, eliminate dead abstractions (#5133)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
50ade01e55
commit
72b981f73b
132 changed files with 2186 additions and 916 deletions
|
|
@ -20,7 +20,6 @@ package org.meshtastic.core.ui.util
|
|||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
|
|
@ -36,13 +35,14 @@ import androidx.core.net.toUri
|
|||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.compose.LifecycleEventEffect
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.eygraber.uri.toAndroidUri
|
||||
import com.eygraber.uri.toKmpUri
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.jetbrains.compose.resources.getString
|
||||
import org.meshtastic.core.common.gpsDisabled
|
||||
import org.meshtastic.core.common.util.CommonUri
|
||||
import org.meshtastic.core.common.util.MeshtasticUri
|
||||
import java.net.URLEncoder
|
||||
|
||||
@Composable
|
||||
|
|
@ -107,16 +107,14 @@ actual fun rememberOpenUrl(): (url: String) -> Unit {
|
|||
@Composable
|
||||
@Suppress("Wrapping")
|
||||
actual fun rememberSaveFileLauncher(
|
||||
onUriReceived: (org.meshtastic.core.common.util.MeshtasticUri) -> Unit,
|
||||
onUriReceived: (org.meshtastic.core.common.util.CommonUri) -> Unit,
|
||||
): (defaultFilename: String, mimeType: String) -> Unit {
|
||||
val launcher =
|
||||
androidx.activity.compose.rememberLauncherForActivityResult(
|
||||
androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult(),
|
||||
) { result ->
|
||||
if (result.resultCode == android.app.Activity.RESULT_OK) {
|
||||
result.data?.data?.let { uri ->
|
||||
onUriReceived(uri.toString().let { org.meshtastic.core.common.util.MeshtasticUri(it) })
|
||||
}
|
||||
result.data?.data?.let { uri -> onUriReceived(uri.toKmpUri()) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -137,7 +135,7 @@ actual fun rememberSaveFileLauncher(
|
|||
actual fun rememberOpenFileLauncher(onUriReceived: (CommonUri?) -> Unit): (mimeType: String) -> Unit {
|
||||
val launcher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
|
||||
onUriReceived(uri?.let { CommonUri(it) })
|
||||
onUriReceived(uri?.let { it.toKmpUri() })
|
||||
}
|
||||
return remember(launcher) { { mimeType -> launcher.launch(mimeType) } }
|
||||
}
|
||||
|
|
@ -151,7 +149,7 @@ actual fun rememberReadTextFromUri(): suspend (uri: CommonUri, maxChars: Int) ->
|
|||
withContext(Dispatchers.IO) {
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
try {
|
||||
val androidUri = Uri.parse(uri.toString())
|
||||
val androidUri = uri.toAndroidUri()
|
||||
context.contentResolver.openInputStream(androidUri)?.use { stream ->
|
||||
stream.bufferedReader().use { reader ->
|
||||
val buffer = CharArray(maxChars)
|
||||
|
|
|
|||
|
|
@ -62,12 +62,13 @@ fun <T : Enum<T>> DropDownPreference(
|
|||
enumEntriesOf(selectedItem).filter { it.name != "UNRECOGNIZED" && !it.isDeprecatedEnumEntry() }
|
||||
}
|
||||
|
||||
val items = enumConstants.map {
|
||||
val label = itemLabel?.invoke(it) ?: it.name
|
||||
val icon = itemIcon?.invoke(it)
|
||||
val color = itemColor?.invoke(it)
|
||||
DropDownItem(it, label, icon, color)
|
||||
}
|
||||
val items =
|
||||
enumConstants.map {
|
||||
val label = itemLabel?.invoke(it) ?: it.name
|
||||
val icon = itemIcon?.invoke(it)
|
||||
val color = itemColor?.invoke(it)
|
||||
DropDownItem(it, label, icon, color)
|
||||
}
|
||||
|
||||
DropDownPreference(
|
||||
title = title,
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import androidx.compose.material3.IconToggleButton
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
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.Modifier
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
|
|
@ -49,7 +49,7 @@ fun EditPasswordPreference(
|
|||
onValueChanged: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var isPasswordVisible by remember { mutableStateOf(false) }
|
||||
var isPasswordVisible by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
EditTextPreference(
|
||||
title = title,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.getValue
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -90,10 +91,10 @@ fun MeshtasticImportFAB(
|
|||
) {
|
||||
sharedContact?.let { importDialog(it, onDismissSharedContact) }
|
||||
|
||||
var expanded by remember { mutableStateOf(false) }
|
||||
var showUrlDialog by remember { mutableStateOf(false) }
|
||||
var isNfcScanning by remember { mutableStateOf(false) }
|
||||
var showNfcDisabledDialog by remember { mutableStateOf(false) }
|
||||
var expanded by rememberSaveable { mutableStateOf(false) }
|
||||
var showUrlDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var isNfcScanning by rememberSaveable { mutableStateOf(false) }
|
||||
var showNfcDisabledDialog by rememberSaveable { mutableStateOf(false) }
|
||||
val openNfcSettings = rememberOpenNfcSettings()
|
||||
|
||||
val barcodeScanner = LocalBarcodeScannerProvider.current { contents -> contents?.let { onImport(it) } }
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ import org.jetbrains.compose.resources.DrawableResource
|
|||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.jetbrains.compose.resources.vectorResource
|
||||
import org.meshtastic.core.common.util.formatString
|
||||
import org.meshtastic.core.common.util.MetricFormatter
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.bad
|
||||
import org.meshtastic.core.resources.fair
|
||||
|
|
@ -154,7 +154,7 @@ fun Snr(snr: Float, modifier: Modifier = Modifier) {
|
|||
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = formatString("%s %.2fdB", stringResource(Res.string.snr), snr),
|
||||
text = "${stringResource(Res.string.snr)} ${MetricFormatter.snr(snr, decimalPlaces = 2)}",
|
||||
color = color,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
)
|
||||
|
|
@ -172,7 +172,7 @@ fun Rssi(rssi: Int, modifier: Modifier = Modifier) {
|
|||
}
|
||||
Text(
|
||||
modifier = modifier,
|
||||
text = formatString("%s %ddBm", stringResource(Res.string.rssi), rssi),
|
||||
text = "${stringResource(Res.string.rssi)} ${MetricFormatter.rssi(rssi)}",
|
||||
color = color,
|
||||
style = MaterialTheme.typography.labelSmall,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.meshtastic.core.common.util.formatString
|
||||
import org.meshtastic.core.common.util.MetricFormatter
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.unknown
|
||||
import org.meshtastic.core.ui.icon.BatteryEmpty
|
||||
|
|
@ -49,7 +49,6 @@ import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
|
|||
import org.meshtastic.core.ui.theme.StatusColors.StatusOrange
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
||||
|
||||
private const val FORMAT = "%d%%"
|
||||
private const val SIZE_ICON = 16
|
||||
|
||||
@Suppress("MagicNumber", "LongMethod")
|
||||
|
|
@ -60,7 +59,7 @@ fun MaterialBatteryInfo(
|
|||
voltage: Float? = null,
|
||||
contentColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||
) {
|
||||
val levelString = formatString(FORMAT, level)
|
||||
val levelString = level?.let { MetricFormatter.percent(it) } ?: stringResource(Res.string.unknown)
|
||||
|
||||
Row(
|
||||
modifier = modifier,
|
||||
|
|
@ -130,7 +129,7 @@ fun MaterialBatteryInfo(
|
|||
?.takeIf { it > 0 }
|
||||
?.let {
|
||||
Text(
|
||||
text = formatString("%.2fV", it),
|
||||
text = MetricFormatter.voltage(it),
|
||||
color = contentColor.copy(alpha = 0.8f),
|
||||
style = MaterialTheme.typography.labelMedium.copy(fontSize = 12.sp),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.unit.sp
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.jetbrains.compose.resources.vectorResource
|
||||
import org.meshtastic.core.common.util.formatString
|
||||
import org.meshtastic.core.common.util.MetricFormatter
|
||||
import org.meshtastic.core.model.Node
|
||||
import org.meshtastic.core.resources.Res
|
||||
import org.meshtastic.core.resources.signal_quality
|
||||
|
|
@ -65,7 +65,10 @@ fun SignalInfo(
|
|||
tint = signalColor,
|
||||
)
|
||||
Text(
|
||||
text = formatString("%.1fdB · %ddBm · %s", node.snr, node.rssi, stringResource(quality.nameRes)),
|
||||
text =
|
||||
"${MetricFormatter.snr(
|
||||
node.snr,
|
||||
)} · ${MetricFormatter.rssi(node.rssi)} · ${stringResource(quality.nameRes)}",
|
||||
style =
|
||||
MaterialTheme.typography.labelSmall.copy(
|
||||
fontWeight = FontWeight.Bold,
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
|
|
@ -117,8 +118,8 @@ fun EmojiPickerDialog(
|
|||
onConfirm: (String) -> Unit,
|
||||
) {
|
||||
val viewModel: EmojiPickerViewModel = koinViewModel()
|
||||
var searchQuery by remember { mutableStateOf("") }
|
||||
var selectedCategoryIndex by remember { mutableStateOf(0) }
|
||||
var searchQuery by rememberSaveable { mutableStateOf("") }
|
||||
var selectedCategoryIndex by rememberSaveable { mutableStateOf(0) }
|
||||
|
||||
val recentEmojis by
|
||||
remember(viewModel.customEmojiFrequency) { derivedStateOf { parseRecents(viewModel.customEmojiFrequency) } }
|
||||
|
|
@ -427,7 +428,7 @@ private fun SectionHeader(title: String) {
|
|||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun EmojiCellWithSkinTone(emoji: Emoji, isSelected: Boolean, onSelect: (String) -> Unit) {
|
||||
var showSkinTonePopup by remember { mutableStateOf(false) }
|
||||
var showSkinTonePopup by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
Box {
|
||||
Box(
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateListOf
|
||||
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.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -89,7 +90,7 @@ fun ScannedQrCodeDialog(
|
|||
onDismiss: () -> Unit,
|
||||
onConfirm: (ChannelSet) -> Unit,
|
||||
) {
|
||||
var shouldReplace by remember { mutableStateOf(incoming.lora_config != null) }
|
||||
var shouldReplace by rememberSaveable { mutableStateOf(incoming.lora_config != null) }
|
||||
|
||||
val channelSet =
|
||||
remember(shouldReplace, channels, incoming) {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ package org.meshtastic.core.ui.util
|
|||
import androidx.compose.runtime.Composable
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.meshtastic.core.common.util.CommonUri
|
||||
import org.meshtastic.core.common.util.MeshtasticUri
|
||||
|
||||
/** Returns a function to open the platform's NFC settings. */
|
||||
@Composable expect fun rememberOpenNfcSettings(): () -> Unit
|
||||
|
|
@ -41,7 +40,7 @@ import org.meshtastic.core.common.util.MeshtasticUri
|
|||
/** Returns a launcher function to prompt the user to save a file. The callback receives the saved file URI. */
|
||||
@Composable
|
||||
expect fun rememberSaveFileLauncher(
|
||||
onUriReceived: (MeshtasticUri) -> Unit,
|
||||
onUriReceived: (CommonUri) -> Unit,
|
||||
): (defaultFilename: String, mimeType: String) -> Unit
|
||||
|
||||
/** Returns a launcher function to prompt the user to open/pick a file. The callback receives the selected file URI. */
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ import org.jetbrains.compose.resources.StringResource
|
|||
import org.jetbrains.compose.resources.getString
|
||||
import org.koin.core.annotation.KoinViewModel
|
||||
import org.meshtastic.core.common.util.CommonUri
|
||||
import org.meshtastic.core.common.util.MeshtasticUri
|
||||
import org.meshtastic.core.database.entity.asDeviceVersion
|
||||
import org.meshtastic.core.model.MeshActivity
|
||||
import org.meshtastic.core.model.MyNodeInfo
|
||||
|
|
@ -99,18 +98,16 @@ class UIViewModel(
|
|||
* 2. **Data Import:** If navigation fails, falls back to legacy contact/channel parsing via
|
||||
* [dispatchMeshtasticUri]. This triggers import dialogs for shared nodes or channel configurations.
|
||||
*/
|
||||
fun handleDeepLink(uri: MeshtasticUri, onInvalid: () -> Unit = {}) {
|
||||
val commonUri = CommonUri.parse(uri.uriString)
|
||||
|
||||
fun handleDeepLink(uri: CommonUri, onInvalid: () -> Unit = {}) {
|
||||
// Try navigation routing first
|
||||
val navKeys = DeepLinkRouter.route(commonUri)
|
||||
val navKeys = DeepLinkRouter.route(uri)
|
||||
if (navKeys != null) {
|
||||
_navigationDeepLink.tryEmit(navKeys)
|
||||
return
|
||||
}
|
||||
|
||||
// Fallback to channel/contact importing
|
||||
commonUri.dispatchMeshtasticUri(
|
||||
uri.dispatchMeshtasticUri(
|
||||
onContact = { setSharedContactRequested(it) },
|
||||
onChannel = { setRequestChannelSet(it) },
|
||||
onInvalid = onInvalid,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ package org.meshtastic.core.ui.viewmodel
|
|||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import co.touchlab.kermit.Logger
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -37,7 +38,6 @@ import org.meshtastic.core.resources.UiText
|
|||
import org.meshtastic.core.resources.unknown_error
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.coroutines.cancellation.CancellationException
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import androidx.compose.ui.text.AnnotatedString
|
|||
import androidx.compose.ui.text.TextLinkStyles
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.meshtastic.core.common.util.CommonUri
|
||||
import org.meshtastic.core.common.util.MeshtasticUri
|
||||
|
||||
actual fun createClipEntry(text: String, label: String): ClipEntry =
|
||||
throw UnsupportedOperationException("ClipEntry instantiation not supported on iOS stub")
|
||||
|
|
@ -41,7 +40,7 @@ actual fun annotatedStringFromHtml(html: String, linkStyles: TextLinkStyles?): A
|
|||
|
||||
@Composable
|
||||
actual fun rememberSaveFileLauncher(
|
||||
onUriReceived: (MeshtasticUri) -> Unit,
|
||||
onUriReceived: (CommonUri) -> Unit,
|
||||
): (defaultFilename: String, mimeType: String) -> Unit = { _, _ -> }
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.compose.resources.StringResource
|
||||
import org.meshtastic.core.common.util.CommonUri
|
||||
import org.meshtastic.core.common.util.MeshtasticUri
|
||||
import java.awt.Desktop
|
||||
import java.awt.FileDialog
|
||||
import java.awt.Frame
|
||||
|
|
@ -61,7 +60,7 @@ actual fun rememberOpenUrl(): (url: String) -> Unit = { url ->
|
|||
/** JVM — Opens a native file dialog to save a file. */
|
||||
@Composable
|
||||
actual fun rememberSaveFileLauncher(
|
||||
onUriReceived: (MeshtasticUri) -> Unit,
|
||||
onUriReceived: (CommonUri) -> Unit,
|
||||
): (defaultFilename: String, mimeType: String) -> Unit = { defaultFilename, _ ->
|
||||
val dialog = FileDialog(null as Frame?, "Save File", FileDialog.SAVE)
|
||||
dialog.file = defaultFilename
|
||||
|
|
@ -70,7 +69,7 @@ actual fun rememberSaveFileLauncher(
|
|||
val dir = dialog.directory
|
||||
if (file != null && dir != null) {
|
||||
val path = File(dir, file)
|
||||
onUriReceived(MeshtasticUri(path.toURI().toString()))
|
||||
onUriReceived(CommonUri.parse(path.toURI().toString()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -83,7 +82,7 @@ actual fun rememberOpenFileLauncher(onUriReceived: (CommonUri?) -> Unit): (mimeT
|
|||
val dir = dialog.directory
|
||||
if (file != null && dir != null) {
|
||||
val path = File(dir, file)
|
||||
onUriReceived(CommonUri(path.toURI()))
|
||||
onUriReceived(CommonUri.parse(path.toURI().toString()))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue