Option to reorder filters

This commit is contained in:
Jay 2020-05-07 01:24:50 -04:00
parent 348795666d
commit f6c9855772
6 changed files with 285 additions and 62 deletions

View File

@ -265,6 +265,8 @@ class PreferencesHelper(val context: Context) {
fun hopperGravity() = flowPrefs.getInt("hopper_gravity", 1) fun hopperGravity() = flowPrefs.getInt("hopper_gravity", 1)
fun filterOrder() = flowPrefs.getString("filter_order", "rudcmt")
// Tutorial preferences // Tutorial preferences
fun shownFilterTutorial() = flowPrefs.getBoolean("shown_filter_tutorial", false) fun shownFilterTutorial() = flowPrefs.getBoolean("shown_filter_tutorial", false)

View File

@ -1,13 +1,19 @@
package eu.kanade.tachiyomi.ui.library.filter package eu.kanade.tachiyomi.ui.library.filter
import android.content.Context import android.content.Context
import android.content.res.Configuration
import android.os.Parcelable import android.os.Parcelable
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.customview.customView
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
@ -16,6 +22,7 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.view.collapse import eu.kanade.tachiyomi.util.view.collapse
import eu.kanade.tachiyomi.util.view.gone
import eu.kanade.tachiyomi.util.view.hide import eu.kanade.tachiyomi.util.view.hide
import eu.kanade.tachiyomi.util.view.inflate import eu.kanade.tachiyomi.util.view.inflate
import eu.kanade.tachiyomi.util.view.isExpanded import eu.kanade.tachiyomi.util.view.isExpanded
@ -44,15 +51,20 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
*/ */
private val preferences: PreferencesHelper by injectLazy() private val preferences: PreferencesHelper by injectLazy()
private val trackManager: TrackManager by injectLazy()
val hasTracking
get() = trackManager.hasLoggedServices()
private lateinit var downloaded: FilterTagGroup private lateinit var downloaded: FilterTagGroup
private lateinit var unreadProgress: FilterTagGroup
private lateinit var unread: FilterTagGroup private lateinit var unread: FilterTagGroup
private lateinit var allUnread: FilterTagGroup
private lateinit var completed: FilterTagGroup private lateinit var completed: FilterTagGroup
private lateinit var tracked: FilterTagGroup private var tracked: FilterTagGroup? = null
private var trackers: FilterTagGroup? = null private var trackers: FilterTagGroup? = null
@ -60,15 +72,18 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
var sheetBehavior: BottomSheetBehavior<View>? = null var sheetBehavior: BottomSheetBehavior<View>? = null
var filterOrder = preferences.filterOrder().get()
private lateinit var clearButton: ImageView private lateinit var clearButton: ImageView
private val filterItems: MutableList<FilterTagGroup> by lazy { private val filterItems: MutableList<FilterTagGroup> by lazy {
val list = mutableListOf<FilterTagGroup>() val list = mutableListOf<FilterTagGroup>()
list.add(unreadProgress)
list.add(unread) list.add(unread)
list.add(downloaded) list.add(downloaded)
list.add(completed) list.add(completed)
if (Injekt.get<TrackManager>().hasLoggedServices()) if (hasTracking)
list.add(tracked) tracked?.let { list.add(it) }
list list
} }
@ -95,6 +110,15 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
stateChanged(state) stateChanged(state)
} }
}) })
if (context.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
second_layout.removeView(view_options)
second_layout.removeView(reorder_filters)
first_layout.addView(view_options)
first_layout.addView(reorder_filters)
second_layout.gone()
}
if (preferences.hideFiltersAtStart().getOrDefault()) { if (preferences.hideFiltersAtStart().getOrDefault()) {
sheetBehavior?.hide() sheetBehavior?.hide()
} }
@ -104,6 +128,9 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
view_options.setOnClickListener { view_options.setOnClickListener {
onGroupClicked(ACTION_DISPLAY) onGroupClicked(ACTION_DISPLAY)
} }
reorder_filters.setOnClickListener {
manageFilterPopup()
}
val activeFilters = hasActiveFiltersFromPref() val activeFilters = hasActiveFiltersFromPref()
if (activeFilters && sheetBehavior.isHidden() && sheetBehavior?.skipCollapsed == false) if (activeFilters && sheetBehavior.isHidden() && sheetBehavior?.skipCollapsed == false)
@ -201,14 +228,16 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
completed = inflate(R.layout.filter_buttons) as FilterTagGroup completed = inflate(R.layout.filter_buttons) as FilterTagGroup
completed.setup(this, R.string.completed, R.string.ongoing) completed.setup(this, R.string.completed, R.string.ongoing)
unreadProgress = inflate(R.layout.filter_buttons) as FilterTagGroup
unreadProgress.setup(this, R.string.not_started, R.string.in_progress)
unread = inflate(R.layout.filter_buttons) as FilterTagGroup unread = inflate(R.layout.filter_buttons) as FilterTagGroup
unread.setup(this, R.string.not_started, R.string.in_progress, R.string.read) unread.setup(this, R.string.unread, R.string.read)
allUnread = inflate(R.layout.filter_buttons) as FilterTagGroup
allUnread.setup(this, R.string.unread)
if (hasTracking) {
tracked = inflate(R.layout.filter_buttons) as FilterTagGroup tracked = inflate(R.layout.filter_buttons) as FilterTagGroup
tracked.setup(this, R.string.tracked, R.string.not_tracked) tracked?.setup(this, R.string.tracked, R.string.not_tracked)
}
reSortViews() reSortViews()
@ -227,7 +256,6 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
if (libraryManga.any { it.mangaType() == Manga.TYPE_MANHWA }) types.add(R.string.manhwa) if (libraryManga.any { it.mangaType() == Manga.TYPE_MANHWA }) types.add(R.string.manhwa)
if (libraryManga.any { it.mangaType() == Manga.TYPE_MANHUA }) types.add(R.string.manhua) if (libraryManga.any { it.mangaType() == Manga.TYPE_MANHUA }) types.add(R.string.manhua)
if (libraryManga.any { it.mangaType() == Manga.TYPE_COMIC }) types.add(R.string.comic) if (libraryManga.any { it.mangaType() == Manga.TYPE_COMIC }) types.add(R.string.comic)
val hasTracking = Injekt.get<TrackManager>().hasLoggedServices()
if (types.isNotEmpty()) { if (types.isNotEmpty()) {
launchUI { launchUI {
val mangaType = inflate(R.layout.filter_buttons) as FilterTagGroup val mangaType = inflate(R.layout.filter_buttons) as FilterTagGroup
@ -238,11 +266,8 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
types.getOrNull(2) types.getOrNull(2)
) )
this@FilterBottomSheet.mangaType = mangaType this@FilterBottomSheet.mangaType = mangaType
filter_layout.addView(mangaType) reorderFilters()
filterItems.remove(tracked) reSortViews()
filterItems.add(mangaType)
if (hasTracking)
filterItems.add(tracked)
} }
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -250,15 +275,14 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
downloaded.setState(preferences.filterDownloaded()) downloaded.setState(preferences.filterDownloaded())
completed.setState(preferences.filterCompleted()) completed.setState(preferences.filterCompleted())
val unreadP = preferences.filterUnread().getOrDefault() val unreadP = preferences.filterUnread().getOrDefault()
if (unreadP == STATE_INCLUDE) { if (unreadP <= 2) {
allUnread.state = 0 unread.state = unreadP - 1
if (!filterItems.contains(allUnread)) } else if (unreadP > 3) {
filterItems.add(allUnread) unreadProgress.state = unreadP - 3
} else if (unreadP > 0) {
unread.state = if (unreadP in 3..4) unreadP - 3 else 2
} }
tracked.setState(preferences.filterTracked()) tracked?.setState(preferences.filterTracked())
mangaType?.setState(preferences.filterMangaType()) mangaType?.setState(preferences.filterMangaType())
reorderFilters()
reSortViews() reSortViews()
} }
@ -274,7 +298,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
serviceNames.getOrNull(1), serviceNames.getOrNull(1),
serviceNames.getOrNull(2) serviceNames.getOrNull(2)
) )
if (tracked.isActivated) { if (tracked?.isActivated == true) {
filter_layout.addView(trackers) filter_layout.addView(trackers)
filterItems.add(trackers!!) filterItems.add(trackers!!)
trackers?.setState(FILTER_TRACKER) trackers?.setState(FILTER_TRACKER)
@ -286,6 +310,79 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
} }
} }
private fun reorderFilters() {
val array = filterOrder.toCharArray().distinct()
filterItems.clear()
for (c in array) {
mapOfFilters(c)?.let {
filterItems.add(it)
}
}
listOfNotNull(unreadProgress, unread, downloaded, completed, mangaType, tracked)
.forEach {
if (!filterItems.contains(it)) {
filterItems.add(it)
}
}
}
private fun indexOf(filterTagGroup: FilterTagGroup): Int {
charOfFilter(filterTagGroup)?.let {
return filterOrder.indexOf(it)
}
return 0
}
private fun addForClear(): Int {
return if (clearButton.parent != null) 1 else 0
}
private fun charOfFilter(filterTagGroup: FilterTagGroup): Char? {
return when (filterTagGroup) {
unreadProgress -> 'u'
unread -> 'r'
downloaded -> 'd'
completed -> 'c'
mangaType -> 'm'
tracked -> 't'
else -> null
}
}
fun manageFilterPopup() {
val recycler = RecyclerView(context)
if (filterOrder.count() != 6)
filterOrder = "urdcmt"
val adapter = FlexibleAdapter(filterOrder.toCharArray().map(::ManageFilterItem),
this, true)
recycler.layoutManager = LinearLayoutManager(context)
recycler.adapter = adapter
adapter.isHandleDragEnabled = true
adapter.isLongPressDragEnabled = true
MaterialDialog(context).title(R.string.reorder_filters)
.customView(view = recycler, scrollable = false)
.negativeButton(android.R.string.cancel)
.positiveButton(android.R.string.ok) {
val order = adapter.currentItems.map { it.char }.joinToString("")
preferences.filterOrder().set(order)
filterOrder = order
clearFilters()
recycler.adapter = null
}
.show()
}
private fun mapOfFilters(char: Char): FilterTagGroup? {
return when (char) {
'u' -> unreadProgress
'r' -> unread
'd' -> downloaded
'c' -> completed
'm' -> mangaType
't' -> if (hasTracking) tracked else null
else -> null
}
}
override fun onFilterClicked(view: FilterTagGroup, index: Int, updatePreference: Boolean) { override fun onFilterClicked(view: FilterTagGroup, index: Int, updatePreference: Boolean) {
if (updatePreference) { if (updatePreference) {
when (view) { when (view) {
@ -293,20 +390,19 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
FILTER_TRACKER = view.nameOf(index) ?: "" FILTER_TRACKER = view.nameOf(index) ?: ""
null null
} }
unread -> { unreadProgress -> {
unread.reset()
preferences.filterUnread().set( preferences.filterUnread().set(
when (index) { when (index) {
in 0..1 -> index + 3 in 0..1 -> index + 3
2 -> 2
else -> 0 else -> 0
} }
) )
null null
} }
allUnread -> { unread -> {
preferences.filterUnread().set(index + 1) unreadProgress.reset()
if (index != 0) unread.reset() preferences.filterUnread()
null
} }
downloaded -> preferences.filterDownloaded() downloaded -> preferences.filterDownloaded()
completed -> preferences.filterCompleted() completed -> preferences.filterCompleted()
@ -316,27 +412,28 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
}?.set(index + 1) }?.set(index + 1)
onGroupClicked(ACTION_FILTER) onGroupClicked(ACTION_FILTER)
} }
if (allUnread.state == 0 && unread.parent != null) { if (view == unread) {
if (index >= 0) {
filter_layout.removeView(unreadProgress)
filterItems.remove(unreadProgress)
} else {
filter_layout.addView(unreadProgress, indexOf(unreadProgress) + addForClear())
filterItems.add(indexOf(unreadProgress), unreadProgress)
}
} else if (view == unreadProgress) {
if (index >= 0) {
filter_layout.removeView(unread) filter_layout.removeView(unread)
filterItems.remove(unread) filterItems.remove(unread)
} else if (allUnread.state != 0 && unread.parent == null) { } else {
filter_layout.addView(unread, 0) filter_layout.addView(unread, indexOf(unread) + addForClear())
filterItems.add(0, unread) filterItems.add(indexOf(unread), unread)
filter_layout.removeView(allUnread)
filterItems.remove(allUnread)
} else if (unread.state in 0..1 && allUnread.parent == null) {
val unreadIndex = filter_layout.indexOfChild(unread) + 1
filter_layout.addView(allUnread, unreadIndex)
filterItems.add(unreadIndex, allUnread)
} else if (unread.state != 0 && allUnread.parent != null) {
filter_layout.removeView(allUnread)
allUnread.reset()
filterItems.remove(allUnread)
} }
if (tracked.isActivated && trackers != null && trackers?.parent == null) { }
filter_layout.addView(trackers)
filterItems.add(trackers!!) if (tracked?.isActivated == true && trackers != null && trackers?.parent == null) {
} else if (!tracked.isActivated && trackers?.parent != null) { filter_layout.addView(trackers, filterItems.indexOf(tracked!!) + 1)
filterItems.add(filterItems.indexOf(tracked!!) + 1, trackers!!)
} else if (tracked?.isActivated == false && trackers?.parent != null) {
filter_layout.removeView(trackers) filter_layout.removeView(trackers)
trackers?.reset() trackers?.reset()
FILTER_TRACKER = "" FILTER_TRACKER = ""
@ -361,16 +458,13 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
val transition = androidx.transition.AutoTransition() val transition = androidx.transition.AutoTransition()
transition.duration = 150 transition.duration = 150
androidx.transition.TransitionManager.beginDelayedTransition(filter_layout, transition) androidx.transition.TransitionManager.beginDelayedTransition(filter_layout, transition)
if (!filterItems.contains(unread)) { reorderFilters()
filterItems.add(0, unread)
}
filterItems.forEach { filterItems.forEach {
it.reset() it.reset()
} }
trackers?.let { trackers?.let {
filterItems.remove(it) filterItems.remove(it)
} }
filterItems.remove(allUnread)
reSortViews() reSortViews()
onGroupClicked(ACTION_FILTER) onGroupClicked(ACTION_FILTER)
} }

View File

@ -0,0 +1,98 @@
package eu.kanade.tachiyomi.ui.library.filter
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.util.view.gone
import kotlinx.android.synthetic.main.categories_item.*
/**
* Category item for a recycler view.
*/
class ManageFilterItem(val char: Char) : AbstractFlexibleItem<ManageFilterItem.Holder>() {
/**
* Returns the layout resource for this item.
*/
override fun getLayoutRes(): Int {
return R.layout.categories_item
}
/**
* Returns a new view holder for this item.
*
* @param view The view of this item.
* @param adapter The adapter of this item.
*/
override fun createViewHolder(
view: View,
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
): Holder {
return Holder(view, adapter)
}
/**
* Binds the given view holder with this item.
*
* @param adapter The adapter of this item.
* @param holder The holder to bind.
* @param position The position of this item in the adapter.
* @param payloads List of partial changes.
*/
override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
holder: Holder,
position: Int,
payloads: MutableList<Any>
) {
holder.bind(char)
}
/**
* Returns true if this item is draggable.
*/
override fun isDraggable(): Boolean {
return true
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other is ManageFilterItem) {
return char == other.char
}
return false
}
override fun hashCode(): Int {
return char.hashCode()
}
class Holder(val view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>) :
BaseFlexibleViewHolder(view, adapter, true) {
init {
image.gone()
edit_button.gone()
edit_text.isEnabled = false
setDragHandleView(reorder)
}
fun bind(char: Char) {
title.setText(
when (char) {
'u' -> R.string.read_progress
'r' -> R.string.unread
'd' -> R.string.downloaded
'c' -> R.string.status
'm' -> R.string.series_type
't' -> R.string.tracked
else -> R.string.unread
}
)
}
}
}

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24.0" android:viewportWidth="24.0"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M16,17.01V10h-2v7.01h-3L15,21l4,-3.99h-3zM9,3L5,6.99h3V14h2V6.99h3L9,3z"/>
</vector>

View File

@ -64,6 +64,7 @@
</HorizontalScrollView> </HorizontalScrollView>
<LinearLayout <LinearLayout
android:id="@+id/first_layout"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="10dp" android:layout_marginStart="10dp"
@ -88,16 +89,35 @@
app:iconTint="?android:attr/textColorPrimary" /> app:iconTint="?android:attr/textColorPrimary" />
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/second_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/view_options" android:id="@+id/view_options"
style="@style/Theme.Widget.Button.TextButton" style="@style/Theme.Widget.Button.TextButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="@string/display_options" android:text="@string/display_options"
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
app:icon="@drawable/ic_tune_white_24dp" app:icon="@drawable/ic_tune_white_24dp"
app:iconTint="?android:attr/textColorPrimary" /> app:iconTint="?android:attr/textColorPrimary" />
<com.google.android.material.button.MaterialButton
android:id="@+id/reorder_filters"
style="@style/Theme.Widget.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="@string/reorder_filters"
android:textColor="?android:attr/textColorPrimary"
app:icon="@drawable/ic_swap_vert_24dp"
app:iconTint="?android:attr/textColorPrimary" />
</LinearLayout>
</LinearLayout> </LinearLayout>
<ImageView <ImageView

View File

@ -117,6 +117,10 @@
<string name="show_all_categories">Show all categories</string> <string name="show_all_categories">Show all categories</string>
<string name="expand_all_categories">Expand all categories</string> <string name="expand_all_categories">Expand all categories</string>
<string name="collapse_all_categories">Collapse all categories</string> <string name="collapse_all_categories">Collapse all categories</string>
<string name="reorder_filters">Reorder filters</string>
<string name="read_progress">Read progress</string>
<string name="series_type">Series type</string>
<!-- Library Sort --> <!-- Library Sort -->
<string name="sort_by_">Sort by: %1$s</string> <string name="sort_by_">Sort by: %1$s</string>