From 6b8e4eeb144c53e0b1984fb8cd2f3106fd2291fd Mon Sep 17 00:00:00 2001 From: Jays2Kings Date: Sat, 10 Apr 2021 02:14:33 -0400 Subject: [PATCH] Preview of Themes in General settings --- .../tachiyomi/ui/setting/PreferenceDSL.kt | 4 + .../ui/setting/SettingsGeneralController.kt | 35 +- .../tachiyomi/ui/setting/ThemePreference.kt | 164 ++++++++ .../kanade/tachiyomi/util/system/ThemeUtil.kt | 181 ++++++++- app/src/main/res/drawable/oval.xml | 6 + app/src/main/res/drawable/right_half_oval.xml | 7 + .../res/drawable/theme_selected_border.xml | 10 + app/src/main/res/layout/theme_item.xml | 355 ++++++++++++++++++ app/src/main/res/layout/themes_preference.xml | 35 ++ app/src/main/res/values/dimens.xml | 15 + 10 files changed, 794 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/setting/ThemePreference.kt create mode 100644 app/src/main/res/drawable/oval.xml create mode 100644 app/src/main/res/drawable/right_half_oval.xml create mode 100644 app/src/main/res/drawable/theme_selected_border.xml create mode 100644 app/src/main/res/layout/theme_item.xml create mode 100644 app/src/main/res/layout/themes_preference.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/PreferenceDSL.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/PreferenceDSL.kt index c22d8419b9..beb97a6644 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/PreferenceDSL.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/PreferenceDSL.kt @@ -32,6 +32,10 @@ inline fun PreferenceGroup.preference(block: (@DSL Preference).() -> Unit): Pref return initThenAdd(Preference(context), block) } +inline fun PreferenceGroup.themePreference(block: (@DSL ThemePreference).() -> Unit): ThemePreference { + return initThenAdd(ThemePreference(context), block) +} + inline fun PreferenceGroup.switchPreference(block: (@DSL SwitchPreferenceCompat).() -> Unit): SwitchPreferenceCompat { return initThenAdd(SwitchPreferenceCompat(context), block) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt index a800e96039..5e24a5bd4c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt @@ -1,5 +1,7 @@ package eu.kanade.tachiyomi.ui.setting +import android.os.Bundle +import android.view.View import androidx.biometric.BiometricManager import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.BuildConfig @@ -8,7 +10,6 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.updater.UpdaterJob import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.util.system.LocaleHelper -import eu.kanade.tachiyomi.util.system.ThemeUtil import eu.kanade.tachiyomi.widget.preference.IntListMatPreference import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys @@ -16,6 +17,8 @@ class SettingsGeneralController : SettingsController() { private val isUpdaterEnabled = BuildConfig.INCLUDE_UPDATER + var lastThemeX: Int? = null + var themePreference: ThemePreference? = null override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply { titleRes = R.string.general @@ -88,18 +91,12 @@ class SettingsGeneralController : SettingsController() { preferenceCategory { titleRes = R.string.display - listPreference(activity) { + themePreference = themePreference { key = Keys.themeStyle titleRes = R.string.app_theme - val enumConstants = ThemeUtil.Themes::class.java.enumConstants - entriesRes = enumConstants?.map { it.nameRes }.orEmpty().toTypedArray() - entryValues = enumConstants?.map { it.name }.orEmpty() - defaultValue = ThemeUtil.Themes.DEFAULT - - onChange { - activity?.recreate() - true - } + lastScrollPostion = lastThemeX + summaryRes = preferences.theme().get().nameRes + activity = this@SettingsGeneralController.activity } switchPreference { @@ -204,4 +201,20 @@ class SettingsGeneralController : SettingsController() { } } } + + override fun onDestroyView(view: View) { + super.onDestroyView(view) + themePreference = null + } + + override fun onSaveViewState(view: View, outState: Bundle) { + outState.putInt(::lastThemeX.name, themePreference?.lastScrollPostion ?: 0) + super.onSaveInstanceState(outState) + } + + override fun onRestoreViewState(view: View, savedViewState: Bundle) { + super.onRestoreViewState(view, savedViewState) + lastThemeX = savedViewState.getInt(::lastThemeX.name) + themePreference?.lastScrollPostion = lastThemeX + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/ThemePreference.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/ThemePreference.kt new file mode 100644 index 0000000000..2c01198c2a --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/ThemePreference.kt @@ -0,0 +1,164 @@ +package eu.kanade.tachiyomi.ui.setting + +import android.app.Activity +import android.content.Context +import android.content.res.ColorStateList +import android.util.AttributeSet +import android.view.View +import androidx.appcompat.app.AppCompatDelegate +import androidx.core.view.isVisible +import androidx.preference.Preference +import androidx.preference.PreferenceViewHolder +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.ISelectionListener +import com.mikepenz.fastadapter.adapters.ItemAdapter +import com.mikepenz.fastadapter.items.AbstractItem +import com.mikepenz.fastadapter.select.SelectExtension +import com.mikepenz.fastadapter.select.getSelectExtension +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.databinding.ThemeItemBinding +import eu.kanade.tachiyomi.databinding.ThemesPreferenceBinding +import eu.kanade.tachiyomi.util.system.ThemeUtil +import eu.kanade.tachiyomi.util.system.dpToPx +import eu.kanade.tachiyomi.util.view.visInvisIf +import uy.kohesive.injekt.injectLazy + +class ThemePreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : + Preference(context, attrs) { + + private lateinit var fastAdapter: FastAdapter + private val itemAdapter = ItemAdapter() + private lateinit var selectExtension: SelectExtension + private val preferences: PreferencesHelper by injectLazy() + var activity: Activity? = null + var lastScrollPostion: Int? = null + lateinit var binding: ThemesPreferenceBinding + val manager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + init { + layoutResource = R.layout.themes_preference + fastAdapter = FastAdapter.with(itemAdapter) + fastAdapter.setHasStableIds(true) + val enumConstants = ThemeUtil.Themes::class.java.enumConstants + val currentTheme = preferences.theme().get() + selectExtension = fastAdapter.getSelectExtension().apply { + isSelectable = true + multiSelect = false + selectionListener = object : ISelectionListener { + override fun onSelectionChanged(item: ThemeItem, selected: Boolean) { + preferences.theme().set(item.theme) + activity?.recreate() + } + } + } + + itemAdapter.set(enumConstants?.map(::ThemeItem).orEmpty()) + itemAdapter.adapterItems.forEach { item -> + item.isSelected = currentTheme == item.theme + } + isSelectable = false + } + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + binding = ThemesPreferenceBinding.bind(holder.itemView) + + binding.themePrefTitle.text = title + binding.themeRecycler.setHasFixedSize(true) + binding.themeRecycler.layoutManager = manager + + binding.themeRecycler.adapter = fastAdapter + + binding.themeRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + lastScrollPostion = + recyclerView.computeHorizontalScrollOffset() // (lastScrollPostion ?: 0) + dx + } + }) + + val enumConstants = ThemeUtil.Themes::class.java.enumConstants + val currentTheme = preferences.theme().get() + if (lastScrollPostion != null) { + val lX = lastScrollPostion!! + (binding.themeRecycler.layoutManager as LinearLayoutManager).apply { + scrollToPositionWithOffset( + lX / 110.dpToPx, + -lX % 110.dpToPx + binding.themeRecycler.paddingStart + ) + } + lastScrollPostion = binding.themeRecycler.computeHorizontalScrollOffset() + } else { + binding.themeRecycler.scrollToPosition( + enumConstants?.indexOf(currentTheme) ?: 0 + ) + } + } + + inner class ThemeItem(val theme: ThemeUtil.Themes) : AbstractItem>() { + + /** defines the type defining this item. must be unique. preferably an id */ + override val type: Int = R.id.theme_card_view + + /** defines the layout which will be used for this item in the list */ + override val layoutRes: Int = R.layout.theme_item + + override var identifier = theme.hashCode().toLong() + + override fun getViewHolder(v: View): FastAdapter.ViewHolder { + return ViewHolder(v) + } + + val colors = theme.getColors() + val darkColors = if (theme.nightMode == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) { + theme.getColors(AppCompatDelegate.MODE_NIGHT_YES) + } else { + null + } + + inner class ViewHolder(view: View) : FastAdapter.ViewHolder(view) { + + val binding = ThemeItemBinding.bind(view) + override fun bindView(item: ThemeItem, payloads: List) { + binding.themeNameText.setText(item.theme.nameRes) + + binding.checkbox.isVisible = item.isSelected + binding.themeSelected.visInvisIf(item.isSelected) + binding.themeToolbar.setBackgroundColor(item.colors.appBar) + binding.themeAppBarText.imageTintList = ColorStateList.valueOf(item.colors.appBarText) + binding.themeHeroImage.imageTintList = ColorStateList.valueOf(item.colors.primaryText) + binding.themePrimaryText.imageTintList = ColorStateList.valueOf(item.colors.primaryText) + binding.themeAccentedButton.imageTintList = ColorStateList.valueOf(item.colors.colorAccent) + binding.themeSecondaryText.imageTintList = ColorStateList.valueOf(item.colors.secondaryText) + binding.themeSecondaryText2.imageTintList = ColorStateList.valueOf(item.colors.secondaryText) + + binding.themeBottomBar.setBackgroundColor(item.colors.bottomBar) + binding.themeItem1.imageTintList = ColorStateList.valueOf(item.colors.inactiveTab) + binding.themeItem2.imageTintList = ColorStateList.valueOf(item.colors.activeTab) + binding.themeItem3.imageTintList = ColorStateList.valueOf(item.colors.inactiveTab) + + binding.themeLayout.setBackgroundColor(item.colors.colorBackground) + binding.darkThemeLayout.isVisible = item.darkColors != null + + if (binding.darkThemeLayout.isVisible && item.darkColors != null) { + binding.darkThemeToolbar.setBackgroundColor(item.darkColors.appBar) + binding.darkThemeAppBarText.imageTintList = ColorStateList.valueOf(item.darkColors.appBarText) + binding.darkThemeLayout.setBackgroundColor(item.darkColors.colorBackground) + binding.darkThemePrimaryText.imageTintList = ColorStateList.valueOf(item.darkColors.primaryText) + binding.darkThemeHeroImage.imageTintList = ColorStateList.valueOf(item.darkColors.primaryText) + binding.darkThemeAccentedButton.imageTintList = ColorStateList.valueOf(item.darkColors.colorAccent) + binding.darkThemeSecondaryText.imageTintList = ColorStateList.valueOf(item.darkColors.secondaryText) + + binding.darkThemeBottomBar.setBackgroundColor(item.darkColors.bottomBar) + binding.darkThemeItem2.imageTintList = ColorStateList.valueOf(item.darkColors.activeTab) + binding.darkThemeItem3.imageTintList = ColorStateList.valueOf(item.darkColors.inactiveTab) + } + } + + override fun unbindView(item: ThemeItem) { + } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ThemeUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ThemeUtil.kt index eb38de605d..55f88e83c0 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ThemeUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ThemeUtil.kt @@ -2,11 +2,14 @@ package eu.kanade.tachiyomi.util.system import android.content.Context import android.graphics.Color +import androidx.annotation.ColorInt import androidx.annotation.StringRes import androidx.annotation.StyleRes import androidx.appcompat.app.AppCompatDelegate +import androidx.core.graphics.ColorUtils import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import kotlin.math.roundToInt object ThemeUtil { @@ -46,15 +49,179 @@ object ThemeUtil { } } - @Suppress("unused") enum class Themes(@StyleRes val styleRes: Int, val nightMode: Int, @StringRes val nameRes: Int) { PURE_WHITE(R.style.Theme_Tachiyomi, AppCompatDelegate.MODE_NIGHT_NO, R.string.white_theme), - LIGHT_BLUE(R.style.Theme_Tachiyomi_AllBlue, AppCompatDelegate.MODE_NIGHT_NO, R.string.light_blue), + LIGHT_BLUE( + R.style.Theme_Tachiyomi_AllBlue, + AppCompatDelegate.MODE_NIGHT_NO, + R.string.light_blue + ), DARK(R.style.Theme_Tachiyomi, AppCompatDelegate.MODE_NIGHT_YES, R.string.dark), - AMOLED(R.style.Theme_Tachiyomi_Amoled, AppCompatDelegate.MODE_NIGHT_YES, R.string.amoled_black), - DARK_BLUE(R.style.Theme_Tachiyomi_AllBlue, AppCompatDelegate.MODE_NIGHT_YES, R.string.dark_blue), - DEFAULT(R.style.Theme_Tachiyomi, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM, R.string.system_default), - DEFAULT_AMOLED(R.style.Theme_Tachiyomi_Amoled, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM, R.string.system_default_amoled), - ALL_BLUE(R.style.Theme_Tachiyomi_AllBlue, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM, R.string.system_default_all_blue), + AMOLED( + R.style.Theme_Tachiyomi_Amoled, + AppCompatDelegate.MODE_NIGHT_YES, + R.string.amoled_black + ), + DARK_BLUE( + R.style.Theme_Tachiyomi_AllBlue, + AppCompatDelegate.MODE_NIGHT_YES, + R.string.dark_blue + ), + DEFAULT( + R.style.Theme_Tachiyomi, + AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM, + R.string.system_default + ), + DEFAULT_AMOLED( + R.style.Theme_Tachiyomi_Amoled, + AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM, + R.string.system_default_amoled + ), + ALL_BLUE( + R.style.Theme_Tachiyomi_AllBlue, + AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM, + R.string.system_default_all_blue + ); + + fun getColors(mode: Int = AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM): Colors { + return when (nightMode) { + AppCompatDelegate.MODE_NIGHT_YES -> darkColors() + AppCompatDelegate.MODE_NIGHT_NO -> lightColors() + AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM -> when (mode) { + AppCompatDelegate.MODE_NIGHT_YES -> darkColors() + else -> lightColors() + } + else -> lightColors() + } + } + + private fun lightColors(): Colors { + return Colors( + lightPrimaryText, + lightSecondaryText, + lightBackground, + lightAccent, + lightAppBar, + lightAppBarText, + lightBottomBar, + lightInactiveTab, + lightActiveTab, + ) + } + + private fun darkColors(): Colors { + return Colors( + darkPrimaryText, + darkSecondaryText, + darkBackground, + darkAccent, + darkAppBar, + darkAppBarText, + darkBottomBar, + darkInactiveTab, + darkActiveTab, + ) + } + + @ColorInt + val lightPrimaryText: Int = Color.parseColor("#DE000000") + + @ColorInt + val darkPrimaryText: Int = Color.parseColor("#FFFFFFFF") + + @ColorInt + val lightSecondaryText: Int = ColorUtils.setAlphaComponent(lightPrimaryText, (0.54f * 255f).roundToInt()) + + @ColorInt + val darkSecondaryText: Int = ColorUtils.setAlphaComponent(darkPrimaryText, (0.54f * 255f).roundToInt()) + + @ColorInt + val lightBackground: Int = Color.parseColor("#FAFAFA") + + @ColorInt + val darkBackground: Int = Color.parseColor( + when (styleRes) { + R.style.Theme_Tachiyomi_Amoled -> "#000000" + else -> "#1C1C1D" + } + ) + + @ColorInt + val lightAccent: Int = Color.parseColor("#2979FF") + + @ColorInt + val darkAccent: Int = Color.parseColor("#3399FF") + + @ColorInt + val lightAppBar: Int = when (styleRes) { + R.style.Theme_Tachiyomi_AllBlue -> Color.parseColor("#54759E") + else -> lightBackground + } + + @ColorInt + val darkAppBar: Int = when (styleRes) { + R.style.Theme_Tachiyomi_AllBlue -> Color.parseColor("#54759E") + else -> darkBackground + } + + @ColorInt + val lightAppBarText: Int = when (styleRes) { + R.style.Theme_Tachiyomi_AllBlue -> Color.parseColor("#FFFFFF") + else -> lightPrimaryText + } + + @ColorInt + val darkAppBarText: Int = when (styleRes) { + R.style.Theme_Tachiyomi_AllBlue -> Color.parseColor("#FFFFFF") + else -> darkPrimaryText + } + + @ColorInt + val lightBottomBar: Int = when (styleRes) { + R.style.Theme_Tachiyomi_AllBlue -> Color.parseColor("#54759E") + else -> Color.parseColor("#FFFFFF") + } + + @ColorInt + val darkBottomBar: Int = when (styleRes) { + R.style.Theme_Tachiyomi_AllBlue -> Color.parseColor("#54759E") + else -> Color.parseColor("#212121") + } + + @ColorInt + val lightInactiveTab: Int = when (styleRes) { + R.style.Theme_Tachiyomi_AllBlue -> Color.parseColor("#80FFFFFF") + else -> Color.parseColor("#C2424242") + } + + @ColorInt + val darkInactiveTab: Int = when (styleRes) { + R.style.Theme_Tachiyomi_AllBlue -> Color.parseColor("#80FFFFFF") + else -> Color.parseColor("#C2FFFFFF") + } + + @ColorInt + val lightActiveTab: Int = when (styleRes) { + R.style.Theme_Tachiyomi_AllBlue -> lightAppBarText + else -> lightAccent + } + + @ColorInt + val darkActiveTab: Int = when (styleRes) { + R.style.Theme_Tachiyomi_AllBlue -> darkAppBarText + else -> darkAccent + } } + + data class Colors( + @ColorInt val primaryText: Int, + @ColorInt val secondaryText: Int, + @ColorInt val colorBackground: Int, + @ColorInt val colorAccent: Int, + @ColorInt val appBar: Int, + @ColorInt val appBarText: Int, + @ColorInt val bottomBar: Int, + @ColorInt val inactiveTab: Int, + @ColorInt val activeTab: Int, + ) } diff --git a/app/src/main/res/drawable/oval.xml b/app/src/main/res/drawable/oval.xml new file mode 100644 index 0000000000..41bc9ee16a --- /dev/null +++ b/app/src/main/res/drawable/oval.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/right_half_oval.xml b/app/src/main/res/drawable/right_half_oval.xml new file mode 100644 index 0000000000..62d48b8645 --- /dev/null +++ b/app/src/main/res/drawable/right_half_oval.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/theme_selected_border.xml b/app/src/main/res/drawable/theme_selected_border.xml new file mode 100644 index 0000000000..4c08f4da8d --- /dev/null +++ b/app/src/main/res/drawable/theme_selected_border.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/theme_item.xml b/app/src/main/res/layout/theme_item.xml new file mode 100644 index 0000000000..e0ce6f5aac --- /dev/null +++ b/app/src/main/res/layout/theme_item.xml @@ -0,0 +1,355 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/themes_preference.xml b/app/src/main/res/layout/themes_preference.xml new file mode 100644 index 0000000000..d58b5edc8c --- /dev/null +++ b/app/src/main/res/layout/themes_preference.xml @@ -0,0 +1,35 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index d2a463e27b..0521e59658 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -9,4 +9,19 @@ 0dp + + 6dp + 6dp + 10dp + 20dp + 30dp + 32dp + 56dp + 20dp + 26dp + 12dp + 6dp + 16dp + +