tachiyomi/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt
2023-11-04 23:28:41 -04:00

218 lines
8.4 KiB
Kotlin

package eu.kanade.presentation.more.settings.screen
import android.app.Activity
import android.content.Context
import android.os.Build
import androidx.annotation.StringRes
import androidx.appcompat.app.AppCompatDelegate
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.core.app.ActivityCompat
import androidx.core.os.LocaleListCompat
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.domain.ui.model.TabletUiMode
import eu.kanade.domain.ui.model.ThemeMode
import eu.kanade.domain.ui.model.setAppCompatDelegateThemeMode
import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.LocaleHelper
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.merge
import org.xmlpull.v1.XmlPullParser
import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.Date
object SettingsAppearanceScreen : SearchableSettings {
@ReadOnlyComposable
@Composable
@StringRes
override fun getTitleRes() = R.string.pref_category_appearance
@Composable
override fun getPreferences(): List<Preference> {
val context = LocalContext.current
val uiPreferences = remember { Injekt.get<UiPreferences>() }
return listOf(
getThemeGroup(context = context, uiPreferences = uiPreferences),
getDisplayGroup(context = context, uiPreferences = uiPreferences),
)
}
@Composable
private fun getThemeGroup(
context: Context,
uiPreferences: UiPreferences,
): Preference.PreferenceGroup {
val themeModePref = uiPreferences.themeMode()
val themeMode by themeModePref.collectAsState()
val appThemePref = uiPreferences.appTheme()
val amoledPref = uiPreferences.themeDarkAmoled()
val amoled by amoledPref.collectAsState()
LaunchedEffect(themeMode) {
setAppCompatDelegateThemeMode(themeMode)
}
LaunchedEffect(Unit) {
merge(appThemePref.changes(), amoledPref.changes())
.drop(2)
.collectLatest { (context as? Activity)?.let { ActivityCompat.recreate(it) } }
}
return Preference.PreferenceGroup(
title = stringResource(R.string.pref_category_theme),
preferenceItems = listOf(
Preference.PreferenceItem.ListPreference(
pref = themeModePref,
title = stringResource(R.string.pref_theme_mode),
entries = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mapOf(
ThemeMode.SYSTEM to stringResource(R.string.theme_system),
ThemeMode.LIGHT to stringResource(R.string.theme_light),
ThemeMode.DARK to stringResource(R.string.theme_dark),
)
} else {
mapOf(
ThemeMode.LIGHT to stringResource(R.string.theme_light),
ThemeMode.DARK to stringResource(R.string.theme_dark),
)
},
),
Preference.PreferenceItem.CustomPreference(
title = stringResource(R.string.pref_app_theme),
) { item ->
val value by appThemePref.collectAsState()
AppThemePreferenceWidget(
title = item.title,
value = value,
amoled = amoled,
onItemClick = { appThemePref.set(it) },
)
},
Preference.PreferenceItem.SwitchPreference(
pref = amoledPref,
title = stringResource(R.string.pref_dark_theme_pure_black),
enabled = themeMode != ThemeMode.LIGHT,
),
),
)
}
@Composable
private fun getDisplayGroup(
context: Context,
uiPreferences: UiPreferences,
): Preference.PreferenceGroup {
val langs = remember { getLangs(context) }
var currentLanguage by remember {
mutableStateOf(AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: "")
}
val now = remember { Date().time }
val dateFormat by uiPreferences.dateFormat().collectAsState()
val formattedNow = remember(dateFormat) {
UiPreferences.dateFormat(dateFormat).format(now)
}
LaunchedEffect(currentLanguage) {
val locale = if (currentLanguage.isEmpty()) {
LocaleListCompat.getEmptyLocaleList()
} else {
LocaleListCompat.forLanguageTags(currentLanguage)
}
AppCompatDelegate.setApplicationLocales(locale)
}
return Preference.PreferenceGroup(
title = stringResource(R.string.pref_category_display),
preferenceItems = listOf(
Preference.PreferenceItem.BasicListPreference(
value = currentLanguage,
title = stringResource(R.string.pref_app_language),
entries = langs,
onValueChanged = { newValue ->
currentLanguage = newValue
true
},
),
Preference.PreferenceItem.ListPreference(
pref = uiPreferences.tabletUiMode(),
title = stringResource(R.string.pref_tablet_ui_mode),
entries = TabletUiMode.entries.associateWith { stringResource(it.titleResId) },
onValueChanged = {
context.toast(R.string.requires_app_restart)
true
},
),
Preference.PreferenceItem.ListPreference(
pref = uiPreferences.dateFormat(),
title = stringResource(R.string.pref_date_format),
entries = DateFormats
.associateWith {
val formattedDate = UiPreferences.dateFormat(it).format(now)
"${it.ifEmpty { stringResource(R.string.label_default) }} ($formattedDate)"
},
),
Preference.PreferenceItem.SwitchPreference(
pref = uiPreferences.relativeTime(),
title = stringResource(R.string.pref_relative_format),
subtitle = stringResource(
R.string.pref_relative_format_summary,
stringResource(R.string.relative_time_today),
formattedNow,
),
),
),
)
}
private fun getLangs(context: Context): Map<String, String> {
val langs = mutableListOf<Pair<String, String>>()
val parser = context.resources.getXml(R.xml.locales_config)
var eventType = parser.eventType
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG && parser.name == "locale") {
for (i in 0..<parser.attributeCount) {
if (parser.getAttributeName(i) == "name") {
val langTag = parser.getAttributeValue(i)
val displayName = LocaleHelper.getDisplayName(langTag)
if (displayName.isNotEmpty()) {
langs.add(Pair(langTag, displayName))
}
}
}
}
eventType = parser.next()
}
langs.sortBy { it.second }
langs.add(0, Pair("", context.getString(R.string.label_default)))
return langs.toMap()
}
}
private val DateFormats = listOf(
"", // Default
"MM/dd/yy",
"dd/MM/yy",
"yyyy-MM-dd",
"dd MMM yyyy",
"MMM dd, yyyy",
)