Option to change app language #282

This commit is contained in:
Koitharu
2023-01-19 18:57:24 +02:00
parent 6f37d95c24
commit 9cb5971182
10 changed files with 167 additions and 9 deletions

2
.idea/gradle.xml generated
View File

@@ -7,7 +7,7 @@
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Embedded JDK" />
<option name="gradleJvm" value="jbr-17" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />

View File

@@ -90,6 +90,7 @@ dependencies {
implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.8.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation "androidx.appcompat:appcompat:1.6.0"
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.activity:activity-ktx:1.6.1'
implementation 'androidx.fragment:fragment-ktx:1.5.5'

View File

@@ -50,6 +50,7 @@ class KotatsuApp : Application(), Configuration.Provider {
enableStrictMode()
}
AppCompatDelegate.setDefaultNightMode(settings.theme)
AppCompatDelegate.setApplicationLocales(settings.appLocales)
setupActivityLifecycleCallbacks()
processLifecycleScope.launch(Dispatchers.Default) {
setupDatabaseObservers()

View File

@@ -7,6 +7,7 @@ import android.provider.Settings
import androidx.appcompat.app.AppCompatDelegate
import androidx.collection.arraySetOf
import androidx.core.content.edit
import androidx.core.os.LocaleListCompat
import androidx.preference.PreferenceManager
import com.google.android.material.color.DynamicColors
import dagger.hilt.android.qualifiers.ApplicationContext
@@ -79,6 +80,17 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
get() = prefs.getInt(KEY_GRID_SIZE, 100)
set(value) = prefs.edit { putInt(KEY_GRID_SIZE, value) }
var appLocales: LocaleListCompat
get() {
val raw = prefs.getString(KEY_APP_LOCALE, null)
return LocaleListCompat.forLanguageTags(raw)
}
set(value) {
prefs.edit {
putString(KEY_APP_LOCALE, value.toLanguageTags())
}
}
val readerPageSwitch: Set<String>
get() = prefs.getStringSet(KEY_READER_SWITCHERS, null) ?: setOf(PAGE_SWITCH_TAPS)
@@ -358,6 +370,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_WEBTOON_ZOOM = "webtoon_zoom"
const val KEY_SHELF_SECTIONS = "shelf_sections_2"
const val KEY_PREFETCH_CONTENT = "prefetch_content"
const val KEY_APP_LOCALE = "app_locale"
// About
const val KEY_APP_UPDATE = "app_update"

View File

@@ -1,27 +1,38 @@
package org.koitharu.kotatsu.settings
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.provider.Settings
import android.view.View
import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.LocaleManagerCompat
import androidx.core.view.postDelayed
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.TwoStatePreference
import com.google.android.material.color.DynamicColors
import dagger.hilt.android.AndroidEntryPoint
import java.util.*
import javax.inject.Inject
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BasePreferenceFragment
import org.koitharu.kotatsu.base.ui.util.ActivityRecreationHandle
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.parsers.util.names
import org.koitharu.kotatsu.parsers.util.toTitleCase
import org.koitharu.kotatsu.settings.protect.ProtectSetupActivity
import org.koitharu.kotatsu.settings.utils.ActivityListPreference
import org.koitharu.kotatsu.settings.utils.SliderPreference
import org.koitharu.kotatsu.utils.ext.getLocalesConfig
import org.koitharu.kotatsu.utils.ext.map
import org.koitharu.kotatsu.utils.ext.setDefaultValueCompat
import org.koitharu.kotatsu.utils.ext.toList
import java.util.Date
import java.util.Locale
import javax.inject.Inject
@AndroidEntryPoint
class AppearanceSettingsFragment :
@@ -52,7 +63,7 @@ class AppearanceSettingsFragment :
entries = entryValues.map { value ->
val formattedDate = settings.getDateFormat(value.toString()).format(now)
if (value == "") {
"${context.getString(R.string.system_default)} ($formattedDate)"
getString(R.string.default_s, formattedDate)
} else {
formattedDate
}
@@ -62,6 +73,20 @@ class AppearanceSettingsFragment :
}
findPreference<TwoStatePreference>(AppSettings.KEY_PROTECT_APP)
?.isChecked = !settings.appPassword.isNullOrEmpty()
findPreference<ActivityListPreference>(AppSettings.KEY_APP_LOCALE)?.run {
initLocalePicker(this)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
activityIntent = Intent(
Settings.ACTION_APP_LOCALE_SETTINGS,
Uri.fromParts("package", context.packageName, null),
)
}
summaryProvider = Preference.SummaryProvider<ActivityListPreference> {
val locale = AppCompatDelegate.getApplicationLocales().get(0)
locale?.getDisplayName(locale)?.toTitleCase(locale) ?: getString(R.string.automatic)
}
setDefaultValueCompat("")
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -79,16 +104,23 @@ class AppearanceSettingsFragment :
AppSettings.KEY_THEME -> {
AppCompatDelegate.setDefaultNightMode(settings.theme)
}
AppSettings.KEY_DYNAMIC_THEME -> {
postRestart()
}
AppSettings.KEY_THEME_AMOLED -> {
postRestart()
}
AppSettings.KEY_APP_PASSWORD -> {
findPreference<TwoStatePreference>(AppSettings.KEY_PROTECT_APP)
?.isChecked = !settings.appPassword.isNullOrEmpty()
}
AppSettings.KEY_APP_LOCALE -> {
AppCompatDelegate.setApplicationLocales(settings.appLocales)
}
}
}
@@ -104,6 +136,7 @@ class AppearanceSettingsFragment :
}
true
}
else -> super.onPreferenceTreeClick(preference)
}
}
@@ -113,4 +146,45 @@ class AppearanceSettingsFragment :
activityRecreationHandle.recreateAll()
}
}
private fun initLocalePicker(preference: ListPreference) {
val locales = resources.getLocalesConfig()
.toList()
.sortedWith(LocaleComparator(preference.context))
preference.entries = Array(locales.size + 1) { i ->
if (i == 0) {
getString(R.string.automatic)
} else {
val lc = locales[i - 1]
lc.getDisplayName(lc).toTitleCase(lc)
}
}
preference.entryValues = Array(locales.size + 1) { i ->
if (i == 0) {
""
} else {
locales[i - 1].toLanguageTag()
}
}
}
private class LocaleComparator(context: Context) : Comparator<Locale> {
private val deviceLocales = LocaleManagerCompat.getSystemLocales(context)
.map { it.language }
override fun compare(a: Locale, b: Locale): Int {
return if (a === b) {
0
} else {
val indexA = deviceLocales.indexOf(a.language)
val indexB = deviceLocales.indexOf(b.language)
if (indexA == -1 && indexB == -1) {
compareValues(a.language, b.language)
} else {
-2 - (indexA - indexB)
}
}
}
}
}

View File

@@ -0,0 +1,38 @@
package org.koitharu.kotatsu.settings.utils
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.util.AttributeSet
import androidx.preference.ListPreference
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
class ActivityListPreference : ListPreference {
var activityIntent: Intent? = null
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context) : super(context)
override fun onClick() {
val intent = activityIntent
if (intent == null) {
super.onClick()
return
}
try {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
e.printStackTraceDebug()
super.onClick()
}
}
}

View File

@@ -8,6 +8,7 @@ import android.content.OperationApplicationException
import android.content.SharedPreferences
import android.content.SyncResult
import android.content.pm.ResolveInfo
import android.content.res.Resources
import android.database.SQLException
import android.graphics.Color
import android.net.ConnectivityManager
@@ -20,6 +21,7 @@ import android.view.Window
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.IntegerRes
import androidx.core.app.ActivityOptionsCompat
import androidx.core.os.LocaleListCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope
import androidx.work.CoroutineWorker
@@ -34,8 +36,12 @@ import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import okio.IOException
import org.json.JSONException
import org.jsoup.internal.StringUtil.StringJoiner
import org.koitharu.kotatsu.BuildConfig
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.utils.InternalResourceHelper
import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException
import kotlin.math.roundToLong
val Context.activityManager: ActivityManager?
@@ -146,3 +152,23 @@ fun scaleUpActivityOptionsOf(view: View): ActivityOptions = ActivityOptions.make
view.width,
view.height,
)
fun Resources.getLocalesConfig(): LocaleListCompat {
val tagsList = StringJoiner(",")
try {
val xpp: XmlPullParser = getXml(R.xml.locales)
while (xpp.eventType != XmlPullParser.END_DOCUMENT) {
if (xpp.eventType == XmlPullParser.START_TAG) {
if (xpp.name == "locale") {
tagsList.add(xpp.getAttributeValue(0))
}
}
xpp.next()
}
} catch (e: XmlPullParserException) {
e.printStackTraceDebug()
} catch (e: IOException) {
e.printStackTraceDebug()
}
return LocaleListCompat.forLanguageTags(tagsList.complete())
}

View File

@@ -401,4 +401,5 @@
<string name="source_disabled">Source disabled</string>
<string name="prefetch_content">Content preloading</string>
<string name="mark_as_current">Mark as current</string>
<string name="language">Language</string>
</resources>

View File

@@ -14,16 +14,16 @@
<locale android:name="in" />
<locale android:name="it" />
<locale android:name="ja" />
<locale android:name="nb-rNO" />
<locale android:name="nb-NO" />
<locale android:name="pl" />
<locale android:name="pt" />
<locale android:name="pt-rBR" />
<locale android:name="pt-BR" />
<locale android:name="ru" />
<locale android:name="si" />
<locale android:name="sr" />
<locale android:name="sv" />
<locale android:name="tr" />
<locale android:name="uk" />
<locale android:name="zh-rCN" />
<locale android:name="zh-rTW" />
<locale android:name="zh-CN" />
<locale android:name="zh-TW" />
</locale-config>

View File

@@ -24,6 +24,10 @@
android:summary="@string/black_dark_theme_summary"
android:title="@string/black_dark_theme" />
<org.koitharu.kotatsu.settings.utils.ActivityListPreference
android:key="app_locale"
android:title="@string/language" />
<ListPreference
android:key="date_format"
android:title="@string/date_format" />
@@ -56,4 +60,4 @@
android:summary="@string/protect_application_summary"
android:title="@string/protect_application" />
</PreferenceScreen>
</PreferenceScreen>