diff --git a/app/src/google/kotlin/org/meshtastic/app/map/MapView.kt b/app/src/google/kotlin/org/meshtastic/app/map/MapView.kt index 530fc0c7b..0418d76b7 100644 --- a/app/src/google/kotlin/org/meshtastic/app/map/MapView.kt +++ b/app/src/google/kotlin/org/meshtastic/app/map/MapView.kt @@ -21,8 +21,6 @@ package org.meshtastic.app.map import android.Manifest import android.app.Activity import android.content.Intent -import android.graphics.Canvas -import android.graphics.Paint import android.net.Uri import android.view.WindowManager import androidx.activity.compose.rememberLauncherForActivityResult @@ -56,7 +54,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import androidx.core.graphics.createBitmap import androidx.lifecycle.compose.collectAsStateWithLifecycle import co.touchlab.kermit.Logger import com.google.accompanist.permissions.ExperimentalPermissionsApi @@ -67,8 +64,6 @@ import com.google.android.gms.location.LocationResult import com.google.android.gms.location.LocationServices import com.google.android.gms.location.Priority import com.google.android.gms.maps.CameraUpdateFactory -import com.google.android.gms.maps.model.BitmapDescriptor -import com.google.android.gms.maps.model.BitmapDescriptorFactory import com.google.android.gms.maps.model.CameraPosition import com.google.android.gms.maps.model.JointType import com.google.android.gms.maps.model.LatLng @@ -798,7 +793,6 @@ private fun MainMapContent( mapFilterState = mapFilterState, myNodeNum = myNodeNum ?: 0, isConnected = isConnected, - unicodeEmojiToBitmapProvider = ::unicodeEmojiToBitmap, onEditWaypointRequest = onEditWaypointRequest, selectedWaypointId = selectedWaypointId, ) @@ -1068,23 +1062,6 @@ internal fun convertIntToEmoji(unicodeCodePoint: Int): String = try { "\uD83D\uDCCD" } -internal fun unicodeEmojiToBitmap(icon: Int): BitmapDescriptor { - val unicodeEmoji = convertIntToEmoji(icon) - val paint = - Paint(Paint.ANTI_ALIAS_FLAG).apply { - textSize = 64f - color = android.graphics.Color.BLACK - textAlign = Paint.Align.CENTER - } - val baseline = -paint.ascent() - val width = (paint.measureText(unicodeEmoji) + 0.5f).toInt() - val height = (baseline + paint.descent() + 0.5f).toInt() - val image = createBitmap(width, height, android.graphics.Bitmap.Config.ARGB_8888) - val canvas = Canvas(image) - canvas.drawText(unicodeEmoji, width / 2f, baseline, paint) - return BitmapDescriptorFactory.fromBitmap(image) -} - @Suppress("NestedBlockDepth") fun Uri.getFileName(context: android.content.Context): String { var name = this.lastPathSegment ?: "layer_$nowMillis" diff --git a/app/src/google/kotlin/org/meshtastic/app/map/component/WaypointMarkers.kt b/app/src/google/kotlin/org/meshtastic/app/map/component/WaypointMarkers.kt index d4a53dcc4..61cdab9f1 100644 --- a/app/src/google/kotlin/org/meshtastic/app/map/component/WaypointMarkers.kt +++ b/app/src/google/kotlin/org/meshtastic/app/map/component/WaypointMarkers.kt @@ -16,15 +16,22 @@ */ package org.meshtastic.app.map.component +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext -import com.google.android.gms.maps.model.BitmapDescriptor +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import com.google.android.gms.maps.model.LatLng +import com.google.maps.android.compose.MapsComposeExperimentalApi import com.google.maps.android.compose.Marker +import com.google.maps.android.compose.rememberComposeBitmapDescriptor import com.google.maps.android.compose.rememberUpdatedMarkerState import kotlinx.coroutines.launch +import org.meshtastic.app.map.convertIntToEmoji import org.meshtastic.core.model.util.GeoConstants.DEG_D import org.meshtastic.core.resources.Res import org.meshtastic.core.resources.locked @@ -32,13 +39,13 @@ import org.meshtastic.core.ui.util.showToast import org.meshtastic.feature.map.BaseMapViewModel import org.meshtastic.proto.Waypoint +@OptIn(MapsComposeExperimentalApi::class) @Composable fun WaypointMarkers( displayableWaypoints: List, mapFilterState: BaseMapViewModel.MapFilterState, myNodeNum: Int, isConnected: Boolean, - unicodeEmojiToBitmapProvider: (Int) -> BitmapDescriptor, onEditWaypointRequest: (Waypoint) -> Unit, selectedWaypointId: Int? = null, ) { @@ -57,14 +64,16 @@ fun WaypointMarkers( } } + val iconCodePoint = if ((waypoint.icon ?: 0) == 0) PUSHPIN else waypoint.icon!! + val emojiText = convertIntToEmoji(iconCodePoint) + val icon = + rememberComposeBitmapDescriptor(iconCodePoint) { + Text(text = emojiText, fontSize = 32.sp, modifier = Modifier.padding(2.dp)) + } + Marker( state = markerState, - icon = - if ((waypoint.icon ?: 0) == 0) { - unicodeEmojiToBitmapProvider(PUSHPIN) // Default icon (Round Pushpin) - } else { - unicodeEmojiToBitmapProvider(waypoint.icon!!) - }, + icon = icon, title = (waypoint.name ?: "").replace('\n', ' ').replace('\b', ' '), snippet = (waypoint.description ?: "").replace('\n', ' ').replace('\b', ' '), visible = true, diff --git a/app/src/test/kotlin/org/meshtastic/app/ui/NavigationAssemblyTest.kt b/app/src/test/kotlin/org/meshtastic/app/ui/NavigationAssemblyTest.kt index ef4dab3e6..0665d50db 100644 --- a/app/src/test/kotlin/org/meshtastic/app/ui/NavigationAssemblyTest.kt +++ b/app/src/test/kotlin/org/meshtastic/app/ui/NavigationAssemblyTest.kt @@ -16,7 +16,7 @@ */ package org.meshtastic.app.ui -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.junit4.v2.createComposeRule import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.rememberNavBackStack diff --git a/core/barcode/src/test/kotlin/org/meshtastic/core/barcode/BarcodeScannerTest.kt b/core/barcode/src/test/kotlin/org/meshtastic/core/barcode/BarcodeScannerTest.kt index bd3490566..e06562cfb 100644 --- a/core/barcode/src/test/kotlin/org/meshtastic/core/barcode/BarcodeScannerTest.kt +++ b/core/barcode/src/test/kotlin/org/meshtastic/core/barcode/BarcodeScannerTest.kt @@ -16,7 +16,7 @@ */ package org.meshtastic.core.barcode -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.junit4.v2.createComposeRule import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith diff --git a/core/common/build.gradle.kts b/core/common/build.gradle.kts index f3e86f0c9..08ec08865 100644 --- a/core/common/build.gradle.kts +++ b/core/common/build.gradle.kts @@ -41,8 +41,6 @@ kotlin { } androidMain.dependencies { api(libs.androidx.core.ktx) } - val androidHostTest by getting { dependencies { implementation(libs.robolectric) } } - commonTest.dependencies { implementation(libs.kotlinx.coroutines.test) } } } diff --git a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/Converters.kt b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/Converters.kt index 35746f68f..67433459c 100644 --- a/core/database/src/commonMain/kotlin/org/meshtastic/core/database/Converters.kt +++ b/core/database/src/commonMain/kotlin/org/meshtastic/core/database/Converters.kt @@ -18,6 +18,7 @@ package org.meshtastic.core.database import androidx.room3.TypeConverter import co.touchlab.kermit.Logger +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import okio.ByteString import okio.ByteString.Companion.toByteString @@ -33,10 +34,12 @@ import org.meshtastic.proto.User @Suppress("TooManyFunctions") class Converters { + @OptIn(ExperimentalSerializationApi::class) private val json = Json { isLenient = true ignoreUnknownKeys = true encodeDefaults = true + exceptionsWithDebugInfo = false } @TypeConverter fun dataFromString(value: String): DataPacket = json.decodeFromString(DataPacket.serializer(), value) diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index 8a5f3fb21..1c0d14a01 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -63,9 +63,6 @@ kotlin { commonTest.dependencies { implementation(projects.core.testing) implementation(libs.kotlinx.coroutines.test) - implementation(libs.turbine) - implementation(libs.kotest.assertions) - implementation(libs.kotest.property) } } } diff --git a/core/network/src/commonMain/kotlin/org/meshtastic/core/network/di/CoreNetworkModule.kt b/core/network/src/commonMain/kotlin/org/meshtastic/core/network/di/CoreNetworkModule.kt index 9b9d49828..0fbed14a8 100644 --- a/core/network/src/commonMain/kotlin/org/meshtastic/core/network/di/CoreNetworkModule.kt +++ b/core/network/src/commonMain/kotlin/org/meshtastic/core/network/di/CoreNetworkModule.kt @@ -16,6 +16,7 @@ */ package org.meshtastic.core.network.di +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import org.koin.core.annotation.ComponentScan import org.koin.core.annotation.Module @@ -24,10 +25,12 @@ import org.koin.core.annotation.Single @Module @ComponentScan("org.meshtastic.core.network") class CoreNetworkModule { + @OptIn(ExperimentalSerializationApi::class) @Single fun provideJson(): Json = Json { isLenient = true ignoreUnknownKeys = true coerceInputValues = true + exceptionsWithDebugInfo = false } } diff --git a/core/network/src/commonMain/kotlin/org/meshtastic/core/network/repository/MQTTRepositoryImpl.kt b/core/network/src/commonMain/kotlin/org/meshtastic/core/network/repository/MQTTRepositoryImpl.kt index 4b95f0191..41fb652ed 100644 --- a/core/network/src/commonMain/kotlin/org/meshtastic/core/network/repository/MQTTRepositoryImpl.kt +++ b/core/network/src/commonMain/kotlin/org/meshtastic/core/network/repository/MQTTRepositoryImpl.kt @@ -35,6 +35,7 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import okio.ByteString.Companion.toByteString import org.koin.core.annotation.Single @@ -62,7 +63,12 @@ class MQTTRepositoryImpl( } private var client: MQTTClient? = null - private val json = Json { ignoreUnknownKeys = true } + + @OptIn(ExperimentalSerializationApi::class) + private val json = Json { + ignoreUnknownKeys = true + exceptionsWithDebugInfo = false + } private val scope = CoroutineScope(dispatchers.default + SupervisorJob()) private var clientJob: Job? = null private val publishSemaphore = Semaphore(20) @@ -115,6 +121,9 @@ class MQTTRepositoryImpl( Logger.d { "MQTT parsed JSON payload successfully" } trySend(MqttClientProxyMessage(topic = topic, text = jsonStr, retained = packet.retain)) + } catch (e: kotlinx.serialization.json.JsonDecodingException) { + @OptIn(ExperimentalSerializationApi::class) + Logger.e(e) { "Failed to parse MQTT JSON: ${e.shortMessage} (path: ${e.path})" } } catch (e: kotlinx.serialization.SerializationException) { Logger.e(e) { "Failed to parse MQTT JSON: ${e.message}" } } catch (e: IllegalArgumentException) { diff --git a/core/takserver/build.gradle.kts b/core/takserver/build.gradle.kts index a1b1a7acb..02343cae3 100644 --- a/core/takserver/build.gradle.kts +++ b/core/takserver/build.gradle.kts @@ -56,9 +56,6 @@ kotlin { commonTest.dependencies { implementation(projects.core.testing) implementation(libs.kotlinx.coroutines.test) - implementation(libs.turbine) - implementation(libs.kotest.assertions) - implementation(libs.kotest.property) } } } diff --git a/core/ui/build.gradle.kts b/core/ui/build.gradle.kts index 05d276cf0..bbe3204e5 100644 --- a/core/ui/build.gradle.kts +++ b/core/ui/build.gradle.kts @@ -70,9 +70,6 @@ kotlin { implementation(projects.core.testing) implementation(libs.junit) implementation(libs.kotlinx.coroutines.test) - implementation(libs.turbine) - implementation(libs.kotest.assertions) - implementation(libs.kotest.property) } val androidHostTest by getting { dependencies { implementation(libs.androidx.test.runner) } } diff --git a/core/ui/src/androidTest/kotlin/org/meshtastic/core/ui/component/AlertHostTest.kt b/core/ui/src/androidTest/kotlin/org/meshtastic/core/ui/component/AlertHostTest.kt index 5afc97a6f..b6abd64b0 100644 --- a/core/ui/src/androidTest/kotlin/org/meshtastic/core/ui/component/AlertHostTest.kt +++ b/core/ui/src/androidTest/kotlin/org/meshtastic/core/ui/component/AlertHostTest.kt @@ -17,7 +17,7 @@ package org.meshtastic.core.ui.component import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.junit4.v2.createComposeRule import androidx.compose.ui.test.onNodeWithText import org.junit.Rule import org.junit.Test diff --git a/core/ui/src/androidTest/kotlin/org/meshtastic/core/ui/component/ImportFabUiTest.kt b/core/ui/src/androidTest/kotlin/org/meshtastic/core/ui/component/ImportFabUiTest.kt index 460a96bc7..cc4f32b8e 100644 --- a/core/ui/src/androidTest/kotlin/org/meshtastic/core/ui/component/ImportFabUiTest.kt +++ b/core/ui/src/androidTest/kotlin/org/meshtastic/core/ui/component/ImportFabUiTest.kt @@ -20,7 +20,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.ui.test.assertDoesNotExist import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.junit4.v2.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick diff --git a/core/ui/src/androidTest/kotlin/org/meshtastic/core/ui/util/AlertManagerUiTest.kt b/core/ui/src/androidTest/kotlin/org/meshtastic/core/ui/util/AlertManagerUiTest.kt index d2a13ff38..5632d39c1 100644 --- a/core/ui/src/androidTest/kotlin/org/meshtastic/core/ui/util/AlertManagerUiTest.kt +++ b/core/ui/src/androidTest/kotlin/org/meshtastic/core/ui/util/AlertManagerUiTest.kt @@ -19,7 +19,7 @@ package org.meshtastic.core.ui.util import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.test.assertIsDisplayed -import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.junit4.v2.createComposeRule import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import org.junit.Rule diff --git a/feature/firmware/build.gradle.kts b/feature/firmware/build.gradle.kts index 8fee603bf..c654e6e6f 100644 --- a/feature/firmware/build.gradle.kts +++ b/feature/firmware/build.gradle.kts @@ -58,11 +58,12 @@ kotlin { androidMain.dependencies { implementation(libs.markdown.renderer.android) } + commonTest.dependencies { implementation(projects.core.testing) } + val androidHostTest by getting { dependencies { implementation(libs.junit) implementation(libs.kotlinx.coroutines.test) - implementation(libs.androidx.compose.ui.test.junit4) implementation(libs.androidx.test.ext.junit) } } diff --git a/feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/ota/dfu/DfuZipParser.kt b/feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/ota/dfu/DfuZipParser.kt index 2763aa414..10a0a5154 100644 --- a/feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/ota/dfu/DfuZipParser.kt +++ b/feature/firmware/src/commonMain/kotlin/org/meshtastic/feature/firmware/ota/dfu/DfuZipParser.kt @@ -17,7 +17,9 @@ package org.meshtastic.feature.firmware.ota.dfu import co.touchlab.kermit.Logger +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonDecodingException private val json = Json { ignoreUnknownKeys = true } @@ -36,7 +38,11 @@ internal fun parseDfuZipEntries(entries: Map): DfuZipPackage val manifest = runCatching { json.decodeFromString(manifestBytes.decodeToString()) } - .getOrElse { e -> throw DfuException.InvalidPackage("Failed to parse manifest.json: ${e.message}") } + .getOrElse { e -> + @OptIn(ExperimentalSerializationApi::class) + val detail = (e as? JsonDecodingException)?.shortMessage ?: e.message + throw DfuException.InvalidPackage("Failed to parse manifest.json: $detail") + } val entry = manifest.manifest.primaryEntry ?: throw DfuException.InvalidPackage("No firmware entry found in manifest.json") diff --git a/feature/intro/build.gradle.kts b/feature/intro/build.gradle.kts index fe05a2b43..242c75bcc 100644 --- a/feature/intro/build.gradle.kts +++ b/feature/intro/build.gradle.kts @@ -44,7 +44,6 @@ kotlin { implementation(libs.junit) implementation(project.dependencies.platform(libs.androidx.compose.bom)) implementation(libs.kotlinx.coroutines.test) - implementation(libs.androidx.compose.ui.test.junit4) } } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c5f0f5cad..d21691950 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -68,7 +68,6 @@ osmdroid-android = "6.1.20" spotless = "8.4.0" wire = "6.2.0" vico = "3.1.0" -dependency-guard = "0.5.0" kable = "0.42.0" kmqtt = "1.0.0" jmdns = "3.6.3" @@ -141,7 +140,6 @@ compose-multiplatform-ui-tooling-preview = { module = "org.jetbrains.compose.ui: compose-multiplatform-resources = { module = "org.jetbrains.compose.components:components-resources", version.ref = "compose-multiplatform" } compose-multiplatform-material3 = { module = "org.jetbrains.compose.material3:material3", version.ref = "compose-multiplatform-material3" } - # JetBrains Material 3 Adaptive (multiplatform — Android, Desktop, iOS) jetbrains-compose-material3-adaptive = { module = "org.jetbrains.compose.material3.adaptive:adaptive", version.ref = "jetbrains-adaptive" } jetbrains-compose-material3-adaptive-layout = { module = "org.jetbrains.compose.material3.adaptive:adaptive-layout", version.ref = "jetbrains-adaptive" } @@ -174,7 +172,6 @@ qrcode-kotlin = { module = "io.github.g0dkar:qrcode-kotlin", version.ref = "qrco kotlin-gradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } dokka-gradlePlugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } - kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version = "0.4.0" } kotlinx-atomicfu = { module = "org.jetbrains.kotlinx:atomicfu", version = "0.32.1" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines-android" } @@ -199,13 +196,10 @@ androidx-test-ext-junit = { module = "androidx.test.ext:junit", version = "1.3.0 androidx-test-runner = { module = "androidx.test:runner", version = "1.7.0" } androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version = "3.7.0" } junit = { module = "junit:junit", version = "4.13.2" } -junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit5" } junit-vintage-engine = { module = "org.junit.vintage:junit-vintage-engine", version.ref = "junit5" } junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "junit-platform" } -mokkery-library = { module = "dev.mokkery:mokkery-runtime", version.ref = "mokkery" } kotest-assertions = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" } kotest-property = { module = "io.kotest:kotest-property", version.ref = "kotest" } -kotest-framework = { module = "io.kotest:kotest-framework-engine", version.ref = "kotest" } kotest-runner-junit6 = { module = "io.kotest:kotest-runner-junit6", version.ref = "kotest" } robolectric = { module = "org.robolectric:robolectric", version = "4.16.1" } turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" }