Skip to content

Commit

Permalink
build: remove dep on SmartToolFactor Compose-Color-Picker-Bundle an…
Browse files Browse the repository at this point in the history
…d import it as a module

* Closes #88
  • Loading branch information
kaajjo committed Jul 3, 2024
1 parent d22a167 commit 8f6ef5a
Show file tree
Hide file tree
Showing 31 changed files with 4,329 additions and 18 deletions.
1 change: 1 addition & 0 deletions Color-Picker/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
51 changes: 51 additions & 0 deletions Color-Picker/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
plugins {
alias(libs.plugins.androidLibrary)
alias(libs.plugins.kotlinAndroid)
}

android {
namespace = "com.kaajjo.color_picker"
compileSdk = 34

defaultConfig {
minSdk = 26

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.14"
}
kotlinOptions {
jvmTarget = "17"
}
}

dependencies {

implementation(libs.core.ktx)
implementation(libs.appcompat)
implementation(libs.material)
implementation(libs.ui)
implementation(libs.foundation.android)
testImplementation(libs.junit)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.espresso.core)
}
Empty file added Color-Picker/consumer-rules.pro
Empty file.
21 changes: 21 additions & 0 deletions Color-Picker/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.kaajjo.color_picker

import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith

/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.kaajjo.color_picker.test", appContext.packageName)
}
}
4 changes: 4 additions & 0 deletions Color-Picker/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
package com.kaajjo.color_picker.extended_gestures

import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.ui.input.pointer.AwaitPointerEventScope
import androidx.compose.ui.input.pointer.PointerEvent
import androidx.compose.ui.input.pointer.PointerEventPass
import androidx.compose.ui.input.pointer.PointerEventPass.Final
import androidx.compose.ui.input.pointer.PointerEventPass.Initial
import androidx.compose.ui.input.pointer.PointerEventPass.Main
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.PointerInputScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

/**
* Reads [awaitFirstDown], and [AwaitPointerEventScope.awaitPointerEvent] to
* get [PointerInputChange] and motion event states
* [onDown], [onMove], and [onUp].
*
* To prevent other pointer functions that call [awaitFirstDown]
* or [AwaitPointerEventScope.awaitPointerEvent]
* (scroll, swipe, detect functions)
* receiving changes call [PointerInputChange.consume] in [onMove] or call
* [PointerInputChange.consume] in [onDown] to prevent events
* that check first pointer interaction.
*
* @param onDown is invoked when first pointer is down initially.
* @param onMove one or multiple pointers are being moved on screen.
* @param onUp last pointer is up
* @param delayAfterDownInMillis is optional delay after [onDown] This delay might be
* required Composables like **Canvas** to process [onDown] before [onMove]
* @param requireUnconsumed is `true` and the first
* down is consumed in the [PointerEventPass.Main] pass, that gesture is ignored.
* @param pass The enumeration of passes where [PointerInputChange]
* traverses up and down the UI tree.
*
* PointerInputChanges traverse throw the hierarchy in the following passes:
*
* 1. [Initial]: Down the tree from ancestor to descendant.
* 2. [Main]: Up the tree from descendant to ancestor.
* 3. [Final]: Down the tree from ancestor to descendant.
*
* These passes serve the following purposes:
*
* 1. Initial: Allows ancestors to consume aspects of [PointerInputChange] before descendants.
* This is where, for example, a scroller may block buttons from getting tapped by other fingers
* once scrolling has started.
* 2. Main: The primary pass where gesture filters should react to and consume aspects of
* [PointerInputChange]s. This is the primary path where descendants will interact with
* [PointerInputChange]s before parents. This allows for buttons to respond to a tap before a
* container of the bottom to respond to a tap.
* 3. Final: This pass is where children can learn what aspects of [PointerInputChange]s were
* consumed by parents during the [Main] pass. For example, this is how a button determines that
* it should no longer respond to fingers lifting off of it because a parent scroller has
* consumed movement in a [PointerInputChange].
*/
suspend fun PointerInputScope.detectMotionEvents(
onDown: (PointerInputChange) -> Unit = {},
onMove: (PointerInputChange) -> Unit = {},
onUp: (PointerInputChange) -> Unit = {},
delayAfterDownInMillis: Long = 0L,
requireUnconsumed: Boolean = true,
pass: PointerEventPass = PointerEventPass.Main
) {

coroutineScope {
awaitEachGesture {

// Wait for at least one pointer to press down, and set first contact position
val down: PointerInputChange = awaitFirstDown(
requireUnconsumed = requireUnconsumed,
pass = pass
)
onDown(down)

var pointer = down
// Main pointer is the one that is down initially
var pointerId = down.id

// If a move event is followed fast enough down is skipped, especially by Canvas
// to prevent it we add delay after first touch
var waitedAfterDown = false

launch {
delay(delayAfterDownInMillis)
waitedAfterDown = true
}

while (true) {

val event: PointerEvent = awaitPointerEvent(pass)

val anyPressed = event.changes.any { it.pressed }

// There are at least one pointer pressed
if (anyPressed) {
// Get pointer that is down, if first pointer is up
// get another and use it if other pointers are also down
// event.changes.first() doesn't return same order
val pointerInputChange =
event.changes.firstOrNull { it.id == pointerId }
?: event.changes.first()

// Next time will check same pointer with this id
pointerId = pointerInputChange.id
pointer = pointerInputChange

if (waitedAfterDown) {
onMove(pointer)
}
} else {
// All of the pointers are up
onUp(pointer)
break
}
}
}
}
}

/**
* Reads [awaitFirstDown], and [AwaitPointerEventScope.awaitPointerEvent] to
* get [PointerInputChange] and motion event states
* [onDown], [onMove], and [onUp]. Unlike overload of this function [onMove] returns
* list of [PointerInputChange] to get data about all pointers that are on the screen.
*
* To prevent other pointer functions that call [awaitFirstDown]
* or [AwaitPointerEventScope.awaitPointerEvent]
* (scroll, swipe, detect functions)
* receiving changes call [PointerInputChange.consume] in [onMove] or call
* [PointerInputChange.consume] in [onDown] to prevent events
* that check first pointer interaction.
*
* @param onDown is invoked when first pointer is down initially.
* @param onMove one or multiple pointers are being moved on screen.
* @param onUp last pointer is up
* @param delayAfterDownInMillis is optional delay after [onDown] This delay might be
* required Composables like **Canvas** to process [onDown] before [onMove]
* @param requireUnconsumed is `true` and the first
* down is consumed in the [PointerEventPass.Main] pass, that gesture is ignored.
* @param pass The enumeration of passes where [PointerInputChange]
* traverses up and down the UI tree.
*
* PointerInputChanges traverse throw the hierarchy in the following passes:
*
* 1. [Initial]: Down the tree from ancestor to descendant.
* 2. [Main]: Up the tree from descendant to ancestor.
* 3. [Final]: Down the tree from ancestor to descendant.
*
* These passes serve the following purposes:
*
* 1. Initial: Allows ancestors to consume aspects of [PointerInputChange] before descendants.
* This is where, for example, a scroller may block buttons from getting tapped by other fingers
* once scrolling has started.
* 2. Main: The primary pass where gesture filters should react to and consume aspects of
* [PointerInputChange]s. This is the primary path where descendants will interact with
* [PointerInputChange]s before parents. This allows for buttons to respond to a tap before a
* container of the bottom to respond to a tap.
* 3. Final: This pass is where children can learn what aspects of [PointerInputChange]s were
* consumed by parents during the [Main] pass. For example, this is how a button determines that
* it should no longer respond to fingers lifting off of it because a parent scroller has
* consumed movement in a [PointerInputChange].
*
*/
suspend fun PointerInputScope.detectMotionEventsAsList(
onDown: (PointerInputChange) -> Unit = {},
onMove: (List<PointerInputChange>) -> Unit = {},
onUp: (PointerInputChange) -> Unit = {},
delayAfterDownInMillis: Long = 0L,
requireUnconsumed: Boolean = true,
pass: PointerEventPass = PointerEventPass.Main
) {

coroutineScope {
awaitEachGesture {
// Wait for at least one pointer to press down, and set first contact position
val down: PointerInputChange = awaitFirstDown(
requireUnconsumed = requireUnconsumed,
pass = pass
)
onDown(down)

var pointer = down
// Main pointer is the one that is down initially
var pointerId = down.id

// If a move event is followed fast enough down is skipped, especially by Canvas
// to prevent it we add delay after first touch
var waitedAfterDown = false

launch {
delay(delayAfterDownInMillis)
waitedAfterDown = true
}

while (true) {

val event: PointerEvent = awaitPointerEvent(pass)

val anyPressed = event.changes.any { it.pressed }

// There are at least one pointer pressed
if (anyPressed) {
// Get pointer that is down, if first pointer is up
// get another and use it if other pointers are also down
// event.changes.first() doesn't return same order
val pointerInputChange =
event.changes.firstOrNull { it.id == pointerId }
?: event.changes.first()

// Next time will check same pointer with this id
pointerId = pointerInputChange.id
pointer = pointerInputChange

if (waitedAfterDown) {
onMove(event.changes)
}

} else {
// All of the pointers are up
onUp(pointer)
break
}
}
}
}
}
Loading

0 comments on commit 8f6ef5a

Please sign in to comment.