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 filterOrder() = flowPrefs.getString("filter_order", "rudcmt")
// Tutorial preferences
fun shownFilterTutorial() = flowPrefs.getBoolean("shown_filter_tutorial", false)

View File

@ -1,13 +1,19 @@
package eu.kanade.tachiyomi.ui.library.filter
import android.content.Context
import android.content.res.Configuration
import android.os.Parcelable
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
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 eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
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.util.system.launchUI
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.inflate
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 trackManager: TrackManager by injectLazy()
val hasTracking
get() = trackManager.hasLoggedServices()
private lateinit var downloaded: FilterTagGroup
private lateinit var unreadProgress: FilterTagGroup
private lateinit var unread: FilterTagGroup
private lateinit var allUnread: FilterTagGroup
private lateinit var completed: FilterTagGroup
private lateinit var tracked: FilterTagGroup
private var tracked: 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 filterOrder = preferences.filterOrder().get()
private lateinit var clearButton: ImageView
private val filterItems: MutableList<FilterTagGroup> by lazy {
val list = mutableListOf<FilterTagGroup>()
list.add(unreadProgress)
list.add(unread)
list.add(downloaded)
list.add(completed)
if (Injekt.get<TrackManager>().hasLoggedServices())
list.add(tracked)
if (hasTracking)
tracked?.let { list.add(it) }
list
}
@ -95,6 +110,15 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
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()) {
sheetBehavior?.hide()
}
@ -104,6 +128,9 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
view_options.setOnClickListener {
onGroupClicked(ACTION_DISPLAY)
}
reorder_filters.setOnClickListener {
manageFilterPopup()
}
val activeFilters = hasActiveFiltersFromPref()
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.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.setup(this, R.string.not_started, R.string.in_progress, R.string.read)
allUnread = inflate(R.layout.filter_buttons) as FilterTagGroup
allUnread.setup(this, R.string.unread)
unread.setup(this, R.string.unread, R.string.read)
if (hasTracking) {
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()
@ -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_MANHUA }) types.add(R.string.manhua)
if (libraryManga.any { it.mangaType() == Manga.TYPE_COMIC }) types.add(R.string.comic)
val hasTracking = Injekt.get<TrackManager>().hasLoggedServices()
if (types.isNotEmpty()) {
launchUI {
val mangaType = inflate(R.layout.filter_buttons) as FilterTagGroup
@ -238,11 +266,8 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
types.getOrNull(2)
)
this@FilterBottomSheet.mangaType = mangaType
filter_layout.addView(mangaType)
filterItems.remove(tracked)
filterItems.add(mangaType)
if (hasTracking)
filterItems.add(tracked)
reorderFilters()
reSortViews()
}
}
withContext(Dispatchers.Main) {
@ -250,15 +275,14 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
downloaded.setState(preferences.filterDownloaded())
completed.setState(preferences.filterCompleted())
val unreadP = preferences.filterUnread().getOrDefault()
if (unreadP == STATE_INCLUDE) {
allUnread.state = 0
if (!filterItems.contains(allUnread))
filterItems.add(allUnread)
} else if (unreadP > 0) {
unread.state = if (unreadP in 3..4) unreadP - 3 else 2
if (unreadP <= 2) {
unread.state = unreadP - 1
} else if (unreadP > 3) {
unreadProgress.state = unreadP - 3
}
tracked.setState(preferences.filterTracked())
tracked?.setState(preferences.filterTracked())
mangaType?.setState(preferences.filterMangaType())
reorderFilters()
reSortViews()
}
@ -274,7 +298,7 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
serviceNames.getOrNull(1),
serviceNames.getOrNull(2)
)
if (tracked.isActivated) {
if (tracked?.isActivated == true) {
filter_layout.addView(trackers)
filterItems.add(trackers!!)
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) {
if (updatePreference) {
when (view) {
@ -293,20 +390,19 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
FILTER_TRACKER = view.nameOf(index) ?: ""
null
}
unread -> {
unreadProgress -> {
unread.reset()
preferences.filterUnread().set(
when (index) {
in 0..1 -> index + 3
2 -> 2
else -> 0
}
)
null
}
allUnread -> {
preferences.filterUnread().set(index + 1)
if (index != 0) unread.reset()
null
unread -> {
unreadProgress.reset()
preferences.filterUnread()
}
downloaded -> preferences.filterDownloaded()
completed -> preferences.filterCompleted()
@ -316,27 +412,28 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
}?.set(index + 1)
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)
filterItems.remove(unread)
} else if (allUnread.state != 0 && unread.parent == null) {
filter_layout.addView(unread, 0)
filterItems.add(0, 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)
} else {
filter_layout.addView(unread, indexOf(unread) + addForClear())
filterItems.add(indexOf(unread), unread)
}
if (tracked.isActivated && trackers != null && trackers?.parent == null) {
filter_layout.addView(trackers)
filterItems.add(trackers!!)
} else if (!tracked.isActivated && trackers?.parent != null) {
}
if (tracked?.isActivated == true && trackers != null && 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)
trackers?.reset()
FILTER_TRACKER = ""
@ -361,16 +458,13 @@ class FilterBottomSheet @JvmOverloads constructor(context: Context, attrs: Attri
val transition = androidx.transition.AutoTransition()
transition.duration = 150
androidx.transition.TransitionManager.beginDelayedTransition(filter_layout, transition)
if (!filterItems.contains(unread)) {
filterItems.add(0, unread)
}
reorderFilters()
filterItems.forEach {
it.reset()
}
trackers?.let {
filterItems.remove(it)
}
filterItems.remove(allUnread)
reSortViews()
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>
<LinearLayout
android:id="@+id/first_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
@ -88,16 +89,35 @@
app:iconTint="?android:attr/textColorPrimary" />
</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
android:id="@+id/view_options"
style="@style/Theme.Widget.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="@string/display_options"
android:textColor="?android:attr/textColorPrimary"
app:icon="@drawable/ic_tune_white_24dp"
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>
<ImageView

View File

@ -117,6 +117,10 @@
<string name="show_all_categories">Show all categories</string>
<string name="expand_all_categories">Expand 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 -->
<string name="sort_by_">Sort by: %1$s</string>