diff --git a/core/prefs/src/main/kotlin/org/meshtastic/core/prefs/di/PrefsModule.kt b/core/prefs/src/main/kotlin/org/meshtastic/core/prefs/di/PrefsModule.kt
index 81d7d9b97..fa3ef467c 100644
--- a/core/prefs/src/main/kotlin/org/meshtastic/core/prefs/di/PrefsModule.kt
+++ b/core/prefs/src/main/kotlin/org/meshtastic/core/prefs/di/PrefsModule.kt
@@ -30,6 +30,8 @@ import org.meshtastic.core.prefs.emoji.CustomEmojiPrefs
import org.meshtastic.core.prefs.emoji.CustomEmojiPrefsImpl
import org.meshtastic.core.prefs.filter.FilterPrefs
import org.meshtastic.core.prefs.filter.FilterPrefsImpl
+import org.meshtastic.core.prefs.homoglyph.HomoglyphPrefs
+import org.meshtastic.core.prefs.homoglyph.HomoglyphPrefsImpl
import org.meshtastic.core.prefs.map.MapConsentPrefs
import org.meshtastic.core.prefs.map.MapConsentPrefsImpl
import org.meshtastic.core.prefs.map.MapPrefs
@@ -54,6 +56,10 @@ import javax.inject.Singleton
@Retention(AnnotationRetention.BINARY)
internal annotation class AnalyticsSharedPreferences
+@Qualifier
+@Retention(AnnotationRetention.BINARY)
+internal annotation class HomoglyphEncodingSharedPreferences
+
@Qualifier
@Retention(AnnotationRetention.BINARY)
internal annotation class AppSharedPreferences
@@ -101,6 +107,8 @@ interface PrefsModule {
@Binds fun bindAnalyticsPrefs(analyticsPrefsImpl: AnalyticsPrefsImpl): AnalyticsPrefs
+ @Binds fun bindHomoglyphEncodingPrefs(homoglyphEncodingPrefsImpl: HomoglyphPrefsImpl): HomoglyphPrefs
+
@Binds fun bindCustomEmojiPrefs(customEmojiPrefsImpl: CustomEmojiPrefsImpl): CustomEmojiPrefs
@Binds fun bindMapConsentPrefs(mapConsentPrefsImpl: MapConsentPrefsImpl): MapConsentPrefs
@@ -127,6 +135,12 @@ interface PrefsModule {
fun provideAnalyticsSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("analytics-prefs", Context.MODE_PRIVATE)
+ @Provides
+ @Singleton
+ @HomoglyphEncodingSharedPreferences
+ fun provideHomoglyphEncodingSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
+ context.getSharedPreferences("homoglyph-encoding-prefs", Context.MODE_PRIVATE)
+
@Provides
@Singleton
@AppSharedPreferences
diff --git a/core/prefs/src/main/kotlin/org/meshtastic/core/prefs/homoglyph/HomoglyphPrefs.kt b/core/prefs/src/main/kotlin/org/meshtastic/core/prefs/homoglyph/HomoglyphPrefs.kt
new file mode 100644
index 000000000..d74962cfe
--- /dev/null
+++ b/core/prefs/src/main/kotlin/org/meshtastic/core/prefs/homoglyph/HomoglyphPrefs.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 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 .
+ */
+package org.meshtastic.core.prefs.homoglyph
+
+import android.content.SharedPreferences
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import org.meshtastic.core.prefs.PrefDelegate
+import org.meshtastic.core.prefs.di.HomoglyphEncodingSharedPreferences
+import javax.inject.Inject
+import javax.inject.Singleton
+
+interface HomoglyphPrefs {
+
+ /** Preference for whether homoglyph encoding is enabled by the user. */
+ var homoglyphEncodingEnabled: Boolean
+
+ /**
+ * Provides a [Flow] that emits the current state of [homoglyphEncodingEnabled] and subsequent changes.
+ *
+ * @return A [Flow] of [Boolean] indicating if homoglyph encoding is enabled.
+ */
+ fun getHomoglyphEncodingEnabledChangesFlow(): Flow
+
+ companion object {
+ /** Key for the homoglyphEncodingEnabled preference. */
+ const val KEY_HOMOGLYPH_ENCODING_ENABLED = "enabled"
+ }
+}
+
+@Singleton
+class HomoglyphPrefsImpl
+@Inject
+constructor(
+ @HomoglyphEncodingSharedPreferences private val homoglyphEncodingSharedPreferences: SharedPreferences,
+) : HomoglyphPrefs {
+ override var homoglyphEncodingEnabled: Boolean by
+ PrefDelegate(homoglyphEncodingSharedPreferences, HomoglyphPrefs.KEY_HOMOGLYPH_ENCODING_ENABLED, false)
+
+ override fun getHomoglyphEncodingEnabledChangesFlow(): Flow = callbackFlow {
+ val listener =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, key ->
+ if (key == HomoglyphPrefs.KEY_HOMOGLYPH_ENCODING_ENABLED) {
+ trySend(homoglyphEncodingEnabled)
+ }
+ }
+ // Emit the initial value
+ trySend(homoglyphEncodingEnabled)
+ homoglyphEncodingSharedPreferences.registerOnSharedPreferenceChangeListener(listener)
+ awaitClose { homoglyphEncodingSharedPreferences.unregisterOnSharedPreferenceChangeListener(listener) }
+ }
+}
diff --git a/core/strings/src/commonMain/composeResources/values/strings.xml b/core/strings/src/commonMain/composeResources/values/strings.xml
index ae910128f..202ec96b4 100644
--- a/core/strings/src/commonMain/composeResources/values/strings.xml
+++ b/core/strings/src/commonMain/composeResources/values/strings.xml
@@ -294,6 +294,7 @@
System default
Choose theme
Provide phone location to mesh
+ Compact encoding for Cyrillic
- Delete message?
- Delete %1$s messages?
diff --git a/feature/messaging/build.gradle.kts b/feature/messaging/build.gradle.kts
index 268ef7c42..21a483faa 100644
--- a/feature/messaging/build.gradle.kts
+++ b/feature/messaging/build.gradle.kts
@@ -62,4 +62,6 @@ dependencies {
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
androidTestImplementation(libs.androidx.test.ext.junit)
+
+ testImplementation(libs.junit)
}
diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/HomoglyphCharacterStringTransformer.kt b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/HomoglyphCharacterStringTransformer.kt
new file mode 100644
index 000000000..9aebea8a0
--- /dev/null
+++ b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/HomoglyphCharacterStringTransformer.kt
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 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 .
+ */
+package org.meshtastic.feature.messaging
+
+/**
+ * This util class allows you to optimize the binary size of the transmitted text message strings. It replaces certain
+ * characters from national alphabets with the characters from the latin alphabet that have an identical appearance
+ * (homoglyphs), for example: cyrillic "А", "С", "у" -> latin "A", "C", "y", etc. According to statistics, such letters
+ * can make up about 20-25% of the total number of letters in the average text. Replacing them with Latin characters
+ * reduces the binary size of the transmitted message. The average transmitted message volume can then fit around
+ * ~140-145 characters instead of ~115-120
+ */
+internal object HomoglyphCharacterStringTransformer {
+
+ /**
+ * Unicode characters from the basic cyrillic block (U+0400-U+04FF), each of which occupies 2 bytes
+ * https://www.compart.com/en/unicode/block/U+0400 Mapped with the corresponding similarly written latin characters,
+ * each of which occupies 1 byte
+ *
+ * Please note that only 100% "reliable", completely visually identical characters are presented will here The
+ * characters that look like latin but contain various descenders, hooks, strokes, etc are not replaced with
+ * "simplified" latin appearance and will remain 2 byte unicode, as usual
+ */
+ private val homoglyphCharactersSubstitutionMapping: Map =
+ mapOf(
+ '\u0405' to 'S', // https://www.compart.com/en/unicode/U+0405 - Cyrillic Capital Letter Dze
+ '\u0406' to
+ 'I', // https://www.compart.com/en/unicode/U+0406 - Cyrillic Capital Letter Byelorussian-Ukrainian I
+ '\u0408' to 'J', // https://www.compart.com/en/unicode/U+0408 - Cyrillic Capital Letter Je
+ '\u0410' to 'A', // https://www.compart.com/en/unicode/U+0410 - Cyrillic Capital Letter A
+ '\u0412' to 'B', // https://www.compart.com/en/unicode/U+0412 - Cyrillic Capital Letter Ve
+ '\u0415' to 'E', // https://www.compart.com/en/unicode/U+0415 - Cyrillic Capital Letter Ie
+ '\u041A' to 'K', // https://www.compart.com/en/unicode/U+041A - Cyrillic Capital Letter Ka
+ '\u041C' to 'M', // https://www.compart.com/en/unicode/U+041C - Cyrillic Capital Letter Em
+ '\u041D' to 'H', // https://www.compart.com/en/unicode/U+041D - Cyrillic Capital Letter En
+ '\u041E' to 'O', // https://www.compart.com/en/unicode/U+041E - Cyrillic Capital Letter O
+ '\u0420' to 'P', // https://www.compart.com/en/unicode/U+0420 - Cyrillic Capital Letter Er
+ '\u0421' to 'C', // https://www.compart.com/en/unicode/U+0421 - Cyrillic Capital Letter Es
+ '\u0422' to 'T', // https://www.compart.com/en/unicode/U+0422 - Cyrillic Capital Letter Te
+ '\u0425' to 'X', // https://www.compart.com/en/unicode/U+0425 - Cyrillic Capital Letter Ha
+ '\u0430' to 'a', // https://www.compart.com/en/unicode/U+0430 - Cyrillic Small Letter A
+ '\u0435' to 'e', // https://www.compart.com/en/unicode/U+0435 - Cyrillic Small Letter Ie
+ '\u043E' to 'o', // https://www.compart.com/en/unicode/U+043E - Cyrillic Small Letter O
+ '\u0440' to 'p', // https://www.compart.com/en/unicode/U+0440 - Cyrillic Small Letter Er
+ '\u0441' to 'c', // https://www.compart.com/en/unicode/U+0441 - Cyrillic Small Letter Es
+ '\u0443' to 'y', // https://www.compart.com/en/unicode/U+0443 - Cyrillic Small Letter U
+ '\u0445' to 'x', // https://www.compart.com/en/unicode/U+0445 - Cyrillic Small Letter Ha
+ '\u0455' to 's', // https://www.compart.com/en/unicode/U+0455 - Cyrillic Small Letter Dze
+ '\u0456' to
+ 'i', // https://www.compart.com/en/unicode/U+0456 - Cyrillic Small Letter Byelorussian-Ukrainian I
+ '\u0458' to 'j', // https://www.compart.com/en/unicode/U+0458 - Cyrillic Small Letter Je
+ '\u04AE' to 'Y', // https://www.compart.com/en/unicode/U+04AE - Cyrillic Capital Letter Straight U
+ '\u0417' to '3', // https://www.compart.com/en/unicode/U+0417 - Cyrillic Capital Letter Ze
+ // Note that capital "ze" here is a bit special - it technically transforms to a digit "three"
+ // The visuals are all the same, across the different fonts etc& The core idea is the same:
+ // We are still replacing 2-byte unicode letter with a digit character that occupies 1 byte in Unicode
+ // But I have to point it out to avoid confusion
+
+ )
+
+ /**
+ * Returns the transformed optimized [String] value, in which some characters of the national alphabets are replaced
+ * with identical Latin characters so that the text takes up fewer bytes and is more compact for transmission.
+ *
+ * @param value original string value.
+ * @return optimized string value.
+ */
+ fun optimizeUtf8StringWithHomoglyphs(value: String): String {
+ val stringBuilder = StringBuilder()
+ for (c in value.toCharArray()) stringBuilder.append(homoglyphCharactersSubstitutionMapping.getOrDefault(c, c))
+ return stringBuilder.toString()
+ }
+}
diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/Message.kt b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/Message.kt
index 996587b5a..10a514317 100644
--- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/Message.kt
+++ b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/Message.kt
@@ -178,6 +178,7 @@ fun MessageScreen(
val quickChatActions by viewModel.quickChatActions.collectAsStateWithLifecycle(initialValue = emptyList())
val pagedMessages = viewModel.getMessagesFromPaged(contactKey).collectAsLazyPagingItems()
val contactSettings by viewModel.contactSettings.collectAsStateWithLifecycle(initialValue = emptyMap())
+ val homoglyphEncodingEnabled by viewModel.homoglyphEncodingEnabled.collectAsStateWithLifecycle(initialValue = false)
// UI State managed within this Composable
var replyingToPacketId by rememberSaveable { mutableStateOf(null) }
@@ -469,6 +470,7 @@ fun MessageScreen(
)
MessageInput(
isEnabled = connectionState.isConnected(),
+ isHomoglyphEncodingEnabled = homoglyphEncodingEnabled,
textFieldState = messageInputState,
onSendMessage = {
val messageText = messageInputState.text.toString().trim()
@@ -938,12 +940,21 @@ private const val MAX_LINES = 3
@Composable
private fun MessageInput(
isEnabled: Boolean,
+ isHomoglyphEncodingEnabled: Boolean,
textFieldState: TextFieldState,
modifier: Modifier = Modifier,
maxByteSize: Int = MESSAGE_CHARACTER_LIMIT_BYTES,
onSendMessage: () -> Unit,
) {
- val currentText = textFieldState.text.toString()
+ val currentTextRaw = textFieldState.text.toString()
+
+ val currentText =
+ if (isHomoglyphEncodingEnabled) {
+ HomoglyphCharacterStringTransformer.optimizeUtf8StringWithHomoglyphs(currentTextRaw)
+ } else {
+ currentTextRaw
+ }
+
val currentByteLength =
remember(currentText) {
// Recalculate only when text changes
@@ -1000,12 +1011,23 @@ private fun MessageInputPreview() {
AppTheme {
Surface {
Column(modifier = Modifier.padding(8.dp)) {
- MessageInput(isEnabled = true, textFieldState = rememberTextFieldState("Hello"), onSendMessage = {})
+ MessageInput(
+ isEnabled = true,
+ isHomoglyphEncodingEnabled = false,
+ textFieldState = rememberTextFieldState("Hello"),
+ onSendMessage = {},
+ )
Spacer(Modifier.size(16.dp))
- MessageInput(isEnabled = false, textFieldState = rememberTextFieldState("Disabled"), onSendMessage = {})
+ MessageInput(
+ isEnabled = false,
+ isHomoglyphEncodingEnabled = false,
+ textFieldState = rememberTextFieldState("Disabled"),
+ onSendMessage = {},
+ )
Spacer(Modifier.size(16.dp))
MessageInput(
isEnabled = true,
+ isHomoglyphEncodingEnabled = false,
textFieldState =
rememberTextFieldState(
"A very long message that might exceed the byte limit " +
@@ -1018,6 +1040,7 @@ private fun MessageInputPreview() {
// Test Japanese characters (multi-byte)
MessageInput(
isEnabled = true,
+ isHomoglyphEncodingEnabled = false,
textFieldState = rememberTextFieldState("こんにちは世界"), // Hello World in Japanese
onSendMessage = {},
maxByteSize = 10,
diff --git a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt
index c58a64ca2..260b36ad0 100644
--- a/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt
+++ b/feature/messaging/src/main/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt
@@ -44,6 +44,7 @@ import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.Capabilities
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.prefs.emoji.CustomEmojiPrefs
+import org.meshtastic.core.prefs.homoglyph.HomoglyphPrefs
import org.meshtastic.core.prefs.ui.UiPrefs
import org.meshtastic.core.service.MeshServiceNotifications
import org.meshtastic.core.service.ServiceAction
@@ -67,6 +68,7 @@ constructor(
private val packetRepository: PacketRepository,
private val uiPrefs: UiPrefs,
private val customEmojiPrefs: CustomEmojiPrefs,
+ private val homoglyphEncodingPrefs: HomoglyphPrefs,
private val meshServiceNotifications: MeshServiceNotifications,
) : ViewModel() {
private val _title = MutableStateFlow("")
@@ -122,6 +124,8 @@ constructor(
?.map { it.first }
?.take(6) ?: listOf("👍", "👎", "😂", "🔥", "❤️", "😮")
+ val homoglyphEncodingEnabled = homoglyphEncodingPrefs.getHomoglyphEncodingEnabledChangesFlow()
+
init {
val contactKey = savedStateHandle.get("contactKey")
if (contactKey != null) {
@@ -204,8 +208,20 @@ constructor(
}
}
}
+
+ // Applying homoglyph encoding to the transmitted string if user has activated the feature
+ // In most cases the value in "str" parameter will already contain the correct
+ // transformed string from the text input. This call here added to make sure that
+ // the feature is effective across all possible message paths (quick-chat, reply, etc.)
+ val dataPacketText: String =
+ if (homoglyphEncodingPrefs.homoglyphEncodingEnabled) {
+ HomoglyphCharacterStringTransformer.optimizeUtf8StringWithHomoglyphs(str)
+ } else {
+ str
+ }
+
val p =
- DataPacket(dest, channel ?: 0, str, replyId).apply {
+ DataPacket(dest, channel ?: 0, dataPacketText, replyId).apply {
from = ourNodeInfo.value?.user?.id ?: DataPacket.ID_LOCAL
}
sendDataPacket(p)
diff --git a/feature/messaging/src/test/kotlin/org/meshtastic/feature/messaging/HomoglyphCharacterTransformTest.kt b/feature/messaging/src/test/kotlin/org/meshtastic/feature/messaging/HomoglyphCharacterTransformTest.kt
new file mode 100644
index 000000000..f521c5e07
--- /dev/null
+++ b/feature/messaging/src/test/kotlin/org/meshtastic/feature/messaging/HomoglyphCharacterTransformTest.kt
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 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 .
+ */
+package org.meshtastic.feature.messaging
+
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class HomoglyphCharacterTransformTest {
+
+ @Test
+ fun `optimizeUtf8StringWithHomoglyphs shrinks binary size of cyrillic text containing some homoglyphs`() {
+ val testString = "Мештастик - это проект с открытым исходным кодом"
+ val transformedTestString = HomoglyphCharacterStringTransformer.optimizeUtf8StringWithHomoglyphs(testString)
+ val testStringBytes = testString.toByteArray(charset = Charsets.UTF_8)
+ val transformedTestStringBytes = transformedTestString.toByteArray(charset = Charsets.UTF_8)
+ val transformedStringBinarySizeShrinked = transformedTestStringBytes.size < testStringBytes.size
+ assertTrue(transformedStringBinarySizeShrinked)
+ }
+
+ @Test
+ fun `optimizeUtf8StringWithHomoglyphs shrinks binary size in half of cyrillic text containing only homoglyphs`() {
+ val testString = "Косуха"
+ val transformedTestString = HomoglyphCharacterStringTransformer.optimizeUtf8StringWithHomoglyphs(testString)
+ val testStringBytes = testString.toByteArray(charset = Charsets.UTF_8)
+ val transformedTestStringBytes = transformedTestString.toByteArray(charset = Charsets.UTF_8)
+ assertEquals(transformedTestStringBytes.size, testStringBytes.size / 2)
+ }
+
+ @Test
+ fun `optimizeUtf8StringWithHomoglyphs does not transform cyrillic text without any homoglyphs`() {
+ val testString = "Близкий"
+ val transformedTestString = HomoglyphCharacterStringTransformer.optimizeUtf8StringWithHomoglyphs(testString)
+ assertEquals(transformedTestString, testString)
+ }
+
+ @Test
+ fun `optimizeUtf8StringWithHomoglyphs does not transform latin text message`() {
+ val testString = "Meshtastic is an open source, off-grid, decentralized mesh network"
+ val transformedTestString = HomoglyphCharacterStringTransformer.optimizeUtf8StringWithHomoglyphs(testString)
+ assertEquals(transformedTestString, testString)
+ }
+
+ @Test
+ fun `optimizeUtf8StringWithHomoglyphs does not transform characters impossible to present by latin letters`() {
+ val testString = "ميشتاستيك هو مصدر مفتوح ، خارج الشبكة ، شبكة شبكة"
+ val transformedTestString = HomoglyphCharacterStringTransformer.optimizeUtf8StringWithHomoglyphs(testString)
+ assertEquals(transformedTestString, testString)
+ }
+}
diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt
index 35e93f25b..316a12743 100644
--- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt
+++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/SettingsScreen.kt
@@ -36,6 +36,7 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
+import androidx.compose.material.icons.filled.Abc
import androidx.compose.material.icons.filled.BugReport
import androidx.compose.material.icons.rounded.AppSettingsAlt
import androidx.compose.material.icons.rounded.FormatPaint
@@ -102,6 +103,7 @@ import org.meshtastic.core.strings.theme
import org.meshtastic.core.strings.theme_dark
import org.meshtastic.core.strings.theme_light
import org.meshtastic.core.strings.theme_system
+import org.meshtastic.core.strings.use_homoglyph_characters_encoding
import org.meshtastic.core.ui.component.DropDownPreference
import org.meshtastic.core.ui.component.ListItem
import org.meshtastic.core.ui.component.MainAppBar
@@ -315,6 +317,15 @@ fun SettingsScreen(
onClick = { settingsViewModel.setProvideLocation(!provideLocation) },
)
+ val homoglyphEncodingEnabled by
+ viewModel.homoglyphEncodingEnabledFlow.collectAsStateWithLifecycle(false)
+ SwitchListItem(
+ text = stringResource(Res.string.use_homoglyph_characters_encoding),
+ checked = homoglyphEncodingEnabled,
+ leadingIcon = Icons.Default.Abc,
+ onClick = { viewModel.toggleHomoglyphCharactersEncodingEnabled() },
+ )
+
val settingsLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) {}
diff --git a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt
index cd5ac5a51..24c9e77f0 100644
--- a/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt
+++ b/feature/settings/src/main/kotlin/org/meshtastic/feature/settings/radio/RadioConfigViewModel.kt
@@ -57,6 +57,7 @@ import org.meshtastic.core.model.Position
import org.meshtastic.core.model.util.toChannelSet
import org.meshtastic.core.navigation.SettingsRoutes
import org.meshtastic.core.prefs.analytics.AnalyticsPrefs
+import org.meshtastic.core.prefs.homoglyph.HomoglyphPrefs
import org.meshtastic.core.prefs.map.MapConsentPrefs
import org.meshtastic.core.service.ConnectionState
import org.meshtastic.core.service.IMeshService
@@ -117,6 +118,7 @@ constructor(
private val locationRepository: LocationRepository,
private val mapConsentPrefs: MapConsentPrefs,
private val analyticsPrefs: AnalyticsPrefs,
+ private val homoglyphEncodingPrefs: HomoglyphPrefs,
) : ViewModel() {
private val meshService: IMeshService?
get() = serviceRepository.meshService
@@ -127,6 +129,12 @@ constructor(
analyticsPrefs.analyticsAllowed = !analyticsPrefs.analyticsAllowed
}
+ val homoglyphEncodingEnabledFlow = homoglyphEncodingPrefs.getHomoglyphEncodingEnabledChangesFlow()
+
+ fun toggleHomoglyphCharactersEncodingEnabled() {
+ homoglyphEncodingPrefs.homoglyphEncodingEnabled = !homoglyphEncodingPrefs.homoglyphEncodingEnabled
+ }
+
private val destNum =
savedStateHandle.get("destNum")
?: runCatching { savedStateHandle.toRoute().destNum }.getOrNull()