Skip to content

Commit

Permalink
fix(ui): disable pull to load when no articles available
Browse files Browse the repository at this point in the history
  • Loading branch information
JunkFood02 committed Apr 28, 2024
1 parent 1bf597d commit 571840a
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 74 deletions.
24 changes: 14 additions & 10 deletions app/src/main/java/me/ash/reader/ui/page/home/reading/PullToLoad.kt
Expand Up @@ -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
Expand Down Expand Up @@ -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].
Expand All @@ -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 {

Expand Down Expand Up @@ -81,7 +83,7 @@ private class ReaderNestedScrollConnection(
}

override suspend fun onPreFling(available: Velocity): Velocity {
onRelease(available.y)
onRelease()
return Velocity.Zero
}
}
Expand Down Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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(
Expand Down
Expand Up @@ -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

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 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 (this != PullToLoadState.Status.Idle) {
Surface(
}

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)
.padding(vertical = 80.dp)
.offset(y = (fraction * 48).dp)
.width(36.dp),
color = MaterialTheme.colorScheme.primary,
shape = MaterialTheme.shapes.extraLarge
.align(Alignment.Center),
) {
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 = ""
) {
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)
)
}
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)
)
}

}

}
}
}

}
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -210,7 +199,11 @@ fun ReadingPage(
showFullScreenImageViewer = true
}
)
PullToLoadIndicator(state = state)
PullToLoadIndicator(
state = state,
canLoadPrevious = isPreviousArticleAvailable,
canLoadNext = isNextArticleAvailable
)
}
}
}
Expand Down Expand Up @@ -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
}
)
},
Expand Down

0 comments on commit 571840a

Please sign in to comment.