diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt index cf1f4a7214..0472cb57cc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt @@ -51,6 +51,7 @@ import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.manga.MangaShortcutManager import eu.kanade.tachiyomi.util.system.executeOnIO import eu.kanade.tachiyomi.util.system.launchIO +import eu.kanade.tachiyomi.widget.TriStateCheckBox import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -531,14 +532,26 @@ class MangaDetailsPresenter( /** * Removes all filters and requests an UI update. */ - fun setFilters(read: Boolean, unread: Boolean, downloaded: Boolean, bookmarked: Boolean) { - manga.readFilter = when { - read -> Manga.CHAPTER_SHOW_READ - unread -> Manga.CHAPTER_SHOW_UNREAD + fun setFilters( + unread: TriStateCheckBox.State, + downloaded: TriStateCheckBox.State, + bookmarked: TriStateCheckBox.State + ) { + manga.readFilter = when (unread) { + TriStateCheckBox.State.CHECKED -> Manga.CHAPTER_SHOW_UNREAD + TriStateCheckBox.State.INVERSED -> Manga.CHAPTER_SHOW_READ + else -> Manga.SHOW_ALL + } + manga.downloadedFilter = when (downloaded) { + TriStateCheckBox.State.CHECKED -> Manga.CHAPTER_SHOW_DOWNLOADED + TriStateCheckBox.State.INVERSED -> Manga.CHAPTER_SHOW_NOT_DOWNLOADED + else -> Manga.SHOW_ALL + } + manga.bookmarkedFilter = when (bookmarked) { + TriStateCheckBox.State.CHECKED -> Manga.CHAPTER_SHOW_BOOKMARKED + TriStateCheckBox.State.INVERSED -> Manga.CHAPTER_SHOW_NOT_BOOKMARKED else -> Manga.SHOW_ALL } - manga.downloadedFilter = if (downloaded) Manga.CHAPTER_SHOW_DOWNLOADED else Manga.SHOW_ALL - manga.bookmarkedFilter = if (bookmarked) Manga.CHAPTER_SHOW_BOOKMARKED else Manga.SHOW_ALL asyncUpdateMangaAndChapters() } @@ -555,7 +568,9 @@ class MangaDetailsPresenter( filtersId.add(if (manga.readFilter == Manga.CHAPTER_SHOW_READ) R.string.read else null) filtersId.add(if (manga.readFilter == Manga.CHAPTER_SHOW_UNREAD) R.string.unread else null) filtersId.add(if (manga.downloadedFilter == Manga.CHAPTER_SHOW_DOWNLOADED) R.string.downloaded else null) + filtersId.add(if (manga.downloadedFilter == Manga.CHAPTER_SHOW_NOT_DOWNLOADED) R.string.not_downloaded else null) filtersId.add(if (manga.bookmarkedFilter == Manga.CHAPTER_SHOW_BOOKMARKED) R.string.bookmarked else null) + filtersId.add(if (manga.bookmarkedFilter == Manga.CHAPTER_SHOW_NOT_BOOKMARKED) R.string.not_bookmarked else null) return filtersId.filterNotNull().joinToString(", ") { preferences.context.getString(it) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterFilterLayout.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterFilterLayout.kt index 8019d2e25d..805bd40907 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterFilterLayout.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChapterFilterLayout.kt @@ -2,10 +2,10 @@ package eu.kanade.tachiyomi.ui.manga.chapter import android.content.Context import android.util.AttributeSet -import android.widget.CompoundButton import android.widget.LinearLayout import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.databinding.ChapterFilterLayoutBinding +import eu.kanade.tachiyomi.widget.TriStateCheckBox class ChapterFilterLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) { @@ -16,38 +16,52 @@ class ChapterFilterLayout @JvmOverloads constructor(context: Context, attrs: Att super.onFinishInflate() binding = ChapterFilterLayoutBinding.bind(this) binding.showAll.setOnCheckedChangeListener(::checkedFilter) - binding.showRead.setOnCheckedChangeListener(::checkedFilter) binding.showUnread.setOnCheckedChangeListener(::checkedFilter) binding.showDownload.setOnCheckedChangeListener(::checkedFilter) binding.showBookmark.setOnCheckedChangeListener(::checkedFilter) } - private fun checkedFilter(checkBox: CompoundButton, isChecked: Boolean) { - if (isChecked) { - if (binding.showAll == checkBox) { - binding.showRead.isChecked = false - binding.showUnread.isChecked = false - binding.showDownload.isChecked = false - binding.showBookmark.isChecked = false + private fun checkedFilter(checkBox: TriStateCheckBox, state: TriStateCheckBox.State) { + if (state != TriStateCheckBox.State.UNCHECKED) { + if (binding.showAll == checkBox && state == TriStateCheckBox.State.CHECKED) { + binding.showUnread.animateDrawableToState(TriStateCheckBox.State.UNCHECKED) + binding.showDownload.animateDrawableToState(TriStateCheckBox.State.UNCHECKED) + binding.showBookmark.animateDrawableToState(TriStateCheckBox.State.UNCHECKED) } else { - binding.showAll.isChecked = false - if (binding.showRead == checkBox) binding.showUnread.isChecked = false - else if (binding.showUnread == checkBox) binding.showRead.isChecked = false + if (binding.showAll == checkBox) { + binding.showAll.state = TriStateCheckBox.State.CHECKED + } else { + binding.showAll.animateDrawableToState(TriStateCheckBox.State.UNCHECKED) + } } - } else if (!binding.showRead.isChecked && !binding.showUnread.isChecked && !binding.showDownload.isChecked && !binding.showBookmark.isChecked) { - binding.showAll.isChecked = true + } else if ( + binding.showUnread.isUnchecked && + binding.showDownload.isUnchecked && + binding.showBookmark.isUnchecked + ) { + binding.showAll.animateDrawableToState(TriStateCheckBox.State.CHECKED) } } fun setCheckboxes(manga: Manga) { - binding.showRead.isChecked = manga.readFilter == Manga.CHAPTER_SHOW_READ - binding.showUnread.isChecked = manga.readFilter == Manga.CHAPTER_SHOW_UNREAD - binding.showDownload.isChecked = manga.downloadedFilter == Manga.CHAPTER_SHOW_DOWNLOADED - binding.showBookmark.isChecked = manga.bookmarkedFilter == Manga.CHAPTER_SHOW_BOOKMARKED + binding.showUnread.state = when (manga.readFilter) { + Manga.CHAPTER_SHOW_UNREAD -> TriStateCheckBox.State.CHECKED + Manga.CHAPTER_SHOW_READ -> TriStateCheckBox.State.INVERSED + else -> TriStateCheckBox.State.UNCHECKED + } + binding.showDownload.state = when (manga.downloadedFilter) { + Manga.CHAPTER_SHOW_DOWNLOADED -> TriStateCheckBox.State.CHECKED + Manga.CHAPTER_SHOW_NOT_DOWNLOADED -> TriStateCheckBox.State.INVERSED + else -> TriStateCheckBox.State.UNCHECKED + } + binding.showBookmark.state = when (manga.bookmarkedFilter) { + Manga.CHAPTER_SHOW_BOOKMARKED -> TriStateCheckBox.State.CHECKED + Manga.CHAPTER_SHOW_NOT_BOOKMARKED -> TriStateCheckBox.State.INVERSED + else -> TriStateCheckBox.State.UNCHECKED + } - binding.showAll.isChecked = !( - binding.showRead.isChecked || binding.showUnread.isChecked || - binding.showDownload.isChecked || binding.showBookmark.isChecked - ) + binding.showAll.isChecked = binding.showUnread.isUnchecked && + binding.showDownload.isUnchecked && + binding.showBookmark.isUnchecked } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSortBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSortBottomSheet.kt index 227d96a0d2..909ed17760 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSortBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersSortBottomSheet.kt @@ -70,10 +70,9 @@ class ChaptersSortBottomSheet(controller: MangaDetailsController) : setOnDismissListener { presenter.setFilters( - binding.chapterFilterLayout.showRead.isChecked, - binding.chapterFilterLayout.showUnread.isChecked, - binding.chapterFilterLayout.showDownload.isChecked, - binding.chapterFilterLayout.showBookmark.isChecked + binding.chapterFilterLayout.showUnread.state, + binding.chapterFilterLayout.showDownload.state, + binding.chapterFilterLayout.showBookmark.state ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterFilter.kt b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterFilter.kt index 9d94ea9c93..db686fe549 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterFilter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/chapter/ChapterFilter.kt @@ -14,15 +14,19 @@ class ChapterFilter(val preferences: PreferencesHelper = Injekt.get(), val downl val readEnabled = manga.readFilter == Manga.CHAPTER_SHOW_READ val unreadEnabled = manga.readFilter == Manga.CHAPTER_SHOW_UNREAD val downloadEnabled = manga.downloadedFilter == Manga.CHAPTER_SHOW_DOWNLOADED + val notDownloadEnabled = manga.downloadedFilter == Manga.CHAPTER_SHOW_NOT_DOWNLOADED val bookmarkEnabled = manga.bookmarkedFilter == Manga.CHAPTER_SHOW_BOOKMARKED + val notBookmarkEnabled = manga.bookmarkedFilter == Manga.CHAPTER_SHOW_NOT_BOOKMARKED // if none of the filters are enabled skip the filtering of them - return if (readEnabled || unreadEnabled || downloadEnabled || bookmarkEnabled) { + return if (readEnabled || unreadEnabled || downloadEnabled || notDownloadEnabled || bookmarkEnabled || notBookmarkEnabled) { chapters.filter { if (readEnabled && it.read.not() || (unreadEnabled && it.read) || (bookmarkEnabled && it.bookmark.not()) || - (downloadEnabled && downloadManager.isChapterDownloaded(it, manga).not()) + (notBookmarkEnabled && it.bookmark) || + (downloadEnabled && downloadManager.isChapterDownloaded(it, manga).not()) || + (notDownloadEnabled && downloadManager.isChapterDownloaded(it, manga)) ) { return@filter false } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/TriStateCheckBox.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/TriStateCheckBox.kt new file mode 100644 index 0000000000..4afdd468ad --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/TriStateCheckBox.kt @@ -0,0 +1,172 @@ +package eu.kanade.tachiyomi.widget + +import android.content.Context +import android.content.res.ColorStateList +import android.util.AttributeSet +import android.view.LayoutInflater +import android.widget.FrameLayout +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.databinding.TriStateCheckBoxBinding +import eu.kanade.tachiyomi.util.system.getResourceColor +import eu.kanade.tachiyomi.util.view.setAnimVectorCompat +import eu.kanade.tachiyomi.util.view.setVectorCompat + +class TriStateCheckBox constructor(context: Context, attrs: AttributeSet?) : + FrameLayout(context, attrs) { + + var text: CharSequence + get() { + return binding.textView.text + } + set(value) { + binding.textView.text = value + } + + var state: State = State.UNCHECKED + set(value) { + field = value + updateDrawable() + } + + var isUnchecked: Boolean + get() = state == State.UNCHECKED + set(value) { + state = if (value) State.UNCHECKED else State.CHECKED + } + + var isChecked: Boolean + get() = state == State.UNCHECKED + set(value) { + state = if (value) State.CHECKED else State.UNCHECKED + } + + private val binding = TriStateCheckBoxBinding.inflate( + LayoutInflater.from(context), + this, + false + ) + private var mOnCheckedChangeListener: OnCheckedChangeListener? = null + + init { + addView(binding.root) + val a = context.obtainStyledAttributes(attrs, R.styleable.TriStateCheckBox, 0, 0) + + val str = a.getString(R.styleable.TriStateCheckBox_android_text) ?: "" + text = str + + val maxLines = a.getInt(R.styleable.TriStateCheckBox_android_maxLines, Int.MAX_VALUE) + binding.textView.maxLines = maxLines + + a.recycle() + + setOnClickListener { + setState( + when (state) { + State.CHECKED -> State.INVERSED + State.UNCHECKED -> State.CHECKED + else -> State.UNCHECKED + }, + true + ) + mOnCheckedChangeListener?.onCheckedChanged(this, state) + } + } + + fun setState(state: State, animated: Boolean = false) { + if (animated) { + animateDrawableToState(state) + } else { + this.state = state + } + } + + /** + * Register a callback to be invoked when the checked state of this button + * changes. + * + * @param listener the callback to call on checked state change + */ + fun setOnCheckedChangeListener(listener: OnCheckedChangeListener?) { + mOnCheckedChangeListener = listener + } + + fun animateDrawableToState(state: State) { + val oldState = this.state + if (state == oldState) return + this.state = state + with(binding.triStateBox) { + when (state) { + State.UNCHECKED -> { + setAnimVectorCompat( + when (oldState) { + State.INVERSED -> R.drawable.anim_check_box_x_to_blank_24dp + else -> R.drawable.anim_checkbox_checked_to_blank_24dp + }, + R.attr.colorControlNormal + ) + backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorControlNormal)) + } + State.CHECKED -> { + setAnimVectorCompat( + R.drawable.anim_check_box_blank_to_checked_24dp, + R.attr.colorAccent + ) + backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorAccent)) + } + State.INVERSED -> { + setAnimVectorCompat( + R.drawable.anim_check_box_checked_to_x_24dp, + R.attr.colorAccentText + ) + backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorAccentText)) + } + } + } + } + + fun updateDrawable() { + with(binding.triStateBox) { + when (state) { + State.UNCHECKED -> { + setVectorCompat( + R.drawable.ic_check_box_outline_blank_24dp, + R.attr.colorControlNormal + ) + backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorControlNormal)) + } + State.CHECKED -> { + setVectorCompat(R.drawable.ic_check_box_24dp, R.attr.colorAccent) + backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorAccent)) + } + State.INVERSED -> { + setVectorCompat( + R.drawable.ic_check_box_x_24dp, + R.attr.colorAccentText + ) + backgroundTintList = ColorStateList.valueOf(context.getResourceColor(R.attr.colorAccentText)) + } + } + } + } + + enum class State { + UNCHECKED, + CHECKED, + INVERSED, + ; + } + + /** + * Interface definition for a callback to be invoked when the checked state + * of a compound button changed. + */ + fun interface OnCheckedChangeListener { + /** + * Called when the checked state of a compound button has changed. + * + * @param buttonView The compound button view whose state has changed. + * @param state The new checked state of buttonView. + */ + fun onCheckedChanged(buttonView: TriStateCheckBox, state: State) + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateCheckBox.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateCheckBox.kt index 3455fb5421..5c56f85d65 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateCheckBox.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/QuadStateCheckBox.kt @@ -13,6 +13,7 @@ class QuadStateCheckBox @JvmOverloads constructor(context: Context, attrs: Attri var state: State = State.UNCHECKED fun animateDrawableToState(state: State) { + if (state == this.state) return when (state) { State.UNCHECKED -> setAnimVectorCompat( when (this.state) { diff --git a/app/src/main/res/layout-sw600dp-land/manga_header_item.xml b/app/src/main/res/layout-sw600dp-land/manga_header_item.xml index f84b3c36d8..81e7c6fdf6 100644 --- a/app/src/main/res/layout-sw600dp-land/manga_header_item.xml +++ b/app/src/main/res/layout-sw600dp-land/manga_header_item.xml @@ -398,7 +398,6 @@ android:padding="5dp" android:textAlignment="textEnd" android:textColor="?android:textColorHint" - app:layout_constraintBaseline_toBaselineOf="@id/chapters_title" app:layout_constraintBottom_toBottomOf="@id/filter_button" app:layout_constraintEnd_toStartOf="@id/filter_button" app:layout_constraintStart_toEndOf="@+id/chapters_title" diff --git a/app/src/main/res/layout-sw600dp-port/manga_header_item.xml b/app/src/main/res/layout-sw600dp-port/manga_header_item.xml index d89e0cc5a8..8780f013fd 100644 --- a/app/src/main/res/layout-sw600dp-port/manga_header_item.xml +++ b/app/src/main/res/layout-sw600dp-port/manga_header_item.xml @@ -398,7 +398,6 @@ android:padding="5dp" android:textAlignment="textEnd" android:textColor="?android:textColorHint" - app:layout_constraintBaseline_toBaselineOf="@id/chapters_title" app:layout_constraintBottom_toBottomOf="@id/filter_button" app:layout_constraintEnd_toStartOf="@id/filter_button" app:layout_constraintStart_toEndOf="@+id/chapters_title" diff --git a/app/src/main/res/layout/chapter_filter_layout.xml b/app/src/main/res/layout/chapter_filter_layout.xml index b9768f2f44..18c05a7edb 100644 --- a/app/src/main/res/layout/chapter_filter_layout.xml +++ b/app/src/main/res/layout/chapter_filter_layout.xml @@ -2,6 +2,7 @@ - - - - - + android:layout_marginEnd="12dp" /> - + + + + + + + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index ab571bccb3..eca1430ae3 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -26,6 +26,11 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0149e2e8a8..62320fb848 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -49,6 +49,7 @@ Chapter %1$d of %2$d All chapters read Bookmarked + Not bookmarked Marked as read Marked as unread Removed bookmark