From 9544dc6dc8b8b61b34b26fc4bdf7d30d7ac5b0e3 Mon Sep 17 00:00:00 2001 From: Wing <44992537+wingio@users.noreply.github.com> Date: Wed, 16 Aug 2023 10:39:12 -0400 Subject: [PATCH 1/4] hopefully fix signing issue --- pullrefresh/build.gradle.kts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pullrefresh/build.gradle.kts b/pullrefresh/build.gradle.kts index cb4acf6..7aa7989 100644 --- a/pullrefresh/build.gradle.kts +++ b/pullrefresh/build.gradle.kts @@ -74,16 +74,17 @@ publishing { } } -if(sonatypeUsername != null && sonatypePassword != null) { +if (sonatypeUsername != null && sonatypePassword != null) { signing { useInMemoryPgpKeys( System.getenv("SIGNING_KEY_ID"), System.getenv("SIGNING_KEY"), - System.getenv("SIGNING_KEY_PASSWORD") + System.getenv("SIGNING_PASSWORD"), ) sign(publishing.publications) } + val dependsOnTasks = mutableListOf() tasks.withType().configureEach { dependsOnTasks.add(name.replace("publish", "sign").replaceAfter("Publication", "")) From b448fcab17b41713f80ea7461a8da1da6fc9365d Mon Sep 17 00:00:00 2001 From: Wing <44992537+wingio@users.noreply.github.com> Date: Thu, 17 Aug 2023 13:57:09 -0400 Subject: [PATCH 2/4] Create fancy README --- README.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 875e60b..a3e5e03 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,73 @@ # pullrefresh -Standalone pull to refresh library for Jetpack Compose multiplatform. +Standalone pull to refresh library for Jetpack Compose multiplatform without the reliance on Material. + +[![Maven Central](https://img.shields.io/maven-central/v/dev.materii.pullrefresh/pullrefresh?style=for-the-badge&label=Maven%20Central)](https://central.sonatype.com/artifact/dev.materii.pullrefresh/pullrefresh/) +[![Repo stars](https://img.shields.io/github/stars/MateriiApps/pullrefresh?style=for-the-badge&logo=github)](https://github.com/MateriiApps/pullrefresh/stargazers) +![Build status](https://img.shields.io/github/actions/workflow/status/MateriiApps/pullrefresh/build.yml?style=for-the-badge&logo=github) + +## Use + +### Add to project + +Gradle (Kotlin): +```kts +implementation("dev.materii.pullrefresh:pullrefresh:$pullRefreshVersion") +``` + +Gradle (Groovy): +```groovy +implementation 'dev.materii.pullrefresh:pullrefresh:$pullRefreshVersion' +``` + +Gradle (Version Catalog): +```toml +materii-pullrefresh = { group = "dev.materii.pullrefresh", name = "pullrefresh", version.ref = "pullrefresh" } +``` + +### Basic setup +> See the [demo project](https://github.com/MateriiApps/pullrefresh/blob/main/demo/src/main/java/dev/materii/pullrefresh/demo/MainActivity.kt). +```kt +@Composable +fun Test() { + var isRefreshing by remember { + mutableStateOf(false) + } + var pullRefreshState = rememberPullRefreshState(refreshing = isRefreshing, onRefresh = { /* Refresh some data here */ }) + + Scaffold( + modifier = Modifier.pullRefresh(pullRefreshState) + ) { + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .padding(it) + .fillMaxSize() + .verticalScroll(rememberScrollState()) + ) { + PullRefreshIndicator( + refreshing = isRefreshing, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter) + ) + } +} +``` + +## Notice +All of the included components come directly from the official Jetpack Compose Material library, with only the bare minimum required. + +``` +Copyright 2022 The Android Open Source Project + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +``` From 213e7f3be9474b83dc9e247a8f338ba1cfdce5b7 Mon Sep 17 00:00:00 2001 From: Wing <44992537+wingio@users.noreply.github.com> Date: Fri, 1 Sep 2023 11:41:34 -0400 Subject: [PATCH 3/4] Remove primitive mutablestates --- build.gradle.kts | 2 +- gradle/libs.versions.toml | 4 ++-- .../kotlin/dev/materii/pullrefresh/PullRefreshState.kt | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 51245b8..7dcb25a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { allprojects { group = "dev.materii.pullrefresh" - version = "1.0.0" + version = "1.0.1" repositories { repositories { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 76571ca..a2e6284 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] agp = "8.2.0-alpha15" kotlin = "1.8.21" -compose-multiplatform = "1.5.0-beta01" +compose-multiplatform = "1.5.0" compose-compiler = "1.4.7" core-ktx = "1.10.1" @@ -9,7 +9,7 @@ core-ktx = "1.10.1" 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.1.1" } +material3 = { group = "androidx.compose.material3", name = "material3", version = "1.2.0-alpha04" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } diff --git a/pullrefresh/src/commonMain/kotlin/dev/materii/pullrefresh/PullRefreshState.kt b/pullrefresh/src/commonMain/kotlin/dev/materii/pullrefresh/PullRefreshState.kt index 68b8740..dfadd31 100644 --- a/pullrefresh/src/commonMain/kotlin/dev/materii/pullrefresh/PullRefreshState.kt +++ b/pullrefresh/src/commonMain/kotlin/dev/materii/pullrefresh/PullRefreshState.kt @@ -107,10 +107,10 @@ class PullRefreshState internal constructor( private val adjustedDistancePulled by derivedStateOf { distancePulled * DragMultiplier } private var _refreshing by mutableStateOf(false) - private var _position by mutableFloatStateOf(0f) - private var distancePulled by mutableFloatStateOf(0f) - private var _threshold by mutableFloatStateOf(threshold) - private var _refreshingOffset by mutableFloatStateOf(refreshingOffset) + private var _position by mutableStateOf(0f) + private var distancePulled by mutableStateOf(0f) + private var _threshold by mutableStateOf(threshold) + private var _refreshingOffset by mutableStateOf(refreshingOffset) internal fun onPull(pullDelta: Float): Float { if (_refreshing) return 0f // Already refreshing, do nothing. From ce06e7450f014031acd4a9659776ebc213c0637a Mon Sep 17 00:00:00 2001 From: rushii <33725716+rushiiMachine@users.noreply.github.com> Date: Tue, 14 Nov 2023 19:13:55 -0800 Subject: [PATCH 4/4] feat: inverse pullrefresh (#4) * feat: inverse pullrefresh + update deps * add debug inspector info * fix buildscript warning * fix docs & m3 version * add inverse to indicator --- build.gradle.kts | 2 +- gradle.properties | 3 ++- gradle/libs.versions.toml | 16 +++++------ gradle/wrapper/gradle-wrapper.properties | 2 +- pullrefresh/build.gradle.kts | 7 +++-- .../dev/materii/pullrefresh/PullRefresh.kt | 27 ++++++++++++++----- .../pullrefresh/PullRefreshIndicator.kt | 7 +++-- .../PullRequestIndicatorTransform.kt | 12 ++++++--- 8 files changed, 51 insertions(+), 25 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 7dcb25a..547f2d9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { allprojects { group = "dev.materii.pullrefresh" - version = "1.0.1" + version = "1.1.0" repositories { repositories { diff --git a/gradle.properties b/gradle.properties index 2f09193..a3af757 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 \ No newline at end of file +org.jetbrains.compose.experimental.macos.enabled=true +kotlin.native.ignoreDisabledTargets=true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a2e6284..0c13eca 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3901c37..36ca995 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/pullrefresh/build.gradle.kts b/pullrefresh/build.gradle.kts index 7aa7989..6bfa5d9 100644 --- a/pullrefresh/build.gradle.kts +++ b/pullrefresh/build.gradle.kts @@ -93,10 +93,13 @@ if (sonatypeUsername != null && sonatypePassword != null) { } kotlin { - android { + applyDefaultHierarchyTemplate() + + androidTarget { publishLibraryVariants("release") } - ios() + iosX64() + iosArm64() iosSimulatorArm64() jvmToolchain(17) jvm("desktop") diff --git a/pullrefresh/src/commonMain/kotlin/dev/materii/pullrefresh/PullRefresh.kt b/pullrefresh/src/commonMain/kotlin/dev/materii/pullrefresh/PullRefresh.kt index 99cc4a0..97df239 100644 --- a/pullrefresh/src/commonMain/kotlin/dev/materii/pullrefresh/PullRefresh.kt +++ b/pullrefresh/src/commonMain/kotlin/dev/materii/pullrefresh/PullRefresh.kt @@ -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) } /** @@ -69,26 +74,32 @@ 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( @@ -96,7 +107,8 @@ private class PullRefreshNestedScrollConnection( 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 } @@ -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 } diff --git a/pullrefresh/src/commonMain/kotlin/dev/materii/pullrefresh/PullRefreshIndicator.kt b/pullrefresh/src/commonMain/kotlin/dev/materii/pullrefresh/PullRefreshIndicator.kt index be33e02..1ee613f 100644 --- a/pullrefresh/src/commonMain/kotlin/dev/materii/pullrefresh/PullRefreshIndicator.kt +++ b/pullrefresh/src/commonMain/kotlin/dev/materii/pullrefresh/PullRefreshIndicator.kt @@ -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( @@ -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 } @@ -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) ) { diff --git a/pullrefresh/src/commonMain/kotlin/dev/materii/pullrefresh/PullRequestIndicatorTransform.kt b/pullrefresh/src/commonMain/kotlin/dev/materii/pullrefresh/PullRequestIndicatorTransform.kt index b83a9b5..0d58be9 100644 --- a/pullrefresh/src/commonMain/kotlin/dev/materii/pullrefresh/PullRequestIndicatorTransform.kt +++ b/pullrefresh/src/commonMain/kotlin/dev/materii/pullrefresh/PullRequestIndicatorTransform.kt @@ -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" @@ -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