Improve mouse interaction

This commit is contained in:
Koitharu
2023-09-14 11:20:22 +03:00
parent f4313525c2
commit d872044252
22 changed files with 307 additions and 64 deletions

View File

@@ -104,6 +104,7 @@ sealed class AdaptiveSheetBehavior {
companion object {
const val STATE_EXPANDED = SideSheetBehavior.STATE_EXPANDED
const val STATE_COLLAPSED = BottomSheetBehavior.STATE_COLLAPSED
const val STATE_SETTLING = SideSheetBehavior.STATE_SETTLING
const val STATE_DRAGGING = SideSheetBehavior.STATE_DRAGGING
const val STATE_HIDDEN = SideSheetBehavior.STATE_HIDDEN
@@ -114,10 +115,11 @@ sealed class AdaptiveSheetBehavior {
else -> null
}
fun from(lp: CoordinatorLayout.LayoutParams): AdaptiveSheetBehavior? = when (val behavior = lp.behavior) {
is BottomSheetBehavior<*> -> Bottom(behavior)
is SideSheetBehavior<*> -> Side(behavior)
else -> null
}
fun from(lp: CoordinatorLayout.LayoutParams): AdaptiveSheetBehavior? =
when (val behavior = lp.behavior) {
is BottomSheetBehavior<*> -> Bottom(behavior)
is SideSheetBehavior<*> -> Side(behavior)
else -> null
}
}
}

View File

@@ -2,7 +2,9 @@ package org.koitharu.kotatsu.core.ui.sheet
import android.content.Context
import android.util.AttributeSet
import android.view.InputDevice
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.widget.LinearLayout
import androidx.annotation.AttrRes
@@ -21,7 +23,8 @@ class AdaptiveSheetHeaderBar @JvmOverloads constructor(
@AttrRes defStyleAttr: Int = 0,
) : LinearLayout(context, attrs, defStyleAttr), AdaptiveSheetCallback {
private val binding = LayoutSheetHeaderAdaptiveBinding.inflate(LayoutInflater.from(context), this)
private val binding =
LayoutSheetHeaderAdaptiveBinding.inflate(LayoutInflater.from(context), this)
private var sheetBehavior: AdaptiveSheetBehavior? = null
var title: CharSequence?
@@ -60,6 +63,28 @@ class AdaptiveSheetHeaderBar @JvmOverloads constructor(
super.onDetachedFromWindow()
}
override fun onGenericMotionEvent(event: MotionEvent): Boolean {
val behavior = sheetBehavior ?: return super.onGenericMotionEvent(event)
if (event.source and InputDevice.SOURCE_CLASS_POINTER != 0) {
if (event.actionMasked == MotionEvent.ACTION_SCROLL) {
if (event.getAxisValue(MotionEvent.AXIS_VSCROLL) < 0f) {
behavior.state = if (
behavior is AdaptiveSheetBehavior.Bottom
&& behavior.state == AdaptiveSheetBehavior.STATE_EXPANDED
) {
AdaptiveSheetBehavior.STATE_COLLAPSED
} else {
AdaptiveSheetBehavior.STATE_HIDDEN
}
} else {
behavior.state = AdaptiveSheetBehavior.STATE_EXPANDED
}
return true
}
}
return super.onGenericMotionEvent(event)
}
override fun onStateChanged(sheet: View, newState: Int) {
}

View File

@@ -2,6 +2,7 @@ package org.koitharu.kotatsu.core.util.ext
import android.app.Activity
import android.graphics.Rect
import android.os.Build
import android.view.View
import android.view.View.MeasureSpec
import android.view.ViewGroup
@@ -140,3 +141,9 @@ fun BaseProgressIndicator<*>.showOrHide(value: Boolean) {
if (isVisible) hide()
}
}
fun View.setOnContextClickListenerCompat(listener: View.OnLongClickListener) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
setOnContextClickListener(listener::onLongClick)
}
}

View File

@@ -1,5 +1,7 @@
package org.koitharu.kotatsu.details.ui
import android.view.InputDevice
import android.view.MotionEvent
import android.view.View
import android.view.View.OnLayoutChangeListener
import androidx.activity.OnBackPressedCallback
@@ -12,7 +14,7 @@ class ChaptersBottomSheetMediator(
private val behavior: BottomSheetBehavior<*>,
) : OnBackPressedCallback(false),
ActionModeListener,
OnLayoutChangeListener {
OnLayoutChangeListener, View.OnGenericMotionListener {
private var lockCounter = 0
@@ -55,6 +57,20 @@ class ChaptersBottomSheetMediator(
}
}
override fun onGenericMotion(v: View?, event: MotionEvent): Boolean {
if (event.source and InputDevice.SOURCE_CLASS_POINTER != 0) {
if (event.actionMasked == MotionEvent.ACTION_SCROLL) {
if (event.getAxisValue(MotionEvent.AXIS_VSCROLL) < 0f) {
behavior.state = BottomSheetBehavior.STATE_COLLAPSED
} else {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
return true
}
}
return false
}
fun lock() {
lockCounter++
behavior.isDraggable = lockCounter <= 0

View File

@@ -49,6 +49,7 @@ import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.setNavigationBarTransparentCompat
import org.koitharu.kotatsu.core.util.ext.setNavigationIconSafe
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ActivityDetailsBinding
import org.koitharu.kotatsu.details.service.MangaPrefetchService
@@ -89,6 +90,7 @@ class DetailsActivity :
}
viewBinding.buttonRead.setOnClickListener(this)
viewBinding.buttonRead.setOnLongClickListener(this)
viewBinding.buttonRead.setOnContextClickListenerCompat(this)
viewBinding.buttonDropdown.setOnClickListener(this)
viewBadge = ViewBadge(viewBinding.buttonRead, this)
@@ -103,6 +105,7 @@ class DetailsActivity :
viewBinding.toolbarChapters?.setNavigationOnClickListener {
behavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
viewBinding.toolbarChapters?.setOnGenericMotionListener(bsMediator)
} else {
chaptersMenuProvider = ChaptersMenuProvider(viewModel, null)
addMenuProvider(chaptersMenuProvider)
@@ -134,13 +137,19 @@ class DetailsActivity :
viewBinding.toolbarChapters?.subtitle = it
viewBinding.textViewSubtitle?.textAndVisible = it
}
viewModel.isChaptersReversed.observe(this, MenuInvalidator(viewBinding.toolbarChapters ?: this))
viewModel.isChaptersReversed.observe(
this,
MenuInvalidator(viewBinding.toolbarChapters ?: this)
)
viewModel.favouriteCategories.observe(this, MenuInvalidator(this))
viewModel.branches.observe(this) {
viewBinding.buttonDropdown.isVisible = it.size > 1
}
viewModel.chapters.observe(this, PrefetchObserver(this))
viewModel.onDownloadStarted.observeEvent(this, DownloadStartedObserver(viewBinding.containerDetails))
viewModel.onDownloadStarted.observeEvent(
this,
DownloadStartedObserver(viewBinding.containerDetails)
)
addMenuProvider(
DetailsMenuProvider(
@@ -243,7 +252,11 @@ class DetailsActivity :
right = insets.right,
)
if (insets.bottom > 0) {
window.setNavigationBarTransparentCompat(this, viewBinding.layoutBottom?.elevation ?: 0f, 0.9f)
window.setNavigationBarTransparentCompat(
this,
viewBinding.layoutBottom?.elevation ?: 0f,
0.9f
)
}
viewBinding.cardChapters?.updateLayoutParams<MarginLayoutParams> {
bottomMargin = insets.bottom + marginEnd
@@ -265,9 +278,18 @@ class DetailsActivity :
}
val text = when {
!info.isValid -> getString(R.string.loading_)
info.currentChapter >= 0 -> getString(R.string.chapter_d_of_d, info.currentChapter + 1, info.totalChapters)
info.currentChapter >= 0 -> getString(
R.string.chapter_d_of_d,
info.currentChapter + 1,
info.totalChapters
)
info.totalChapters == 0 -> getString(R.string.no_chapters)
else -> resources.getQuantityString(R.plurals.chapters, info.totalChapters, info.totalChapters)
else -> resources.getQuantityString(
R.plurals.chapters,
info.totalChapters,
info.totalChapters
)
}
viewBinding.toolbarChapters?.title = text
viewBinding.textViewTitle?.text = text
@@ -286,7 +308,12 @@ class DetailsActivity :
append(' ')
append(' ')
inSpans(
ForegroundColorSpan(v.context.getThemeColor(android.R.attr.textColorSecondary, Color.LTGRAY)),
ForegroundColorSpan(
v.context.getThemeColor(
android.R.attr.textColorSecondary,
Color.LTGRAY
)
),
RelativeSizeSpan(0.74f),
) {
append(branch.count.toString())
@@ -305,7 +332,8 @@ class DetailsActivity :
val manga = viewModel.manga.value ?: return
val chapterId = viewModel.historyInfo.value.history?.chapterId
if (chapterId != null && manga.chapters?.none { x -> x.id == chapterId } == true) {
val snackbar = makeSnackbar(getString(R.string.chapter_is_missing), Snackbar.LENGTH_SHORT)
val snackbar =
makeSnackbar(getString(R.string.chapter_is_missing), Snackbar.LENGTH_SHORT)
snackbar.show()
} else {
startActivity(
@@ -331,7 +359,10 @@ class DetailsActivity :
view.isVisible = isVisible
}
private fun makeSnackbar(text: CharSequence, @BaseTransientBottomBar.Duration duration: Int): Snackbar {
private fun makeSnackbar(
text: CharSequence,
@BaseTransientBottomBar.Duration duration: Int,
): Snackbar {
val sb = Snackbar.make(viewBinding.containerDetails, text, duration)
if (viewBinding.layoutBottom?.isVisible == true) {
sb.anchorView = viewBinding.toolbarChapters

View File

@@ -16,6 +16,7 @@ import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.getThemeColor
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.resolveDp
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ItemExploreButtonsBinding
@@ -44,7 +45,12 @@ fun exploreButtonsAD(
if (item.isRandomLoading) {
val icon = CircularProgressDrawable(context)
icon.strokeWidth = context.resources.resolveDp(2f)
icon.setColorSchemeColors(context.getThemeColor(materialR.attr.colorPrimary, Color.DKGRAY))
icon.setColorSchemeColors(
context.getThemeColor(
materialR.attr.colorPrimary,
Color.DKGRAY
)
)
binding.buttonRandom.icon = icon
icon.start()
} else {
@@ -88,7 +94,13 @@ fun exploreSourceListItemAD(
listener: OnListItemClickListener<MangaSourceItem>,
lifecycleOwner: LifecycleOwner,
) = adapterDelegateViewBinding<MangaSourceItem, ListModel, ItemExploreSourceListBinding>(
{ layoutInflater, parent -> ItemExploreSourceListBinding.inflate(layoutInflater, parent, false) },
{ layoutInflater, parent ->
ItemExploreSourceListBinding.inflate(
layoutInflater,
parent,
false
)
},
on = { item, _, _ -> item is MangaSourceItem && !item.isGrid },
) {
@@ -96,6 +108,7 @@ fun exploreSourceListItemAD(
binding.root.setOnClickListener(eventListener)
binding.root.setOnLongClickListener(eventListener)
binding.root.setOnContextClickListenerCompat(eventListener)
bind {
binding.textViewTitle.text = item.source.title
@@ -115,7 +128,13 @@ fun exploreSourceGridItemAD(
listener: OnListItemClickListener<MangaSourceItem>,
lifecycleOwner: LifecycleOwner,
) = adapterDelegateViewBinding<MangaSourceItem, ListModel, ItemExploreSourceGridBinding>(
{ layoutInflater, parent -> ItemExploreSourceGridBinding.inflate(layoutInflater, parent, false) },
{ layoutInflater, parent ->
ItemExploreSourceGridBinding.inflate(
layoutInflater,
parent,
false
)
},
on = { item, _, _ -> item is MangaSourceItem && item.isGrid },
) {
@@ -123,6 +142,7 @@ fun exploreSourceGridItemAD(
binding.root.setOnClickListener(eventListener)
binding.root.setOnLongClickListener(eventListener)
binding.root.setOnContextClickListenerCompat(eventListener)
bind {
binding.textViewTitle.text = item.source.title

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.list.ui.adapter
import android.view.View
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import com.google.android.material.badge.BadgeDrawable
@@ -10,6 +11,7 @@ import org.koitharu.kotatsu.core.ui.image.TrimTransformation
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.databinding.ItemMangaGridBinding
import org.koitharu.kotatsu.list.ui.ListModelDiffCallback
@@ -28,12 +30,13 @@ fun mangaGridItemAD(
) {
var badge: BadgeDrawable? = null
itemView.setOnClickListener {
clickListener.onItemClick(item.manga, it)
}
itemView.setOnLongClickListener {
clickListener.onItemLongClick(item.manga, it)
val eventListener = object : View.OnClickListener, View.OnLongClickListener {
override fun onClick(v: View) = clickListener.onItemClick(item.manga, v)
override fun onLongClick(v: View): Boolean = clickListener.onItemLongClick(item.manga, v)
}
itemView.setOnClickListener(eventListener)
itemView.setOnLongClickListener(eventListener)
itemView.setOnContextClickListenerCompat(eventListener)
sizeResolver.attachToView(lifecycleOwner, itemView, binding.textViewTitle, binding.progressView)
bind { payloads ->

View File

@@ -13,6 +13,7 @@ import org.koitharu.kotatsu.core.ui.image.TrimTransformation
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ItemMangaListDetailsBinding
@@ -45,6 +46,7 @@ fun mangaListDetailedItemAD(
}
itemView.setOnClickListener(listenerAdapter)
itemView.setOnLongClickListener(listenerAdapter)
itemView.setOnContextClickListenerCompat(listenerAdapter)
binding.buttonRead.setOnClickListener(listenerAdapter)
binding.chipsTags.onChipClickListener = listenerAdapter

View File

@@ -1,5 +1,6 @@
package org.koitharu.kotatsu.list.ui.adapter
import android.view.View
import androidx.lifecycle.LifecycleOwner
import coil.ImageLoader
import com.google.android.material.badge.BadgeDrawable
@@ -9,6 +10,7 @@ import org.koitharu.kotatsu.core.ui.image.TrimTransformation
import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.core.util.ext.enqueueWith
import org.koitharu.kotatsu.core.util.ext.newImageRequest
import org.koitharu.kotatsu.core.util.ext.setOnContextClickListenerCompat
import org.koitharu.kotatsu.core.util.ext.source
import org.koitharu.kotatsu.core.util.ext.textAndVisible
import org.koitharu.kotatsu.databinding.ItemMangaListBinding
@@ -25,12 +27,13 @@ fun mangaListItemAD(
) {
var badge: BadgeDrawable? = null
itemView.setOnClickListener {
clickListener.onItemClick(item.manga, it)
}
itemView.setOnLongClickListener {
clickListener.onItemLongClick(item.manga, it)
val eventListener = object : View.OnClickListener, View.OnLongClickListener {
override fun onClick(v: View) = clickListener.onItemClick(item.manga, v)
override fun onLongClick(v: View): Boolean = clickListener.onItemLongClick(item.manga, v)
}
itemView.setOnClickListener(eventListener)
itemView.setOnLongClickListener(eventListener)
itemView.setOnContextClickListenerCompat(eventListener)
bind {
binding.textViewTitle.text = item.title

View File

@@ -376,9 +376,14 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
val padding = if (isOpened) 0 else resources.getDimensionPixelOffset(R.dimen.margin_normal)
viewBinding.appbar.updatePadding(left = padding, right = padding)
adjustFabVisibility(isSearchOpened = isOpened)
supportActionBar?.setHomeAsUpIndicator(
if (isOpened) materialR.drawable.abc_ic_ab_back_material else materialR.drawable.abc_ic_search_api_material,
)
supportActionBar?.apply {
setHomeAsUpIndicator(
if (isOpened) materialR.drawable.abc_ic_ab_back_material else materialR.drawable.abc_ic_search_api_material,
)
setHomeActionContentDescription(
if (isOpened) R.string.back else R.string.search,
)
}
viewBinding.searchView.setHintCompat(
if (isOpened) R.string.search_hint else R.string.search_manga,
)
@@ -394,7 +399,7 @@ class MainActivity : BaseActivity<ActivityMainBinding>(), AppBarOwner, BottomNav
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.POST_NOTIFICATIONS),
1
1,
)
}
}

View File

@@ -1,7 +1,11 @@
package org.koitharu.kotatsu.reader.ui.pager.reversed
import android.os.Bundle
import android.view.InputDevice
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.core.view.children
import com.google.android.material.snackbar.Snackbar
@@ -26,9 +30,11 @@ import org.koitharu.kotatsu.reader.ui.pager.standard.NoAnimPageTransformer
import org.koitharu.kotatsu.reader.ui.pager.standard.PagerReaderFragment
import javax.inject.Inject
import kotlin.math.absoluteValue
import kotlin.math.sign
@AndroidEntryPoint
class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>() {
class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>(),
View.OnGenericMotionListener {
@Inject
lateinit var networkState: NetworkState
@@ -47,6 +53,7 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
adapter = readerAdapter
offscreenPageLimit = 2
doOnPageChanged(::notifyPageChanged)
setOnGenericMotionListener(this@ReversedReaderFragment)
}
viewModel.pageAnimation.observe(viewLifecycleOwner) {
@@ -69,6 +76,20 @@ class ReversedReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>
super.onDestroyView()
}
override fun onGenericMotion(v: View?, event: MotionEvent): Boolean {
if (event.source and InputDevice.SOURCE_CLASS_POINTER != 0) {
if (event.actionMasked == MotionEvent.ACTION_SCROLL) {
val axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL)
val withCtrl = event.metaState and KeyEvent.META_CTRL_MASK != 0
if (!withCtrl) {
switchPageBy(-axisValue.sign.toInt())
return true
}
}
}
return false
}
override fun onCreateAdapter() = ReversedPagesAdapter(
lifecycleOwner = viewLifecycleOwner,
loader = pageLoader,

View File

@@ -35,6 +35,7 @@ open class PageHolder(
binding.ssiv.bindToLifecycle(owner)
binding.ssiv.isEagerLoadingEnabled = !context.isLowRamDevice()
binding.ssiv.addOnImageEventListener(delegate)
binding.ssiv.setOnGenericMotionListener(SsivZoomListener())
@Suppress("LeakingThis")
bindingInfo.buttonRetry.setOnClickListener(this)
@Suppress("LeakingThis")

View File

@@ -1,7 +1,11 @@
package org.koitharu.kotatsu.reader.ui.pager.standard
import android.os.Bundle
import android.view.InputDevice
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.core.view.children
import com.google.android.material.snackbar.Snackbar
@@ -24,9 +28,11 @@ import org.koitharu.kotatsu.reader.ui.pager.BaseReaderFragment
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import javax.inject.Inject
import kotlin.math.absoluteValue
import kotlin.math.sign
@AndroidEntryPoint
class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>() {
class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>(),
View.OnGenericMotionListener {
@Inject
lateinit var networkState: NetworkState
@@ -39,12 +45,16 @@ class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>()
container: ViewGroup?,
) = FragmentReaderStandardBinding.inflate(inflater, container, false)
override fun onViewBindingCreated(binding: FragmentReaderStandardBinding, savedInstanceState: Bundle?) {
override fun onViewBindingCreated(
binding: FragmentReaderStandardBinding,
savedInstanceState: Bundle?,
) {
super.onViewBindingCreated(binding, savedInstanceState)
with(binding.pager) {
adapter = readerAdapter
offscreenPageLimit = 2
doOnPageChanged(::notifyPageChanged)
setOnGenericMotionListener(this@PagerReaderFragment)
}
viewModel.pageAnimation.observe(viewLifecycleOwner) {
@@ -67,28 +77,43 @@ class PagerReaderFragment : BaseReaderFragment<FragmentReaderStandardBinding>()
super.onDestroyView()
}
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) = coroutineScope {
val items = async {
requireAdapter().setItems(pages)
yield()
}
if (pendingState != null) {
val position = pages.indexOfFirst {
it.chapterId == pendingState.chapterId && it.index == pendingState.page
override fun onGenericMotion(v: View?, event: MotionEvent): Boolean {
if (event.source and InputDevice.SOURCE_CLASS_POINTER != 0) {
if (event.actionMasked == MotionEvent.ACTION_SCROLL) {
val axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL)
val withCtrl = event.metaState and KeyEvent.META_CTRL_MASK != 0
if (!withCtrl) {
switchPageBy(-axisValue.sign.toInt())
return true
}
}
items.await()
if (position != -1) {
requireViewBinding().pager.setCurrentItem(position, false)
notifyPageChanged(position)
} else {
Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT)
.show()
}
} else {
items.await()
}
return false
}
override suspend fun onPagesChanged(pages: List<ReaderPage>, pendingState: ReaderState?) =
coroutineScope {
val items = async {
requireAdapter().setItems(pages)
yield()
}
if (pendingState != null) {
val position = pages.indexOfFirst {
it.chapterId == pendingState.chapterId && it.index == pendingState.page
}
items.await()
if (position != -1) {
requireViewBinding().pager.setCurrentItem(position, false)
notifyPageChanged(position)
} else {
Snackbar.make(requireView(), R.string.not_found_404, Snackbar.LENGTH_SHORT)
.show()
}
} else {
items.await()
}
}
override fun onCreateAdapter() = PagesAdapter(
lifecycleOwner = viewLifecycleOwner,
loader = pageLoader,

View File

@@ -0,0 +1,32 @@
package org.koitharu.kotatsu.reader.ui.pager.standard
import android.graphics.PointF
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.View
import android.view.View.OnGenericMotionListener
import android.view.animation.DecelerateInterpolator
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
class SsivZoomListener : OnGenericMotionListener {
override fun onGenericMotion(v: View?, event: MotionEvent): Boolean {
val ssiv = v as? SubsamplingScaleImageView ?: return false
if (event.source and InputDevice.SOURCE_CLASS_POINTER != 0) {
if (event.actionMasked == MotionEvent.ACTION_SCROLL) {
val axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL)
val withCtrl = event.metaState and KeyEvent.META_CTRL_MASK != 0
if (withCtrl || ssiv.scale > ssiv.minScale) {
val center = PointF(event.x, event.y)
val scale = ssiv.scale + axisValue * 1.6f
(ssiv.animateScaleAndCenter(scale, center) ?: return false)
.withInterpolator(DecelerateInterpolator())
.start()
return true
}
}
}
return false
}
}

View File

@@ -7,6 +7,8 @@ import android.graphics.Rect
import android.graphics.RectF
import android.util.AttributeSet
import android.view.GestureDetector
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent
import android.view.ScaleGestureDetector
import android.view.animation.AccelerateDecelerateInterpolator
@@ -16,6 +18,7 @@ import androidx.core.view.GestureDetectorCompat
private const val MAX_SCALE = 2.5f
private const val MIN_SCALE = 0.5f
private const val WHEEL_SCALE_FACTOR = 0.2f
class WebtoonScalingFrame @JvmOverloads constructor(
context: Context,
@@ -77,10 +80,26 @@ class WebtoonScalingFrame @JvmOverloads constructor(
return super.dispatchTouchEvent(ev)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
halfWidth = measuredWidth / 2f
halfHeight = measuredHeight / 2f
override fun onGenericMotionEvent(event: MotionEvent): Boolean {
if (event.source and InputDevice.SOURCE_CLASS_POINTER != 0) {
if (event.actionMasked == MotionEvent.ACTION_SCROLL) {
val withCtrl = event.metaState and KeyEvent.META_CTRL_MASK != 0
if (withCtrl) {
val axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL)
val newScale =
(scale + axisValue * WHEEL_SCALE_FACTOR).coerceIn(MIN_SCALE, MAX_SCALE)
scaleChild(newScale, event.x, event.y)
return true
}
}
}
return super.onGenericMotionEvent(event)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
halfWidth = w / 2f
halfHeight = h / 2f
}
private fun invalidateTarget() {
@@ -161,7 +180,12 @@ class WebtoonScalingFrame @JvmOverloads constructor(
private inner class GestureListener : GestureDetector.SimpleOnGestureListener(), Runnable {
override fun onScroll(e1: MotionEvent?, e2: MotionEvent, distanceX: Float, distanceY: Float): Boolean {
override fun onScroll(
e1: MotionEvent?,
e2: MotionEvent,
distanceX: Float,
distanceY: Float,
): Boolean {
if (scale <= 1f) return false
transformMatrix.postTranslate(-distanceX, -distanceY)
invalidateTarget()
@@ -181,7 +205,12 @@ class WebtoonScalingFrame @JvmOverloads constructor(
return true
}
override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean {
override fun onFling(
e1: MotionEvent?,
e2: MotionEvent,
velocityX: Float,
velocityY: Float,
): Boolean {
if (scale <= 1) return false
overScroller.fling(
@@ -200,7 +229,10 @@ class WebtoonScalingFrame @JvmOverloads constructor(
override fun run() {
if (overScroller.computeScrollOffset()) {
transformMatrix.postTranslate(overScroller.currX - transX, overScroller.currY - transY)
transformMatrix.postTranslate(
overScroller.currX - transX,
overScroller.currY - transY
)
invalidateTarget()
postOnAnimation(this)
}

View File

@@ -26,6 +26,17 @@
<solid android:color="@color/selector_overlay" />
</shape>
</item>
<item
android:bottom="2dp"
android:left="2dp"
android:right="2dp"
android:state_hovered="true"
android:top="2dp">
<shape android:shape="rectangle">
<corners android:radius="@dimen/list_selector_corner" />
<solid android:color="@color/selector_overlay" />
</shape>
</item>
<item
android:bottom="2dp"
android:left="2dp"

View File

@@ -64,6 +64,7 @@
android:focusable="true"
android:focusableInTouchMode="true"
app:contentInsetStartWithNavigation="0dp"
app:navigationContentDescription="@string/search"
app:navigationIcon="?attr/actionModeWebSearchDrawable">
<org.koitharu.kotatsu.search.ui.widget.SearchEditText

View File

@@ -48,6 +48,7 @@
android:focusable="true"
android:focusableInTouchMode="true"
app:contentInsetStartWithNavigation="0dp"
app:navigationContentDescription="@string/search"
app:navigationIcon="?attr/actionModeWebSearchDrawable">
<org.koitharu.kotatsu.search.ui.widget.SearchEditText

View File

@@ -5,6 +5,7 @@
android:id="@+id/frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.koitharu.kotatsu.reader.ui.pager.webtoon.WebtoonRecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"

View File

@@ -60,6 +60,7 @@
android:padding="@dimen/margin_small"
android:scaleType="center"
android:src="@drawable/abc_ic_menu_overflow_material"
android:tooltipText="@string/more"
app:tint="?colorControlNormal" />
<ImageView
@@ -70,7 +71,8 @@
android:contentDescription="@string/add"
android:padding="@dimen/margin_small"
android:scaleType="center"
android:src="@drawable/ic_add" />
android:src="@drawable/ic_add"
android:tooltipText="@string/add" />
<ImageView
android:id="@+id/imageView_remove"
@@ -80,6 +82,7 @@
android:contentDescription="@string/remove"
android:padding="@dimen/margin_small"
android:scaleType="center"
android:src="@drawable/ic_delete" />
android:src="@drawable/ic_delete"
android:tooltipText="@string/remove" />
</LinearLayout>

View File

@@ -7,7 +7,8 @@
android:id="@+id/action_app_update"
android:icon="@drawable/ic_app_update"
android:orderInCategory="1"
android:title="@string/update"
android:title="@string/app_update_available"
android:titleCondensed="@string/update"
android:visible="false"
app:showAsAction="ifRoom" />