diff --git a/build.gradle.kts b/build.gradle.kts index 6c913e4..3a68d33 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath("com.android.tools.build:gradle:8.1.4") + classpath("com.android.tools.build:gradle:8.3.0") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.0") } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ea07000..515c3ae 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Aug 18 16:00:36 CDT 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts index fddd126..3fa5595 100644 --- a/sample/build.gradle.kts +++ b/sample/build.gradle.kts @@ -11,7 +11,7 @@ android { defaultConfig { applicationId = "moe.tlaster.swiper" minSdk = 21 - targetSdk = 33 + targetSdk = 34 versionCode = 1 versionName = "1.0" @@ -59,7 +59,7 @@ dependencies { implementation("androidx.compose.ui:ui-tooling:${rootProject.extra["compose_version"]}") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0") implementation("androidx.activity:activity-compose:1.8.2") - testImplementation("junit:junit:4.+") + testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation("androidx.compose.ui:ui-test-junit4:${rootProject.extra["compose_version"]}") diff --git a/sample/src/main/java/moe/tlaster/swiper/sample/MainActivity.kt b/sample/src/main/java/moe/tlaster/swiper/sample/MainActivity.kt index 9ff976d..495512a 100644 --- a/sample/src/main/java/moe/tlaster/swiper/sample/MainActivity.kt +++ b/sample/src/main/java/moe/tlaster/swiper/sample/MainActivity.kt @@ -4,20 +4,30 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.background +import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.* import androidx.compose.material.Button +import androidx.compose.material.Icon +import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.Scaffold +import androidx.compose.material.Surface import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import moe.tlaster.swiper.Direction import moe.tlaster.swiper.Swiper import moe.tlaster.swiper.rememberSwiperState @@ -27,43 +37,74 @@ class MainActivity : ComponentActivity() { setContent { MaterialTheme { Scaffold { - var show by remember { - mutableStateOf(false) - } - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Button(onClick = { show = true }) { - Text(text = "ClickMe") - } - Text(text = "show: $show") - } - if (show) { - val state = rememberSwiperState( - onDismiss = { - show = false - } - ) - Box( - modifier = Modifier - .fillMaxSize() - .background(Color.Black.copy(alpha = 1 - state.progress)) - .alpha(1 - state.progress), - ) { - Swiper( - state = state, - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(400.dp) - .background(Color.Red) - ) - } - } - } + Surface ( + modifier = Modifier.padding(it) + ){ + var show by remember { mutableStateOf(false) } + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Button(onClick = { show = true }) { + Text(text = "ClickMe") + } + Text(text = "show: $show") + } + if (show) { + val state = rememberSwiperState( + onDismiss = { + show = false + } + ) + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.Black.copy(alpha = 1 - state.progress)) + .alpha(1 - state.progress), + ) { + Swiper( + state = state, + orientation = Orientation.Horizontal, + direction = Direction.Left + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .height(400.dp) + .background(Color.Gray) + ){ + Column ( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = Alignment.CenterHorizontally + ){ + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + val scope = rememberCoroutineScope() + IconButton( + onClick = { + scope.launch(Dispatchers.Default){ + state.dismissIt() + } + }) { + Icon( + imageVector = Icons.Default.Close, + contentDescription = "Close it", + tint = Color.White + ) + } + } + } + } + } + } + } + } + } } } diff --git a/swiper/build.gradle.kts b/swiper/build.gradle.kts index eedc7a2..cf7dc23 100644 --- a/swiper/build.gradle.kts +++ b/swiper/build.gradle.kts @@ -7,7 +7,7 @@ plugins { android { namespace = "moe.tlaster.swiper" - compileSdk = 33 + compileSdk = 34 defaultConfig { minSdk = 21 @@ -34,7 +34,7 @@ dependencies { androidTestImplementation(composeBom) implementation("androidx.compose.foundation:foundation") - testImplementation("junit:junit:4.+") + testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") } @@ -45,7 +45,7 @@ afterEvaluate { create("release") { groupId = "moe.tlaster" artifactId = "swiper" - version = "0.7.3" + version = "0.8.0" from(components["release"]) } diff --git a/swiper/src/main/java/moe/tlaster/swiper/Direction.kt b/swiper/src/main/java/moe/tlaster/swiper/Direction.kt new file mode 100644 index 0000000..e4cf409 --- /dev/null +++ b/swiper/src/main/java/moe/tlaster/swiper/Direction.kt @@ -0,0 +1,9 @@ +package moe.tlaster.swiper + +enum class Direction { + Left, + Right, + Up, + Down +} + diff --git a/swiper/src/main/java/moe/tlaster/swiper/DismissHeight.kt b/swiper/src/main/java/moe/tlaster/swiper/DismissHeight.kt new file mode 100644 index 0000000..0f6f06a --- /dev/null +++ b/swiper/src/main/java/moe/tlaster/swiper/DismissHeight.kt @@ -0,0 +1,6 @@ +package moe.tlaster.swiper + +object DismissHeight { + const val HALF = 0.5f +} + diff --git a/swiper/src/main/java/moe/tlaster/swiper/Swiper.kt b/swiper/src/main/java/moe/tlaster/swiper/Swiper.kt index f55c38c..53b8ec9 100644 --- a/swiper/src/main/java/moe/tlaster/swiper/Swiper.kt +++ b/swiper/src/main/java/moe/tlaster/swiper/Swiper.kt @@ -19,6 +19,9 @@ fun Swiper( orientation: Orientation = Orientation.Vertical, enabled: Boolean = true, reverseDirection: Boolean = false, + direction: Direction = Direction.Up, + dismissHeight: Float = DismissHeight.HALF, + animDurationMillis: Int = 500, content: @Composable () -> Unit, ) { val scope = rememberCoroutineScope() @@ -27,6 +30,9 @@ fun Swiper( ) { LaunchedEffect(constraints.maxHeight) { state.maxHeight = constraints.maxHeight + state.direction = direction + state.dismissHeight = dismissHeight + state.animDuration = animDurationMillis } Layout( modifier = Modifier.draggable( @@ -50,11 +56,11 @@ fun Swiper( }, ), content = content, - ) { measurables, constraints -> + ) { measureScope, constraints -> layout(constraints.maxWidth, constraints.maxHeight) { val offset = state.offset val childConstraints = constraints.copy(minWidth = 0, minHeight = 0) - measurables + measureScope .map { it.measure(childConstraints) } diff --git a/swiper/src/main/java/moe/tlaster/swiper/SwiperState.kt b/swiper/src/main/java/moe/tlaster/swiper/SwiperState.kt index 118120a..c786831 100644 --- a/swiper/src/main/java/moe/tlaster/swiper/SwiperState.kt +++ b/swiper/src/main/java/moe/tlaster/swiper/SwiperState.kt @@ -1,10 +1,16 @@ package moe.tlaster.swiper import androidx.compose.animation.core.Animatable -import androidx.compose.runtime.* +import androidx.compose.animation.core.tween +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.listSaver import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import kotlin.math.abs import kotlin.math.absoluteValue import kotlin.math.withSign @@ -41,6 +47,9 @@ class SwiperState( } internal var dismissed by mutableStateOf(false) private var _offset = Animatable(initialOffset) + private lateinit var _direction: Direction + private var _dismissHeight: Float = 0f + private var _animDuration: Int = 0 val offset: Float get() = _offset.value @@ -55,18 +64,47 @@ class SwiperState( _offset.snapTo(value) } + internal var direction: Direction = Direction.Up + set(value) { + _direction = value + field = value + } + + internal var dismissHeight: Float = 0f + set(value) { + _dismissHeight = value + field = value + } + + internal var animDuration: Int = 0 + set(value) { + _animDuration = value + field = value + } + internal suspend fun fling(velocity: Float) { val value = _offset.value - when { - velocity.absoluteValue > 4000f -> { - dismiss(velocity) - } - value.absoluteValue < maxHeight * 0.5 -> { - restore() - } - value.absoluteValue < maxHeight -> { - dismiss(velocity) + val calcCurrentHeight = abs(value) / 1000 + if(velocity.absoluteValue == 0.0f && calcCurrentHeight > dismissHeight){ + when { + _direction == Direction.Up && value < 0.0 -> { + dismiss(velocity) + } + _direction == Direction.Down && value > 0.0 -> { + dismiss(velocity) + } + _direction == Direction.Left && value < 0.0 -> { + dismiss(velocity) + } + _direction == Direction.Right && value > 0.0 -> { + dismiss(velocity) + } + else -> { + restore() + } } + }else { + restore() } } @@ -83,6 +121,24 @@ class SwiperState( dismissed = false } + private suspend fun clickToDismiss(){ + dismissed = true + when (_direction) { + Direction.Right, Direction.Up -> { + _offset.animateTo(( - maxHeight.toFloat()), initialVelocity = 0.0f, animationSpec = tween(_animDuration)) + onDismiss.invoke() + } + Direction.Left, Direction.Down -> { + _offset.animateTo(maxHeight.toFloat(), initialVelocity = 0.0f, animationSpec = tween(_animDuration)) + onDismiss.invoke() + } + } + restore() + } + suspend fun dismissIt(){ + clickToDismiss() + } + companion object { fun Saver( onStart: () -> Unit = {}, @@ -99,9 +155,10 @@ class SwiperState( onStart = onStart, onDismiss = onDismiss, onEnd = onEnd, - initialOffset = it[0], + initialOffset = it[0] ) } ) } -} \ No newline at end of file +} +