Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {

allprojects {
group = "dev.materii.pullrefresh"
version = "1.0.1"
version = "1.1.0"

repositories {
repositories {
Expand Down
3 changes: 2 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ kotlin.code.style=official
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true
org.jetbrains.compose.experimental.uikit.enabled=true
org.jetbrains.compose.experimental.macos.enabled=true
org.jetbrains.compose.experimental.macos.enabled=true
kotlin.native.ignoreDisabledTargets=true
16 changes: 8 additions & 8 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
[versions]
agp = "8.2.0-alpha15"
kotlin = "1.8.21"
compose-multiplatform = "1.5.0"
compose-compiler = "1.4.7"
core-ktx = "1.10.1"
agp = "8.1.3"
kotlin = "1.9.20"
compose-multiplatform = "1.5.10"
compose-compiler = "1.5.4"
core-ktx = "1.12.0"

[libraries]
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version = "2.6.1" }
activity-compose = { group = "androidx.activity", name = "activity-compose", version = "1.7.2" }
material3 = { group = "androidx.compose.material3", name = "material3", version = "1.2.0-alpha04" }
lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version = "2.6.2" }
activity-compose = { group = "androidx.activity", name = "activity-compose", version = "1.8.0" }
material3 = { group = "androidx.compose.material3", name = "material3", version = "1.2.0-alpha09" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Tue Aug 15 15:56:38 EDT 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
7 changes: 5 additions & 2 deletions pullrefresh/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -93,10 +93,13 @@ if (sonatypeUsername != null && sonatypePassword != null) {
}

kotlin {
android {
applyDefaultHierarchyTemplate()

androidTarget {
publishLibraryVariants("release")
}
ios()
iosX64()
iosArm64()
iosSimulatorArm64()
jvmToolchain(17)
jvm("desktop")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,24 @@ import androidx.compose.ui.unit.Velocity
* @param state The [PullRefreshState] associated with this pull-to-refresh component.
* The state will be updated by this modifier.
* @param enabled If not enabled, all scroll delta and fling velocity will be ignored.
* @param inverse If true, then this will be instead activated from the bottom of a scrollable
* container instead of the top. **NOTE:** Overscroll **MUST** be disabled on the scrollable
* container for this to work. (`LocalOverscrollConfiguration provides null`)
*/
// TODO(b/244423199): Move pullRefresh into its own material library similar to material-ripple.
fun Modifier.pullRefresh(
state: PullRefreshState,
enabled: Boolean = true
enabled: Boolean = true,
inverse: Boolean = false,
) = inspectable(
inspectorInfo = debugInspectorInfo {
name = "pullRefresh"
properties["state"] = state
properties["enabled"] = enabled
properties["inverse"] = inverse
}
) {
Modifier.pullRefresh(state::onPull, state::onRelease, enabled)
Modifier.pullRefresh(state::onPull, state::onRelease, enabled, inverse)
}

/**
Expand All @@ -69,34 +74,41 @@ fun Modifier.pullRefresh(
* pullRefresh. This is invoked before any remaining velocity is passed to the child.
* @param enabled If not enabled, all scroll delta and fling velocity will be ignored and neither
* [onPull] nor [onRelease] will be invoked.
* @param inverse If true, then this will be instead activated from the bottom of a scrollable
* container instead of the top. **NOTE:** Overscroll **MUST** be disabled on the scrollable
* container for this to work. (`LocalOverscrollConfiguration provides null`)
*/
fun Modifier.pullRefresh(
onPull: (pullDelta: Float) -> Float,
onRelease: suspend (flingVelocity: Float) -> Float,
enabled: Boolean = true
enabled: Boolean = true,
inverse: Boolean = false,
) = inspectable(
inspectorInfo = debugInspectorInfo {
name = "pullRefresh"
properties["onPull"] = onPull
properties["onRelease"] = onRelease
properties["enabled"] = enabled
properties["inverse"] = inverse
}
) {
Modifier.nestedScroll(PullRefreshNestedScrollConnection(onPull, onRelease, enabled))
Modifier.nestedScroll(PullRefreshNestedScrollConnection(onPull, onRelease, enabled, inverse))
}

private class PullRefreshNestedScrollConnection(
private val onPull: (pullDelta: Float) -> Float,
private val onRelease: suspend (flingVelocity: Float) -> Float,
private val enabled: Boolean
private val enabled: Boolean,
private val inverse: Boolean,
) : NestedScrollConnection {

override fun onPreScroll(
available: Offset,
source: NestedScrollSource
): Offset = when {
!enabled -> Offset.Zero
source == Drag && available.y < 0 -> Offset(0f, onPull(available.y)) // Swiping up
!inverse && source == Drag && available.y < 0 -> Offset(0f, onPull(available.y)) // Swiping up
inverse && source == Drag && available.y > 0 -> Offset(0f, onPull(-available.y)) // Swiping down when inverse
else -> Offset.Zero
}

Expand All @@ -106,7 +118,8 @@ private class PullRefreshNestedScrollConnection(
source: NestedScrollSource
): Offset = when {
!enabled -> Offset.Zero
source == Drag && available.y > 0 -> Offset(0f, onPull(available.y)) // Pulling down
!inverse && source == Drag && available.y > 0 -> Offset(0f, onPull(available.y)) // Pulling down
inverse && source == Drag && available.y < 0 -> Offset(0f, onPull(-available.y)) // Pulling up when inverse
else -> Offset.Zero
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ import kotlin.math.*
* @param backgroundColor The color of the indicator's background.
* @param contentColor The color of the indicator's arc and arrow.
* @param scale A boolean controlling whether the indicator's size scales with pull progress or not.
* @param flipped Whether the indicator is drawn emanating from the bottom instead.
* This should be used with the `inverse` param of `pullRefresh`.
*/
@Composable
fun PullRefreshIndicator(
Expand All @@ -61,7 +63,8 @@ fun PullRefreshIndicator(
modifier: Modifier = Modifier,
backgroundColor: Color = Color.White,
contentColor: Color = Color.Blue,
scale: Boolean = false
scale: Boolean = false,
flipped: Boolean = false,
) {
val showElevation by remember(refreshing, state) {
derivedStateOf { refreshing || state.position > 0.5f }
Expand All @@ -70,7 +73,7 @@ fun PullRefreshIndicator(
Box(
modifier = modifier
.size(IndicatorSize)
.pullRefreshIndicatorTransform(state, scale)
.pullRefreshIndicatorTransform(state, scale, flipped)
.shadow(if (showElevation) Elevation else 0.dp, SpinnerShape, clip = true)
.background(color = backgroundColor, shape = SpinnerShape)
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,12 @@ import androidx.compose.ui.platform.inspectable
*
* @param state The [PullRefreshState] which determines the position of the indicator.
* @param scale A boolean controlling whether the indicator's size scales with pull progress or not.
* @param flipped Whether the indicator is drawn emanating from the bottom instead.
*/
fun Modifier.pullRefreshIndicatorTransform(
state: PullRefreshState,
scale: Boolean = false,
flipped: Boolean = false,
) = inspectable(
inspectorInfo = debugInspectorInfo {
name = "pullRefreshIndicatorTransform"
Expand All @@ -50,16 +52,20 @@ fun Modifier.pullRefreshIndicatorTransform(
// only ever really want to clip at the top edge.
.drawWithContent {
clipRect(
top = 0f,
top = if (!flipped) 0f else -Float.MAX_VALUE,
left = -Float.MAX_VALUE,
right = Float.MAX_VALUE,
bottom = Float.MAX_VALUE
bottom = if (!flipped) Float.MAX_VALUE else size.height,
) {
this@drawWithContent.drawContent()
}
}
.graphicsLayer {
translationY = state.position - size.height
translationY = if (!flipped) {
state.position - size.height
} else {
-state.position + size.height
}

if (scale && !state.refreshing) {
val scaleFraction = LinearOutSlowInEasing
Expand Down