chore: fix build warnings and migrate deprecated APIs (#6130)

This commit is contained in:
James Rich
2026-07-07 13:17:42 -05:00
committed by GitHub
parent 5fde2d3679
commit 33fbb9f7fc
41 changed files with 104 additions and 60 deletions

View File

@@ -1046,8 +1046,8 @@ private fun CacheInfoDialog(mapView: MapView, onDismiss: () -> Unit) {
onDismiss = onDismiss,
negativeButton = { TextButton(onClick = { onDismiss() }) { Text(text = stringResource(Res.string.close)) } },
) {
val capacityMb = (cacheCapacity / (1024 * 1024)).toLong()
val usageMb = (currentCacheUsage / (1024 * 1024)).toLong()
val capacityMb = cacheCapacity / (1024 * 1024)
val usageMb = currentCacheUsage / (1024 * 1024)
Text(modifier = Modifier.padding(16.dp), text = stringResource(Res.string.map_cache_info, capacityMb, usageMb))
}
}

View File

@@ -1230,6 +1230,7 @@ private fun offsetPolyline(
// region --- Map Layers ---
@OptIn(MapsComposeExperimentalApi::class)
@Composable
private fun MapLayerOverlay(layerItem: MapLayerItem, mapViewModel: MapViewModel) {
val context = LocalContext.current

View File

@@ -19,8 +19,7 @@ package org.meshtastic.app.map.model
class CustomTileSource {
companion object {
fun getTileSource(index: Int) {
index
}
// No-op stub for the Google flavor (osmdroid tile sources are fdroid-only).
fun getTileSource(index: Int) {}
}
}

View File

@@ -38,6 +38,7 @@ import kotlin.test.Test
class KoinVerificationTest {
@OptIn(org.koin.core.annotation.KoinExperimentalAPI::class)
@Test
fun verifyKoinConfiguration() {
AppKoinModule()

View File

@@ -18,6 +18,7 @@ package org.meshtastic.buildlogic
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.project
import org.jetbrains.dokka.gradle.DokkaExtension
import java.net.URI
@@ -73,5 +74,5 @@ fun Project.configureDokkaAggregation(subprojectPaths: List<String>) {
// Add each subproject as a Dokka dependency using declared paths rather than
// iterating live subproject objects. This avoids cross-project configuration
// access and is compatible with Gradle Isolated Projects.
subprojectPaths.forEach { path -> dependencies.add("dokka", project(path)) }
subprojectPaths.forEach { path -> dependencies.add("dokka", dependencies.project(path)) }
}

View File

@@ -19,6 +19,7 @@ package org.meshtastic.buildlogic
import kotlinx.kover.gradle.plugin.dsl.KoverProjectExtension
import org.gradle.api.Project
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.project
fun Project.configureKover() {
val isCi = providers.gradleProperty("ci").map { it.toBoolean() }.getOrElse(false)
@@ -64,5 +65,5 @@ fun Project.configureKover() {
* Isolated Projects. The list should match the modules declared in `settings.gradle.kts`.
*/
fun Project.configureKoverAggregation(subprojectPaths: List<String>) {
subprojectPaths.forEach { path -> dependencies.add("kover", project(path)) }
subprojectPaths.forEach { path -> dependencies.add("kover", dependencies.project(path)) }
}

View File

@@ -44,7 +44,7 @@ kotlin {
implementation(projects.core.testing)
}
val androidHostTest by getting {
getByName("androidHostTest") {
dependencies {
implementation(projects.core.testing)
implementation(libs.kotlinx.coroutines.test)

View File

@@ -46,6 +46,10 @@ internal fun resolveKableScanFilter(serviceUuid: Uuid?, address: String?): Kable
else -> KableScanFilter.None
}
// Kable's Advertisement.identifier is an expect typealias: String on Android/JVM/JS but Uuid on Apple.
// toString() looks redundant when compiling the Android/JVM view (hence the warning) but is required to
// normalize the Apple Uuid to the String this result carries, so it must stay.
@Suppress("REDUNDANT_CALL_OF_CONVERSION_METHOD")
private fun Advertisement.toScanResult(): KableScanResult =
KableScanResult(identifier = identifier.toString(), name = name, advertisement = this)

View File

@@ -49,7 +49,7 @@ kotlin {
}
// Room / SQLite runtime shared between Android and Desktop JVM targets
val jvmAndroidMain by getting {
getByName("jvmAndroidMain") {
dependencies {
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.paging)
@@ -67,7 +67,7 @@ kotlin {
implementation(libs.kotlinx.coroutines.test)
}
val androidHostTest by getting {
getByName("androidHostTest") {
dependencies {
// JVM variant provides the host-platform native for BundledSQLiteDriver (same as core:database)
runtimeOnly("androidx.sqlite:sqlite-bundled-jvm:2.7.0")

View File

@@ -270,7 +270,7 @@ class AiFunctionProviderImpl(
when {
totalCount == 0 -> 0
onlineCount == 0 -> HEALTH_SCORE_DEGRADED
else -> (HEALTH_SCORE_BASE + (HEALTH_SCORE_ONLINE_RATIO * onlineCount) / totalCount).toInt()
else -> HEALTH_SCORE_BASE + (HEALTH_SCORE_ONLINE_RATIO * onlineCount) / totalCount
}
// Find most recent packet: max lastHeard across all nodes (convert seconds to ms)

View File

@@ -50,7 +50,7 @@ kotlin {
implementation(libs.androidx.room.testing)
}
val androidHostTest by getting {
getByName("androidHostTest") {
dependencies {
implementation(libs.androidx.sqlite.bundled)
// JVM variant provides the host-platform native for BundledSQLiteDriver
@@ -60,7 +60,7 @@ kotlin {
implementation(libs.junit)
}
}
val androidDeviceTest by getting {
getByName("androidDeviceTest") {
dependencies {
implementation(libs.androidx.room.testing)
implementation(libs.androidx.test.ext.junit)

View File

@@ -28,7 +28,7 @@ kotlin {
android { withHostTest {} }
sourceSets {
val jvmTest by getting {
getByName("jvmTest") {
dependencies {
implementation(libs.konsist)
implementation(libs.junit)

View File

@@ -60,7 +60,7 @@ kotlin {
api(libs.androidx.annotation)
api(libs.androidx.core.ktx)
}
val androidDeviceTest by getting {
getByName("androidDeviceTest") {
dependencies {
implementation(libs.androidx.test.ext.junit)
implementation(libs.androidx.test.runner)

View File

@@ -55,7 +55,7 @@ kotlin {
implementation(libs.jetbrains.lifecycle.runtime)
}
val jvmMain by getting {
getByName("jvmMain") {
dependencies {
implementation(libs.ktor.client.java)
implementation(libs.jserialcomm)

View File

@@ -114,6 +114,9 @@ class MQTTRepositoryImpl(
scope.launch { safeCatching { c?.close() }.onFailure { e -> Logger.w(e) { "MQTT clean disconnect failed" } } }
}
// json_enabled is deprecated in the protobuf schema but remains the only way to toggle MQTT JSON
// publish/consume, so we must keep reading it until the firmware/proto provides a replacement.
@Suppress("DEPRECATION")
@OptIn(ExperimentalSerializationApi::class)
override val proxyMessageFlow: Flow<MqttClientProxyMessage> = callbackFlow {
// Append a per-connection random id. myId identifies the *node* (and is null →

View File

@@ -52,7 +52,7 @@ kotlin {
implementation(libs.koin.androidx.workmanager)
}
val androidHostTest by getting {
getByName("androidHostTest") {
dependencies {
implementation(projects.core.testing)
implementation(libs.androidx.test.ext.junit)

View File

@@ -35,7 +35,10 @@ import org.meshtastic.core.repository.StoredPassphrase
@Single(binds = [LockdownPassphraseStore::class])
class LockdownPassphraseStoreImpl(app: Application) : LockdownPassphraseStore {
@Suppress("TooGenericExceptionCaught")
// androidx.security.crypto (MasterKey / EncryptedSharedPreferences) is deprecated by Google with no
// drop-in AndroidX replacement yet. Migrating encrypted storage is a separate, security-sensitive
// effort; suppress until a stable replacement (e.g. Tink) is adopted.
@Suppress("TooGenericExceptionCaught", "DEPRECATION")
private val prefs: SharedPreferences? by lazy {
try {
val masterKey = MasterKey.Builder(app).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build()

View File

@@ -34,7 +34,10 @@ import org.meshtastic.core.repository.StoredSecurityKeys
@Single(binds = [SecurityKeyBackupStore::class])
class SecurityKeyBackupStoreImpl(app: Application) : SecurityKeyBackupStore {
@Suppress("TooGenericExceptionCaught")
// androidx.security.crypto (MasterKey / EncryptedSharedPreferences) is deprecated by Google with no
// drop-in AndroidX replacement yet. Migrating encrypted storage is a separate, security-sensitive
// effort; suppress until a stable replacement (e.g. Tink) is adopted.
@Suppress("TooGenericExceptionCaught", "DEPRECATION")
private val prefs: SharedPreferences? by lazy {
try {
val masterKey = MasterKey.Builder(app).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build()

View File

@@ -717,7 +717,7 @@ class SharedRadioInterfaceService(
// would replay stale bytes ahead of the next session's firmware handshake, since the channel
// outlives the orchestrator's per-start scope.
@Suppress("EmptyWhileBlock", "ControlFlowWithEmptyBody")
while (_receivedData.tryReceive().isSuccess) Unit
while (_receivedData.tryReceive().isSuccess) {}
}
override fun onConnect() {

View File

@@ -23,11 +23,17 @@ import nl.adaptivity.xmlutil.serialization.XML
import kotlin.time.Clock
import kotlin.time.Instant
private val xmlParser = XML {
// xmlutil 1.0.0 moved repairNamespaces from the policy builder to the top-level XML config.
repairNamespaces = false
defaultPolicy { ignoreUnknownChildren() }
}
// XML.compat preserves xmlutil's pre-1.0 serialization defaults. The non-deprecated 1.0 builders
// (XML.recommended/XML.V1) intentionally change serialization defaults, which would alter the CoT XML
// wire format we exchange with ATAK/TAK servers. Staying on the compat policy until that migration can
// be validated against real TAK interop; suppress the soft-deprecation on the factory itself.
@Suppress("DEPRECATION")
private val xmlParser =
XML.compat {
// xmlutil 1.0.0 moved repairNamespaces from the policy builder to the top-level XML config.
repairNamespaces = false
defaultPolicy { ignoreUnknownChildren() }
}
class CoTXmlParser(private val xml: String) {
fun parse(): Result<CoTMessage> = try {

View File

@@ -46,10 +46,16 @@ object TAKDataPackageGenerator {
private const val CLIENT_P12_FILE_NAME = "client.p12"
private const val PACKAGE_NAME = "Meshtastic_TAK_Server"
private val xmlSerializer = XML {
xmlDeclMode = XmlDeclMode.Charset
indentString = " "
}
// XML.compat preserves xmlutil's pre-1.0 serialization defaults. The non-deprecated 1.0 builders
// (XML.recommended/XML.V1) intentionally change serialization defaults, which would alter the CoT
// XML wire format consumed by ATAK/TAK servers. Staying on the compat policy until that migration
// can be validated against real TAK interop; suppress the soft-deprecation on the factory itself.
@Suppress("DEPRECATION")
private val xmlSerializer =
XML.compat {
xmlDeclMode = XmlDeclMode.Charset
indentString = " "
}
/**
* Platform-specific hook for reading the bundled TLS certificate bytes. Default implementation lives in

View File

@@ -109,7 +109,7 @@ class FakeRadioInterfaceService(override val serviceScope: CoroutineScope = Main
override fun resetReceivedBuffer() {
@Suppress("EmptyWhileBlock", "ControlFlowWithEmptyBody")
while (_receivedData.tryReceive().isSuccess) Unit
while (_receivedData.tryReceive().isSuccess) {}
}
// --- Helper methods for testing ---

View File

@@ -64,7 +64,7 @@ kotlin {
implementation(libs.jetbrains.lifecycle.runtime.compose)
}
val jvmAndroidMain by getting { dependencies { implementation(libs.compose.multiplatform.ui.tooling) } }
getByName("jvmAndroidMain") { dependencies { implementation(libs.compose.multiplatform.ui.tooling) } }
androidMain.dependencies { implementation(libs.androidx.activity.compose) }

View File

@@ -332,6 +332,10 @@ actual fun isWifiUnavailable(): Boolean {
* the transport reduction can delegate to the platform-agnostic [anyNetworkScanTransportAvailable] helper (which is
* unit-tested in `commonTest`).
*/
// ConnectivityManager.allNetworks is deprecated (API 31+) in favor of NetworkCallback-based discovery,
// but a synchronous snapshot of active networks is exactly what this transport probe needs. Suppress
// until a callback-based rewrite is warranted.
@Suppress("DEPRECATION")
private fun ConnectivityManager.hasLocalNetwork(): Boolean {
val transports =
allNetworks.mapNotNull { network ->

View File

@@ -52,10 +52,10 @@ internal class EmojiPickerViewModel(
emojiRepository.preload()
_isLoaded.value = true
} catch (e: MissingResourceException) {
Logger.e("EmojiPickerViewModel", e) { "Failed to load emoji data" }
Logger.e(tag = "EmojiPickerViewModel", throwable = e) { "Failed to load emoji data" }
_loadError.value = true
} catch (e: IllegalStateException) {
Logger.e("EmojiPickerViewModel", e) { "Failed to load emoji data" }
Logger.e(tag = "EmojiPickerViewModel", throwable = e) { "Failed to load emoji data" }
_loadError.value = true
}
}

View File

@@ -195,7 +195,7 @@ fun normalizeReplacementSettings(
}
/** True when a [ChannelSettings] carries no name and no PSK — a placeholder, not an intended channel. */
private fun ChannelSettings.isPlaceholder(): Boolean = name.isNullOrBlank() && (psk == null || psk.size == 0)
private fun ChannelSettings.isPlaceholder(): Boolean = name.isNullOrBlank() && psk.size == 0
/**
* Applies an imported [ChannelSet] as an authoritative replacement to the radio and local cache.

View File

@@ -46,7 +46,7 @@ actual fun rememberSaveFileLauncher(
@Composable
actual fun rememberOpenFileLauncher(onUriReceived: (CommonUri?) -> Unit): (mimeType: String) -> Unit = { _ -> }
@Composable actual fun rememberReadTextFromUri(): suspend (CommonUri, Int) -> String? = { _, _ -> null }
@Composable actual fun rememberReadTextFromUri(): suspend (uri: CommonUri, maxChars: Int) -> String? = { _, _ -> null }
@Composable actual fun KeepScreenOn(enabled: Boolean) {}

View File

@@ -76,7 +76,9 @@ actual fun rememberSaveFileLauncher(
/** JVM — Opens a native file dialog to pick a file. */
@Composable
actual fun rememberOpenFileLauncher(onUriReceived: (CommonUri?) -> Unit): (mimeType: String) -> Unit = { _ ->
val dialog = FileDialog(null as? Frame, "Open File", FileDialog.LOAD)
// Explicit Frame? local disambiguates the FileDialog(Frame, ...) overload from FileDialog(Dialog, ...)
val parentFrame: Frame? = null
val dialog = FileDialog(parentFrame, "Open File", FileDialog.LOAD)
dialog.isVisible = true
val file = dialog.file
val dir = dialog.directory

View File

@@ -25,7 +25,6 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.emptyFlow
import org.meshtastic.core.model.ConnectionState
import org.meshtastic.core.model.DeviceType
@@ -72,8 +71,8 @@ class NoopRadioInterfaceService : RadioInterfaceService {
override fun isMockTransport(): Boolean = false
override val receivedData = MutableSharedFlow<ByteArray>()
override val meshActivity: Flow<MeshActivity> = MutableSharedFlow<MeshActivity>().asFlow()
override val connectionError: Flow<String> = MutableSharedFlow<String>().asFlow()
override val meshActivity: Flow<MeshActivity> = MutableSharedFlow<MeshActivity>()
override val connectionError: Flow<String> = MutableSharedFlow<String>()
override fun sendToRadio(bytes: ByteArray) {
logWarn("NoopRadioInterfaceService.sendToRadio(${bytes.size} bytes)")

View File

@@ -257,12 +257,15 @@ class HomeScreen(
}
}
return ConversationItem.Builder()
.setId(conversation.contactKey)
.setTitle(CarText.create(conversation.displayName))
.setMessages(messages)
.setSelf(selfPerson)
.setConversationCallback(callback)
// car-app 1.7 deprecated the no-arg Builder + individual setters in favor of the
// required-args constructor (id, title, self, messages, conversationCallback).
return ConversationItem.Builder(
conversation.contactKey,
CarText.create(conversation.displayName),
selfPerson,
messages,
callback,
)
.setGroupConversation(conversation.contactKey.contains(NodeAddress.ID_BROADCAST))
.build()
}

View File

@@ -42,7 +42,7 @@ kotlin {
androidMain.dependencies { implementation(libs.usb.serial.android) }
val androidHostTest by getting {
getByName("androidHostTest") {
dependencies {
implementation(projects.core.testing)
implementation(libs.kotlinx.coroutines.test)

View File

@@ -34,12 +34,12 @@ import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuAnchorType
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
@@ -361,7 +361,7 @@ private fun DwellTimePicker(
readOnly = true,
enabled = enabled,
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
modifier = Modifier.fillMaxWidth().menuAnchor(MenuAnchorType.PrimaryNotEditable),
modifier = Modifier.fillMaxWidth().menuAnchor(ExposedDropdownMenuAnchorType.PrimaryNotEditable),
)
ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
DWELL_OPTIONS.forEach { minutes ->

View File

@@ -85,7 +85,7 @@ internal fun MeshBeaconInvitationCard(
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
if (region != null && region != RegionCode.UNSET) {
if (region != RegionCode.UNSET) {
Text(
text = stringResource(Res.string.mesh_beacon_offer_region, region.name),
style = MaterialTheme.typography.labelMedium,

View File

@@ -57,8 +57,8 @@ kotlin {
*
* This task runs automatically before resource generation tasks.
*/
val syncDocsToComposeResources by
tasks.registering(Sync::class) {
val syncDocsToComposeResources =
tasks.register<Sync>("syncDocsToComposeResources") {
description = "Syncs docs/en/ markdown source into composeResources for in-app bundling"
group = "docs"
@@ -102,8 +102,8 @@ tasks
// clean, or commit deliberately when refreshing the docs/Jekyll image set. Add a CI staleness gate
// if drift becomes a problem.
val syncTranslatedDocsToComposeResources by
tasks.registering(Copy::class) {
val syncTranslatedDocsToComposeResources =
tasks.register<Copy>("syncTranslatedDocsToComposeResources") {
description = "Syncs Crowdin-translated docs into locale-qualified composeResources"
group = "docs"

View File

@@ -145,7 +145,6 @@ import org.meshtastic.core.ui.icon.Dangerous
import org.meshtastic.core.ui.icon.Folder
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.icon.Refresh
import org.meshtastic.core.ui.icon.SystemUpdate
import org.meshtastic.core.ui.icon.Usb
import org.meshtastic.core.ui.icon.Warning
import org.meshtastic.core.ui.icon.Wifi
@@ -462,7 +461,6 @@ private fun ReadyState(
FirmwareUpdateMethod.Ble -> MeshtasticIcons.Bluetooth
FirmwareUpdateMethod.Usb -> MeshtasticIcons.Usb
FirmwareUpdateMethod.Wifi -> MeshtasticIcons.Wifi
else -> MeshtasticIcons.SystemUpdate
},
contentDescription = null,
)

View File

@@ -21,6 +21,7 @@ import androidx.lifecycle.viewModelScope
import co.touchlab.kermit.Logger
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.delay
@@ -134,6 +135,7 @@ class FirmwareUpdateViewModel(
}
}
@OptIn(DelicateCoroutinesApi::class)
override fun onCleared() {
super.onCleared()
// viewModelScope is already cancelled when onCleared() runs, so launch cleanup on the

View File

@@ -176,8 +176,13 @@ constructor(
val logs = args[LOGS_INDEX] as LogsGroup
val identity = args[IDENTITY_INDEX] as IdentityGroup
val metadata = args[METADATA_INDEX] as MetadataGroup
@Suppress("UNCHECKED_CAST")
val requests = args[REQUESTS_INDEX] as Pair<List<MeshLog>, List<MeshLog>>
val (hw, deviceLinks) = args[HARDWARE_INDEX] as Pair<DeviceHardware?, List<DeviceLink>>
@Suppress("UNCHECKED_CAST")
val hardwareAndLinks = args[HARDWARE_INDEX] as Pair<DeviceHardware?, List<DeviceLink>>
val (hw, deviceLinks) = hardwareAndLinks
val (trReqs, niReqs) = requests
val isLocal = node.num == identity.ourNode?.num

View File

@@ -49,7 +49,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.patrykandpatrick.vico.compose.cartesian.VicoScrollState
import com.patrykandpatrick.vico.compose.cartesian.axis.Axis
import com.patrykandpatrick.vico.compose.cartesian.data.lineSeries
import com.patrykandpatrick.vico.compose.cartesian.data.lineModel
import com.patrykandpatrick.vico.compose.cartesian.layer.LineCartesianLayer
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.stringResource
@@ -223,7 +223,7 @@ private fun AirQualityChart(
activeMetrics.forEachIndexed { index, metric ->
val metricData = metricDataSets[index]
if (metricData.isNotEmpty()) {
lineSeries {
lineModel {
series(x = metricData.map { it.time }, y = metricData.map { metric.getValue(it) ?: 0f })
}
}

View File

@@ -133,7 +133,7 @@ fun GenericMetricChart(
bottomAxis = bottomAxis,
marker = marker,
markerVisibilityListener = markerVisibilityListener,
persistentMarkers = { _ -> if (selectedX != null && marker != null) marker at selectedX else null },
persistentMarkers = { _ -> if (selectedX != null && marker != null) marker at selectedX },
fadingEdges = rememberFadingEdges(),
decorations = decorations,
// Telemetry timestamps arrive at irregular intervals. Without an explicit

View File

@@ -58,7 +58,7 @@
<ID>ModifierMissing:ExternalNotificationConfigScreen.android.kt:@Suppress("TooGenericExceptionCaught") @Composable actual fun RingtoneTrailingIcon</ID>
<ID>ModifierMissing:FilterSettingsScreen.kt:@Composable fun FilterSettingsScreen</ID>
<ID>ModifierMissing:LoRaConfigItemList.kt:@Composable fun LoRaConfigScreen</ID>
<ID>ModifierMissing:MQTTConfigItemList.kt:@Composable fun MQTTConfigScreen</ID>
<ID>ModifierMissing:MQTTConfigItemList.kt:@Suppress("DEPRECATION") @Composable fun MQTTConfigScreen</ID>
<ID>ModifierMissing:MapReportingPreference.kt:@Suppress("LongMethod") @Composable fun MapReportingPreference</ID>
<ID>ModifierMissing:ModuleConfigurationScreen.kt:@Composable fun ModuleConfigurationScreen</ID>
<ID>ModifierMissing:NeighborInfoConfigItemList.kt:@Composable fun NeighborInfoConfigScreen</ID>

View File

@@ -89,6 +89,9 @@ import org.meshtastic.core.ui.component.TitledCard
import org.meshtastic.feature.settings.radio.RadioConfigViewModel
import org.meshtastic.proto.ModuleConfig
// json_enabled is deprecated in the protobuf schema but remains the only toggle for MQTT JSON
// publish/consume, so the settings UI must keep exposing it until the proto provides a replacement.
@Suppress("DEPRECATION")
@Composable
fun MQTTConfigScreen(viewModel: RadioConfigViewModel, onBack: () -> Unit) {
val state by viewModel.radioConfigState.collectAsStateWithLifecycle()