Converted long press for hide and pin into a swipe and button

respectively
This commit is contained in:
Jay 2020-04-19 14:52:22 -04:00
parent 5871572442
commit aef79bafad
10 changed files with 170 additions and 32 deletions

View File

@ -21,6 +21,10 @@ data class LangItem(val code: String) : AbstractHeaderItem<LangHolder>() {
return R.layout.source_header_item return R.layout.source_header_item
} }
override fun isSwipeable(): Boolean {
return false
}
/** /**
* Creates a new view holder for this item. * Creates a new view holder for this item.
*/ */

View File

@ -29,6 +29,11 @@ class SourceAdapter(val controller: SourceController) :
*/ */
val latestClickListener: OnLatestClickListener = controller 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. * Listener which should be called when user clicks browse.
* Note: Should only be handled by [SourceController] * Note: Should only be handled by [SourceController]

View File

@ -10,13 +10,12 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.SearchView 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.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.snackbar.Snackbar
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R 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.applyWindowInsetsForRootController
import eu.kanade.tachiyomi.util.view.scrollViewWith import eu.kanade.tachiyomi.util.view.scrollViewWith
import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener import eu.kanade.tachiyomi.util.view.setOnQueryTextChangeListener
import eu.kanade.tachiyomi.util.view.snack
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.android.synthetic.main.extensions_bottom_sheet.* import kotlinx.android.synthetic.main.extensions_bottom_sheet.*
import kotlinx.android.synthetic.main.main_activity.* import kotlinx.android.synthetic.main.main_activity.*
@ -54,7 +54,6 @@ import kotlin.math.max
*/ */
class SourceController : NucleusController<SourcePresenter>(), class SourceController : NucleusController<SourcePresenter>(),
FlexibleAdapter.OnItemClickListener, FlexibleAdapter.OnItemClickListener,
FlexibleAdapter.OnItemLongClickListener,
SourceAdapter.OnBrowseClickListener, SourceAdapter.OnBrowseClickListener,
RootSearchInterface, RootSearchInterface,
BottomSheetController, BottomSheetController,
@ -77,6 +76,8 @@ class SourceController : NucleusController<SourcePresenter>(),
var showingExtensions = false var showingExtensions = false
var snackbar: Snackbar? = null
/** /**
* Called when controller is initialized. * Called when controller is initialized.
*/ */
@ -114,6 +115,7 @@ class SourceController : NucleusController<SourcePresenter>(),
// Create recycler and set adapter. // Create recycler and set adapter.
recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context) recycler.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(view.context)
recycler.adapter = adapter recycler.adapter = adapter
adapter?.isSwipeEnabled = true
// recycler.addItemDecoration(SourceDividerItemDecoration(view.context)) // recycler.addItemDecoration(SourceDividerItemDecoration(view.context))
val attrsArray = intArrayOf(android.R.attr.actionBarSize) val attrsArray = intArrayOf(android.R.attr.actionBarSize)
val array = view.context.obtainStyledAttributes(attrsArray) val array = view.context.obtainStyledAttributes(attrsArray)
@ -216,29 +218,22 @@ class SourceController : NucleusController<SourcePresenter>(),
return false return false
} }
override fun onItemLongClick(position: Int) { fun hideCatalogue(position: Int) {
val activity = activity ?: return val source = (adapter?.getItem(position) as? SourceItem)?.source ?: 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) {
val current = preferences.hiddenSources().getOrDefault() val current = preferences.hiddenSources().getOrDefault()
preferences.hiddenSources().set(current + source.id.toString()) preferences.hiddenSources().set(current + source.id.toString())
presenter.updateSources() 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) { private fun pinCatalogue(source: Source, isPinned: Boolean) {
@ -256,7 +251,10 @@ class SourceController : NucleusController<SourcePresenter>(),
* Called when browse is clicked in [SourceAdapter] * Called when browse is clicked in [SourceAdapter]
*/ */
override fun onBrowseClick(position: Int) { 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)
} }
/** /**

View File

@ -1,8 +1,11 @@
package eu.kanade.tachiyomi.ui.source package eu.kanade.tachiyomi.ui.source
import android.content.res.ColorStateList
import android.view.View import android.view.View
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.icon import eu.kanade.tachiyomi.source.icon
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder 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.gone
import eu.kanade.tachiyomi.util.view.roundTextIcon import eu.kanade.tachiyomi.util.view.roundTextIcon
import eu.kanade.tachiyomi.util.view.visible import eu.kanade.tachiyomi.util.view.visible
@ -19,6 +22,9 @@ class SourceHolder(view: View, val adapter: SourceAdapter) :
get() = card*/ get() = card*/
init { init {
source_pin.setOnClickListener {
adapter.browseClickListener.onBrowseClick(adapterPosition)
}
source_latest.setOnClickListener { source_latest.setOnClickListener {
adapter.latestClickListener.onLatestClick(adapterPosition) adapter.latestClickListener.onLatestClick(adapterPosition)
} }
@ -31,6 +37,20 @@ class SourceHolder(view: View, val adapter: SourceAdapter) :
// Set source name // Set source name
title.text = 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. // Set circle letter image.
itemView.post { itemView.post {
val icon = source.icon() val icon = source.icon()
@ -44,4 +64,16 @@ class SourceHolder(view: View, val adapter: SourceAdapter) :
source_latest.gone() source_latest.gone()
} }
} }
override fun getFrontView(): View {
return card
}
override fun getRearLeftView(): View {
return left_view
}
override fun getRearRightView(): View {
return right_view
}
} }

View File

@ -7,6 +7,7 @@ import eu.davidea.flexibleadapter.items.AbstractSectionableItem
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.LocalSource
/** /**
* Item that contains source information. * Item that contains source information.
@ -14,7 +15,7 @@ import eu.kanade.tachiyomi.source.CatalogueSource
* @param source Instance of [CatalogueSource] containing source information. * @param source Instance of [CatalogueSource] containing source information.
* @param header The header for this item. * @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<SourceHolder, LangItem>(header) { AbstractSectionableItem<SourceHolder, LangItem>(header) {
/** /**
@ -24,17 +25,40 @@ data class SourceItem(val source: CatalogueSource, val header: LangItem? = null)
return R.layout.source_item return R.layout.source_item
} }
override fun isSwipeable(): Boolean {
return source.id != LocalSource.ID
}
/** /**
* Creates a new view holder for this item. * Creates a new view holder for this item.
*/ */
override fun createViewHolder(view: View, adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>): SourceHolder { override fun createViewHolder(
view: View,
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
): SourceHolder {
return SourceHolder(view, adapter as SourceAdapter) return SourceHolder(view, adapter as SourceAdapter)
} }
/** /**
* Binds this item to the given view holder. * Binds this item to the given view holder.
*/ */
override fun bindViewHolder(adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>, holder: SourceHolder, position: Int, payloads: MutableList<Any>) { override fun bindViewHolder(
adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>,
holder: SourceHolder,
position: Int,
payloads: MutableList<Any>
) {
holder.bind(this) 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()
}
} }

View File

@ -33,6 +33,7 @@ class SourcePresenter(
* Subscription for retrieving enabled sources. * Subscription for retrieving enabled sources.
*/ */
private var sourceSubscription: Subscription? = null private var sourceSubscription: Subscription? = null
private var lastUsedSubscription: Subscription? = null
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)
@ -63,11 +64,12 @@ class SourcePresenter(
var sourceItems = byLang.flatMap { var sourceItems = byLang.flatMap {
val langItem = LangItem(it.key) val langItem = LangItem(it.key)
it.value.map { source -> it.value.map { source ->
val isPinned = source.id.toString() in pinnedCatalogues
if (source.id.toString() in pinnedCatalogues) { if (source.id.toString() in pinnedCatalogues) {
pinnedSources.add(SourceItem(source, LangItem(PINNED_KEY))) pinnedSources.add(SourceItem(source, LangItem(PINNED_KEY)))
} }
SourceItem(source, langItem) SourceItem(source, langItem, isPinned)
} }
} }
@ -80,20 +82,25 @@ class SourcePresenter(
} }
private fun loadLastUsedSource() { private fun loadLastUsedSource() {
lastUsedSubscription?.unsubscribe()
val sharedObs = preferences.lastUsedCatalogueSource().asObservable().share() val sharedObs = preferences.lastUsedCatalogueSource().asObservable().share()
// Emit the first item immediately but delay subsequent emissions by 500ms. // Emit the first item immediately but delay subsequent emissions by 500ms.
Observable.merge( lastUsedSubscription = Observable.merge(
sharedObs.take(1), sharedObs.take(1),
sharedObs.skip(1).delay(500, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())) sharedObs.skip(1).delay(500, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()))
.distinctUntilChanged() .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) .subscribeLatestCache(SourceController::setLastUsedSource)
} }
fun updateSources() { fun updateSources() {
sources = getEnabledSources() sources = getEnabledSources()
loadSources() loadSources()
loadLastUsedSource()
} }
/** /**

View File

@ -0,0 +1,8 @@
<!-- drawable/pin.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z" />
</vector>

View File

@ -0,0 +1,8 @@
<!-- drawable/pin_outline.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:width="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path android:fillColor="#000" android:pathData="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12M8.8,14L10,12.8V4H14V12.8L15.2,14H8.8Z" />
</vector>

View File

@ -6,6 +6,45 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<FrameLayout
android:id="@+id/right_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/red_error"
android:visibility="gone">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/close_right"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end|center"
android:gravity="center"
android:layout_marginEnd="21dp"
android:contentDescription="@string/cancel"
android:src="@drawable/ic_close_white_24dp"
android:text="@string/hide"
android:textColor="@color/md_white_1000" />
</FrameLayout>
<FrameLayout
android:id="@+id/left_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/red_error">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/close_left"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start|center"
android:layout_marginStart="21dp"
android:gravity="center"
android:src="@drawable/ic_close_white_24dp"
android:text="@string/hide"
android:textColor="@color/md_white_1000" />
</FrameLayout>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/card" android:id="@+id/card"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -43,14 +82,26 @@
android:id="@+id/source_latest" android:id="@+id/source_latest"
style="@style/Theme.Widget.Button.TextButton" style="@style/Theme.Widget.Button.TextButton"
android:textColor="?colorAccent" android:textColor="?colorAccent"
android:layout_marginEnd="12dp" android:layout_marginEnd="4dp"
android:textAllCaps="false" android:textAllCaps="false"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:letterSpacing="0.0" android:letterSpacing="0.0"
android:text="@string/view_latest" android:text="@string/view_latest"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintEnd_toStartOf="@id/source_pin"/>
<ImageButton
android:id="@+id/source_pin"
android:layout_marginEnd="8dp"
style="@style/Theme.Widget.CustomImageButton"
android:src="@drawable/ic_pin_24dp"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="10dp"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout> </FrameLayout>

View File

@ -213,6 +213,7 @@
<string name="search_extensions">Search extensions…</string> <string name="search_extensions">Search extensions…</string>
<string name="all_sources">All sources</string> <string name="all_sources">All sources</string>
<string name="sources">Sources</string> <string name="sources">Sources</string>
<string name="source_hidden">Source hidden</string>
<!-- Other Screens --> <!-- Other Screens -->