Multiple sources selection
This commit is contained in:
@@ -18,6 +18,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.db.TABLE_HISTORY
|
import org.koitharu.kotatsu.core.db.TABLE_HISTORY
|
||||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||||
@@ -150,7 +151,7 @@ class AppShortcutManager @Inject constructor(
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun buildShortcutInfo(source: MangaSource): ShortcutInfoCompat {
|
private suspend fun buildShortcutInfo(source: MangaSource): ShortcutInfoCompat = withContext(Dispatchers.Default) {
|
||||||
val icon = runCatchingCancellable {
|
val icon = runCatchingCancellable {
|
||||||
coil.execute(
|
coil.execute(
|
||||||
ImageRequest.Builder(context)
|
ImageRequest.Builder(context)
|
||||||
@@ -163,7 +164,7 @@ class AppShortcutManager @Inject constructor(
|
|||||||
onSuccess = { IconCompat.createWithAdaptiveBitmap(it) },
|
onSuccess = { IconCompat.createWithAdaptiveBitmap(it) },
|
||||||
onFailure = { IconCompat.createWithResource(context, R.drawable.ic_shortcut_default) },
|
onFailure = { IconCompat.createWithResource(context, R.drawable.ic_shortcut_default) },
|
||||||
)
|
)
|
||||||
return ShortcutInfoCompat.Builder(context, source.name)
|
ShortcutInfoCompat.Builder(context, source.name)
|
||||||
.setShortLabel(source.title)
|
.setShortLabel(source.title)
|
||||||
.setLongLabel(source.title)
|
.setLongLabel(source.title)
|
||||||
.setIcon(icon)
|
.setIcon(icon)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.graphics.Paint
|
|||||||
import android.graphics.RectF
|
import android.graphics.RectF
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
@@ -19,7 +20,10 @@ import com.google.android.material.R as materialR
|
|||||||
class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {
|
class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {
|
||||||
|
|
||||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||||
private val defaultRadius = context.resources.getDimension(materialR.dimen.abc_control_corner_material)
|
private val radius = context.resources.getDimension(materialR.dimen.abc_control_corner_material)
|
||||||
|
private val checkIcon = ContextCompat.getDrawable(context, materialR.drawable.ic_mtrl_checked_circle)
|
||||||
|
private val iconOffset = context.resources.getDimensionPixelOffset(R.dimen.chapter_check_offset)
|
||||||
|
private val iconSize = context.resources.getDimensionPixelOffset(R.dimen.chapter_check_size)
|
||||||
private val strokeColor = context.getThemeColor(materialR.attr.colorPrimary, Color.RED)
|
private val strokeColor = context.getThemeColor(materialR.attr.colorPrimary, Color.RED)
|
||||||
private val fillColor = ColorUtils.setAlphaComponent(
|
private val fillColor = ColorUtils.setAlphaComponent(
|
||||||
ColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f),
|
ColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f),
|
||||||
@@ -32,11 +36,12 @@ class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecor
|
|||||||
98,
|
98,
|
||||||
)
|
)
|
||||||
paint.style = Paint.Style.FILL
|
paint.style = Paint.Style.FILL
|
||||||
hasBackground = false
|
hasBackground = true
|
||||||
hasForeground = true
|
hasForeground = true
|
||||||
isIncludeDecorAndMargins = false
|
isIncludeDecorAndMargins = false
|
||||||
|
|
||||||
paint.strokeWidth = context.resources.getDimension(R.dimen.selection_stroke_width)
|
paint.strokeWidth = context.resources.getDimension(R.dimen.selection_stroke_width)
|
||||||
|
checkIcon?.setTint(strokeColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemId(parent: RecyclerView, child: View): Long {
|
override fun getItemId(parent: RecyclerView, child: View): Long {
|
||||||
@@ -45,6 +50,19 @@ class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecor
|
|||||||
return item.chapter.id
|
return item.chapter.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onDrawBackground(
|
||||||
|
canvas: Canvas,
|
||||||
|
parent: RecyclerView,
|
||||||
|
child: View,
|
||||||
|
bounds: RectF,
|
||||||
|
state: RecyclerView.State,
|
||||||
|
) {
|
||||||
|
if (child is CardView) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
canvas.drawRoundRect(bounds, radius, radius, paint)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDrawForeground(
|
override fun onDrawForeground(
|
||||||
canvas: Canvas,
|
canvas: Canvas,
|
||||||
parent: RecyclerView,
|
parent: RecyclerView,
|
||||||
@@ -52,16 +70,24 @@ class ChaptersSelectionDecoration(context: Context) : AbstractSelectionItemDecor
|
|||||||
bounds: RectF,
|
bounds: RectF,
|
||||||
state: RecyclerView.State
|
state: RecyclerView.State
|
||||||
) {
|
) {
|
||||||
val radius = if (child is CardView) {
|
if (child !is CardView) {
|
||||||
child.radius
|
return
|
||||||
} else {
|
|
||||||
defaultRadius
|
|
||||||
}
|
}
|
||||||
|
val radius = child.radius
|
||||||
paint.color = fillColor
|
paint.color = fillColor
|
||||||
paint.style = Paint.Style.FILL
|
paint.style = Paint.Style.FILL
|
||||||
canvas.drawRoundRect(bounds, radius, radius, paint)
|
canvas.drawRoundRect(bounds, radius, radius, paint)
|
||||||
paint.color = strokeColor
|
paint.color = strokeColor
|
||||||
paint.style = Paint.Style.STROKE
|
paint.style = Paint.Style.STROKE
|
||||||
canvas.drawRoundRect(bounds, radius, radius, paint)
|
canvas.drawRoundRect(bounds, radius, radius, paint)
|
||||||
|
checkIcon?.run {
|
||||||
|
setBounds(
|
||||||
|
(bounds.right - iconSize - iconOffset).toInt(),
|
||||||
|
(bounds.top + iconOffset).toInt(),
|
||||||
|
(bounds.right - iconOffset).toInt(),
|
||||||
|
(bounds.top + iconOffset + iconSize).toInt(),
|
||||||
|
)
|
||||||
|
draw(canvas)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,10 +97,10 @@ class MangaSourcesRepository @Inject constructor(
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun setSourceEnabled(source: MangaSource, isEnabled: Boolean): ReversibleHandle {
|
suspend fun setSourcesEnabled(sources: Collection<MangaSource>, isEnabled: Boolean): ReversibleHandle {
|
||||||
dao.setEnabled(source.name, isEnabled)
|
setSourcesEnabledImpl(sources, isEnabled)
|
||||||
return ReversibleHandle {
|
return ReversibleHandle {
|
||||||
dao.setEnabled(source.name, !isEnabled)
|
setSourcesEnabledImpl(sources, !isEnabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,6 +171,18 @@ class MangaSourcesRepository @Inject constructor(
|
|||||||
return dao.findAll().isEmpty()
|
return dao.findAll().isEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun setSourcesEnabledImpl(sources: Collection<MangaSource>, isEnabled: Boolean) {
|
||||||
|
if (sources.size == 1) { // fast path
|
||||||
|
dao.setEnabled(sources.first().name, isEnabled)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
db.withTransaction {
|
||||||
|
for (source in sources) {
|
||||||
|
dao.setEnabled(source.name, isEnabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getNewSources(): MutableSet<MangaSource> {
|
private suspend fun getNewSources(): MutableSet<MangaSource> {
|
||||||
val entities = dao.findAll()
|
val entities = dao.findAll()
|
||||||
val result = EnumSet.copyOf(remoteSources)
|
val result = EnumSet.copyOf(remoteSources)
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import android.content.DialogInterface
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.view.ActionMode
|
||||||
import androidx.core.content.pm.ShortcutManagerCompat
|
|
||||||
import androidx.core.graphics.Insets
|
import androidx.core.graphics.Insets
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
@@ -24,12 +24,14 @@ import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver
|
|||||||
import org.koitharu.kotatsu.core.os.AppShortcutManager
|
import org.koitharu.kotatsu.core.os.AppShortcutManager
|
||||||
import org.koitharu.kotatsu.core.ui.BaseFragment
|
import org.koitharu.kotatsu.core.ui.BaseFragment
|
||||||
import org.koitharu.kotatsu.core.ui.dialog.TwoButtonsAlertDialog
|
import org.koitharu.kotatsu.core.ui.dialog.TwoButtonsAlertDialog
|
||||||
|
import org.koitharu.kotatsu.core.ui.list.ListSelectionController
|
||||||
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
|
||||||
import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
|
import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner
|
||||||
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
|
import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver
|
||||||
import org.koitharu.kotatsu.core.ui.util.SpanSizeResolver
|
import org.koitharu.kotatsu.core.ui.util.SpanSizeResolver
|
||||||
import org.koitharu.kotatsu.core.ui.widgets.TipView
|
import org.koitharu.kotatsu.core.ui.widgets.TipView
|
||||||
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
|
import org.koitharu.kotatsu.core.util.ext.addMenuProvider
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.findAppCompatDelegate
|
||||||
import org.koitharu.kotatsu.core.util.ext.observe
|
import org.koitharu.kotatsu.core.util.ext.observe
|
||||||
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
import org.koitharu.kotatsu.core.util.ext.observeEvent
|
||||||
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
||||||
@@ -44,6 +46,7 @@ import org.koitharu.kotatsu.list.ui.model.ListHeader
|
|||||||
import org.koitharu.kotatsu.list.ui.model.TipModel
|
import org.koitharu.kotatsu.list.ui.model.TipModel
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.parsers.util.mapNotNullToSet
|
||||||
import org.koitharu.kotatsu.search.ui.MangaListActivity
|
import org.koitharu.kotatsu.search.ui.MangaListActivity
|
||||||
import org.koitharu.kotatsu.settings.SettingsActivity
|
import org.koitharu.kotatsu.settings.SettingsActivity
|
||||||
import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment
|
import org.koitharu.kotatsu.settings.newsources.NewSourcesDialogFragment
|
||||||
@@ -56,7 +59,7 @@ class ExploreFragment :
|
|||||||
BaseFragment<FragmentExploreBinding>(),
|
BaseFragment<FragmentExploreBinding>(),
|
||||||
RecyclerViewOwner,
|
RecyclerViewOwner,
|
||||||
ExploreListEventListener,
|
ExploreListEventListener,
|
||||||
OnListItemClickListener<MangaSourceItem>, TipView.OnButtonClickListener {
|
OnListItemClickListener<MangaSourceItem>, TipView.OnButtonClickListener, ListSelectionController.Callback2 {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var coil: ImageLoader
|
lateinit var coil: ImageLoader
|
||||||
@@ -66,6 +69,7 @@ class ExploreFragment :
|
|||||||
|
|
||||||
private val viewModel by viewModels<ExploreViewModel>()
|
private val viewModel by viewModels<ExploreViewModel>()
|
||||||
private var exploreAdapter: ExploreAdapter? = null
|
private var exploreAdapter: ExploreAdapter? = null
|
||||||
|
private var sourceSelectionController: ListSelectionController? = null
|
||||||
|
|
||||||
override val recyclerView: RecyclerView
|
override val recyclerView: RecyclerView
|
||||||
get() = requireViewBinding().recyclerView
|
get() = requireViewBinding().recyclerView
|
||||||
@@ -79,11 +83,18 @@ class ExploreFragment :
|
|||||||
exploreAdapter = ExploreAdapter(coil, viewLifecycleOwner, this, this, this) { manga, view ->
|
exploreAdapter = ExploreAdapter(coil, viewLifecycleOwner, this, this, this) { manga, view ->
|
||||||
startActivity(DetailsActivity.newIntent(view.context, manga))
|
startActivity(DetailsActivity.newIntent(view.context, manga))
|
||||||
}
|
}
|
||||||
|
sourceSelectionController = ListSelectionController(
|
||||||
|
appCompatDelegate = checkNotNull(findAppCompatDelegate()),
|
||||||
|
decoration = SourceSelectionDecoration(binding.root.context),
|
||||||
|
registryOwner = this,
|
||||||
|
callback = this,
|
||||||
|
)
|
||||||
with(binding.recyclerView) {
|
with(binding.recyclerView) {
|
||||||
adapter = exploreAdapter
|
adapter = exploreAdapter
|
||||||
setHasFixedSize(true)
|
setHasFixedSize(true)
|
||||||
SpanSizeResolver(this, resources.getDimensionPixelSize(R.dimen.explore_grid_width)).attach()
|
SpanSizeResolver(this, resources.getDimensionPixelSize(R.dimen.explore_grid_width)).attach()
|
||||||
addItemDecoration(TypedListSpacingDecoration(context, false))
|
addItemDecoration(TypedListSpacingDecoration(context, false))
|
||||||
|
checkNotNull(sourceSelectionController).attachToRecyclerView(this)
|
||||||
}
|
}
|
||||||
addMenuProvider(ExploreMenuProvider(binding.root.context))
|
addMenuProvider(ExploreMenuProvider(binding.root.context))
|
||||||
viewModel.content.observe(viewLifecycleOwner) {
|
viewModel.content.observe(viewLifecycleOwner) {
|
||||||
@@ -100,6 +111,7 @@ class ExploreFragment :
|
|||||||
|
|
||||||
override fun onDestroyView() {
|
override fun onDestroyView() {
|
||||||
super.onDestroyView()
|
super.onDestroyView()
|
||||||
|
sourceSelectionController = null
|
||||||
exploreAdapter = null
|
exploreAdapter = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,18 +159,15 @@ class ExploreFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemClick(item: MangaSourceItem, view: View) {
|
override fun onItemClick(item: MangaSourceItem, view: View) {
|
||||||
|
if (sourceSelectionController?.onItemClick(item.id) == true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
val intent = MangaListActivity.newIntent(view.context, item.source)
|
val intent = MangaListActivity.newIntent(view.context, item.source)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemLongClick(item: MangaSourceItem, view: View): Boolean {
|
override fun onItemLongClick(item: MangaSourceItem, view: View): Boolean {
|
||||||
val menu = PopupMenu(view.context, view)
|
return sourceSelectionController?.onItemLongClick(item.id) ?: false
|
||||||
menu.inflate(R.menu.popup_source)
|
|
||||||
menu.menu.findItem(R.id.action_shortcut)
|
|
||||||
?.isVisible = ShortcutManagerCompat.isRequestPinShortcutSupported(view.context)
|
|
||||||
menu.setOnMenuItemClickListener(SourceMenuListener(item))
|
|
||||||
menu.show()
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRetryClick(error: Throwable) = Unit
|
override fun onRetryClick(error: Throwable) = Unit
|
||||||
@@ -167,6 +176,54 @@ class ExploreFragment :
|
|||||||
startActivity(Intent(context ?: return, SourcesCatalogActivity::class.java))
|
startActivity(Intent(context ?: return, SourcesCatalogActivity::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSelectionChanged(controller: ListSelectionController, count: Int) {
|
||||||
|
viewBinding?.recyclerView?.invalidateItemDecorations()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
|
||||||
|
mode.menuInflater.inflate(R.menu.mode_source, menu)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode, menu: Menu): Boolean {
|
||||||
|
val isSingleSelection = controller.count == 1
|
||||||
|
menu.findItem(R.id.action_settings).isVisible = isSingleSelection
|
||||||
|
menu.findItem(R.id.action_shortcut).isVisible = isSingleSelection
|
||||||
|
return super.onPrepareActionMode(controller, mode, menu)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActionItemClicked(controller: ListSelectionController, mode: ActionMode, item: MenuItem): Boolean {
|
||||||
|
val selectedSources = controller.peekCheckedIds().mapNotNullToSet { id ->
|
||||||
|
MangaSource.entries.getOrNull(id.toInt())
|
||||||
|
}
|
||||||
|
if (selectedSources.isEmpty()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
when (item.itemId) {
|
||||||
|
R.id.action_settings -> {
|
||||||
|
val source = selectedSources.singleOrNull() ?: return false
|
||||||
|
startActivity(SettingsActivity.newSourceSettingsIntent(requireContext(), source))
|
||||||
|
mode.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.action_disable -> {
|
||||||
|
viewModel.disableSources(selectedSources)
|
||||||
|
mode.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
R.id.action_shortcut -> {
|
||||||
|
val source = selectedSources.singleOrNull() ?: return false
|
||||||
|
viewLifecycleScope.launch {
|
||||||
|
shortcutManager.requestPinShortcut(source)
|
||||||
|
}
|
||||||
|
mode.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private fun onOpenManga(manga: Manga) {
|
private fun onOpenManga(manga: Manga) {
|
||||||
val intent = DetailsActivity.newIntent(context ?: return, manga)
|
val intent = DetailsActivity.newIntent(context ?: return, manga)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
@@ -194,30 +251,4 @@ class ExploreFragment :
|
|||||||
.create()
|
.create()
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class SourceMenuListener(
|
|
||||||
private val sourceItem: MangaSourceItem,
|
|
||||||
) : PopupMenu.OnMenuItemClickListener {
|
|
||||||
|
|
||||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
|
||||||
when (item.itemId) {
|
|
||||||
R.id.action_settings -> {
|
|
||||||
startActivity(SettingsActivity.newSourceSettingsIntent(requireContext(), sourceItem.source))
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.action_disable -> {
|
|
||||||
viewModel.disableSource(sourceItem.source)
|
|
||||||
}
|
|
||||||
|
|
||||||
R.id.action_shortcut -> {
|
|
||||||
viewLifecycleScope.launch {
|
|
||||||
shortcutManager.requestPinShortcut(sourceItem.source)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,10 +92,11 @@ class ExploreViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disableSource(source: MangaSource) {
|
fun disableSources(sources: Collection<MangaSource>) {
|
||||||
launchJob(Dispatchers.Default) {
|
launchJob(Dispatchers.Default) {
|
||||||
val rollback = sourcesRepository.setSourceEnabled(source, isEnabled = false)
|
val rollback = sourcesRepository.setSourcesEnabled(sources, isEnabled = false)
|
||||||
onActionDone.call(ReversibleAction(R.string.source_disabled, rollback))
|
val message = if (sources.size == 1) R.string.source_disabled else R.string.sources_disabled
|
||||||
|
onActionDone.call(ReversibleAction(message, rollback))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package org.koitharu.kotatsu.explore.ui
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.RectF
|
||||||
|
import android.view.View
|
||||||
|
import androidx.core.graphics.ColorUtils
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import androidx.recyclerview.widget.RecyclerView.NO_ID
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.ui.list.decor.AbstractSelectionItemDecoration
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.getItem
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||||
|
import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem
|
||||||
|
import com.google.android.material.R as materialR
|
||||||
|
|
||||||
|
class SourceSelectionDecoration(context: Context) : AbstractSelectionItemDecoration() {
|
||||||
|
|
||||||
|
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
||||||
|
private val strokeColor = context.getThemeColor(materialR.attr.colorPrimary, Color.RED)
|
||||||
|
private val fillColor = ColorUtils.setAlphaComponent(
|
||||||
|
ColorUtils.blendARGB(strokeColor, context.getThemeColor(materialR.attr.colorSurface), 0.8f),
|
||||||
|
0x74,
|
||||||
|
)
|
||||||
|
private val defaultRadius = context.resources.getDimension(R.dimen.list_selector_corner)
|
||||||
|
|
||||||
|
init {
|
||||||
|
hasBackground = false
|
||||||
|
hasForeground = true
|
||||||
|
isIncludeDecorAndMargins = false
|
||||||
|
paint.strokeWidth = context.resources.getDimension(R.dimen.selection_stroke_width)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemId(parent: RecyclerView, child: View): Long {
|
||||||
|
val holder = parent.getChildViewHolder(child) ?: return NO_ID
|
||||||
|
val item = holder.getItem(MangaSourceItem::class.java) ?: return NO_ID
|
||||||
|
return item.id
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDrawForeground(
|
||||||
|
canvas: Canvas,
|
||||||
|
parent: RecyclerView,
|
||||||
|
child: View,
|
||||||
|
bounds: RectF,
|
||||||
|
state: RecyclerView.State,
|
||||||
|
) {
|
||||||
|
paint.color = fillColor
|
||||||
|
paint.style = Paint.Style.FILL
|
||||||
|
canvas.drawRoundRect(bounds, defaultRadius, defaultRadius, paint)
|
||||||
|
paint.color = strokeColor
|
||||||
|
paint.style = Paint.Style.STROKE
|
||||||
|
canvas.drawRoundRect(bounds, defaultRadius, defaultRadius, paint)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,9 @@ data class MangaSourceItem(
|
|||||||
val isGrid: Boolean,
|
val isGrid: Boolean,
|
||||||
) : ListModel {
|
) : ListModel {
|
||||||
|
|
||||||
|
val id: Long
|
||||||
|
get() = source.ordinal.toLong()
|
||||||
|
|
||||||
override fun areItemsTheSame(other: ListModel): Boolean {
|
override fun areItemsTheSame(other: ListModel): Boolean {
|
||||||
return other is MangaSourceItem && other.source == source
|
return other is MangaSourceItem && other.source == source
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ class SearchSuggestionViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun onSourceToggle(source: MangaSource, isEnabled: Boolean) {
|
fun onSourceToggle(source: MangaSource, isEnabled: Boolean) {
|
||||||
launchJob(Dispatchers.Default) {
|
launchJob(Dispatchers.Default) {
|
||||||
sourcesRepository.setSourceEnabled(source, isEnabled)
|
sourcesRepository.setSourcesEnabled(setOf(source), isEnabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class NewSourcesViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) {
|
fun onItemEnabledChanged(item: SourceConfigItem.SourceItem, isEnabled: Boolean) {
|
||||||
launchJob(Dispatchers.Default) {
|
launchJob(Dispatchers.Default) {
|
||||||
repository.setSourceEnabled(item.source, isEnabled)
|
repository.setSourcesEnabled(setOf(item.source), isEnabled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
|||||||
import org.koitharu.kotatsu.core.util.ext.call
|
import org.koitharu.kotatsu.core.util.ext.call
|
||||||
import org.koitharu.kotatsu.core.util.ext.require
|
import org.koitharu.kotatsu.core.util.ext.require
|
||||||
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||||
import org.koitharu.kotatsu.parsers.config.ConfigKey
|
|
||||||
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
import org.koitharu.kotatsu.parsers.exception.AuthRequiredException
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -75,7 +74,7 @@ class SourceSettingsViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun setEnabled(value: Boolean) {
|
fun setEnabled(value: Boolean) {
|
||||||
launchJob(Dispatchers.Default) {
|
launchJob(Dispatchers.Default) {
|
||||||
mangaSourcesRepository.setSourceEnabled(source, value)
|
mangaSourcesRepository.setSourcesEnabled(setOf(source), value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class SourcesCatalogViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun addSource(source: MangaSource) {
|
fun addSource(source: MangaSource) {
|
||||||
launchJob(Dispatchers.Default) {
|
launchJob(Dispatchers.Default) {
|
||||||
val rollback = repository.setSourceEnabled(source, true)
|
val rollback = repository.setSourcesEnabled(setOf(source), true)
|
||||||
onActionDone.call(ReversibleAction(R.string.source_enabled, rollback))
|
onActionDone.call(ReversibleAction(R.string.source_enabled, rollback))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ class SourcesManageViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun setEnabled(source: MangaSource, isEnabled: Boolean) {
|
fun setEnabled(source: MangaSource, isEnabled: Boolean) {
|
||||||
launchJob(Dispatchers.Default) {
|
launchJob(Dispatchers.Default) {
|
||||||
val rollback = repository.setSourceEnabled(source, isEnabled)
|
val rollback = repository.setSourcesEnabled(setOf(source), isEnabled)
|
||||||
if (!isEnabled) {
|
if (!isEnabled) {
|
||||||
onActionDone.call(ReversibleAction(R.string.source_disabled, rollback))
|
onActionDone.call(ReversibleAction(R.string.source_disabled, rollback))
|
||||||
}
|
}
|
||||||
|
|||||||
12
app/src/main/res/drawable/ic_pin.xml
Normal file
12
app/src/main/res/drawable/ic_pin.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12M8.8,14L10,12.8V4H14V12.8L15.2,14H8.8Z" />
|
||||||
|
</vector>
|
||||||
23
app/src/main/res/menu/mode_source.xml
Normal file
23
app/src/main/res/menu/mode_source.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_disable"
|
||||||
|
android:icon="@drawable/ic_eye_off"
|
||||||
|
android:title="@string/disable"
|
||||||
|
app:showAsAction="ifRoom|withText" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_shortcut"
|
||||||
|
android:icon="@drawable/ic_pin"
|
||||||
|
android:title="@string/create_shortcut"
|
||||||
|
app:showAsAction="ifRoom|withText" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_settings"
|
||||||
|
android:icon="@drawable/ic_settings"
|
||||||
|
android:title="@string/settings"
|
||||||
|
app:showAsAction="ifRoom|withText" />
|
||||||
|
</menu>
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<menu
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_settings"
|
|
||||||
android:title="@string/settings" />
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_shortcut"
|
|
||||||
android:title="@string/create_shortcut" />
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_disable"
|
|
||||||
android:title="@string/disable" />
|
|
||||||
|
|
||||||
</menu>
|
|
||||||
@@ -642,4 +642,5 @@
|
|||||||
<string name="authors">Authors</string>
|
<string name="authors">Authors</string>
|
||||||
<string name="blocked_by_server_message">You are blocked by the server. Try to use a different network connection (VPN, Proxy, etc.)</string>
|
<string name="blocked_by_server_message">You are blocked by the server. Try to use a different network connection (VPN, Proxy, etc.)</string>
|
||||||
<string name="disable">Disable</string>
|
<string name="disable">Disable</string>
|
||||||
|
<string name="sources_disabled">Sources disabled</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user