diff --git a/app/build.gradle b/app/build.gradle
index 35041ae4..b3404ed2 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -37,7 +37,7 @@ android {
storePassword keystoreProperties['storePassword']
}
}
- compileSdkVersion 32
+ compileSdkVersion 33
defaultConfig {
applicationId "com.geeksville.mesh"
minSdkVersion 21 // The oldest emulator image I have tried is 22 (though 21 probably works)
@@ -65,7 +65,7 @@ android {
defaultConfig {
// We have to list all translated languages here, because some of our libs have bogus languages that google play
// doesn't like and we need to strip them (gr)
- resConfigs "cs", "de", "el", "en", "es", "fi", "fr", "ga", "ht", "it", "ja", "ko-rKR", "nl", "no", "pl", "pt", "pt-rBR", "ro", "ru", "sk", "sl", "sq", "sv", "tr", "zh"
+ resConfigs "cs", "de", "el", "en", "es", "fi", "fr", "fr-rHT", "ga", "hu", "it", "ja", "ko", "nl", "nb", "pl", "pt", "pt-rBR", "ro", "ru", "sk", "sl", "sq", "sv", "tr", "zh", "uk"
ndk {
// abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
@@ -125,7 +125,11 @@ protobuf {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation 'androidx.appcompat:appcompat:1.5.0'
+ def appcompat_version = '1.6.0-rc01'
+ implementation "androidx.appcompat:appcompat:$appcompat_version"
+ // For loading and tinting drawables on older versions of the platform
+ implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
+
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.fragment:fragment-ktx:1.5.2'
implementation 'androidx.cardview:cardview:1.0.0'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a7334fba..8e5f079c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -81,11 +81,8 @@
android:roundIcon="@mipmap/ic_launcher2_round"
android:supportsRtl="true"
android:hardwareAccelerated="true"
- android:theme="@style/AppTheme">
-
-
+ android:theme="@style/AppTheme"
+ android:localeConfig="@xml/locales_config">
+
+
+
+
= 17) {
- val locale = if (langCode == "zz")
- Locale.getDefault()
- else
- createLocale(langCode)
- config.setLocale(locale)
-
- if(Build.VERSION.SDK_INT > 24) {
- //Using createNewConfigurationContext will cause CompanionDeviceManager to crash
- applyOverrideConfiguration(config)
- super.attachBaseContext(newBase)
- }else {
- super.attachBaseContext(newBase.createConfigurationContext(config))
- }
- } else {
- super.attachBaseContext(newBase)
- }
- }
-
- private fun createLocale(language: String): Locale {
- val langArray = language.split("_")
- return if (langArray.size == 2) {
- Locale(langArray[0], langArray[1])
- } else {
- Locale(langArray[0])
- }
- }
-
-}
diff --git a/app/src/main/java/com/geeksville/mesh/MainActivity.kt b/app/src/main/java/com/geeksville/mesh/MainActivity.kt
index d20176c6..76d87460 100644
--- a/app/src/main/java/com/geeksville/mesh/MainActivity.kt
+++ b/app/src/main/java/com/geeksville/mesh/MainActivity.kt
@@ -20,6 +20,7 @@ import android.widget.TextView
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
@@ -42,6 +43,7 @@ import com.geeksville.mesh.repository.radio.SerialInterface
import com.geeksville.mesh.service.*
import com.geeksville.mesh.ui.*
import com.geeksville.mesh.util.Exceptions
+import com.geeksville.mesh.util.LanguageUtils
import com.geeksville.mesh.util.exceptionReporter
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
@@ -106,7 +108,7 @@ eventually:
*/
@AndroidEntryPoint
-class MainActivity : BaseActivity(), Logging {
+class MainActivity : AppCompatActivity(), Logging {
private lateinit var binding: ActivityMainBinding
@@ -200,6 +202,11 @@ class MainActivity : BaseActivity(), Logging {
if (!prefs.getBoolean("app_intro_completed", false)) {
startActivity(Intent(this, AppIntroduction::class.java))
}
+ // First run: migrate in-app language prefs to appcompat
+ if (prefs.getString("lang", LanguageUtils.SYSTEM_DEFAULT) != LanguageUtils.SYSTEM_MANAGED) {
+ LanguageUtils.migrateLanguagePrefs(prefs)
+ }
+ info("in-app language is ${LanguageUtils.getLocale()}")
binding = ActivityMainBinding.inflate(layoutInflater)
@@ -843,7 +850,6 @@ class MainActivity : BaseActivity(), Logging {
editor.putInt("theme", 0)
editor.apply()
- delegate.applyDayNight()
dialog.dismiss()
}
1 -> {
@@ -851,15 +857,13 @@ class MainActivity : BaseActivity(), Logging {
editor.putInt("theme", 1)
editor.apply()
- delegate.applyDayNight()
dialog.dismiss()
}
- 2 -> {
+ else -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
editor.putInt("theme", 2)
editor.apply()
- delegate.applyDayNight()
dialog.dismiss()
}
@@ -875,34 +879,23 @@ class MainActivity : BaseActivity(), Logging {
/// If nothing is found set FOLLOW SYSTEM option
when (prefs.getInt("theme", 2)) {
- 0 -> {
- AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
- delegate.applyDayNight()
- }
- 1 -> {
- AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
- delegate.applyDayNight()
- }
- 2 -> {
- AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
- delegate.applyDayNight()
- }
+ 0 -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
+ 1 -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
+ else -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
}
}
private fun chooseLangDialog() {
-
/// Prepare dialog and its items
val builder = MaterialAlertDialogBuilder(this)
builder.setTitle(getString(R.string.preferences_language))
- val languageLabels by lazy { resources.getStringArray(R.array.language_entries) }
- val languageValues by lazy { resources.getStringArray(R.array.language_values) }
+ val languageTags = LanguageUtils.getLanguageTags(this)
+ val languageLabels = languageTags.map { it.first }.toTypedArray()
+ val languageValues = languageTags.map { it.second }
/// Load preferences and its value
- val prefs = UIViewModel.getPreferences(this)
- val editor: SharedPreferences.Editor = prefs.edit()
- val lang = prefs.getString("lang", "zz")
+ val lang = LanguageUtils.getLocale()
debug("Lang from prefs: $lang")
builder.setSingleChoiceItems(
@@ -911,8 +904,7 @@ class MainActivity : BaseActivity(), Logging {
) { dialog, which ->
val selectedLang = languageValues[which]
debug("Set lang pref to $selectedLang")
- editor.putString("lang", selectedLang)
- editor.apply()
+ LanguageUtils.setLocale(selectedLang)
dialog.dismiss()
}
val dialog = builder.create()
diff --git a/app/src/main/java/com/geeksville/mesh/ui/MeshApp.kt b/app/src/main/java/com/geeksville/mesh/ui/MeshApp.kt
deleted file mode 100644
index f54648d9..00000000
--- a/app/src/main/java/com/geeksville/mesh/ui/MeshApp.kt
+++ /dev/null
@@ -1,6 +0,0 @@
-package com.geeksville.mesh.ui
-
-import com.geeksville.mesh.android.Logging
-
-
-object UILog : Logging
diff --git a/app/src/main/java/com/geeksville/mesh/util/LanguageUtils.kt b/app/src/main/java/com/geeksville/mesh/util/LanguageUtils.kt
new file mode 100644
index 00000000..3a85ffac
--- /dev/null
+++ b/app/src/main/java/com/geeksville/mesh/util/LanguageUtils.kt
@@ -0,0 +1,66 @@
+package com.geeksville.mesh.util
+
+import android.content.Context
+import android.content.SharedPreferences
+import androidx.appcompat.app.AppCompatDelegate
+import androidx.core.content.edit
+import androidx.core.os.LocaleListCompat
+import com.geeksville.mesh.android.Logging
+import com.geeksville.mesh.R
+import org.xmlpull.v1.XmlPullParser
+import java.util.Locale
+
+object LanguageUtils : Logging {
+
+ const val SYSTEM_DEFAULT = "zz"
+ const val SYSTEM_MANAGED = "appcompat"
+
+ fun getLocale(): String {
+ return AppCompatDelegate.getApplicationLocales().toLanguageTags().ifEmpty { SYSTEM_DEFAULT }
+ }
+
+ fun setLocale(lang: String) {
+ AppCompatDelegate.setApplicationLocales(
+ if (lang == SYSTEM_DEFAULT) LocaleListCompat.getEmptyLocaleList()
+ else LocaleListCompat.forLanguageTags(lang)
+ )
+ }
+
+ fun migrateLanguagePrefs(prefs: SharedPreferences) {
+ val currentLang = prefs.getString("lang", SYSTEM_DEFAULT) ?: SYSTEM_DEFAULT
+ debug("Migrating in-app language prefs: $currentLang")
+ prefs.edit { putString("lang", SYSTEM_MANAGED) }
+ setLocale(currentLang)
+ }
+
+ /**
+ * Build a list from locales_config.xml
+ * of native language names paired to its Locale tag (ex: "English", "en")
+ */
+ fun getLanguageTags(context: Context): List> {
+ val languageTags = mutableListOf(SYSTEM_DEFAULT)
+ try {
+ context.resources.getXml(R.xml.locales_config).use {
+ while (it.eventType != XmlPullParser.END_DOCUMENT) {
+ if (it.eventType == XmlPullParser.START_TAG && it.name == "locale") {
+ languageTags += it.getAttributeValue(0)
+ }
+ it.next()
+ }
+ }
+ } catch (e: Exception) {
+ errormsg("Error parsing locale_config.xml ${e.message}")
+ }
+ fun getDisplayLanguage(tag: String): String {
+ val loc = Locale(tag)
+ return when (tag) {
+ SYSTEM_DEFAULT -> context.getString(R.string.preferences_system_default)
+ "fr-HT" -> context.getString(R.string.fr_HT)
+ "pt-BR" -> context.getString(R.string.pt_BR)
+ else -> loc.getDisplayLanguage(loc)
+ .replaceFirstChar { if (it.isLowerCase()) it.titlecase(loc) else it.toString() }
+ }
+ }
+ return languageTags.map { getDisplayLanguage(it) to it }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/values-ht/strings.xml b/app/src/main/res/values-fr-rHT/strings.xml
similarity index 100%
rename from app/src/main/res/values-ht/strings.xml
rename to app/src/main/res/values-fr-rHT/strings.xml
diff --git a/app/src/main/res/values-ko-rKR/strings.xml b/app/src/main/res/values-ko/strings.xml
similarity index 99%
rename from app/src/main/res/values-ko-rKR/strings.xml
rename to app/src/main/res/values-ko/strings.xml
index 8e23145b..7d3d676a 100644
--- a/app/src/main/res/values-ko-rKR/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -68,7 +68,7 @@
단거리 (저속)
중거리 (저속)
장거리 (저속)
- 언어 (restart needed)
+ 언어
시스템 기본값
디버그 패널
500 last messages
diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-nb/strings.xml
similarity index 100%
rename from app/src/main/res/values-no/strings.xml
rename to app/src/main/res/values-nb/strings.xml
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 57896332..f0d311c7 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -126,7 +126,7 @@
W przypadku tej funkcji musisz przyznać opcję uprawnień lokalizacji „Zezwalaj przez cały czas”.
Anuluj (brak dostępu do radia)
Zezwól (pokaże okno dialogowe)
- Język (wymagany restart)
+ Język
Domyślny systemu
Typ map
Ponów
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index 7ac9000b..dee38c12 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -8,7 +8,7 @@
Não definido
Status da conexão
icone da aplicação
- Nome de usuário desconhecido
+ Nome desconhecido
Avatar do usuário
ei, encontrei o esconderijo, está aqui ao lado do tigre grande. estou um pouco assustado.
Enviar Texto
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index a06ccaff..6716772a 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -7,7 +7,7 @@
Não Definido
Estado da Conexão
icone da aplicação
- Nome Desconhecido
+ Nome desconhecido
Avatar do Usuário
Hey, encontrei o cache, está aqui ao lado do grande tigre. Estou um pouco assustado.
Enviar Texto
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index bc4b2273..38719253 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -124,7 +124,7 @@
Завантажити регіон
Ви не підключені до Інтернету, ви не можете завантажити офлайн-карту
Не вдалося завантажити пакет стилів
- Мова (потрібне перезавантаження)
+ Мова
Системні налаштунки за умовчанням
Джерело карти
Перенадіслати
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 1b2a470a..9e140b49 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -1,94 +1,5 @@
-
-
- - @string/preferences_system_default
-
- - English
-
- - Čeština
-
- - Chinese 中文
-
- - Deutsch
-
- - Español
-
- - Français
-
- - Gaeilge
-
- - Greek ελληνικά
-
- - Haiti
-
- - Italiano
-
- - Japanese 日本語
-
- - Korean 한국어
-
- - Magyar
-
- - Nederlands
-
- - Norge
-
- - Polski
-
- - Português
-
- - Português do Brasil
-
- - Română
-
- - Russian Pусский
-
- - Shqip
-
- - Slovenský
-
- - Slovenščina
-
- - Suomi
-
- - Svenska
-
- - Türkçe
-
- - Українська
-
-
-
- - zz
- - en
- - cs
- - zh
- - de
- - es
- - fr
- - ga
- - el
- - ht
- - it
- - ja
- - ko_KR
- - hu
- - nl
- - no
- - pl
- - pt
- - pt_BR
- - ro
- - ru
- - sq
- - sk
- - sl
- - fi
- - sv
- - tr
- - uk
-
- OpenStreetMap
- USGS TOPO
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 593e743e..722e6cb5 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,4 +1,8 @@
+ // Language tags native names (not available via .getDisplayLanguage)
+ Kreyòl ayisyen
+ Português do Brasil
+
Meshtastic
Settings
Channel Name
@@ -129,7 +133,7 @@
Download Region
You are not connected to the internet, you cannot download an offline map
Unable to download style pack
- Language (restart needed)
+ Language
System default
Map Source
Resend
diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml
new file mode 100644
index 00000000..07ca0b02
--- /dev/null
+++ b/app/src/main/res/xml/locales_config.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+