From aef79bafadaf643640bf8fe843d2cab29615571b Mon Sep 17 00:00:00 2001 From: Jay Date: Sun, 19 Apr 2020 14:52:22 -0400 Subject: [PATCH] Converted long press for hide and pin into a swipe and button respectively --- .../eu/kanade/tachiyomi/ui/source/LangItem.kt | 4 ++ .../tachiyomi/ui/source/SourceAdapter.kt | 5 ++ .../tachiyomi/ui/source/SourceController.kt | 44 +++++++------- .../tachiyomi/ui/source/SourceHolder.kt | 32 +++++++++++ .../kanade/tachiyomi/ui/source/SourceItem.kt | 30 +++++++++- .../tachiyomi/ui/source/SourcePresenter.kt | 13 ++++- app/src/main/res/drawable/ic_pin_24dp.xml | 8 +++ .../main/res/drawable/ic_pin_outline_24dp.xml | 8 +++ app/src/main/res/layout/source_item.xml | 57 ++++++++++++++++++- app/src/main/res/values/strings.xml | 1 + 10 files changed, 170 insertions(+), 32 deletions(-) create mode 100644 app/src/main/res/drawable/ic_pin_24dp.xml create mode 100644 app/src/main/res/drawable/ic_pin_outline_24dp.xml diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/LangItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/LangItem.kt index 0a692f3ed4..888809f805 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/LangItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/LangItem.kt @@ -21,6 +21,10 @@ data class LangItem(val code: String) : AbstractHeaderItem() { return R.layout.source_header_item } + override fun isSwipeable(): Boolean { + return false + } + /** * Creates a new view holder for this item. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceAdapter.kt index f6323daf22..92da8eaeea 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceAdapter.kt @@ -29,6 +29,11 @@ class SourceAdapter(val controller: SourceController) : */ val latestClickListener: OnLatestClickListener = controller + override fun onItemSwiped(position: Int, direction: Int) { + super.onItemSwiped(position, direction) + controller.hideCatalogue(position) + } + /** * Listener which should be called when user clicks browse. * Note: Should only be handled by [SourceController] diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceController.kt index 1e5e4e5abe..a911186cce 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceController.kt @@ -10,13 +10,12 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.SearchView -import com.afollestad.materialdialogs.MaterialDialog -import com.afollestad.materialdialogs.list.listItems import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.changehandler.FadeChangeHandler import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.snackbar.Snackbar import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R @@ -38,6 +37,7 @@ import eu.kanade.tachiyomi.ui.source.latest.LatestUpdatesController import eu.kanade.tachiyomi.util.view.applyWindowInsetsForRootController import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener +import eu.kanade.tachiyomi.util.view.snack import kotlinx.android.parcel.Parcelize import kotlinx.android.synthetic.main.extensions_bottom_sheet.* import kotlinx.android.synthetic.main.main_activity.* @@ -54,7 +54,6 @@ import kotlin.math.max */ class SourceController : NucleusController(), FlexibleAdapter.OnItemClickListener, - FlexibleAdapter.OnItemLongClickListener, SourceAdapter.OnBrowseClickListener, RootSearchInterface, BottomSheetController, @@ -77,6 +76,8 @@ class SourceController : NucleusController(), var showingExtensions = false + var snackbar: Snackbar? = null + /** * Called when controller is initialized. */ @@ -114,6 +115,7 @@ class SourceController : NucleusController(), // Create recycler and set adapter. recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context) recycler.adapter = adapter + adapter?.isSwipeEnabled = true // recycler.addItemDecoration(SourceDividerItemDecoration(view.context)) val attrsArray = intArrayOf(android.R.attr.actionBarSize) val array = view.context.obtainStyledAttributes(attrsArray) @@ -216,29 +218,22 @@ class SourceController : NucleusController(), return false } - override fun onItemLongClick(position: Int) { - val activity = activity ?: return - val item = adapter?.getItem(position) as? SourceItem ?: return - val isPinned = item.header?.code?.equals(SourcePresenter.PINNED_KEY) ?: false - - MaterialDialog(activity) - .title(text = item.source.name) - .listItems(items = listOf( - activity.getString(R.string.hide), - activity.getString(if (isPinned) R.string.unpin else R.string.pin) - ), waitForPositiveButton = false, selection = { _, index, _ -> - when (index) { - 0 -> hideCatalogue(item.source) - 1 -> pinCatalogue(item.source, isPinned) - } - }).show() - } - - private fun hideCatalogue(source: Source) { + fun hideCatalogue(position: Int) { + val source = (adapter?.getItem(position) as? SourceItem)?.source ?: return val current = preferences.hiddenSources().getOrDefault() preferences.hiddenSources().set(current + source.id.toString()) presenter.updateSources() + + snackbar = view?.snack(R.string.source_hidden, Snackbar.LENGTH_INDEFINITE) { + anchorView = ext_bottom_sheet + setAction(R.string.undo) { + val newCurrent = preferences.hiddenSources().getOrDefault() + preferences.hiddenSources().set(newCurrent - source.id.toString()) + presenter.updateSources() + } + } + (activity as? MainActivity)?.setUndoSnackBar(snackbar) } private fun pinCatalogue(source: Source, isPinned: Boolean) { @@ -256,7 +251,10 @@ class SourceController : NucleusController(), * Called when browse is clicked in [SourceAdapter] */ override fun onBrowseClick(position: Int) { - onItemClick(view!!, position) + val item = adapter?.getItem(position) as? SourceItem ?: return + val isPinned = item.isPinned ?: item.header?.code?.equals(SourcePresenter.PINNED_KEY) + ?: false + pinCatalogue(item.source, isPinned) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceHolder.kt index eba14af225..384c7a9c9a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceHolder.kt @@ -1,8 +1,11 @@ package eu.kanade.tachiyomi.ui.source +import android.content.res.ColorStateList import android.view.View +import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.icon import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder +import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.view.gone import eu.kanade.tachiyomi.util.view.roundTextIcon import eu.kanade.tachiyomi.util.view.visible @@ -19,6 +22,9 @@ class SourceHolder(view: View, val adapter: SourceAdapter) : get() = card*/ init { + source_pin.setOnClickListener { + adapter.browseClickListener.onBrowseClick(adapterPosition) + } source_latest.setOnClickListener { adapter.latestClickListener.onLatestClick(adapterPosition) } @@ -31,6 +37,20 @@ class SourceHolder(view: View, val adapter: SourceAdapter) : // Set source name title.text = source.name + val isPinned = item.isPinned ?: item.header?.code?.equals(SourcePresenter.PINNED_KEY) ?: false + source_pin.apply { + imageTintList = ColorStateList.valueOf( + context.getResourceColor( + if (isPinned) R.attr.colorAccent + else android.R.attr.textColorSecondary + ) + ) + setImageResource( + if (isPinned) R.drawable.ic_pin_24dp + else R.drawable.ic_pin_outline_24dp + ) + } + // Set circle letter image. itemView.post { val icon = source.icon() @@ -44,4 +64,16 @@ class SourceHolder(view: View, val adapter: SourceAdapter) : source_latest.gone() } } + + override fun getFrontView(): View { + return card + } + + override fun getRearLeftView(): View { + return left_view + } + + override fun getRearRightView(): View { + return right_view + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceItem.kt index 5f8b24eb31..63f71a44a6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourceItem.kt @@ -7,6 +7,7 @@ import eu.davidea.flexibleadapter.items.AbstractSectionableItem import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.source.CatalogueSource +import eu.kanade.tachiyomi.source.LocalSource /** * Item that contains source information. @@ -14,7 +15,7 @@ import eu.kanade.tachiyomi.source.CatalogueSource * @param source Instance of [CatalogueSource] containing source information. * @param header The header for this item. */ -data class SourceItem(val source: CatalogueSource, val header: LangItem? = null) : +class SourceItem(val source: CatalogueSource, header: LangItem? = null, val isPinned: Boolean? = null) : AbstractSectionableItem(header) { /** @@ -24,17 +25,40 @@ data class SourceItem(val source: CatalogueSource, val header: LangItem? = null) return R.layout.source_item } + override fun isSwipeable(): Boolean { + return source.id != LocalSource.ID + } + /** * Creates a new view holder for this item. */ - override fun createViewHolder(view: View, adapter: FlexibleAdapter>): SourceHolder { + override fun createViewHolder( + view: View, + adapter: FlexibleAdapter> + ): SourceHolder { return SourceHolder(view, adapter as SourceAdapter) } /** * Binds this item to the given view holder. */ - override fun bindViewHolder(adapter: FlexibleAdapter>, holder: SourceHolder, position: Int, payloads: MutableList) { + override fun bindViewHolder( + adapter: FlexibleAdapter>, + holder: SourceHolder, + position: Int, + payloads: MutableList + ) { holder.bind(this) } + + override fun equals(other: Any?): Boolean { + if (other is SourceItem) { + return source.id == other.source.id && header?.code == other.header?.code + } + return false + } + + override fun hashCode(): Int { + return source.id.hashCode() + (header?.code?.hashCode() ?: 0).toInt() + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourcePresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourcePresenter.kt index e53190b82f..772024e00c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourcePresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/SourcePresenter.kt @@ -33,6 +33,7 @@ class SourcePresenter( * Subscription for retrieving enabled sources. */ private var sourceSubscription: Subscription? = null + private var lastUsedSubscription: Subscription? = null override fun onCreate(savedState: Bundle?) { super.onCreate(savedState) @@ -63,11 +64,12 @@ class SourcePresenter( var sourceItems = byLang.flatMap { val langItem = LangItem(it.key) it.value.map { source -> + val isPinned = source.id.toString() in pinnedCatalogues if (source.id.toString() in pinnedCatalogues) { pinnedSources.add(SourceItem(source, LangItem(PINNED_KEY))) } - SourceItem(source, langItem) + SourceItem(source, langItem, isPinned) } } @@ -80,20 +82,25 @@ class SourcePresenter( } private fun loadLastUsedSource() { + lastUsedSubscription?.unsubscribe() val sharedObs = preferences.lastUsedCatalogueSource().asObservable().share() // Emit the first item immediately but delay subsequent emissions by 500ms. - Observable.merge( + lastUsedSubscription = Observable.merge( sharedObs.take(1), sharedObs.skip(1).delay(500, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())) .distinctUntilChanged() - .map { (sourceManager.get(it) as? CatalogueSource)?.let { SourceItem(it) } } + .map { (sourceManager.get(it) as? CatalogueSource)?.let { source -> + val pinnedCatalogues = preferences.pinnedCatalogues().getOrDefault() + val isPinned = source.id.toString() in pinnedCatalogues + SourceItem(source, null, isPinned) } } .subscribeLatestCache(SourceController::setLastUsedSource) } fun updateSources() { sources = getEnabledSources() loadSources() + loadLastUsedSource() } /** diff --git a/app/src/main/res/drawable/ic_pin_24dp.xml b/app/src/main/res/drawable/ic_pin_24dp.xml new file mode 100644 index 0000000000..fe0e5245a2 --- /dev/null +++ b/app/src/main/res/drawable/ic_pin_24dp.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_pin_outline_24dp.xml b/app/src/main/res/drawable/ic_pin_outline_24dp.xml new file mode 100644 index 0000000000..7eee272389 --- /dev/null +++ b/app/src/main/res/drawable/ic_pin_outline_24dp.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/source_item.xml b/app/src/main/res/layout/source_item.xml index ed1afb2e4b..aee31cf1e4 100644 --- a/app/src/main/res/layout/source_item.xml +++ b/app/src/main/res/layout/source_item.xml @@ -6,6 +6,45 @@ android:layout_width="match_parent" android:layout_height="wrap_content"> + + + + + + + + + + + + app:layout_constraintEnd_toStartOf="@id/source_pin"/> + + - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 63a98b159d..1af93d25c0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -213,6 +213,7 @@ Search extensions… All sources Sources + Source hidden