mirror of
https://github.com/Ashinch/ReadYou.git
synced 2025-02-01 20:07:24 +01:00
fix(ui): disable pull to load when no articles available
This commit is contained in:
parent
1bf597d32e
commit
571840a2fa
@ -2,8 +2,10 @@ package me.ash.reader.ui.page.home.reading
|
||||
|
||||
|
||||
import androidx.compose.animation.core.FloatExponentialDecaySpec
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.animate
|
||||
import androidx.compose.animation.core.animateDecay
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.MutatorMutex
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
@ -33,7 +35,7 @@ import me.ash.reader.ui.page.home.reading.PullToLoadDefaults.ContentOffsetMultip
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.sqrt
|
||||
|
||||
private const val TAG = "PullRelease"
|
||||
private const val TAG = "PullToLoad"
|
||||
|
||||
/**
|
||||
* A [NestedScrollConnection] that provides scroll events to a hoisted [state].
|
||||
@ -51,7 +53,7 @@ private class ReaderNestedScrollConnection(
|
||||
private val enabled: Boolean,
|
||||
private val onPreScroll: (Float) -> Float,
|
||||
private val onPostScroll: (Float) -> Float,
|
||||
private val onRelease: (Float) -> Unit,
|
||||
private val onRelease: () -> Unit,
|
||||
private val onScroll: ((Float) -> Unit)? = null
|
||||
) : NestedScrollConnection {
|
||||
|
||||
@ -81,7 +83,7 @@ private class ReaderNestedScrollConnection(
|
||||
}
|
||||
|
||||
override suspend fun onPreFling(available: Velocity): Velocity {
|
||||
onRelease(available.y)
|
||||
onRelease()
|
||||
return Velocity.Zero
|
||||
}
|
||||
}
|
||||
@ -217,21 +219,22 @@ class PullToLoadState internal constructor(
|
||||
return if (offsetPulled.signOpposites(pullDelta)) onPull(pullDelta) else 0f
|
||||
}
|
||||
|
||||
internal fun onRelease(velocity: Float): Float {
|
||||
internal fun onRelease(): Float {
|
||||
// Snap to 0f and hide the indicator
|
||||
animateDistanceTo(0f)
|
||||
|
||||
when (status) {
|
||||
// We don't change the pull offset here because the animation for loading another content
|
||||
// should be handled outside, and this state will be soon disposed
|
||||
Status.PulledDown -> {
|
||||
onLoadPrevious.value()
|
||||
}
|
||||
|
||||
Status.PulledUp -> {
|
||||
animateDistanceTo(0f)
|
||||
onLoadNext.value()
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Snap to 0f and hide the indicator
|
||||
animateDistanceTo(0f)
|
||||
|
||||
}
|
||||
}
|
||||
return 0f
|
||||
@ -247,7 +250,8 @@ class PullToLoadState internal constructor(
|
||||
animate(
|
||||
initialValue = offsetPulled,
|
||||
targetValue = float,
|
||||
initialVelocity = velocity
|
||||
initialVelocity = velocity,
|
||||
animationSpec = spring(dampingRatio = Spring.DampingRatioNoBouncy)
|
||||
) { value, _ ->
|
||||
offsetPulled = value
|
||||
}
|
||||
@ -310,7 +314,7 @@ fun Modifier.pullToLoad(
|
||||
state: PullToLoadState,
|
||||
contentOffsetMultiple: Int = ContentOffsetMultiple,
|
||||
onScroll: ((Float) -> Unit)? = null,
|
||||
enabled: Boolean = true
|
||||
enabled: Boolean = true,
|
||||
): Modifier =
|
||||
nestedScroll(
|
||||
ReaderNestedScrollConnection(
|
||||
|
@ -20,70 +20,117 @@ import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.unit.dp
|
||||
import me.ash.reader.ui.page.home.reading.PullToLoadState.Status.Idle
|
||||
import me.ash.reader.ui.page.home.reading.PullToLoadState.Status.PulledDown
|
||||
import me.ash.reader.ui.page.home.reading.PullToLoadState.Status.PulledUp
|
||||
import me.ash.reader.ui.page.home.reading.PullToLoadState.Status.PullingDown
|
||||
import me.ash.reader.ui.page.home.reading.PullToLoadState.Status.PullingUp
|
||||
import kotlin.math.abs
|
||||
|
||||
@Composable
|
||||
fun BoxScope.PullToLoadIndicator(state:PullToLoadState) {
|
||||
state.status.run {
|
||||
val fraction = state.offsetFraction
|
||||
val absFraction = abs(fraction)
|
||||
val imageVector = when (this) {
|
||||
PullToLoadState.Status.PulledDown -> Icons.Outlined.KeyboardArrowUp
|
||||
PullToLoadState.Status.PulledUp -> Icons.Outlined.KeyboardArrowDown
|
||||
else -> null
|
||||
}
|
||||
fun BoxScope.PullToLoadIndicator(
|
||||
state: PullToLoadState,
|
||||
canLoadPrevious: Boolean = true,
|
||||
canLoadNext: Boolean = true
|
||||
) {
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
val status = state.status
|
||||
|
||||
val alignment = if (fraction < 0f) {
|
||||
Alignment.BottomCenter
|
||||
} else {
|
||||
Alignment.TopCenter
|
||||
}
|
||||
if (this != PullToLoadState.Status.Idle) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.align(alignment)
|
||||
.padding(vertical = 80.dp)
|
||||
.offset(y = (fraction * 48).dp)
|
||||
.width(36.dp),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
shape = MaterialTheme.shapes.extraLarge
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center),
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = imageVector, modifier = Modifier.align(
|
||||
Alignment.CenterHorizontally
|
||||
), transitionSpec = {
|
||||
(fadeIn(animationSpec = tween(220, delayMillis = 0)))
|
||||
.togetherWith(fadeOut(animationSpec = tween(90)))
|
||||
}, label = ""
|
||||
) {
|
||||
if (it != null) {
|
||||
Icon(
|
||||
imageVector = it,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 4.dp)
|
||||
.padding(vertical = (2 * absFraction).dp)
|
||||
.size(32.dp)
|
||||
)
|
||||
} else {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.width(36.dp)
|
||||
.height((12 * absFraction).dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(status) {
|
||||
when {
|
||||
canLoadPrevious && status == PulledDown -> {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
|
||||
}
|
||||
canLoadNext && status == PulledUp -> {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
val fraction = state.offsetFraction
|
||||
val absFraction = abs(fraction)
|
||||
|
||||
val imageVector = when (status) {
|
||||
PulledDown -> Icons.Outlined.KeyboardArrowUp
|
||||
PulledUp -> Icons.Outlined.KeyboardArrowDown
|
||||
else -> null
|
||||
}
|
||||
|
||||
val alignment = if (fraction < 0f) {
|
||||
Alignment.BottomCenter
|
||||
} else {
|
||||
Alignment.TopCenter
|
||||
}
|
||||
|
||||
val visible = remember(status, canLoadPrevious, canLoadNext) {
|
||||
when (status) {
|
||||
Idle -> {
|
||||
false
|
||||
}
|
||||
|
||||
PullingUp, PulledUp -> {
|
||||
canLoadNext
|
||||
}
|
||||
|
||||
PulledDown, PullingDown -> {
|
||||
canLoadPrevious
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (visible) {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.align(alignment)
|
||||
.padding(vertical = 80.dp)
|
||||
.offset(y = (fraction * 48).dp)
|
||||
.width(36.dp),
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
shape = MaterialTheme.shapes.extraLarge
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center),
|
||||
) {
|
||||
AnimatedContent(
|
||||
targetState = imageVector, modifier = Modifier.align(
|
||||
Alignment.CenterHorizontally
|
||||
), transitionSpec = {
|
||||
(fadeIn(animationSpec = tween(220, delayMillis = 0)))
|
||||
.togetherWith(fadeOut(animationSpec = tween(90)))
|
||||
}, label = ""
|
||||
) {
|
||||
if (it != null) {
|
||||
Icon(
|
||||
imageVector = it,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.onPrimary,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 4.dp)
|
||||
.padding(vertical = (2 * absFraction).dp)
|
||||
.size(32.dp)
|
||||
)
|
||||
} else {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.width(36.dp)
|
||||
.height((12 * absFraction).dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -23,7 +23,6 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
@ -115,10 +114,10 @@ fun ReadingPage(
|
||||
navController.popBackStack()
|
||||
},
|
||||
)
|
||||
val context = LocalContext.current
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
|
||||
val isNextArticleAvailable = !readerState.nextArticleId.isNullOrEmpty()
|
||||
val isPreviousArticleAvailable = !readerState.previousArticleId.isNullOrEmpty()
|
||||
|
||||
|
||||
if (readerState.articleId != null) {
|
||||
// Content
|
||||
@ -159,16 +158,6 @@ fun ReadingPage(
|
||||
)
|
||||
|
||||
|
||||
LaunchedEffect(state.status) {
|
||||
when (state.status) {
|
||||
PullToLoadState.Status.PulledDown, PullToLoadState.Status.PulledUp -> {
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
val listState = rememberSaveable(
|
||||
inputs = arrayOf(content),
|
||||
saver = LazyListState.Saver
|
||||
@ -210,7 +199,11 @@ fun ReadingPage(
|
||||
showFullScreenImageViewer = true
|
||||
}
|
||||
)
|
||||
PullToLoadIndicator(state = state)
|
||||
PullToLoadIndicator(
|
||||
state = state,
|
||||
canLoadPrevious = isPreviousArticleAvailable,
|
||||
canLoadNext = isNextArticleAvailable
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -252,7 +245,8 @@ fun ReadingPage(
|
||||
onSuccess = { context.showToast(context.getString(R.string.image_saved)) },
|
||||
onFailure = {
|
||||
// FIXME: crash the app for error report
|
||||
th -> throw th
|
||||
th ->
|
||||
throw th
|
||||
}
|
||||
)
|
||||
},
|
||||
|
Loading…
x
Reference in New Issue
Block a user