diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 60d9fa2814..1c83c4a076 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt index 14d32cc091..a3974b7dee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/FilterBottomSheet.kt @@ -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? = null + var filterOrder = preferences.filterOrder().get() + private lateinit var clearButton: ImageView private val filterItems: MutableList by lazy { val list = mutableListOf() + list.add(unreadProgress) list.add(unread) list.add(downloaded) list.add(completed) - if (Injekt.get().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) + unread.setup(this, R.string.unread, R.string.read) - allUnread = inflate(R.layout.filter_buttons) as FilterTagGroup - allUnread.setup(this, R.string.unread) - - tracked = inflate(R.layout.filter_buttons) as FilterTagGroup - tracked.setup(this, R.string.tracked, R.string.not_tracked) + if (hasTracking) { + tracked = inflate(R.layout.filter_buttons) as FilterTagGroup + 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().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) { - 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) + 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 { + 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) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/ManageFilterItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/ManageFilterItem.kt new file mode 100644 index 0000000000..e8588f7a03 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/filter/ManageFilterItem.kt @@ -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() { + + /** + * 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> + ): 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>, + holder: Holder, + position: Int, + payloads: MutableList + ) { + 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>) : + 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 + } + ) + } + } +} diff --git a/app/src/main/res/drawable/ic_swap_vert_24dp.xml b/app/src/main/res/drawable/ic_swap_vert_24dp.xml new file mode 100644 index 0000000000..2bae693a67 --- /dev/null +++ b/app/src/main/res/drawable/ic_swap_vert_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/filter_bottom_sheet.xml b/app/src/main/res/layout/filter_bottom_sheet.xml index edb86606a8..3594cb455a 100644 --- a/app/src/main/res/layout/filter_bottom_sheet.xml +++ b/app/src/main/res/layout/filter_bottom_sheet.xml @@ -64,6 +64,7 @@ - + android:orientation="horizontal"> + + + + + + Show all categories Expand all categories Collapse all categories + Reorder filters + + Read progress + Series type Sort by: %1$s