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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +