From 50ac04b043991fb8538427c733d0907d39466c8e Mon Sep 17 00:00:00 2001 From: "Mr. Pine" Date: Mon, 27 Mar 2023 14:52:00 +0200 Subject: [PATCH 1/6] Upgrade AGP --- .gitignore | 1 + .idea/gradle.xml | 2 ++ .idea/misc.xml | 2 +- build.gradle | 6 +++--- gradle/wrapper/gradle-wrapper.properties | 2 +- zoomables/build.gradle | 1 + zoomables/src/main/AndroidManifest.xml | 2 +- 7 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 5fd2736..e9df19a 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ local.properties /SecretRingKey.gpg /secring.gpg +/zoomables_testing \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml index e2dd078..3a0291d 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -7,10 +7,12 @@ diff --git a/.idea/misc.xml b/.idea/misc.xml index 2a4d5b5..95ee4c6 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/build.gradle b/build.gradle index 559435e..9729c8d 100644 --- a/build.gradle +++ b/build.gradle @@ -4,9 +4,9 @@ buildscript { } }// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '7.1.2' apply false - id 'com.android.library' version '7.1.2' apply false - id 'org.jetbrains.kotlin.android' version '1.6.10' apply false + id 'com.android.application' version '7.4.2' apply false + id 'com.android.library' version '7.4.2' apply false + id 'org.jetbrains.kotlin.android' version '1.6.21' apply false id "io.github.gradle-nexus.publish-plugin" version "1.1.0" id "org.jetbrains.dokka" version "1.6.10" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3eed18a..a8ad32f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Mar 02 00:37:49 CET 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/zoomables/build.gradle b/zoomables/build.gradle index 09148a3..804ad43 100644 --- a/zoomables/build.gradle +++ b/zoomables/build.gradle @@ -38,6 +38,7 @@ android { buildFeatures { compose true } + namespace 'de.mr_pine.zoomables' } tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { diff --git a/zoomables/src/main/AndroidManifest.xml b/zoomables/src/main/AndroidManifest.xml index b784098..44008a4 100644 --- a/zoomables/src/main/AndroidManifest.xml +++ b/zoomables/src/main/AndroidManifest.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file From 8c40125960c085f98be5840563351811a298de72 Mon Sep 17 00:00:00 2001 From: "Mr. Pine" Date: Mon, 27 Mar 2023 17:01:19 +0200 Subject: [PATCH 2/6] Migrate to gradle.kts and use version catalog --- build.gradle | 18 ----- build.gradle.kts | 14 ++++ gradle/libs.versions.toml | 18 +++++ gradle/wrapper/gradle-wrapper.properties | 4 +- settings.gradle => settings.gradle.kts | 3 +- zoomables/build.gradle | 83 ----------------------- zoomables/build.gradle.kts | 85 ++++++++++++++++++++++++ zoomables/proguard-rules.pro | 2 +- 8 files changed, 122 insertions(+), 105 deletions(-) delete mode 100644 build.gradle create mode 100644 build.gradle.kts create mode 100644 gradle/libs.versions.toml rename settings.gradle => settings.gradle.kts (86%) delete mode 100644 zoomables/build.gradle create mode 100644 zoomables/build.gradle.kts diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 9729c8d..0000000 --- a/build.gradle +++ /dev/null @@ -1,18 +0,0 @@ -buildscript { - ext { - compose_version = '1.1.1' - } -}// Top-level build file where you can add configuration options common to all sub-projects/modules. -plugins { - id 'com.android.application' version '7.4.2' apply false - id 'com.android.library' version '7.4.2' apply false - id 'org.jetbrains.kotlin.android' version '1.6.21' apply false - id "io.github.gradle-nexus.publish-plugin" version "1.1.0" - id "org.jetbrains.dokka" version "1.6.10" -} - -task clean(type: Delete) { - delete rootProject.buildDir -} - -apply from: "${rootDir}/scripts/publish-root.gradle" \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..50ec4f6 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,14 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + kotlin("android") version libs.versions.kotlin.get() apply false + id("io.github.gradle-nexus.publish-plugin") version "1.1.0" + alias(libs.plugins.dokka) +} + +tasks.create("clean") { + delete(rootProject.buildDir) +} + +apply(from = "${rootDir}/scripts/publish-root.gradle") \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..e5fe657 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,18 @@ +[versions] +composeCompiler = "1.1.1" +compose = "1.1.1" +ktx = "1.7.0" +library = "7.4.2" +application = "7.4.2" +kotlin = "1.6.10" + +[libraries] +androidx-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "ktx" } +compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose" } +compose-ui-util = { group = "androidx.compose.ui", name = "ui-util", version.ref = "compose" } +compose-material = { group = "androidx.compose.material", name = "material", version.ref = "compose" } + +[plugins] +android-library = { id = "com.android.library", version.ref = "library" } +android-application = { id = "com.android.application", version.ref = "application" } +dokka = { id = "org.jetbrains.dokka", version.ref = "kotlin" } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a8ad32f..444e3c4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Mar 02 00:37:49 CET 2022 +#Mon Mar 27 16:36:42 CEST 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-rc-1-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle b/settings.gradle.kts similarity index 86% rename from settings.gradle rename to settings.gradle.kts index f9192b9..3118717 100644 --- a/settings.gradle +++ b/settings.gradle.kts @@ -14,4 +14,5 @@ dependencyResolutionManagement { } } rootProject.name = "Zoomables Library" -include ':zoomables' +include(":zoomables") +include(":zoomables_testing") diff --git a/zoomables/build.gradle b/zoomables/build.gradle deleted file mode 100644 index 804ad43..0000000 --- a/zoomables/build.gradle +++ /dev/null @@ -1,83 +0,0 @@ -plugins { - id 'com.android.library' - id 'org.jetbrains.kotlin.android' - id 'maven-publish' - id 'kotlin-android' -} - -android { - compileSdk 31 - - defaultConfig { - minSdk 22 - targetSdk 31 - - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - consumerProguardFiles "consumer-rules.pro" - } - - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - - composeOptions { - kotlinCompilerExtensionVersion compose_version - } - - kotlinOptions { - jvmTarget = '1.8' - } - - buildFeatures { - compose true - } - namespace 'de.mr_pine.zoomables' -} - -tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { - kotlinOptions.freeCompilerArgs += ["-Xexplicit-api=strict"] -} - -dependencies { - - implementation 'androidx.core:core-ktx:1.7.0' - implementation "androidx.compose.ui:ui:$compose_version" - implementation "androidx.compose.ui:ui-util:$compose_version" - implementation "androidx.compose.material:material:$compose_version" - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.3' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' -} - -ext{ - PUBLISH_GROUP_ID = 'de.mr-pine.utils' - PUBLISH_VERSION = '1.1.2' - PUBLISH_ARTIFACT_ID = 'zoomables' -} - -apply from: "${rootProject.projectDir}/scripts/publish-module.gradle" - -// Because the components are created only during the afterEvaluate phase, you must -// configure your publications using the afterEvaluate() lifecycle method. -afterEvaluate { - publishing { - publications { - mavenLocal(MavenPublication) { - // Applies the component for the release build variant. - from components.release - - // You can then customize attributes of the publication as shown below. - groupId = PUBLISH_GROUP_ID - artifactId = PUBLISH_ARTIFACT_ID - version = PUBLISH_VERSION - } - } - } -} \ No newline at end of file diff --git a/zoomables/build.gradle.kts b/zoomables/build.gradle.kts new file mode 100644 index 0000000..ba53d33 --- /dev/null +++ b/zoomables/build.gradle.kts @@ -0,0 +1,85 @@ +import org.jetbrains.kotlin.gradle.tasks.* + +plugins { + alias(libs.plugins.android.library) + kotlin("android") + `maven-publish` +} + +android { + compileSdk = 31 + + defaultConfig { + minSdk = 22 + targetSdk = 31 + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() + } + + kotlinOptions { + jvmTarget = "1.8" + } + + buildFeatures { + compose = true + } + namespace = "de.mr_pine.zoomables" +} + +tasks.withType { + kotlinOptions.freeCompilerArgs += listOf("-Xexplicit-api=strict") +} + +dependencies { + + implementation(libs.androidx.ktx) + implementation(libs.compose.ui) + implementation(libs.compose.ui.util) + implementation(libs.compose.material) + testImplementation("junit:junit:4.13.2") + androidTestImplementation("androidx.test.ext:junit:1.1.3") + androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") +} + +/*ext { + PUBLISH_GROUP_ID = "de.mr-pine.utils" + PUBLISH_VERSION = "1.1.2" + PUBLISH_ARTIFACT_ID = "zoomables" +}*/ + +//apply(from = "${rootProject.projectDir}/scripts/publish-module.gradle") + +// Because the components are created only during the afterEvaluate phase, you must +// configure your publications using the afterEvaluate() lifecycle method. +/* +afterEvaluate { + publishing { + publications { + mavenLocal(MavenPublication) { + // Applies the component for the release build variant. + from(components.release) + + // You can then customize attributes of the publication as shown below. + groupId = PUBLISH_GROUP_ID + artifactId = PUBLISH_ARTIFACT_ID + version = PUBLISH_VERSION + } + } + } +}*/ diff --git a/zoomables/proguard-rules.pro b/zoomables/proguard-rules.pro index 481bb43..ff59496 100644 --- a/zoomables/proguard-rules.pro +++ b/zoomables/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html From 0bb03cd69014ea8e1ee009138695c1caef2ae088 Mon Sep 17 00:00:00 2001 From: "Mr. Pine" Date: Mon, 27 Mar 2023 17:29:13 +0200 Subject: [PATCH 3/6] Dependency Updates --- build.gradle.kts | 4 +- gradle/libs.versions.toml | 8 +- zoomables/build.gradle.kts | 8 +- .../kotlin/de/mr_pine/zoomables/Zoomables.kt | 201 ++++++++---------- 4 files changed, 102 insertions(+), 119 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 50ec4f6..e57c3f3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,9 +2,9 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.android.library) apply false + alias(libs.plugins.dokka) apply false kotlin("android") version libs.versions.kotlin.get() apply false - id("io.github.gradle-nexus.publish-plugin") version "1.1.0" - alias(libs.plugins.dokka) + id("io.github.gradle-nexus.publish-plugin") version "1.3.0" } tasks.create("clean") { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e5fe657..fd227eb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,10 +1,10 @@ [versions] -composeCompiler = "1.1.1" -compose = "1.1.1" -ktx = "1.7.0" +composeCompiler = "1.4.4" +compose = "1.4.0" +ktx = "1.9.0" library = "7.4.2" application = "7.4.2" -kotlin = "1.6.10" +kotlin = "1.8.10" [libraries] androidx-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "ktx" } diff --git a/zoomables/build.gradle.kts b/zoomables/build.gradle.kts index ba53d33..1528f73 100644 --- a/zoomables/build.gradle.kts +++ b/zoomables/build.gradle.kts @@ -4,14 +4,14 @@ plugins { alias(libs.plugins.android.library) kotlin("android") `maven-publish` + alias(libs.plugins.dokka) } android { - compileSdk = 31 + compileSdk = 33 defaultConfig { minSdk = 22 - targetSdk = 31 } buildTypes { @@ -53,8 +53,8 @@ dependencies { implementation(libs.compose.ui.util) implementation(libs.compose.material) testImplementation("junit:junit:4.13.2") - androidTestImplementation("androidx.test.ext:junit:1.1.3") - androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") + androidTestImplementation("androidx.test.ext:junit:1.1.5") + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") } /*ext { diff --git a/zoomables/src/main/kotlin/de/mr_pine/zoomables/Zoomables.kt b/zoomables/src/main/kotlin/de/mr_pine/zoomables/Zoomables.kt index e9edc98..8c1801a 100644 --- a/zoomables/src/main/kotlin/de/mr_pine/zoomables/Zoomables.kt +++ b/zoomables/src/main/kotlin/de/mr_pine/zoomables/Zoomables.kt @@ -57,7 +57,7 @@ public fun Zoomable( var composableCenter by remember { mutableStateOf(Offset.Zero) } var transformOffset by remember { mutableStateOf(Offset.Zero) } - val doubleTapFunction = onDoubleTap?: { + val doubleTapFunction = onDoubleTap ?: { if (zoomableState.scale.value != 1f) { coroutineScope.launch { zoomableState.animateBy( @@ -70,7 +70,6 @@ public fun Zoomable( } else { coroutineScope.launch { zoomableState.animateZoomToPosition(2f, position = it, composableCenter) - //zoomableState.animateZoomBy(2f) } Unit } @@ -82,7 +81,8 @@ public fun Zoomable( zoom: Float, transformRotation: Float ) { - val rotationChange = if(zoomableState.rotationBehavior == ZoomableState.Rotation.DISABLED) 0f else transformRotation + val rotationChange = + if (zoomableState.rotationBehavior == ZoomableState.Rotation.DISABLED) 0f else transformRotation val tempOffset = zoomableState.offset.value + pan @@ -103,8 +103,7 @@ public fun Zoomable( val x1 = cos(alpha1) * hyp1 val y1 = sin(alpha1) * hyp1 - transformOffset = - centroid - (composableCenter - tempOffset) - Offset(x1.toFloat(), y1.toFloat()) + transformOffset = centroid - (composableCenter - tempOffset) - Offset(x1.toFloat(), y1.toFloat()) coroutineScope.launch { zoomableState.transform { @@ -118,131 +117,115 @@ public fun Zoomable( } - Box( - Modifier - .pointerInput(Unit) { - detectTapGestures( - onDoubleTap = doubleTapFunction - ) - } - .pointerInput(Unit) { - forEachGesture { - awaitPointerEventScope { - var transformRotation = 0f - var zoom = 1f - var pan = Offset.Zero - var pastTouchSlop = false - val touchSlop = viewConfiguration.touchSlop - var lockedToPanZoom = false - var drag: PointerInputChange? - var overSlop = Offset.Zero + Box(Modifier + .pointerInput(Unit) { + detectTapGestures( + onDoubleTap = doubleTapFunction + ) + } + .pointerInput(Unit) { + awaitEachGesture { + var transformRotation = 0f + var zoom = 1f + var pan = Offset.Zero + var pastTouchSlop = false + val touchSlop = viewConfiguration.touchSlop + var lockedToPanZoom = false + var drag: PointerInputChange? + var overSlop = Offset.Zero - val down = awaitFirstDown(requireUnconsumed = false) + val down = awaitFirstDown(requireUnconsumed = false) - var transformEventCounter = 0 - do { - val event = awaitPointerEvent() - val canceled = event.changes.fastAny { it.positionChangeConsumed() } - var relevant = true - if (event.changes.size > 1) { - if (!canceled) { - val zoomChange = event.calculateZoom() - val rotationChange = event.calculateRotation() - val panChange = event.calculatePan() + var transformEventCounter = 0 + do { + val event = awaitPointerEvent() + val canceled = event.changes.fastAny { it.isConsumed } + var relevant = true + if (event.changes.size > 1) { + if (!canceled) { + val zoomChange = event.calculateZoom() + val rotationChange = event.calculateRotation() + val panChange = event.calculatePan() - if (!pastTouchSlop) { - zoom *= zoomChange - transformRotation += rotationChange - pan += panChange + if (!pastTouchSlop) { + zoom *= zoomChange + transformRotation += rotationChange + pan += panChange - val centroidSize = - event.calculateCentroidSize(useCurrent = false) - val zoomMotion = abs(1 - zoom) * centroidSize - val rotationMotion = - abs(transformRotation * PI.toFloat() * centroidSize / 180f) - val panMotion = pan.getDistance() + val centroidSize = event.calculateCentroidSize(useCurrent = false) + val zoomMotion = abs(1 - zoom) * centroidSize + val rotationMotion = + abs(transformRotation * PI.toFloat() * centroidSize / 180f) + val panMotion = pan.getDistance() - if (zoomMotion > touchSlop || - rotationMotion > touchSlop || - panMotion > touchSlop - ) { - pastTouchSlop = true - lockedToPanZoom = - zoomableState.rotationBehavior == ZoomableState.Rotation.LOCK_ROTATION_ON_ZOOM_PAN && rotationMotion < touchSlop - } - } + if (zoomMotion > touchSlop || rotationMotion > touchSlop || panMotion > touchSlop) { + pastTouchSlop = true + lockedToPanZoom = + zoomableState.rotationBehavior == ZoomableState.Rotation.LOCK_ROTATION_ON_ZOOM_PAN && rotationMotion < touchSlop + } + } - if (pastTouchSlop) { - val eventCentroid = - event.calculateCentroid(useCurrent = false) - val effectiveRotation = - if (lockedToPanZoom) 0f else rotationChange - if (effectiveRotation != 0f || - zoomChange != 1f || - panChange != Offset.Zero - ) { - onTransformGesture( - eventCentroid, - panChange, - zoomChange, - effectiveRotation - ) - } - event.changes.fastForEach { - if (it.positionChanged()) { - it.consumeAllChanges() - } - } + if (pastTouchSlop) { + val eventCentroid = event.calculateCentroid(useCurrent = false) + val effectiveRotation = if (lockedToPanZoom) 0f else rotationChange + if (effectiveRotation != 0f || zoomChange != 1f || panChange != Offset.Zero) { + onTransformGesture( + eventCentroid, panChange, zoomChange, effectiveRotation + ) + } + event.changes.fastForEach { + if (it.positionChanged()) { + it.consume() } } - } else if (transformEventCounter > 3) relevant = false - transformEventCounter++ - } while (!canceled && event.changes.fastAny { it.pressed } && relevant) + } + } + } else if (transformEventCounter > 3) relevant = false + transformEventCounter++ + } while (!canceled && event.changes.fastAny { it.pressed } && relevant) - if (zoomableState.dragGesturesEnabled()) { - do { - awaitPointerEvent() - drag = awaitTouchSlopOrCancellation(down.id) { change, over -> - change.consumePositionChange() - overSlop = over + if (zoomableState.dragGesturesEnabled()) { + do { + awaitPointerEvent() + drag = awaitTouchSlopOrCancellation(down.id) { change, over -> + if (change.positionChange() != Offset.Zero) change.consume() + overSlop = over + } + } while (drag != null && !drag.isConsumed) + if (drag != null) { + dragOffset = Offset.Zero + if (zoomableState.scale.value !in 0.92f..1.08f) { + coroutineScope.launch { + zoomableState.transform { + transformBy(1f, overSlop, 0f) } - } while (drag != null && !drag.positionChangeConsumed()) - if (drag != null) { - dragOffset = Offset.Zero + } + } else { + dragOffset += overSlop + } + if (drag(drag.id) { if (zoomableState.scale.value !in 0.92f..1.08f) { - coroutineScope.launch { - zoomableState.transform { - transformBy(1f, overSlop, 0f) - } - } + zoomableState.offset.value += it.positionChange() } else { - dragOffset += overSlop + dragOffset += it.positionChange() } - if (drag(drag.id) { - if (zoomableState.scale.value !in 0.92f..1.08f) { - zoomableState.offset.value += it.positionChange() - } else { - dragOffset += it.positionChange() - } - it.consumePositionChange() - } - ) { - if (zoomableState.scale.value in 0.92f..1.08f) { - val offsetX = dragOffset.x - if (offsetX > minimumSwipeDistance) { - onSwipeRight() + if (it.positionChange() != Offset.Zero) it.consume() + }) { + if (zoomableState.scale.value in 0.92f..1.08f) { + val offsetX = dragOffset.x + if (offsetX > minimumSwipeDistance) { + onSwipeRight() - } else if (offsetX < -minimumSwipeDistance) { - onSwipeLeft() - } - } + } else if (offsetX < -minimumSwipeDistance) { + onSwipeLeft() } } } } } } + } ) { Box( modifier = Modifier From 074f48c85214e51cdaa4de72790b509e09372a10 Mon Sep 17 00:00:00 2001 From: "Mr. Pine" Date: Tue, 28 Mar 2023 23:54:53 +0200 Subject: [PATCH 4/6] Introduce DragGestureMode and fix #11 --- .../de/mr_pine/zoomables/DragGestureMode.kt | 7 + .../de/mr_pine/zoomables/ZoomableImage.kt | 39 +++- .../de/mr_pine/zoomables/ZoomableState.kt | 6 +- .../kotlin/de/mr_pine/zoomables/Zoomables.kt | 211 +++++++++--------- 4 files changed, 145 insertions(+), 118 deletions(-) create mode 100644 zoomables/src/main/kotlin/de/mr_pine/zoomables/DragGestureMode.kt diff --git a/zoomables/src/main/kotlin/de/mr_pine/zoomables/DragGestureMode.kt b/zoomables/src/main/kotlin/de/mr_pine/zoomables/DragGestureMode.kt new file mode 100644 index 0000000..5cd454b --- /dev/null +++ b/zoomables/src/main/kotlin/de/mr_pine/zoomables/DragGestureMode.kt @@ -0,0 +1,7 @@ +package de.mr_pine.zoomables + +public enum class DragGestureMode { + DISABLED, + PAN, + SWIPE_GESTURES; +} \ No newline at end of file diff --git a/zoomables/src/main/kotlin/de/mr_pine/zoomables/ZoomableImage.kt b/zoomables/src/main/kotlin/de/mr_pine/zoomables/ZoomableImage.kt index ef31f84..c64dbc7 100644 --- a/zoomables/src/main/kotlin/de/mr_pine/zoomables/ZoomableImage.kt +++ b/zoomables/src/main/kotlin/de/mr_pine/zoomables/ZoomableImage.kt @@ -23,7 +23,7 @@ import kotlinx.coroutines.CoroutineScope * @param contentDescription text for accessibility see [Image] for further info * @param onSwipeLeft Optional function to run when user swipes from right to left - does nothing by default * @param onSwipeRight Optional function to run when user swipes from left to right - does nothing by default - * @param dragGesturesEnabled A function with a [ZoomableState] scope that returns a boolean value to enable/disable dragging gestures (swiping and panning). Returns `true` by default. *Note*: For some use cases it may be required that only panning is possible. Use `{!notTransformed}` in that case + * @param dragGestureMode A function with a [ZoomableState] scope that returns a boolean value to enable/disable dragging gestures (swiping and panning). Returns `true` by default. *Note*: For some use cases it may be required that only panning is possible. Use `{!notTransformed}` in that case * @param onDoubleTap Optional function to run when user double taps. Zooms in by 2x when scale is currently 1 and zooms out to scale = 1 when zoomed in when null (default) */ @Composable @@ -35,7 +35,7 @@ public fun ZoomableImage( contentDescription: String? = null, onSwipeLeft: () -> Unit = {}, onSwipeRight: () -> Unit = {}, - dragGesturesEnabled: ZoomableState.() -> Boolean = { true }, + dragGestureMode: ZoomableState.() -> DragGestureMode = { if (zoomed) DragGestureMode.SWIPE_GESTURES else DragGestureMode.PAN }, onDoubleTap: ((Offset) -> Unit)? = null ) { Zoomable( @@ -43,7 +43,7 @@ public fun ZoomableImage( zoomableState = zoomableState, onSwipeLeft = onSwipeLeft, onSwipeRight = onSwipeRight, - dragGesturesEnabled = dragGesturesEnabled, + dragGestureMode = dragGestureMode, onDoubleTap = onDoubleTap ) { Image(bitmap = bitmap, contentDescription = contentDescription, modifier = modifier) @@ -60,7 +60,7 @@ public fun ZoomableImage( * @param contentDescription text for accessibility see [Image] for further info * @param onSwipeLeft Optional function to run when user swipes from right to left - does nothing by default * @param onSwipeRight Optional function to run when user swipes from left to right - does nothing by default - * @param dragGesturesEnabled A function with a [ZoomableState] scope that returns a boolean value to enable/disable dragging gestures (swiping and panning). Returns `true` by default. *Note*: For some use cases it may be required that only panning is possible. Use `{!notTransformed}` in that case + * @param dragGestureMode A function with a [ZoomableState] scope that returns a boolean value to enable/disable dragging gestures (swiping and panning). Returns `true` by default. *Note*: For some use cases it may be required that only panning is possible. Use `{!notTransformed}` in that case * @param onDoubleTap Optional function to run when user double taps. Zooms in by 2x when scale is currently 1 and zooms out to scale = 1 when zoomed in when null (default) */ @Composable @@ -72,7 +72,7 @@ public fun ZoomableImage( contentDescription: String? = null, onSwipeLeft: () -> Unit = {}, onSwipeRight: () -> Unit = {}, - dragGesturesEnabled: ZoomableState.() -> Boolean = { true }, + dragGestureMode: ZoomableState.() -> DragGestureMode = { if (zoomed) DragGestureMode.SWIPE_GESTURES else DragGestureMode.PAN }, onDoubleTap: ((Offset) -> Unit)? = null ) { Zoomable( @@ -80,7 +80,7 @@ public fun ZoomableImage( zoomableState = zoomableState, onSwipeLeft = onSwipeLeft, onSwipeRight = onSwipeRight, - dragGesturesEnabled = dragGesturesEnabled, + dragGestureMode = dragGestureMode, onDoubleTap = onDoubleTap ) { Image( @@ -101,7 +101,7 @@ public fun ZoomableImage( * @param contentDescription text for accessibility see [Image] for further info * @param onSwipeLeft Optional function to run when user swipes from right to left - does nothing by default * @param onSwipeRight Optional function to run when user swipes from left to right - does nothing by default - * @param dragGesturesEnabled A function with a [ZoomableState] scope that returns a boolean value to enable/disable dragging gestures (swiping and panning). Returns `true` by default. *Note*: For some use cases it may be required that only panning is possible. Use `{!notTransformed}` in that case + * @param dragGestureMode A function with a [ZoomableState] scope that returns a boolean value to enable/disable dragging gestures (swiping and panning). Returns `true` by default. *Note*: For some use cases it may be required that only panning is possible. Use `{!notTransformed}` in that case * @param onDoubleTap Optional function to run when user double taps. Zooms in by 2x when scale is currently 1 and zooms out to scale = 1 when zoomed in when null (default) */ @Composable @@ -113,7 +113,7 @@ public fun ZoomableImage( contentDescription: String? = null, onSwipeLeft: () -> Unit = {}, onSwipeRight: () -> Unit = {}, - dragGesturesEnabled: ZoomableState.() -> Boolean = { true }, + dragGestureMode: ZoomableState.() -> DragGestureMode = { if (zoomed) DragGestureMode.SWIPE_GESTURES else DragGestureMode.PAN }, onDoubleTap: ((Offset) -> Unit)? = null ) { Zoomable( @@ -121,7 +121,7 @@ public fun ZoomableImage( zoomableState = zoomableState, onSwipeLeft = onSwipeLeft, onSwipeRight = onSwipeRight, - dragGesturesEnabled = dragGesturesEnabled, + dragGestureMode = dragGestureMode, onDoubleTap = onDoubleTap ) { Image(painter = painter, contentDescription = contentDescription, modifier = modifier) @@ -147,7 +147,12 @@ public fun EasyZoomableImage( ) { val coroutineScope = rememberCoroutineScope() val zoomableState = rememberZoomableState() - Zoomable(coroutineScope = coroutineScope, zoomableState = zoomableState, onSwipeLeft = onSwipeLeft, onSwipeRight = onSwipeRight) { + Zoomable( + coroutineScope = coroutineScope, + zoomableState = zoomableState, + onSwipeLeft = onSwipeLeft, + onSwipeRight = onSwipeRight + ) { Image(bitmap = bitmap, contentDescription = contentDescription, modifier = modifier) } } @@ -172,7 +177,12 @@ public fun EasyZoomableImage( val coroutineScope = rememberCoroutineScope() val zoomableState = rememberZoomableState() - Zoomable(coroutineScope = coroutineScope, zoomableState = zoomableState, onSwipeLeft = onSwipeLeft, onSwipeRight = onSwipeRight) { + Zoomable( + coroutineScope = coroutineScope, + zoomableState = zoomableState, + onSwipeLeft = onSwipeLeft, + onSwipeRight = onSwipeRight + ) { Image( imageVector = imageVector, contentDescription = contentDescription, @@ -200,7 +210,12 @@ public fun EasyZoomableImage( ) { val coroutineScope = rememberCoroutineScope() val zoomableState = rememberZoomableState() - Zoomable(coroutineScope = coroutineScope, zoomableState = zoomableState, onSwipeLeft = onSwipeLeft, onSwipeRight = onSwipeRight) { + Zoomable( + coroutineScope = coroutineScope, + zoomableState = zoomableState, + onSwipeLeft = onSwipeLeft, + onSwipeRight = onSwipeRight + ) { Image(painter = painter, contentDescription = contentDescription, modifier = modifier) } } diff --git a/zoomables/src/main/kotlin/de/mr_pine/zoomables/ZoomableState.kt b/zoomables/src/main/kotlin/de/mr_pine/zoomables/ZoomableState.kt index 54a49d5..3cb9ef9 100644 --- a/zoomables/src/main/kotlin/de/mr_pine/zoomables/ZoomableState.kt +++ b/zoomables/src/main/kotlin/de/mr_pine/zoomables/ZoomableState.kt @@ -17,6 +17,7 @@ import kotlin.math.cos import kotlin.math.sin import kotlin.math.sqrt +private const val zoomedThreshold = 1.0E-3f /** * An implementation of [TransformableState] containing the values for the current [scale], [offset] and [rotation]. It's normally obtained using [rememberTransformableState] @@ -43,9 +44,12 @@ public class ZoomableState( onTransformation: ZoomableState.(zoomChange: Float, panChange: Offset, rotationChange: Float) -> Unit ) : TransformableState { + public val zoomed: Boolean + get() = scale.value in (1 - zoomedThreshold)..(1 + zoomedThreshold) + public val notTransformed: Boolean get() { - return scale.value in (1 - 1.0E-3f)..(1 + 1.0E-3f) && offset.value.getDistanceSquared() in -1.0E-6f..1.0E-6f && rotation.value in -1.0E-3f..1.0E-3f + return zoomed && offset.value.getDistanceSquared() in -1.0E-6f..1.0E-6f && rotation.value in -1.0E-3f..1.0E-3f } private val transformScope: TransformScope = object : TransformScope { diff --git a/zoomables/src/main/kotlin/de/mr_pine/zoomables/Zoomables.kt b/zoomables/src/main/kotlin/de/mr_pine/zoomables/Zoomables.kt index 8c1801a..466910a 100644 --- a/zoomables/src/main/kotlin/de/mr_pine/zoomables/Zoomables.kt +++ b/zoomables/src/main/kotlin/de/mr_pine/zoomables/Zoomables.kt @@ -12,7 +12,10 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.graphicsLayer -import androidx.compose.ui.input.pointer.* +import androidx.compose.ui.input.pointer.PointerInputChange +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.input.pointer.positionChange +import androidx.compose.ui.input.pointer.positionChanged import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.util.fastAny @@ -34,18 +37,17 @@ import kotlin.math.* * * @param coroutineScope used for smooth asynchronous zoom/pan/rotation animations * @param zoomableState Contains the current transform states - obtained via [rememberZoomableState] - * @param dragGesturesEnabled A function with a [ZoomableState] scope that returns a boolean value to enable/disable dragging gestures (swiping and panning). Returns `true` by default. *Note*: For some use cases it may be required that only panning is possible. Use `{!notTransformed}` in that case + * @param dragGestureMode A function with a [ZoomableState] scope that returns a boolean value to enable/disable dragging gestures (swiping and panning). Returns `true` by default. *Note*: For some use cases it may be required that only panning is possible. Use `{!notTransformed}` in that case * @param onSwipeLeft Optional function to run when user swipes from right to left - does nothing by default * @param onSwipeRight Optional function to run when user swipes from left to right - does nothing by default * @param minimumSwipeDistance Minimum distance the user has to travel on the screen for it to count as swiping * @param onDoubleTap Optional function to run when user double taps. Zooms in by 2x to the touch point when scale is currently 1 and zooms out to scale = 1 when zoomed in when `null` (default) */ - @Composable public fun Zoomable( coroutineScope: CoroutineScope, zoomableState: ZoomableState, - dragGesturesEnabled: ZoomableState.() -> Boolean = { true }, + dragGestureMode: ZoomableState.() -> DragGestureMode = { if (zoomed) DragGestureMode.SWIPE_GESTURES else DragGestureMode.PAN }, onSwipeLeft: () -> Unit = {}, onSwipeRight: () -> Unit = {}, minimumSwipeDistance: Int = 0, @@ -76,10 +78,7 @@ public fun Zoomable( } fun onTransformGesture( - centroid: Offset, - pan: Offset, - zoom: Float, - transformRotation: Float + centroid: Offset, pan: Offset, zoom: Float, transformRotation: Float ) { val rotationChange = if (zoomableState.rotationBehavior == ZoomableState.Rotation.DISABLED) 0f else transformRotation @@ -103,7 +102,8 @@ public fun Zoomable( val x1 = cos(alpha1) * hyp1 val y1 = sin(alpha1) * hyp1 - transformOffset = centroid - (composableCenter - tempOffset) - Offset(x1.toFloat(), y1.toFloat()) + transformOffset = + centroid - (composableCenter - tempOffset) - Offset(x1.toFloat(), y1.toFloat()) coroutineScope.launch { zoomableState.transform { @@ -117,116 +117,119 @@ public fun Zoomable( } - Box(Modifier - .pointerInput(Unit) { - detectTapGestures( - onDoubleTap = doubleTapFunction - ) - } - .pointerInput(Unit) { - awaitEachGesture { - var transformRotation = 0f - var zoom = 1f - var pan = Offset.Zero - var pastTouchSlop = false - val touchSlop = viewConfiguration.touchSlop - var lockedToPanZoom = false - var drag: PointerInputChange? - var overSlop = Offset.Zero - - val down = awaitFirstDown(requireUnconsumed = false) - + Box( + Modifier + .pointerInput(Unit) { + detectTapGestures( + onDoubleTap = doubleTapFunction + ) + } + .pointerInput(Unit) { + awaitEachGesture { + var transformRotation = 0f + var zoom = 1f + var pan = Offset.Zero + var pastTouchSlop = false + val touchSlop = viewConfiguration.touchSlop + var lockedToPanZoom = false + var drag: PointerInputChange? + var overSlop = Offset.Zero - var transformEventCounter = 0 - do { - val event = awaitPointerEvent() - val canceled = event.changes.fastAny { it.isConsumed } - var relevant = true - if (event.changes.size > 1) { - if (!canceled) { - val zoomChange = event.calculateZoom() - val rotationChange = event.calculateRotation() - val panChange = event.calculatePan() + var transformEventCounter = 0 + do { + val event = awaitPointerEvent() + val canceled = event.changes.fastAny { it.isConsumed } + var relevant = true + if (event.changes.size > 1) { + if (!canceled) { + val zoomChange = event.calculateZoom() + val rotationChange = event.calculateRotation() + val panChange = event.calculatePan() - if (!pastTouchSlop) { - zoom *= zoomChange - transformRotation += rotationChange - pan += panChange + if (!pastTouchSlop) { + zoom *= zoomChange + transformRotation += rotationChange + pan += panChange - val centroidSize = event.calculateCentroidSize(useCurrent = false) - val zoomMotion = abs(1 - zoom) * centroidSize - val rotationMotion = - abs(transformRotation * PI.toFloat() * centroidSize / 180f) - val panMotion = pan.getDistance() + val centroidSize = + event.calculateCentroidSize(useCurrent = false) + val zoomMotion = abs(1 - zoom) * centroidSize + val rotationMotion = + abs(transformRotation * PI.toFloat() * centroidSize / 180f) + val panMotion = pan.getDistance() - if (zoomMotion > touchSlop || rotationMotion > touchSlop || panMotion > touchSlop) { - pastTouchSlop = true - lockedToPanZoom = - zoomableState.rotationBehavior == ZoomableState.Rotation.LOCK_ROTATION_ON_ZOOM_PAN && rotationMotion < touchSlop + if (zoomMotion > touchSlop || rotationMotion > touchSlop || panMotion > touchSlop) { + pastTouchSlop = true + lockedToPanZoom = + zoomableState.rotationBehavior == ZoomableState.Rotation.LOCK_ROTATION_ON_ZOOM_PAN && rotationMotion < touchSlop + } } - } - if (pastTouchSlop) { - val eventCentroid = event.calculateCentroid(useCurrent = false) - val effectiveRotation = if (lockedToPanZoom) 0f else rotationChange - if (effectiveRotation != 0f || zoomChange != 1f || panChange != Offset.Zero) { - onTransformGesture( - eventCentroid, panChange, zoomChange, effectiveRotation - ) - } - event.changes.fastForEach { - if (it.positionChanged()) { - it.consume() + if (pastTouchSlop) { + val eventCentroid = event.calculateCentroid(useCurrent = false) + val effectiveRotation = + if (lockedToPanZoom) 0f else rotationChange + if (effectiveRotation != 0f || zoomChange != 1f || panChange != Offset.Zero) { + onTransformGesture( + eventCentroid, panChange, zoomChange, effectiveRotation + ) + } + event.changes.fastForEach { + if (it.positionChanged()) { + it.consume() + } } } } - } - } else if (transformEventCounter > 3) relevant = false - transformEventCounter++ - } while (!canceled && event.changes.fastAny { it.pressed } && relevant) + } else if (transformEventCounter > 3) relevant = false + transformEventCounter++ + } while (!canceled && event.changes.fastAny { it.pressed } && relevant) - if (zoomableState.dragGesturesEnabled()) { - do { - awaitPointerEvent() - drag = awaitTouchSlopOrCancellation(down.id) { change, over -> - if (change.positionChange() != Offset.Zero) change.consume() - overSlop = over - } - } while (drag != null && !drag.isConsumed) - if (drag != null) { - dragOffset = Offset.Zero - if (zoomableState.scale.value !in 0.92f..1.08f) { - coroutineScope.launch { - zoomableState.transform { - transformBy(1f, overSlop, 0f) + if (zoomableState.dragGestureMode() != DragGestureMode.DISABLED) { + do { + val event = awaitPointerEvent() + drag = event.changes.firstOrNull()?.id?.let { pointerId -> + awaitTouchSlopOrCancellation(pointerId) { change, over -> + if (change.positionChange() != Offset.Zero) change.consume() + overSlop = over } } - } else { - dragOffset += overSlop - } - if (drag(drag.id) { - if (zoomableState.scale.value !in 0.92f..1.08f) { - zoomableState.offset.value += it.positionChange() - } else { - dragOffset += it.positionChange() + } while (drag != null && !drag.isConsumed) + if (drag != null) { + dragOffset = Offset.Zero + when (zoomableState.dragGestureMode()) { + DragGestureMode.PAN -> coroutineScope.launch { + zoomableState.transform { + transformBy(1f, overSlop, 0f) + } } - if (it.positionChange() != Offset.Zero) it.consume() - }) { - if (zoomableState.scale.value in 0.92f..1.08f) { - val offsetX = dragOffset.x - if (offsetX > minimumSwipeDistance) { - onSwipeRight() + DragGestureMode.SWIPE_GESTURES -> dragOffset += overSlop + else -> {} + } + if (drag(drag.id) { + when (zoomableState.dragGestureMode()) { + DragGestureMode.PAN -> { + zoomableState.offset.value += it.positionChange() + } + DragGestureMode.SWIPE_GESTURES -> dragOffset += it.positionChange() + else -> {} + } + if (it.positionChange() != Offset.Zero) it.consume() + }) { + if (zoomableState.dragGestureMode() == DragGestureMode.SWIPE_GESTURES) { + val offsetX = dragOffset.x + if (offsetX > minimumSwipeDistance) { + onSwipeRight() - } else if (offsetX < -minimumSwipeDistance) { - onSwipeLeft() + } else if (offsetX < -minimumSwipeDistance) { + onSwipeLeft() + } } } } } } - } - } - ) { + }) { Box( modifier = Modifier .clip(RectangleShape) @@ -242,11 +245,9 @@ public fun Zoomable( rotationZ = zoomableState.rotation.value ) .onGloballyPositioned { coordinates -> - val localOffset = - Offset( - coordinates.size.width.toFloat() / 2, - coordinates.size.height.toFloat() / 2 - ) + val localOffset = Offset( + coordinates.size.width.toFloat() / 2, coordinates.size.height.toFloat() / 2 + ) val windowOffset = coordinates.localToWindow(localOffset) composableCenter = coordinates.parentLayoutCoordinates?.windowToLocal(windowOffset) From c98f669f62228e88e241e9f3bcbdb4eed38f0340 Mon Sep 17 00:00:00 2001 From: "Mr. Pine" Date: Wed, 29 Mar 2023 12:38:02 +0200 Subject: [PATCH 5/6] Docs, change notTransformed to transformed --- .../main/kotlin/de/mr_pine/zoomables/ZoomableImage.kt | 6 +++--- .../main/kotlin/de/mr_pine/zoomables/ZoomableState.kt | 11 ++++++----- .../src/main/kotlin/de/mr_pine/zoomables/Zoomables.kt | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/zoomables/src/main/kotlin/de/mr_pine/zoomables/ZoomableImage.kt b/zoomables/src/main/kotlin/de/mr_pine/zoomables/ZoomableImage.kt index c64dbc7..07b9e74 100644 --- a/zoomables/src/main/kotlin/de/mr_pine/zoomables/ZoomableImage.kt +++ b/zoomables/src/main/kotlin/de/mr_pine/zoomables/ZoomableImage.kt @@ -23,7 +23,7 @@ import kotlinx.coroutines.CoroutineScope * @param contentDescription text for accessibility see [Image] for further info * @param onSwipeLeft Optional function to run when user swipes from right to left - does nothing by default * @param onSwipeRight Optional function to run when user swipes from left to right - does nothing by default - * @param dragGestureMode A function with a [ZoomableState] scope that returns a boolean value to enable/disable dragging gestures (swiping and panning). Returns `true` by default. *Note*: For some use cases it may be required that only panning is possible. Use `{!notTransformed}` in that case + * @param dragGestureMode A function with a [ZoomableState] scope that returns a [DragGestureMode] value that signals which drag gesture should currently be active. By default panning is enabled when zoomed, else swipe gestures are enabled. * @param onDoubleTap Optional function to run when user double taps. Zooms in by 2x when scale is currently 1 and zooms out to scale = 1 when zoomed in when null (default) */ @Composable @@ -60,7 +60,7 @@ public fun ZoomableImage( * @param contentDescription text for accessibility see [Image] for further info * @param onSwipeLeft Optional function to run when user swipes from right to left - does nothing by default * @param onSwipeRight Optional function to run when user swipes from left to right - does nothing by default - * @param dragGestureMode A function with a [ZoomableState] scope that returns a boolean value to enable/disable dragging gestures (swiping and panning). Returns `true` by default. *Note*: For some use cases it may be required that only panning is possible. Use `{!notTransformed}` in that case + * @param dragGestureMode A function with a [ZoomableState] scope that returns a [DragGestureMode] value that signals which drag gesture should currently be active. By default panning is enabled when zoomed, else swipe gestures are enabled. * @param onDoubleTap Optional function to run when user double taps. Zooms in by 2x when scale is currently 1 and zooms out to scale = 1 when zoomed in when null (default) */ @Composable @@ -101,7 +101,7 @@ public fun ZoomableImage( * @param contentDescription text for accessibility see [Image] for further info * @param onSwipeLeft Optional function to run when user swipes from right to left - does nothing by default * @param onSwipeRight Optional function to run when user swipes from left to right - does nothing by default - * @param dragGestureMode A function with a [ZoomableState] scope that returns a boolean value to enable/disable dragging gestures (swiping and panning). Returns `true` by default. *Note*: For some use cases it may be required that only panning is possible. Use `{!notTransformed}` in that case + * @param dragGestureMode A function with a [ZoomableState] scope that returns a [DragGestureMode] value that signals which drag gesture should currently be active. By default panning is enabled when zoomed, else swipe gestures are enabled. * @param onDoubleTap Optional function to run when user double taps. Zooms in by 2x when scale is currently 1 and zooms out to scale = 1 when zoomed in when null (default) */ @Composable diff --git a/zoomables/src/main/kotlin/de/mr_pine/zoomables/ZoomableState.kt b/zoomables/src/main/kotlin/de/mr_pine/zoomables/ZoomableState.kt index 3cb9ef9..dd83b0d 100644 --- a/zoomables/src/main/kotlin/de/mr_pine/zoomables/ZoomableState.kt +++ b/zoomables/src/main/kotlin/de/mr_pine/zoomables/ZoomableState.kt @@ -23,6 +23,8 @@ private const val zoomedThreshold = 1.0E-3f * An implementation of [TransformableState] containing the values for the current [scale], [offset] and [rotation]. It's normally obtained using [rememberTransformableState] * Other than [TransformableState] obtained by [rememberTransformableState], [ZoomableState] exposes [scale], [offset] and [rotation] * + * As + * * @param scale [MutableState]<[Float]> of the scale this state is initialized with * @param offset [MutableState]<[Offset]> of the offset this state is initialized with * @param rotation [MutableState]<[Float]> in degrees of the rotation this state is initialized with @@ -34,7 +36,8 @@ private const val zoomedThreshold = 1.0E-3f * @property scale The current scale as [MutableState]<[Float]> * @property offset The current offset as [MutableState]<[Offset]> * @property rotation The current rotation in degrees as [MutableState]<[Float]> - * @property notTransformed `true` if [scale] is `1`, [offset] is [Offset.Zero] and [rotation] is `0` + * @property transformed `false` if [scale] is `1`, [offset] is [Offset.Zero] and [rotation] is `0` + * @property zoomed Whether the content is zoomed (in or out) */ public class ZoomableState( public var scale: MutableState, @@ -47,10 +50,8 @@ public class ZoomableState( public val zoomed: Boolean get() = scale.value in (1 - zoomedThreshold)..(1 + zoomedThreshold) - public val notTransformed: Boolean - get() { - return zoomed && offset.value.getDistanceSquared() in -1.0E-6f..1.0E-6f && rotation.value in -1.0E-3f..1.0E-3f - } + public val transformed: Boolean + get() = zoomed || offset.value.getDistanceSquared() !in -1.0E-6f..1.0E-6f || rotation.value !in -1.0E-3f..1.0E-3f private val transformScope: TransformScope = object : TransformScope { override fun transformBy(zoomChange: Float, panChange: Offset, rotationChange: Float) = diff --git a/zoomables/src/main/kotlin/de/mr_pine/zoomables/Zoomables.kt b/zoomables/src/main/kotlin/de/mr_pine/zoomables/Zoomables.kt index 466910a..302f52a 100644 --- a/zoomables/src/main/kotlin/de/mr_pine/zoomables/Zoomables.kt +++ b/zoomables/src/main/kotlin/de/mr_pine/zoomables/Zoomables.kt @@ -37,7 +37,7 @@ import kotlin.math.* * * @param coroutineScope used for smooth asynchronous zoom/pan/rotation animations * @param zoomableState Contains the current transform states - obtained via [rememberZoomableState] - * @param dragGestureMode A function with a [ZoomableState] scope that returns a boolean value to enable/disable dragging gestures (swiping and panning). Returns `true` by default. *Note*: For some use cases it may be required that only panning is possible. Use `{!notTransformed}` in that case + * @param dragGestureMode A function with a [ZoomableState] scope that returns a [DragGestureMode] value that signals which drag gesture should currently be active. By default panning is enabled when zoomed, else swipe gestures are enabled. * @param onSwipeLeft Optional function to run when user swipes from right to left - does nothing by default * @param onSwipeRight Optional function to run when user swipes from left to right - does nothing by default * @param minimumSwipeDistance Minimum distance the user has to travel on the screen for it to count as swiping From 36a3ed14f87e8936b6a8e9a88d021dcee023db96 Mon Sep 17 00:00:00 2001 From: "Mr. Pine" Date: Wed, 29 Mar 2023 14:32:26 +0200 Subject: [PATCH 6/6] Configure release tasks --- .gitignore | 3 +- .idea/gradle.xml | 1 + build.gradle.kts | 12 +++- buildSrc/build.gradle.kts | 7 ++ buildSrc/src/main/kotlin/PublishData.kt | 7 ++ buildSrc/src/main/kotlin/artifact.kt | 5 ++ scripts/publish-module.gradle | 92 ------------------------- scripts/publish-root.gradle | 34 --------- zoomables/build.gradle.kts | 70 +++++++++++++------ 9 files changed, 81 insertions(+), 150 deletions(-) create mode 100644 buildSrc/build.gradle.kts create mode 100644 buildSrc/src/main/kotlin/PublishData.kt create mode 100644 buildSrc/src/main/kotlin/artifact.kt delete mode 100644 scripts/publish-module.gradle delete mode 100644 scripts/publish-root.gradle diff --git a/.gitignore b/.gitignore index e9df19a..26449cf 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ local.properties /SecretRingKey.gpg /secring.gpg -/zoomables_testing \ No newline at end of file +/zoomables_testing +/buildSrc/src/main/kotlin/myPublishData.kt diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 3a0291d..a47b1ae 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -11,6 +11,7 @@