kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
update in-app language picker (#538)
rodzic
8dca9ea8b6
commit
c9a81c72e0
|
@ -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'
|
||||
|
|
|
@ -81,11 +81,8 @@
|
|||
android:roundIcon="@mipmap/ic_launcher2_round"
|
||||
android:supportsRtl="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<meta-data
|
||||
android:name="com.mixpanel.android.MPConfig.DisableViewCrawler"
|
||||
android:value="true" />
|
||||
android:theme="@style/AppTheme"
|
||||
android:localeConfig="@xml/locales_config">
|
||||
|
||||
<!-- Default crash collection and analytics off until we (possibly) turn it on in application.onCreate -->
|
||||
<meta-data
|
||||
|
@ -112,6 +109,15 @@
|
|||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
android:enabled="false"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="autoStoreLocales"
|
||||
android:value="true" />
|
||||
</service>
|
||||
|
||||
<!-- zxing for QR Code scanning: lock portrait orientation -->
|
||||
<activity
|
||||
android:name="com.journeyapps.barcodescanner.CaptureActivity"
|
||||
|
|
|
@ -1,49 +0,0 @@
|
|||
package com.geeksville.mesh
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import java.util.*
|
||||
|
||||
open class BaseActivity: AppCompatActivity(), Logging {
|
||||
|
||||
override fun attachBaseContext(newBase: Context) {
|
||||
val res = newBase.resources
|
||||
val config = res.configuration
|
||||
|
||||
// get chosen language from preference
|
||||
val prefs = UIViewModel.getPreferences(newBase)
|
||||
val langCode: String = prefs.getString("lang","zz") ?: ""
|
||||
debug("langCode is $langCode")
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 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])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
package com.geeksville.mesh.ui
|
||||
|
||||
import com.geeksville.mesh.android.Logging
|
||||
|
||||
|
||||
object UILog : Logging
|
|
@ -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<Pair<String, String>> {
|
||||
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 }
|
||||
}
|
||||
}
|
|
@ -68,7 +68,7 @@
|
|||
<string name="modem_config_slow_short">단거리 (저속)</string>
|
||||
<string name="modem_config_slow_medium">중거리 (저속)</string>
|
||||
<string name="modem_config_slow_long">장거리 (저속)</string>
|
||||
<string name="preferences_language">언어 (restart needed)</string>
|
||||
<string name="preferences_language">언어</string>
|
||||
<string name="preferences_system_default">시스템 기본값</string>
|
||||
<string name="debug_panel">디버그 패널</string>
|
||||
<string name="debug_last_messages">500 last messages</string>
|
|
@ -126,7 +126,7 @@
|
|||
<string name="why_background_required">W przypadku tej funkcji musisz przyznać opcję uprawnień lokalizacji „Zezwalaj przez cały czas”.</string>
|
||||
<string name="cancel_no_radio">Anuluj (brak dostępu do radia)</string>
|
||||
<string name="allow_will_show">Zezwól (pokaże okno dialogowe)</string>
|
||||
<string name="preferences_language">Język (wymagany restart)</string>
|
||||
<string name="preferences_language">Język</string>
|
||||
<string name="preferences_system_default">Domyślny systemu</string>
|
||||
<string name="preferences_map_style">Typ map</string>
|
||||
<string name="resend">Ponów</string>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<string name="unset">Não definido</string>
|
||||
<string name="connection_status">Status da conexão</string>
|
||||
<string name="application_icon">icone da aplicação</string>
|
||||
<string name="unknown_username">Nome de usuário desconhecido</string>
|
||||
<string name="unknown_username">Nome desconhecido</string>
|
||||
<string name="user_avatar">Avatar do usuário</string>
|
||||
<string name="sample_message">ei, encontrei o esconderijo, está aqui ao lado do tigre grande. estou um pouco assustado.</string>
|
||||
<string name="send_text">Enviar Texto</string>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<string name="unset">Não Definido</string>
|
||||
<string name="connection_status">Estado da Conexão</string>
|
||||
<string name="application_icon">icone da aplicação</string>
|
||||
<string name="unknown_username">Nome Desconhecido</string>
|
||||
<string name="unknown_username">Nome desconhecido</string>
|
||||
<string name="user_avatar">Avatar do Usuário</string>
|
||||
<string name="sample_message">Hey, encontrei o cache, está aqui ao lado do grande tigre. Estou um pouco assustado.</string>
|
||||
<string name="send_text">Enviar Texto</string>
|
||||
|
|
|
@ -124,7 +124,7 @@
|
|||
<string name="download_region_dialog_title">Завантажити регіон</string>
|
||||
<string name="download_region_connection_alert">Ви не підключені до Інтернету, ви не можете завантажити офлайн-карту</string>
|
||||
<string name="download_failed">Не вдалося завантажити пакет стилів</string>
|
||||
<string name="preferences_language">Мова (потрібне перезавантаження)</string>
|
||||
<string name="preferences_language">Мова</string>
|
||||
<string name="preferences_system_default">Системні налаштунки за умовчанням</string>
|
||||
<string name="preferences_map_style">Джерело карти</string>
|
||||
<string name="resend">Перенадіслати</string>
|
||||
|
|
|
@ -1,94 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string-array name="language_entries" translatable="false">
|
||||
<!-- zz -->
|
||||
<item>@string/preferences_system_default</item>
|
||||
<!-- en -->
|
||||
<item>English</item>
|
||||
<!-- cs -->
|
||||
<item>Čeština</item>
|
||||
<!-- zh -->
|
||||
<item>Chinese 中文 </item>
|
||||
<!-- de -->
|
||||
<item>Deutsch</item>
|
||||
<!-- es -->
|
||||
<item>Español</item>
|
||||
<!-- fr -->
|
||||
<item>Français</item>
|
||||
<!-- ga -->
|
||||
<item>Gaeilge</item>
|
||||
<!-- el -->
|
||||
<item>Greek ελληνικά</item>
|
||||
<!-- ht -->
|
||||
<item>Haiti</item>
|
||||
<!-- it -->
|
||||
<item>Italiano</item>
|
||||
<!-- ja -->
|
||||
<item>Japanese 日本語</item>
|
||||
<!-- ko-rKR -->
|
||||
<item>Korean 한국어</item>
|
||||
<!-- hu -->
|
||||
<item>Magyar</item>
|
||||
<!-- nl -->
|
||||
<item>Nederlands</item>
|
||||
<!-- no -->
|
||||
<item>Norge</item>
|
||||
<!-- pl -->
|
||||
<item>Polski</item>
|
||||
<!-- pt -->
|
||||
<item>Português</item>
|
||||
<!-- pt_BR -->
|
||||
<item>Português do Brasil</item>
|
||||
<!-- ro -->
|
||||
<item>Română</item>
|
||||
<!-- ru -->
|
||||
<item>Russian Pусский</item>
|
||||
<!-- sq -->
|
||||
<item>Shqip</item>
|
||||
<!-- sk -->
|
||||
<item>Slovenský</item>
|
||||
<!-- sl -->
|
||||
<item>Slovenščina</item>
|
||||
<!-- fi -->
|
||||
<item>Suomi</item>
|
||||
<!-- sv -->
|
||||
<item>Svenska</item>
|
||||
<!-- tr -->
|
||||
<item>Türkçe</item>
|
||||
<!-- uk -->
|
||||
<item>Українська</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="language_values" translatable="false">
|
||||
<item>zz</item>
|
||||
<item>en</item>
|
||||
<item>cs</item>
|
||||
<item>zh</item>
|
||||
<item>de</item>
|
||||
<item>es</item>
|
||||
<item>fr</item>
|
||||
<item>ga</item>
|
||||
<item>el</item>
|
||||
<item>ht</item>
|
||||
<item>it</item>
|
||||
<item>ja</item>
|
||||
<item>ko_KR</item>
|
||||
<item>hu</item>
|
||||
<item>nl</item>
|
||||
<item>no</item>
|
||||
<item>pl</item>
|
||||
<item>pt</item>
|
||||
<item>pt_BR</item>
|
||||
<item>ro</item>
|
||||
<item>ru</item>
|
||||
<item>sq</item>
|
||||
<item>sk</item>
|
||||
<item>sl</item>
|
||||
<item>fi</item>
|
||||
<item>sv</item>
|
||||
<item>tr</item>
|
||||
<item>uk</item>
|
||||
</string-array>
|
||||
<string-array name="map_styles">
|
||||
<item>OpenStreetMap</item>
|
||||
<item>USGS TOPO</item>
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
<resources>
|
||||
// Language tags native names (not available via .getDisplayLanguage)
|
||||
<string name="fr_HT" translatable="false">Kreyòl ayisyen</string>
|
||||
<string name="pt_BR" translatable="false">Português do Brasil</string>
|
||||
|
||||
<string name="app_name" translatable="false">Meshtastic</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
<string name="channel_name">Channel Name</string>
|
||||
|
@ -129,7 +133,7 @@
|
|||
<string name="download_region_dialog_title">Download Region</string>
|
||||
<string name="download_region_connection_alert">You are not connected to the internet, you cannot download an offline map</string>
|
||||
<string name="download_failed">Unable to download style pack</string>
|
||||
<string name="preferences_language">Language (restart needed)</string>
|
||||
<string name="preferences_language">Language</string>
|
||||
<string name="preferences_system_default">System default</string>
|
||||
<string name="preferences_map_style">Map Source</string>
|
||||
<string name="resend">Resend</string>
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<locale android:name="en"/> <!-- English (ultimate fallback locale) -->
|
||||
<locale android:name="cs"/> <!-- Czech -->
|
||||
<locale android:name="zh"/> <!-- Chinese -->
|
||||
<locale android:name="de"/> <!-- German -->
|
||||
<locale android:name="es"/> <!-- Spanish (Spain) -->
|
||||
<locale android:name="fr"/> <!-- French (France) -->
|
||||
<locale android:name="ga"/> <!-- Irish -->
|
||||
<locale android:name="el"/> <!-- Greek -->
|
||||
<locale android:name="fr-HT"/> <!-- Haitian Creole -->
|
||||
<locale android:name="it"/> <!-- Italian -->
|
||||
<locale android:name="ja"/> <!-- Japanese -->
|
||||
<locale android:name="ko"/> <!-- Korean -->
|
||||
<locale android:name="hu"/> <!-- Hungarian -->
|
||||
<locale android:name="nl"/> <!-- Dutch -->
|
||||
<locale android:name="nb"/> <!-- Norwegian -->
|
||||
<locale android:name="pl"/> <!-- Polish -->
|
||||
<locale android:name="pt"/> <!-- Portuguese -->
|
||||
<locale android:name="pt-BR"/> <!-- Portuguese (Brazil) -->
|
||||
<locale android:name="ro"/> <!-- Romanian -->
|
||||
<locale android:name="ru"/> <!-- Russian -->
|
||||
<locale android:name="sq"/> <!-- Albanian -->
|
||||
<locale android:name="sk"/> <!-- Slovak -->
|
||||
<locale android:name="sl"/> <!-- Slovenian -->
|
||||
<locale android:name="fi"/> <!-- Finnish -->
|
||||
<locale android:name="sv"/> <!-- Swedish -->
|
||||
<locale android:name="tr"/> <!-- Turkish -->
|
||||
<locale android:name="uk"/> <!-- Ukrainian -->
|
||||
</locale-config>
|
Ładowanie…
Reference in New Issue