mirror of
https://github.com/home-assistant/android
synced 2024-10-15 12:32:54 +00:00
Update and fix in-app language picker (#3080)
* Manage app language using AndroidX, integrate with Android 13 - Manage the app language using AndroidX to fix the setting not being applied and to be able to easily integrate with Android 13's system setting * Move locales_config.xml to common * Generate locales_config.xml when downloading translations * Fix multiple variants for languages being collapsed into one * Fix language codes with region variants * Don't split languages when using app bundles * Rename
This commit is contained in:
parent
5a38f95482
commit
c50ce30b5b
14
.github/actions/download-translations/action.yml
vendored
14
.github/actions/download-translations/action.yml
vendored
|
@ -35,4 +35,18 @@ runs:
|
|||
|
||||
echo "Download Complete, unzipping"
|
||||
unzip -n strings.zip
|
||||
echo "Unzipped strings, generating locales_config.xml"
|
||||
|
||||
XML_START='<?xml version="1.0" encoding="utf-8"?>\n<locale-config xmlns:android="http://schemas.android.com/apk/res/android">\n'
|
||||
XML_LOCALES=''
|
||||
XML_END='</locale-config>'
|
||||
for i in common/src/main/res/values*/strings.xml; do
|
||||
FOLDER="$(basename $(dirname $i))"
|
||||
CODE="${FOLDER#*-}" # remove "values-"
|
||||
CODE="${CODE/-r/-}" # replace region "-rXX" with "-XX"
|
||||
if [ "$CODE" == "values" ]; then CODE="en"; fi
|
||||
XML_LOCALES="$XML_LOCALES <locale android:name=\"$CODE\"/>\n"
|
||||
done
|
||||
printf "$XML_START$XML_LOCALES$XML_END" > common/src/main/res/xml/locales_config.xml
|
||||
|
||||
echo "Complete"
|
||||
|
|
|
@ -29,6 +29,12 @@ android {
|
|||
versionCode = System.getenv("VERSION_CODE")?.toIntOrNull() ?: 1
|
||||
|
||||
manifestPlaceholders["sentryRelease"] = "$applicationId@$versionName"
|
||||
|
||||
bundle {
|
||||
language {
|
||||
enableSplit = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
|
@ -149,7 +155,7 @@ dependencies {
|
|||
implementation("com.google.dagger:hilt-android:2.44.2")
|
||||
kapt("com.google.dagger:hilt-android-compiler:2.44.2")
|
||||
|
||||
implementation("androidx.appcompat:appcompat:1.5.1")
|
||||
implementation("androidx.appcompat:appcompat:1.6.0-rc01")
|
||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.1")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
implementation("androidx.recyclerview:recyclerview:1.2.1")
|
||||
|
|
|
@ -68,7 +68,9 @@
|
|||
android:theme="@style/Theme.HomeAssistant"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
tools:ignore="GoogleAppIndexingWarning">
|
||||
android:localeConfig="@xml/locales_config"
|
||||
tools:ignore="GoogleAppIndexingWarning"
|
||||
tools:targetApi="tiramisu">
|
||||
|
||||
<!-- Start things like SensorWorker on device boot -->
|
||||
<receiver android:name=".websocket.WebsocketBroadcastReceiver"
|
||||
|
@ -534,6 +536,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>
|
||||
|
||||
<receiver
|
||||
android:name=".notifications.NotificationActionReceiver"
|
||||
android:enabled="true"
|
||||
|
|
|
@ -2,12 +2,4 @@ package io.homeassistant.companion.android
|
|||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
open class BaseActivity : AppCompatActivity() {
|
||||
//
|
||||
// @Inject
|
||||
// lateinit var lm: LanguagesManager
|
||||
//
|
||||
// override fun attachBaseContext(newBase: Context) {
|
||||
// super.attachBaseContext(lm.getContextWrapper(newBase))
|
||||
// }
|
||||
}
|
||||
open class BaseActivity : AppCompatActivity()
|
||||
|
|
|
@ -17,6 +17,7 @@ import io.homeassistant.companion.android.common.sensors.LastUpdateManager
|
|||
import io.homeassistant.companion.android.database.AppDatabase
|
||||
import io.homeassistant.companion.android.database.settings.SensorUpdateFrequencySetting
|
||||
import io.homeassistant.companion.android.sensors.SensorReceiver
|
||||
import io.homeassistant.companion.android.settings.language.LanguagesManager
|
||||
import io.homeassistant.companion.android.websocket.WebsocketBroadcastReceiver
|
||||
import io.homeassistant.companion.android.widgets.button.ButtonWidget
|
||||
import io.homeassistant.companion.android.widgets.entity.EntityWidget
|
||||
|
@ -39,6 +40,9 @@ open class HomeAssistantApplication : Application() {
|
|||
@Inject
|
||||
lateinit var keyChainRepository: KeyChainRepository
|
||||
|
||||
@Inject
|
||||
lateinit var languagesManager: LanguagesManager
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
|
@ -49,6 +53,8 @@ open class HomeAssistantApplication : Application() {
|
|||
)
|
||||
}
|
||||
|
||||
languagesManager.applyCurrentLang()
|
||||
|
||||
// This will make sure we start/stop when we actually need too.
|
||||
registerReceiver(
|
||||
WebsocketBroadcastReceiver(),
|
||||
|
|
|
@ -436,10 +436,6 @@ class SettingsFragment constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onLangSettingsChanged() {
|
||||
requireActivity().recreate()
|
||||
}
|
||||
|
||||
private fun onDisplaySsidScreen() {
|
||||
val permissionsToCheck: Array<String> = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
|
||||
|
|
|
@ -106,10 +106,7 @@ class SettingsPresenterImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
"themes" -> themesManager.saveTheme(value)
|
||||
"languages" -> {
|
||||
langsManager.saveLang(value)
|
||||
settingsView.onLangSettingsChanged()
|
||||
}
|
||||
"languages" -> langsManager.saveLang(value)
|
||||
else -> throw IllegalArgumentException("No string found by this key: $key")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,4 @@ interface SettingsView {
|
|||
fun updateExternalUrl(url: String, useCloud: Boolean)
|
||||
|
||||
fun updateSsids(ssids: Set<String>)
|
||||
|
||||
fun onLangSettingsChanged()
|
||||
}
|
||||
|
|
|
@ -1,124 +1,101 @@
|
|||
package io.homeassistant.companion.android.settings.language
|
||||
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.os.LocaleList
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatDelegate
|
||||
import androidx.core.os.LocaleListCompat
|
||||
import io.homeassistant.companion.android.common.data.prefs.PrefsRepository
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.util.Locale
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import javax.inject.Inject
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
||||
class LanguagesManager @Inject constructor(
|
||||
private var prefs: PrefsRepository
|
||||
) {
|
||||
companion object {
|
||||
private const val DEF_LOCALE = "default"
|
||||
}
|
||||
private const val TAG = "LanguagesManager"
|
||||
|
||||
fun getAppVersion(): String? {
|
||||
return runBlocking {
|
||||
prefs.getAppVersion()
|
||||
}
|
||||
}
|
||||
|
||||
fun saveAppVersion(ver: String) {
|
||||
return runBlocking {
|
||||
prefs.saveAppVersion(ver)
|
||||
}
|
||||
const val DEF_LOCALE = "default"
|
||||
private const val SYSTEM_MANAGES_LOCALE = "system_managed"
|
||||
}
|
||||
|
||||
fun getCurrentLang(): String {
|
||||
return runBlocking {
|
||||
val lang = prefs.getCurrentLang()
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
migrateLangSetting()
|
||||
AppCompatDelegate.getApplicationLocales().toLanguageTags().ifEmpty { DEF_LOCALE }
|
||||
} else {
|
||||
if (lang.isNullOrEmpty()) {
|
||||
prefs.saveLang(DEF_LOCALE)
|
||||
DEF_LOCALE
|
||||
} else lang
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun saveLang(lang: String?) {
|
||||
return runBlocking {
|
||||
if (!lang.isNullOrEmpty()) {
|
||||
val currentLang = getCurrentLang()
|
||||
if (currentLang != lang) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
val languages =
|
||||
if (lang == DEF_LOCALE) LocaleListCompat.getEmptyLocaleList()
|
||||
else LocaleListCompat.forLanguageTags(lang)
|
||||
AppCompatDelegate.setApplicationLocales(languages) // Applying will also save it
|
||||
} else if (currentLang != lang) {
|
||||
prefs.saveLang(lang)
|
||||
applyCurrentLang()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getLocales(): String? {
|
||||
fun applyCurrentLang() = runBlocking {
|
||||
migrateLangSetting()
|
||||
|
||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
|
||||
val lang = getCurrentLang()
|
||||
val languages =
|
||||
if (lang == DEF_LOCALE) LocaleListCompat.getEmptyLocaleList()
|
||||
else LocaleListCompat.forLanguageTags(lang)
|
||||
AppCompatDelegate.setApplicationLocales(languages)
|
||||
} // else on Android 13+ the system will manage the app's language
|
||||
}
|
||||
|
||||
private suspend fun migrateLangSetting() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return
|
||||
val lang = prefs.getCurrentLang()
|
||||
if (lang == SYSTEM_MANAGES_LOCALE) return
|
||||
|
||||
// First run on Android 13: save in AndroidX, update app preference
|
||||
val languages =
|
||||
if (lang == DEF_LOCALE) LocaleListCompat.getEmptyLocaleList()
|
||||
else LocaleListCompat.forLanguageTags(lang)
|
||||
AppCompatDelegate.setApplicationLocales(languages)
|
||||
prefs.saveLang(SYSTEM_MANAGES_LOCALE)
|
||||
}
|
||||
|
||||
fun getLocaleTags(context: Context): List<String> {
|
||||
return runBlocking {
|
||||
prefs.getLocales()
|
||||
val languagesList = mutableListOf<String>()
|
||||
try {
|
||||
context.resources.getXml(commonR.xml.locales_config).use {
|
||||
var tagType = it.eventType
|
||||
while (tagType != XmlPullParser.END_DOCUMENT) {
|
||||
if (tagType == XmlPullParser.START_TAG && it.name == "locale") {
|
||||
languagesList += it.getAttributeValue("http://schemas.android.com/apk/res/android", "name")
|
||||
}
|
||||
tagType = it.next()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Exception while parsing locale config XML", e)
|
||||
}
|
||||
|
||||
fun saveLocales(locales: String) {
|
||||
return runBlocking {
|
||||
prefs.saveLocales(locales)
|
||||
languagesList
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeLocale(lang: String): Locale {
|
||||
return if (lang.contains('-')) {
|
||||
Locale(lang.split('-')[0], lang.split('-')[1])
|
||||
} else {
|
||||
Locale(lang)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDefaultLocale(config: Configuration): Locale {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
config.locales.get(0)
|
||||
} else {
|
||||
config.locale
|
||||
}
|
||||
}
|
||||
|
||||
private fun getDeviceLocale(): Locale {
|
||||
return getDefaultLocale(Resources.getSystem().configuration)
|
||||
}
|
||||
|
||||
private fun getApplicationLocale(context: Context): Locale {
|
||||
return getDefaultLocale(context.resources.configuration)
|
||||
}
|
||||
|
||||
fun getContextWrapper(context: Context): ContextWrapper {
|
||||
return when {
|
||||
getCurrentLang() == DEF_LOCALE && getApplicationLocale(context) != getDeviceLocale() -> {
|
||||
ContextWrapper(updateContext(context, getDeviceLocale()))
|
||||
}
|
||||
getCurrentLang() != DEF_LOCALE -> {
|
||||
val locale = makeLocale(getCurrentLang())
|
||||
ContextWrapper(updateContext(context, locale))
|
||||
}
|
||||
else -> {
|
||||
ContextWrapper(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateContext(context: Context, locale: Locale): Context {
|
||||
val resources: Resources = context.resources
|
||||
val configuration: Configuration = resources.configuration
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
val localeList = LocaleList(locale)
|
||||
LocaleList.setDefault(localeList)
|
||||
configuration.setLocales(localeList)
|
||||
} else {
|
||||
configuration.locale = locale
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
context.createConfigurationContext(configuration)
|
||||
} else {
|
||||
resources.updateConfiguration(configuration, resources.displayMetrics)
|
||||
}
|
||||
return context
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
package io.homeassistant.companion.android.settings.language
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.os.LocaleList
|
||||
import android.util.DisplayMetrics
|
||||
import io.homeassistant.companion.android.BuildConfig
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
import io.homeassistant.companion.android.common.R as commonR
|
||||
|
@ -18,52 +13,21 @@ class LanguagesProvider @Inject constructor(
|
|||
val listAppLocales = sortedMapOf<String, String>()
|
||||
val resources = context.resources
|
||||
|
||||
if (langManager.getAppVersion() != BuildConfig.VERSION_NAME || langManager.getLocales().isNullOrEmpty()) {
|
||||
val listLocales = resources.assets.locales
|
||||
val defString = getStringResource(context, "")
|
||||
var supportedLocales = ""
|
||||
|
||||
listLocales.forEach {
|
||||
if (getStringResource(context, it) != defString || it == Locale.ENGLISH.language) {
|
||||
val name = makeLocale(it).displayLanguage.capitalize()
|
||||
listAppLocales["$name ($it)"] = it
|
||||
supportedLocales += "$it,"
|
||||
}
|
||||
}
|
||||
langManager.saveAppVersion(BuildConfig.VERSION_NAME)
|
||||
langManager.saveLocales(supportedLocales)
|
||||
} else {
|
||||
val listLocales = langManager.getLocales()!!.split(',')
|
||||
listLocales.forEach {
|
||||
if (it.isNotEmpty()) {
|
||||
val name = makeLocale(it).displayLanguage.capitalize()
|
||||
listAppLocales["$name ($it)"] = it
|
||||
}
|
||||
val locales = langManager.getLocaleTags(context)
|
||||
locales.forEach {
|
||||
val locale = makeLocale(it)
|
||||
var display = locale.getDisplayLanguage(locale).replaceFirstChar { char ->
|
||||
if (char.isLowerCase()) char.titlecase(locale) else char.toString()
|
||||
}
|
||||
if (locale.country.isNotBlank()) display += " (${locale.getDisplayCountry(locale)})"
|
||||
listAppLocales[display] = it
|
||||
}
|
||||
|
||||
val languages = mutableMapOf(resources.getString(commonR.string.lang_option_label_default) to resources.getString(commonR.string.lang_option_value_default))
|
||||
val languages = mutableMapOf(resources.getString(commonR.string.lang_option_label_default) to LanguagesManager.DEF_LOCALE)
|
||||
languages.putAll(listAppLocales)
|
||||
return languages
|
||||
}
|
||||
|
||||
private fun getStringResource(context: Context, lang: String): String {
|
||||
val resource = commonR.string.application_version
|
||||
val resources = context.resources
|
||||
val configuration = resources.configuration
|
||||
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
configuration.setLocales(LocaleList(makeLocale(lang)))
|
||||
val c = context.createConfigurationContext(configuration)
|
||||
c.getString(resource)
|
||||
} else {
|
||||
val metrics = DisplayMetrics()
|
||||
configuration.locale = makeLocale(lang)
|
||||
val res = Resources(context.assets, metrics, configuration)
|
||||
res.getString(resource)
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeLocale(lang: String): Locale {
|
||||
return if (lang.contains('-')) {
|
||||
Locale(lang.split('-')[0], lang.split('-')[1])
|
||||
|
|
|
@ -85,7 +85,6 @@ import io.homeassistant.companion.android.nfc.WriteNfcTag
|
|||
import io.homeassistant.companion.android.sensors.SensorReceiver
|
||||
import io.homeassistant.companion.android.sensors.SensorWorker
|
||||
import io.homeassistant.companion.android.settings.SettingsActivity
|
||||
import io.homeassistant.companion.android.settings.language.LanguagesManager
|
||||
import io.homeassistant.companion.android.themes.ThemesManager
|
||||
import io.homeassistant.companion.android.util.ChangeLog
|
||||
import io.homeassistant.companion.android.util.DataUriDownloadManager
|
||||
|
@ -164,9 +163,6 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
|
|||
@Inject
|
||||
lateinit var changeLog: ChangeLog
|
||||
|
||||
@Inject
|
||||
lateinit var languagesManager: LanguagesManager
|
||||
|
||||
@Inject
|
||||
lateinit var urlRepository: UrlRepository
|
||||
|
||||
|
@ -651,7 +647,6 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
|
|||
if (presenter.isKeepScreenOnEnabled())
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
|
||||
currentLang = languagesManager.getCurrentLang()
|
||||
currentAutoplay = presenter.isAutoPlayVideoEnabled()
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
|
@ -693,7 +688,7 @@ class WebViewActivity : BaseActivity(), io.homeassistant.companion.android.webvi
|
|||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
if ((currentLang != languagesManager.getCurrentLang()) || currentAutoplay != presenter.isAutoPlayVideoEnabled())
|
||||
if (currentAutoplay != presenter.isAutoPlayVideoEnabled())
|
||||
recreate()
|
||||
|
||||
appLocked = presenter.isAppLocked()
|
||||
|
|
4
common/src/main/res/xml/locales_config.xml
Normal file
4
common/src/main/res/xml/locales_config.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<locale android:name="en"/>
|
||||
</locale-config>
|
Loading…
Reference in a new issue