From 6240fe1dfc9615b0be4bee6e54070e9d21a68b87 Mon Sep 17 00:00:00 2001 From: arkon <eugcheung94@gmail.com> Date: Mon, 23 Aug 2021 10:54:44 -0400 Subject: [PATCH] Update app theme preference UI Heavily influenced by TachiyomiJ2K. --- .../ui/base/activity/BaseThemedActivity.kt | 13 +- .../ui/setting/SettingsGeneralController.kt | 8 +- .../widget/preference/ThemesPreference.kt | 40 ++++ .../preference/ThemesPreferenceAdapter.kt | 67 +++++++ app/src/main/res/drawable/oval.xml | 6 + app/src/main/res/layout/pref_theme_item.xml | 187 ++++++++++++++++++ app/src/main/res/layout/pref_themes_list.xml | 26 +++ 7 files changed, 338 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/preference/ThemesPreference.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/widget/preference/ThemesPreferenceAdapter.kt create mode 100644 app/src/main/res/drawable/oval.xml create mode 100644 app/src/main/res/layout/pref_theme_item.xml create mode 100644 app/src/main/res/layout/pref_themes_list.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseThemedActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseThemedActivity.kt index 694751ac4..5e75acd81 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseThemedActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/base/activity/BaseThemedActivity.kt @@ -18,8 +18,13 @@ abstract class BaseThemedActivity : AppCompatActivity() { companion object { fun AppCompatActivity.applyAppTheme(preferences: PreferencesHelper) { + getThemeResIds(preferences.appTheme().get(), preferences.themeDarkAmoled().get()) + .forEach { setTheme(it) } + } + + fun getThemeResIds(appTheme: PreferenceValues.AppTheme, isAmoled: Boolean): List<Int> { val resIds = mutableListOf<Int>() - when (preferences.appTheme().get()) { + when (appTheme) { PreferenceValues.AppTheme.MONET -> { resIds += R.style.Theme_Tachiyomi_Monet } @@ -53,13 +58,11 @@ abstract class BaseThemedActivity : AppCompatActivity() { } } - if (preferences.themeDarkAmoled().get()) { + if (isAmoled) { resIds += R.style.ThemeOverlay_Tachiyomi_Amoled } - resIds.forEach { - setTheme(it) - } + return resIds } } } 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 a57e1ee09..a3ad186ab 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 @@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.preference.asImmediateFlow import eu.kanade.tachiyomi.util.preference.defaultValue import eu.kanade.tachiyomi.util.preference.entriesRes +import eu.kanade.tachiyomi.util.preference.initThenAdd import eu.kanade.tachiyomi.util.preference.intListPreference import eu.kanade.tachiyomi.util.preference.listPreference import eu.kanade.tachiyomi.util.preference.onChange @@ -17,6 +18,7 @@ import eu.kanade.tachiyomi.util.preference.preferenceCategory import eu.kanade.tachiyomi.util.preference.switchPreference import eu.kanade.tachiyomi.util.preference.titleRes import eu.kanade.tachiyomi.util.system.isTablet +import eu.kanade.tachiyomi.widget.preference.ThemesPreference import kotlinx.coroutines.flow.launchIn import java.util.Date import eu.kanade.tachiyomi.data.preference.PreferenceKeys as Keys @@ -146,7 +148,7 @@ class SettingsGeneralController : SettingsController() { summary = "%s" } - listPreference { + initThenAdd(ThemesPreference(context)) { key = Keys.appTheme titleRes = R.string.pref_app_theme @@ -158,10 +160,8 @@ class SettingsGeneralController : SettingsController() { } it.titleResId != null && monetFilter } - entriesRes = appThemes.map { it.titleResId!! }.toTypedArray() - entryValues = appThemes.map { it.name }.toTypedArray() + entries = appThemes defaultValue = appThemes[0].name - summary = "%s" onChange { activity?.recreate() diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ThemesPreference.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ThemesPreference.kt new file mode 100644 index 000000000..2dec5a024 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ThemesPreference.kt @@ -0,0 +1,40 @@ +package eu.kanade.tachiyomi.widget.preference + +import android.content.Context +import android.util.AttributeSet +import androidx.preference.ListPreference +import androidx.preference.PreferenceViewHolder +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.preference.PreferenceValues + +class ThemesPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : + ListPreference(context, attrs), + ThemesPreferenceAdapter.OnItemClickListener { + + private val adapter = ThemesPreferenceAdapter(this) + + init { + layoutResource = R.layout.pref_themes_list + } + + override fun onBindViewHolder(holder: PreferenceViewHolder) { + super.onBindViewHolder(holder) + + val themesList = holder.findViewById(R.id.themes_list) as RecyclerView + themesList.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + themesList.adapter = adapter + } + + override fun onItemClick(position: Int) { + value = entries[position].name + callChangeListener(value) + } + + var entries: List<PreferenceValues.AppTheme> = emptyList() + set(value) { + field = value + adapter.setItems(value) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ThemesPreferenceAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ThemesPreferenceAdapter.kt new file mode 100644 index 000000000..14f85ff44 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/preference/ThemesPreferenceAdapter.kt @@ -0,0 +1,67 @@ +package eu.kanade.tachiyomi.widget.preference + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.view.ContextThemeWrapper +import androidx.recyclerview.widget.RecyclerView +import eu.kanade.tachiyomi.data.preference.PreferenceValues +import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.databinding.PrefThemeItemBinding +import eu.kanade.tachiyomi.ui.base.activity.BaseThemedActivity +import uy.kohesive.injekt.injectLazy + +class ThemesPreferenceAdapter(private val clickListener: OnItemClickListener) : + RecyclerView.Adapter<ThemesPreferenceAdapter.ThemeViewHolder>() { + + private val preferences: PreferencesHelper by injectLazy() + + private var themes = emptyList<PreferenceValues.AppTheme>() + + private lateinit var binding: PrefThemeItemBinding + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ThemeViewHolder { + val themeResIds = BaseThemedActivity.getThemeResIds(themes[viewType], preferences.themeDarkAmoled().get()) + val themedContext = themeResIds.fold(parent.context) { + context, themeResId -> ContextThemeWrapper(context, themeResId) + } + + binding = PrefThemeItemBinding.inflate(LayoutInflater.from(themedContext), parent, false) + return ThemeViewHolder(binding.root) + } + + override fun getItemViewType(position: Int): Int = position + + override fun getItemCount(): Int = themes.size + + override fun onBindViewHolder(holder: ThemesPreferenceAdapter.ThemeViewHolder, position: Int) { + holder.bind(themes[position]) + } + + fun setItems(themes: List<PreferenceValues.AppTheme>) { + this.themes = themes + notifyDataSetChanged() + } + + inner class ThemeViewHolder(private val view: View) : RecyclerView.ViewHolder(view) { + fun bind(appTheme: PreferenceValues.AppTheme) { + binding.name.text = view.context.getString(appTheme.titleResId!!) + + // Rounded corners + binding.coverContainer1.clipToOutline = true + binding.coverContainer2.clipToOutline = true + + binding.themeCard.isChecked = preferences.appTheme().get() == appTheme + + listOf(binding.root, binding.themeCard).forEach { + it.setOnClickListener { + clickListener.onItemClick(bindingAdapterPosition) + } + } + } + } + + interface OnItemClickListener { + fun onItemClick(position: Int) + } +} diff --git a/app/src/main/res/drawable/oval.xml b/app/src/main/res/drawable/oval.xml new file mode 100644 index 000000000..6df5dac5c --- /dev/null +++ b/app/src/main/res/drawable/oval.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <corners android:radius="20dp" /> + <solid android:color="?android:attr/colorBackground" /> +</shape> diff --git a/app/src/main/res/layout/pref_theme_item.xml b/app/src/main/res/layout/pref_theme_item.xml new file mode 100644 index 000000000..ba73c2bff --- /dev/null +++ b/app/src/main/res/layout/pref_theme_item.xml @@ -0,0 +1,187 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="110dp" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="4dp"> + + <com.google.android.material.card.MaterialCardView + android:id="@+id/theme_card" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:checkable="true" + android:clickable="true" + android:focusable="true" + android:importantForAccessibility="no" + app:cardCornerRadius="@dimen/card_radius" + app:cardElevation="0dp"> + + <androidx.constraintlayout.widget.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="176dp" + android:background="?android:attr/colorBackground"> + + <View + android:id="@+id/top_nav" + android:layout_width="0dp" + android:layout_height="20dp" + android:background="?attr/colorToolbar" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <ImageView + android:id="@+id/top_nav_text" + android:layout_width="50dp" + android:layout_height="10dp" + android:layout_marginStart="4dp" + android:src="@drawable/oval" + app:layout_constraintBottom_toBottomOf="@+id/top_nav" + app:layout_constraintStart_toStartOf="@+id/top_nav" + app:layout_constraintTop_toTopOf="@+id/top_nav" + app:tint="?attr/colorOnToolbar" /> + + <ImageView + android:id="@+id/heading" + android:layout_width="80dp" + android:layout_height="8dp" + android:layout_marginStart="4dp" + android:layout_marginTop="4dp" + android:src="@drawable/oval" + app:layout_constraintStart_toStartOf="@+id/top_nav" + app:layout_constraintTop_toBottomOf="@+id/top_nav" + app:tint="?attr/colorAccent" /> + + <FrameLayout + android:id="@+id/cover_container1" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginStart="4dp" + android:layout_marginTop="4dp" + android:layout_marginEnd="2dp" + android:background="@drawable/rounded_rectangle" + app:layout_constraintDimensionRatio="2:3" + app:layout_constraintEnd_toStartOf="@+id/cover_container2" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/heading"> + + <View + android:layout_width="match_parent" + android:layout_height="match_parent" + android:alpha="0.5" + android:background="?attr/colorOnSurface" /> + + </FrameLayout> + + <View + android:id="@+id/cover_badge" + android:layout_width="8dp" + android:layout_height="12dp" + android:layout_marginStart="2dp" + android:layout_marginTop="2dp" + android:background="?attr/colorAccent" + app:layout_constraintStart_toStartOf="@+id/cover_container1" + app:layout_constraintTop_toTopOf="@+id/cover_container1" /> + + <FrameLayout + android:id="@+id/cover_container2" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginStart="2dp" + android:layout_marginTop="4dp" + android:layout_marginEnd="4dp" + android:background="@drawable/rounded_rectangle" + app:layout_constraintDimensionRatio="2:3" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/cover_container1" + app:layout_constraintTop_toBottomOf="@+id/heading"> + + <View + android:layout_width="match_parent" + android:layout_height="match_parent" + android:alpha="0.5" + android:background="?attr/colorOnSurface" /> + + </FrameLayout> + + <View + android:id="@+id/bottom_nav" + android:layout_width="0dp" + android:layout_height="20dp" + android:background="?attr/colorToolbar" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" /> + + <ImageView + android:id="@+id/bottom_nav_selected_item" + android:layout_width="14dp" + android:layout_height="14dp" + android:src="@drawable/oval" + app:layout_constraintBottom_toBottomOf="@+id/bottom_nav" + app:layout_constraintEnd_toStartOf="@+id/bottom_nav_unselected_item1" + app:layout_constraintStart_toStartOf="@+id/bottom_nav" + app:layout_constraintTop_toTopOf="@+id/bottom_nav" + app:tint="?attr/colorPrimary" /> + + <ImageView + android:id="@+id/bottom_nav_unselected_item1" + android:layout_width="14dp" + android:layout_height="14dp" + android:src="@drawable/oval" + app:layout_constraintBottom_toBottomOf="@+id/bottom_nav" + app:layout_constraintEnd_toStartOf="@+id/bottom_nav_unselected_item2" + app:layout_constraintStart_toEndOf="@+id/bottom_nav_selected_item" + app:layout_constraintTop_toTopOf="@+id/bottom_nav" + app:tint="?attr/colorOnToolbar" /> + + <ImageView + android:id="@+id/bottom_nav_unselected_item2" + android:layout_width="14dp" + android:layout_height="14dp" + android:src="@drawable/oval" + app:layout_constraintBottom_toBottomOf="@+id/bottom_nav" + app:layout_constraintEnd_toStartOf="@+id/bottom_nav_unselected_item3" + app:layout_constraintStart_toEndOf="@+id/bottom_nav_unselected_item1" + app:layout_constraintTop_toTopOf="@+id/bottom_nav" + app:tint="?attr/colorOnToolbar" /> + + <ImageView + android:id="@+id/bottom_nav_unselected_item3" + android:layout_width="14dp" + android:layout_height="14dp" + android:src="@drawable/oval" + app:layout_constraintBottom_toBottomOf="@+id/bottom_nav" + app:layout_constraintEnd_toStartOf="@+id/bottom_nav_unselected_item4" + app:layout_constraintStart_toEndOf="@+id/bottom_nav_unselected_item2" + app:layout_constraintTop_toTopOf="@+id/bottom_nav" + app:tint="?attr/colorOnToolbar" /> + + <ImageView + android:id="@+id/bottom_nav_unselected_item4" + android:layout_width="14dp" + android:layout_height="14dp" + android:src="@drawable/oval" + app:layout_constraintBottom_toBottomOf="@+id/bottom_nav" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/bottom_nav_unselected_item3" + app:layout_constraintTop_toTopOf="@+id/bottom_nav" + app:tint="?attr/colorOnToolbar" /> + + </androidx.constraintlayout.widget.ConstraintLayout> + + </com.google.android.material.card.MaterialCardView> + + <TextView + android:id="@+id/name" + android:layout_width="match_parent" + android:layout_height="32sp" + android:maxLines="2" + android:layout_marginTop="4dp" + android:textAlignment="center" + android:textAppearance="@style/TextAppearance.MaterialComponents.Caption" + tools:text="Theme Name" /> + +</LinearLayout> diff --git a/app/src/main/res/layout/pref_themes_list.xml b/app/src/main/res/layout/pref_themes_list.xml new file mode 100644 index 000000000..a957aa4c7 --- /dev/null +++ b/app/src/main/res/layout/pref_themes_list.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <TextView + android:id="@android:id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingHorizontal="16dp" + android:paddingTop="4dp" + android:textAppearance="@style/TextAppearance.AppCompat.Menu" + tools:text="App theme" /> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/themes_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingHorizontal="16dp" + android:paddingVertical="8dp" + android:clipToPadding="false" + tools:listitem="@layout/pref_theme_item" /> + +</LinearLayout>