refactor(ui): compose resources, domain layer (#4628)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-02-22 21:39:50 -06:00 committed by GitHub
parent 96adc70401
commit 2676a51647
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
322 changed files with 3031 additions and 2790 deletions

View file

@ -34,7 +34,7 @@ BarcodeScanner(
```mermaid
graph TB
:core:barcode[barcode]:::android-library
:core:barcode -.-> :core:strings
:core:barcode -.-> :core:resources
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-application-compose fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;

View file

@ -29,7 +29,7 @@ configure<LibraryExtension> {
}
dependencies {
implementation(project(":core:strings"))
implementation(project(":core:resources"))
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3)

View file

@ -64,8 +64,8 @@ import com.google.zxing.MultiFormatReader
import com.google.zxing.PlanarYUVLuminanceSource
import com.google.zxing.common.HybridBinarizer
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.close
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.close
import java.nio.ByteBuffer
import java.util.concurrent.Executors

View file

@ -64,8 +64,8 @@ import com.google.mlkit.vision.barcode.BarcodeScanning
import com.google.mlkit.vision.barcode.common.Barcode
import com.google.mlkit.vision.common.InputImage
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.close
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.close
import java.util.concurrent.Executors
@Composable

View file

@ -53,7 +53,8 @@ constructor(
hasPermissions = true,
),
)
val state: StateFlow<BluetoothState> = _state.asStateFlow()
val state: StateFlow<BluetoothState>
get() = _state.asStateFlow()
init {
processLifecycle.coroutineScope.launch(dispatchers.default) {

View file

@ -67,9 +67,11 @@ constructor(
// 1. Emit cached data first, regardless of staleness.
// This gives the UI something to show immediately.
val cachedRelease = localDataSource.getLatestRelease(releaseType)
cachedRelease?.let {
Logger.d { "Emitting cached firmware for $releaseType (isStale=${it.isStale()})" }
emit(it.asExternalModel())
if (cachedRelease != null) {
Logger.d { "Emitting cached firmware for $releaseType (isStale=${cachedRelease.isStale()})" }
emit(cachedRelease.asExternalModel())
} else {
emit(null)
}
// 2. If the cache was fresh and we are not forcing a refresh, we're done.

View file

@ -30,7 +30,7 @@ graph TB
:core:database -.-> :core:di
:core:database -.-> :core:model
:core:database -.-> :core:proto
:core:database -.-> :core:strings
:core:database -.-> :core:resources
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-application-compose fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;

View file

@ -36,7 +36,7 @@ dependencies {
implementation(projects.core.di)
implementation(projects.core.model)
implementation(projects.core.proto)
implementation(projects.core.strings)
implementation(projects.core.resources)
implementation(libs.androidx.room.paging)
implementation(libs.kotlinx.serialization.json)

View file

@ -19,33 +19,33 @@ package org.meshtastic.core.database.model
import org.jetbrains.compose.resources.StringResource
import org.meshtastic.core.database.entity.Reaction
import org.meshtastic.core.model.MessageStatus
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.delivery_confirmed
import org.meshtastic.core.strings.error
import org.meshtastic.core.strings.message_delivery_status
import org.meshtastic.core.strings.message_status_enroute
import org.meshtastic.core.strings.message_status_queued
import org.meshtastic.core.strings.message_status_sfpp_confirmed
import org.meshtastic.core.strings.message_status_sfpp_routing
import org.meshtastic.core.strings.routing_error_admin_bad_session_key
import org.meshtastic.core.strings.routing_error_admin_public_key_unauthorized
import org.meshtastic.core.strings.routing_error_bad_request
import org.meshtastic.core.strings.routing_error_duty_cycle_limit
import org.meshtastic.core.strings.routing_error_got_nak
import org.meshtastic.core.strings.routing_error_max_retransmit
import org.meshtastic.core.strings.routing_error_no_channel
import org.meshtastic.core.strings.routing_error_no_interface
import org.meshtastic.core.strings.routing_error_no_response
import org.meshtastic.core.strings.routing_error_no_route
import org.meshtastic.core.strings.routing_error_none
import org.meshtastic.core.strings.routing_error_not_authorized
import org.meshtastic.core.strings.routing_error_pki_failed
import org.meshtastic.core.strings.routing_error_pki_send_fail_public_key
import org.meshtastic.core.strings.routing_error_pki_unknown_pubkey
import org.meshtastic.core.strings.routing_error_rate_limit_exceeded
import org.meshtastic.core.strings.routing_error_timeout
import org.meshtastic.core.strings.routing_error_too_large
import org.meshtastic.core.strings.unrecognized
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.delivery_confirmed
import org.meshtastic.core.resources.error
import org.meshtastic.core.resources.message_delivery_status
import org.meshtastic.core.resources.message_status_enroute
import org.meshtastic.core.resources.message_status_queued
import org.meshtastic.core.resources.message_status_sfpp_confirmed
import org.meshtastic.core.resources.message_status_sfpp_routing
import org.meshtastic.core.resources.routing_error_admin_bad_session_key
import org.meshtastic.core.resources.routing_error_admin_public_key_unauthorized
import org.meshtastic.core.resources.routing_error_bad_request
import org.meshtastic.core.resources.routing_error_duty_cycle_limit
import org.meshtastic.core.resources.routing_error_got_nak
import org.meshtastic.core.resources.routing_error_max_retransmit
import org.meshtastic.core.resources.routing_error_no_channel
import org.meshtastic.core.resources.routing_error_no_interface
import org.meshtastic.core.resources.routing_error_no_response
import org.meshtastic.core.resources.routing_error_no_route
import org.meshtastic.core.resources.routing_error_none
import org.meshtastic.core.resources.routing_error_not_authorized
import org.meshtastic.core.resources.routing_error_pki_failed
import org.meshtastic.core.resources.routing_error_pki_send_fail_public_key
import org.meshtastic.core.resources.routing_error_pki_unknown_pubkey
import org.meshtastic.core.resources.routing_error_rate_limit_exceeded
import org.meshtastic.core.resources.routing_error_timeout
import org.meshtastic.core.resources.routing_error_too_large
import org.meshtastic.core.resources.unrecognized
import org.meshtastic.proto.MeshPacket
import org.meshtastic.proto.Routing

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 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
@ -14,18 +14,17 @@
* 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.database.model
import org.jetbrains.compose.resources.StringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.node_sort_alpha
import org.meshtastic.core.strings.node_sort_channel
import org.meshtastic.core.strings.node_sort_distance
import org.meshtastic.core.strings.node_sort_hops_away
import org.meshtastic.core.strings.node_sort_last_heard
import org.meshtastic.core.strings.node_sort_via_favorite
import org.meshtastic.core.strings.node_sort_via_mqtt
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.node_sort_alpha
import org.meshtastic.core.resources.node_sort_channel
import org.meshtastic.core.resources.node_sort_distance
import org.meshtastic.core.resources.node_sort_hops_away
import org.meshtastic.core.resources.node_sort_last_heard
import org.meshtastic.core.resources.node_sort_via_favorite
import org.meshtastic.core.resources.node_sort_via_mqtt
enum class NodeSortOption(val sqlValue: String, val stringRes: StringResource) {
LAST_HEARD("last_heard", Res.string.node_sort_last_heard),

View file

@ -1,7 +1,7 @@
# `:core:strings`
# `:core:resources`
## Overview
The `:core:strings` module is the centralized source for all UI strings and localizable resources. It uses the **Compose Multiplatform Resource** library to provide a type-safe way to access strings.
The `:core:resources` module is the centralized source for all UI strings and localizable resources. It uses the **Compose Multiplatform Resource** library to provide a type-safe way to access strings.
## Key Features
@ -13,8 +13,8 @@ The library provides a standard way to access strings in Jetpack Compose.
```kotlin
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.your_string_key
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.your_string_key
Text(text = stringResource(Res.string.your_string_key))
```
@ -24,7 +24,7 @@ Text(text = stringResource(Res.string.your_string_key))
<!--region graph-->
```mermaid
graph TB
:core:strings[strings]:::kmp-library
:core:resources[strings]:::kmp-library
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-application-compose fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;

View file

@ -29,5 +29,5 @@ kotlin {
compose.resources {
publicResClass = true
packageOfResClass = "org.meshtastic.core.strings"
packageOfResClass = "org.meshtastic.core.resources"
}

View file

@ -14,7 +14,7 @@
* 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.strings
package org.meshtastic.core.resources
import kotlinx.coroutines.runBlocking
import org.jetbrains.compose.resources.StringResource

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:pathData="M620,440q-25,0 -42.5,-17.5T560,380q0,-17 9.5,-34.5t20.5,-32q11,-14.5 20.5,-24l9.5,-9.5 9.5,9.5q9.5,9.5 20.5,24t20.5,32Q680,363 680,380q0,25 -17.5,42.5T620,440ZM780,320q-25,0 -42.5,-17.5T720,260q0,-17 9.5,-34.5t20.5,-32q11,-14.5 20.5,-24l9.5,-9.5 9.5,9.5q9.5,9.5 20.5,24t20.5,32Q840,243 840,260q0,25 -17.5,42.5T780,320ZM780,560q-25,0 -42.5,-17.5T720,500q0,-17 9.5,-34.5t20.5,-32q11,-14.5 20.5,-24l9.5,-9.5 9.5,9.5q9.5,9.5 20.5,24t20.5,32Q840,483 840,500q0,25 -17.5,42.5T780,560ZM360,840q-83,0 -141.5,-58.5T160,640q0,-48 21,-89.5t59,-70.5v-240q0,-50 35,-85t85,-35q50,0 85,35t35,85v240q38,29 59,70.5t21,89.5q0,83 -58.5,141.5T360,840ZM240,640h240q0,-29 -12.5,-54T432,544l-32,-24v-280q0,-17 -11.5,-28.5T360,200q-17,0 -28.5,11.5T320,240v280l-32,24q-23,17 -35.5,42T240,640Z"
android:fillColor="#e8eaed"/>
</vector>

View file

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M21,11a1,1 0,0 1,1 1a10,10 0,0 1,-5 8.656a1,1 0,0 1,-1.302 -0.268l-0.064,-0.098l-3,-5.19a0.995,0.995 0,0 1,-0.133 -0.542l0.01,-0.11l0.023,-0.106l0.034,-0.106l0.046,-0.1l0.056,-0.094l0.067,-0.089a0.994,0.994 0,0 1,0.165 -0.155l0.098,-0.064a2,2 0,0 0,0.993 -1.57l0.007,-0.163a1,1 0,0 1,0.883 -0.994l0.117,-0.007h6z" />
<path
android:fillColor="@android:color/white"
android:pathData="M7,3.344a10,10 0,0 1,10 0a1,1 0,0 1,0.418 1.262l-0.052,0.104l-3,5.19l-0.064,0.098a0.994,0.994 0,0 1,-0.155 0.165l-0.089,0.067a1,1 0,0 1,-0.195 0.102l-0.105,0.034l-0.107,0.022a1.003,1.003 0,0 1,-0.547 -0.07l-0.104,-0.052a2,2 0,0 0,-1.842 -0.082l-0.158,0.082a1,1 0,0 1,-1.302 -0.268l-0.064,-0.098l-3,-5.19a1,1 0,0 1,0.366 -1.366z" />
<path
android:fillColor="@android:color/white"
android:pathData="M9,11a1,1 0,0 1,0.993 0.884l0.007,0.117a2,2 0,0 0,0.861 1.645l0.237,0.152a0.994,0.994 0,0 1,0.165 0.155l0.067,0.089l0.056,0.095l0.045,0.099c0.014,0.036 0.026,0.07 0.035,0.106l0.022,0.107l0.011,0.11a0.994,0.994 0,0 1,-0.08 0.437l-0.053,0.104l-3,5.19a1,1 0,0 1,-1.366 0.366a10,10 0,0 1,-5 -8.656a1,1 0,0 1,0.883 -0.993l0.117,-0.007h6z" />
</vector>

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="200dp" android:viewportHeight="32" android:viewportWidth="32" android:width="200dp">
<path android:fillColor="#000000" android:pathData="M24.5,30a5.202,5.202 0,0 1,-4.626 -8.08L23.49,16.538a1.217,1.217 0,0 1,2.02 0L29.06,21.815A5.492,5.492 0,0 1,30 24.751,5.385 5.385,0 0,1 24.5,30ZM24.5,18.62 L21.564,22.987A3.208,3.208 0,0 0,24.5 28,3.385 3.385,0 0,0 28,24.751a3.435,3.435 0,0 0,-0.63 -1.867Z"/>
<path android:fillColor="#000000" android:pathData="M11,16V11h1a4.004,4.004 0,0 0,4 -4V4H13a3.978,3.978 0,0 0,-2.747 1.107A6.003,6.003 0,0 0,5 2H2V5a6.007,6.007 0,0 0,6 6H9v5H2v2H16V16ZM13,6h1V7a2.002,2.002 0,0 1,-2 2H11V8A2.002,2.002 0,0 1,13 6ZM8,9A4.004,4.004 0,0 1,4 5V4H5A4.004,4.004 0,0 1,9 8V9Z"/>
<path android:fillColor="#000000" android:pathData="M2,21h14v2h-14z"/>
<path android:fillColor="#000000" android:pathData="M2,26h14v2h-14z"/>
</vector>

View file

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="200dp" android:viewportHeight="32" android:viewportWidth="32" android:width="200dp">
<path android:fillColor="#000000" android:pathData="M11,16V11h1a4.004,4.004 0,0 0,4 -4V4H13a3.978,3.978 0,0 0,-2.747 1.107A6.003,6.003 0,0 0,5 2H2V5a6.007,6.007 0,0 0,6 6H9v5H2v2H16V16ZM13,6h1V7a2.002,2.002 0,0 1,-2 2H11V8A2.002,2.002 0,0 1,13 6ZM8,9A4.004,4.004 0,0 1,4 5V4H5A4.004,4.004 0,0 1,9 8V9Z"/>
<path android:fillColor="#000000" android:pathData="M2,21h14v2h-14z"/>
<path android:fillColor="#000000" android:pathData="M2,26h14v2h-14z"/>
<path android:fillColor="#000000" android:pathData="M25,30a4.986,4.986 0,0 1,-3 -8.98L22,15a3,3 0,0 1,6 0v6.02A4.986,4.986 0,0 1,25 30ZM25,14a1.001,1.001 0,0 0,-1 1v7.13l-0.497,0.289A2.968,2.968 0,0 0,22 25a3,3 0,0 0,6 0,2.968 2.968,0 0,0 -1.503,-2.581L26,22.13L26,15A1.001,1.001 0,0 0,25 14Z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M23,11.99l-2.44,-2.79l0.34,-3.69l-3.61,-0.82L15.4,1.5L12,2.96L8.6,1.5L6.71,4.69L3.1,5.5L3.44,9.2L1,11.99l2.44,2.79l-0.34,3.7l3.61,0.82L8.6,22.5l3.4,-1.47l3.4,1.46l1.89,-3.19l3.61,-0.82l-0.34,-3.69L23,11.99zM19.05,13.47l-0.56,0.65l0.08,0.85l0.18,1.95l-1.9,0.43l-0.84,0.19l-0.44,0.74l-0.99,1.68l-1.78,-0.77L12,18.85l-0.79,0.34l-1.78,0.77l-0.99,-1.67l-0.44,-0.74l-0.84,-0.19l-1.9,-0.43l0.18,-1.96l0.08,-0.85l-0.56,-0.65l-1.29,-1.47l1.29,-1.48l0.56,-0.65L5.43,9.01L5.25,7.07l1.9,-0.43l0.84,-0.19l0.44,-0.74l0.99,-1.68l1.78,0.77L12,5.14l0.79,-0.34l1.78,-0.77l0.99,1.68l0.44,0.74l0.84,0.19l1.9,0.43l-0.18,1.95l-0.08,0.85l0.56,0.65l1.29,1.47L19.05,13.47z"
android:fillColor="#e3e3e3"/>
</vector>

View file

@ -0,0 +1,121 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="909.88dp"
android:height="546.86dp"
android:viewportWidth="909.88"
android:viewportHeight="546.86">
<path
android:pathData="m898.52,135.44h4.69a5.67,5.67 0,0 1,5.67 5.67v84.65a5.67,5.67 0,0 1,-5.67 5.67h-4.69"
android:fillColor="#9f9f9e"/>
<path
android:pathData="M12.7,104.75L886.82,104.75A11.7,11.7 0,0 1,898.52 116.45L898.52,534.16A11.7,11.7 0,0 1,886.82 545.86L12.7,545.86A11.7,11.7 0,0 1,1 534.16L1,116.45A11.7,11.7 0,0 1,12.7 104.75z"
android:fillColor="#cbcccb"/>
<path
android:pathData="m34.47,104.75v113.48a3.67,3.67 0,0 0,3.67 3.67h41a2.35,2.35 0,0 1,2.35 2.35L81.49,545.86L870.95,545.86L870.95,104.75ZM845.99,520.86L106.53,520.86L106.53,213.96a17.06,17.06 0,0 0,-17.06 -17.06h-27.5a2.5,2.5 0,0 1,-2.5 -2.5v-62.15a2.5,2.5 0,0 1,2.5 -2.5h784z"
android:fillColor="#9f9f9e"/>
<path
android:pathData="M845.99,129.75L845.99,520.86L106.53,520.86L106.53,213.96a17,17 0,0 0,-7.2 -13.92v-70.29z"
android:fillColor="#cbcccb"/>
<path
android:pathData="m99.33,129.75v70.29a17,17 0,0 0,-9.86 -3.14h-27.5a2.5,2.5 0,0 1,-2.5 -2.5v-62.15a2.5,2.5 0,0 1,2.5 -2.5z"
android:fillColor="#b7b7b7"/>
<path
android:pathData="M25.45,253.39h13.53v148.4h-13.53z"
android:fillColor="#9f9f9e"/>
<path
android:pathData="m430.64,95.71h71.71a2.55,2.55 0,0 1,2.55 2.55v6.48h-76.8v-6.48a2.55,2.55 0,0 1,2.54 -2.55z"
android:fillColor="#b1a368"/>
<path
android:pathData="m436.27,3.17h60.88a6.2,4.85 0,0 1,6.2 4.85v77.33h-73.28v-77.33a6.2,4.85 0,0 1,6.2 -4.85z"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#050606"/>
<path
android:pathData="M425.88,20.14L506.28,20.14A5.55,5.55 0,0 1,511.83 25.69L511.83,70.55A5.55,5.55 0,0 1,506.28 76.1L425.88,76.1A5.55,5.55 0,0 1,420.33 70.55L420.33,25.69A5.55,5.55 0,0 1,425.88 20.14z"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#050606"/>
<path
android:pathData="m511.8,24.48v47.25a5.52,4.31 0,0 1,-5.55 4.34h-80.37a5.55,4.34 0,0 1,-5.59 -4.34v-47.25a5.55,4.34 0,0 1,5.59 -4.34h80.51a5.52,4.31 0,0 1,5.41 4.34z"
android:strokeWidth="3.16706"
android:fillColor="#9f9f9e"
android:strokeColor="#050606"/>
<path
android:pathData="M433.29,85.68h65.99v10.03h-65.99z"
android:fillColor="#b1a368"/>
<path
android:pathData="M845.99,129.75L845.99,520.86L106.53,520.86L106.53,213.96a17.06,17.06 0,0 0,-17.06 -17.06h-27.5a2.5,2.5 0,0 1,-2.5 -2.5v-62.15a2.5,2.5 0,0 1,2.5 -2.5h784m25,-25L34.47,104.75v113.48a3.68,3.68 0,0 0,3.67 3.67h41a2.35,2.35 0,0 1,2.35 2.35L81.49,545.86L870.95,545.86L870.95,104.75Z"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#050606"/>
<path
android:pathData="M99.34,200.04L99.34,129.75"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#050606"/>
<path
android:pathData="M25.45,253.39h13.53v148.4h-13.53z"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#050606"/>
<path
android:pathData="m898.52,135.44h4.69a5.67,5.67 0,0 1,5.67 5.67v84.65a5.67,5.67 0,0 1,-5.67 5.67h-4.69"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#050606"/>
<path
android:pathData="m430.64,95.71h71.71a2.55,2.55 0,0 1,2.55 2.55v6.48h-76.8v-6.48a2.55,2.55 0,0 1,2.54 -2.55z"
android:strokeWidth="2.04"
android:fillColor="#00000000"
android:strokeColor="#050606"/>
<path
android:pathData="M433.29,85.68h65.99v10.03h-65.99z"
android:strokeWidth="1.99"
android:fillColor="#00000000"
android:strokeColor="#050606"/>
<path
android:pathData="m78.62,152.33a14,14 0,1 0,14 14,13.95 13.95,0 0,0 -14,-14zM78.62,173.83a7.55,7.55 0,1 1,7.54 -7.55,7.55 7.55,0 0,1 -7.54,7.55z"
android:fillColor="#9f9f9e"/>
<path
android:pathData="M78.62,166.28m-7.55,0a7.55,7.55 0,1 1,15.1 0a7.55,7.55 0,1 1,-15.1 0"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#050606"/>
<path
android:pathData="M78.62,166.28m-13.95,0a13.95,13.95 0,1 1,27.9 0a13.95,13.95 0,1 1,-27.9 0"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#050606"/>
<path
android:pathData="m445.36,440.05c0,11.52 10.38,20.86 23.19,20.86 12.81,0 23.19,-9.34 23.19,-20.86 0,-11.52 -10.38,-20.86 -23.19,-20.86 -12.81,0 -23.19,9.34 -23.19,20.86z"
android:strokeWidth="0.458227"
android:fillColor="#4d4d4d"/>
<path
android:pathData="m469.4,538.4c-119.83,0 -217.32,-93.41 -217.32,-208.23 0,-114.82 97.48,-208.23 217.32,-208.23 119.83,0 217.32,93.41 217.32,208.23 0,114.82 -97.48,208.23 -217.32,208.23zM469.4,151.82c-102.64,0 -186.13,80.01 -186.13,178.35 0,98.33 83.5,178.35 186.13,178.35 102.62,0 186.13,-80.02 186.13,-178.35 0,-98.34 -83.51,-178.35 -186.13,-178.35z"
android:strokeWidth="0.474832"
android:fillColor="#4d4d4d"/>
<path
android:pathData="m468.56,391.97c-8.54,0 -15.46,-6.23 -15.46,-13.91v-23.51c0,-22.75 19.33,-40.13 36.4,-55.47 12.51,-11.26 25.45,-22.89 25.45,-32.16 0,-23.18 -20.81,-42.04 -46.39,-42.04 -26.01,0 -46.39,18.05 -46.39,41.09 0,7.68 -6.93,13.91 -15.46,13.91 -8.54,0 -15.46,-6.23 -15.46,-13.91 0,-37.99 34.68,-68.9 77.31,-68.9 42.63,0 77.31,31.33 77.31,69.85 0,20.82 -17.55,36.59 -34.51,51.84 -13.45,12.07 -27.34,24.56 -27.34,35.78v23.51c0,7.68 -6.93,13.92 -15.46,13.92z"
android:strokeWidth="0.458227"
android:fillColor="#4d4d4d"
android:strokeColor="#000000"/>
<path
android:pathData="M12.7,104.75L886.82,104.75A11.7,11.7 0,0 1,898.52 116.45L898.52,534.16A11.7,11.7 0,0 1,886.82 545.86L12.7,545.86A11.7,11.7 0,0 1,1 534.16L1,116.45A11.7,11.7 0,0 1,12.7 104.75z"
android:strokeWidth="2"
android:fillColor="#00000000"
android:strokeColor="#050606"/>
<path
android:pathData="m107.42,363.03 l-0.24,-156.31 -2.99,-3.72 -2.99,-3.72v-34.09,-34.09l150.65,0.05 150.65,0.05 -8.28,3.07c-19.32,7.16 -34.46,14.82 -50.22,25.41 -50.58,33.98 -84.36,88.87 -91.06,147.96 -1.43,12.63 -0.64,44.7 1.39,55.76 7.76,42.44 25.98,77.93 55.68,108.42 17.38,17.85 33.99,30.3 55.43,41.55l11.31,5.93 -134.54,0.02 -134.54,0.02z"
android:strokeWidth="0.92"
android:fillColor="#ffffff"
android:fillAlpha="0"/>
<path
android:pathData="m107.42,363.03 l-0.24,-156.31 -2.99,-3.72 -2.99,-3.72v-34.09,-34.09l150.65,0.05 150.65,0.05 -8.28,3.07c-19.32,7.16 -34.46,14.82 -50.22,25.41 -50.58,33.98 -84.36,88.87 -91.06,147.96 -1.43,12.63 -0.64,44.7 1.39,55.76 7.76,42.44 25.98,77.93 55.68,108.42 17.38,17.85 33.99,30.3 55.43,41.55l11.31,5.93 -134.54,0.02 -134.54,0.02z"
android:strokeWidth="0.92"
android:fillColor="#ffffff"
android:fillAlpha="0"/>
<path
android:pathData="m107.42,363.03 l-0.24,-156.31 -2.99,-3.72 -2.99,-3.72v-34.09,-34.09l150.65,0.05 150.65,0.05 -8.28,3.07c-19.32,7.16 -34.46,14.82 -50.22,25.41 -50.58,33.98 -84.36,88.87 -91.06,147.96 -1.43,12.63 -0.64,44.7 1.39,55.76 7.76,42.44 25.98,77.93 55.68,108.42 17.38,17.85 33.99,30.3 55.43,41.55l11.31,5.93 -134.54,0.02 -134.54,0.02z"
android:strokeWidth="0.92"
android:fillColor="#ffffff"
android:fillAlpha="0"/>
</vector>

View file

Before

Width:  |  Height:  |  Size: 690 B

After

Width:  |  Height:  |  Size: 690 B

Before After
Before After

View file

@ -0,0 +1,84 @@
/*
* Copyright (c) 2025-2026 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.resources
import androidx.compose.runtime.Composable
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.resources.stringResource
/**
* A wrapper class for UI text that can be either a dynamic string or a localized string resource. This allows passing
* text from domain/data layers to the UI without resolving strings early.
*/
sealed class UiText {
data class DynamicString(val value: String) : UiText()
class Resource(val res: StringResource, vararg val args: Any) : UiText() {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as Resource
if (res != other.res) return false
if (!args.contentEquals(other.args)) return false
return true
}
override fun hashCode(): Int {
var result = res.hashCode()
result = 31 * result + args.contentHashCode()
return result
}
}
@Composable
fun asString(): String = when (this) {
is DynamicString -> value
is Resource -> {
val resolvedArgs =
args.map { arg ->
when (arg) {
is StringResource -> stringResource(arg)
is UiText -> arg.asString()
else -> arg
}
}
@Suppress("SpreadOperator")
stringResource(res, *resolvedArgs.toTypedArray())
}
}
/** Resolves the string in a suspend context. Useful for non-composable code like snackbars. */
suspend fun resolve(): String = when (this) {
is DynamicString -> value
is Resource -> {
val resolvedArgs =
args.map { arg ->
when (arg) {
is StringResource -> getString(arg)
is UiText -> arg.resolve()
else -> arg
}
}
@Suppress("SpreadOperator")
getString(res, *resolvedArgs.toTypedArray())
}
}
}

View file

@ -33,8 +33,8 @@ Most components are designed to be used with the **Compose Multiplatform Resourc
```kotlin
import org.meshtastic.core.ui.component.MeshtasticResourceDialog
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.ok
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.ok
MeshtasticResourceDialog(
title = Res.string.your_title,
@ -59,7 +59,7 @@ graph TB
:core:ui -.-> :core:prefs
:core:ui -.-> :core:proto
:core:ui -.-> :core:service
:core:ui -.-> :core:strings
:core:ui -.-> :core:resources
classDef android-application fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;
classDef android-application-compose fill:#CAFFBF,stroke:#000,stroke-width:2px,color:#000;

View file

@ -35,7 +35,7 @@ dependencies {
implementation(projects.core.prefs)
implementation(projects.core.proto)
implementation(projects.core.service)
implementation(projects.core.strings)
implementation(projects.core.resources)
implementation(libs.accompanist.permissions)
implementation(libs.androidx.activity.compose)

View file

@ -42,9 +42,9 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.cancel
import org.meshtastic.core.strings.okay
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.cancel
import org.meshtastic.core.resources.okay
/**
* A comprehensive and flexible dialog component for the Meshtastic application.

View file

@ -37,9 +37,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.clear
import org.meshtastic.core.strings.close
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.clear
import org.meshtastic.core.resources.close
@OptIn(ExperimentalMaterial3Api::class)
@Composable

View file

@ -22,8 +22,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.PreviewLightDark
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.channel
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.channel
import org.meshtastic.core.ui.icon.Channel
import org.meshtastic.core.ui.icon.Counter0
import org.meshtastic.core.ui.icon.Counter1

View file

@ -30,8 +30,8 @@ import com.google.zxing.common.BitMatrix
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.util.getSharedContactUrl
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.share_contact
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.share_contact
import org.meshtastic.proto.SharedContact
/**

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 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
@ -14,7 +14,6 @@
* 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.component
import android.content.ClipData
@ -29,8 +28,8 @@ import androidx.compose.ui.platform.ClipEntry
import androidx.compose.ui.platform.LocalClipboard
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.copy
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.copy
@Composable
fun CopyIconButton(

View file

@ -22,8 +22,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.PreviewLightDark
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.distance
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.distance
import org.meshtastic.core.ui.icon.Distance
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.theme.AppTheme

View file

@ -47,9 +47,9 @@ import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.model.Channel
import org.meshtastic.core.model.util.base64ToByteString
import org.meshtastic.core.model.util.encodeToString
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.error
import org.meshtastic.core.strings.reset
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.error
import org.meshtastic.core.resources.reset
@Suppress("LongMethod", "CyclomaticComplexMethod", "MagicNumber")
@Composable

View file

@ -39,13 +39,13 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.add
import org.meshtastic.core.strings.delete
import org.meshtastic.core.strings.gpio_pin
import org.meshtastic.core.strings.ignore_incoming
import org.meshtastic.core.strings.name
import org.meshtastic.core.strings.type
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.add
import org.meshtastic.core.resources.delete
import org.meshtastic.core.resources.gpio_pin
import org.meshtastic.core.resources.ignore_incoming
import org.meshtastic.core.resources.name
import org.meshtastic.core.resources.type
import org.meshtastic.proto.RemoteHardwarePin
import org.meshtastic.proto.RemoteHardwarePinType

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 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
@ -14,7 +14,6 @@
* 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.component
import androidx.compose.foundation.text.KeyboardActions
@ -35,9 +34,9 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.hide_password
import org.meshtastic.core.strings.show_password
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.hide_password
import org.meshtastic.core.resources.show_password
@Composable
fun EditPasswordPreference(

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2025 Meshtastic LLC
* Copyright (c) 2025-2026 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
@ -14,7 +14,6 @@
* 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.component
import androidx.compose.foundation.layout.Box
@ -44,8 +43,8 @@ import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.error
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.error
@Composable
fun SignedIntegerEditTextPreference(

View file

@ -24,9 +24,9 @@ import androidx.compose.ui.tooling.preview.Preview
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.model.util.metersIn
import org.meshtastic.core.model.util.toString
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.altitude
import org.meshtastic.core.strings.elevation_suffix
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.altitude
import org.meshtastic.core.resources.elevation_suffix
import org.meshtastic.core.ui.icon.Elevation
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.proto.Config.DisplayConfig.DisplayUnits

View file

@ -22,8 +22,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.PreviewLightDark
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.hops_away
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.hops_away
import org.meshtastic.core.ui.icon.Hops
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.theme.AppTheme

View file

@ -41,22 +41,22 @@ import androidx.core.net.toUri
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.barcode.rememberBarcodeScanner
import org.meshtastic.core.nfc.NfcScannerEffect
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.cancel
import org.meshtastic.core.strings.import_label
import org.meshtastic.core.strings.input_channel_url
import org.meshtastic.core.strings.input_shared_contact_url
import org.meshtastic.core.strings.nfc_disabled
import org.meshtastic.core.strings.okay
import org.meshtastic.core.strings.open_settings
import org.meshtastic.core.strings.scan_channels_nfc
import org.meshtastic.core.strings.scan_channels_qr
import org.meshtastic.core.strings.scan_nfc
import org.meshtastic.core.strings.scan_nfc_text
import org.meshtastic.core.strings.scan_shared_contact_nfc
import org.meshtastic.core.strings.scan_shared_contact_qr
import org.meshtastic.core.strings.share_channels_qr
import org.meshtastic.core.strings.url
import org.meshtastic.core.resources.Res
import org.meshtastic.core.resources.cancel
import org.meshtastic.core.resources.import_label
import org.meshtastic.core.resources.input_channel_url
import org.meshtastic.core.resources.input_shared_contact_url
import org.meshtastic.core.resources.nfc_disabled
import org.meshtastic.core.resources.okay
import org.meshtastic.core.resources.open_settings
import org.meshtastic.core.resources.scan_channels_nfc
import org.meshtastic.core.resources.scan_channels_qr
import org.meshtastic.core.resources.scan_nfc
import org.meshtastic.core.resources.scan_nfc_text
import org.meshtastic.core.resources.scan_shared_contact_nfc
import org.meshtastic.core.resources.scan_shared_contact_qr
import org.meshtastic.core.resources.share_channels_qr
import org.meshtastic.core.resources.url
import org.meshtastic.core.ui.icon.MeshtasticIcons
import org.meshtastic.core.ui.icon.QrCode2
import org.meshtastic.core.ui.theme.AppTheme

Some files were not shown because too many files have changed in this diff Show more