Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Drag events #124

Merged
merged 22 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
29 changes: 11 additions & 18 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
buildscript {
// Buildscript is evaluated before everything else so we can't use getExtOrDefault
def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["TrueSheet_kotlinVersion"]
ext.getExtOrDefault = {name ->
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['TrueSheet_' + name]
}

repositories {
google()
mavenCentral()
}

dependencies {
classpath "com.android.tools.build:gradle:7.2.1"
classpath "com.android.tools.build:gradle:8.7.2"
// noinspection DifferentKotlinGradleVersion
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
}
}

def reactNativeArchitectures() {
def value = rootProject.getProperties().get("reactNativeArchitectures")
return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
}

def isNewArchitectureEnabled() {
return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
Expand All @@ -26,14 +23,13 @@ def isNewArchitectureEnabled() {
apply plugin: "com.android.library"
apply plugin: "kotlin-android"

// TODO:
// When running example, comment this block!
// Not sure what's going on but we are getting multiple definition error when this is enabled.
if (isNewArchitectureEnabled()) {
apply plugin: "com.facebook.react"
}

def getExtOrDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["TrueSheet_" + name]
}

def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["TrueSheet_" + name]).toInteger()
}
Expand All @@ -49,7 +45,7 @@ def supportsNamespace() {

android {
if (supportsNamespace()) {
namespace "com.lodev09.truesheet"
namespace "com.truesheet"

sourceSets {
main {
Expand Down Expand Up @@ -90,11 +86,8 @@ repositories {
def kotlin_version = getExtOrDefault("kotlinVersion")

dependencies {
// For < 0.71, this will be from the local maven repo
// For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+"
implementation "com.facebook.react:react-android"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'com.google.android.material:material:1.12.0'
implementation "com.google.android.material:material:1.12.0"
}

10 changes: 5 additions & 5 deletions android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
TrueSheet_kotlinVersion=1.7.0
TrueSheet_minSdkVersion=21
TrueSheet_targetSdkVersion=31
TrueSheet_compileSdkVersion=31
TrueSheet_ndkversion=21.4.7075529
TrueSheet_kotlinVersion=2.0.21
TrueSheet_minSdkVersion=24
TrueSheet_targetSdkVersion=34
TrueSheet_compileSdkVersion=35
TrueSheet_ndkversion=27.1.12297006
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ class TrueSheetDialog(private val reactContext: ThemedReactContext, private val
setContentView(rootSheetView)

sheetView = rootSheetView.parent as ViewGroup
sheetView.setBackgroundColor(Color.TRANSPARENT)

sheetView.setBackgroundColor(backgroundColor)
sheetView.clipToOutline = true

// Setup window params to adjust layout based on Keyboard state
window?.apply {
Expand Down
22 changes: 22 additions & 0 deletions android/src/main/java/com/lodev09/truesheet/TrueSheetEvent.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.lodev09.truesheet

import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.events.Event

class TrueSheetEvent(surfaceId: Int, viewId: Int, private val name: String, private val data: WritableMap?) :
Event<TrueSheetEvent>(surfaceId, viewId) {
override fun getEventName() = name
override fun getEventData(): WritableMap = data ?: Arguments.createMap()

companion object {
const val MOUNT = "mount"
const val PRESENT = "present"
const val DISMISS = "dismiss"
const val SIZE_CHANGE = "sizeChange"
const val DRAG_BEGIN = "dragBegin"
const val DRAG_CHANGE = "dragChange"
const val DRAG_END = "dragEnd"
const val CONTAINER_SIZE_CHANGE = "containerSizeChange"
}
}
134 changes: 105 additions & 29 deletions android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,16 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewStructure
import android.view.accessibility.AccessibilityEvent
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.LifecycleEventListener
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.events.EventDispatcher
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.lodev09.truesheet.core.RootSheetView
import com.lodev09.truesheet.core.Utils
import com.lodev09.truesheet.events.ContainerSizeChangeEvent
import com.lodev09.truesheet.events.DismissEvent
import com.lodev09.truesheet.events.MountEvent
import com.lodev09.truesheet.events.PresentEvent
import com.lodev09.truesheet.events.SizeChangeEvent

class TrueSheetView(context: Context) :
ViewGroup(context),
Expand All @@ -33,6 +30,11 @@ class TrueSheetView(context: Context) :
var initialIndex: Int = -1
var initialIndexAnimated: Boolean = true

/**
* Determines if the sheet is being dragged by the user.
*/
private var isDragging = false

/**
* Current activeIndex.
*/
Expand Down Expand Up @@ -70,7 +72,10 @@ class TrueSheetView(context: Context) :
// Configure Sheet Dialog
sheetDialog.apply {
setOnSizeChangeListener { w, h ->
eventDispatcher?.dispatchEvent(ContainerSizeChangeEvent(surfaceId, id, Utils.toDIP(w.toFloat()), Utils.toDIP(h.toFloat())))
val data = Arguments.createMap()
data.putDouble("width", Utils.toDIP(w.toFloat()).toDouble())
data.putDouble("height", Utils.toDIP(h.toFloat()).toDouble())
dispatchEvent(TrueSheetEvent.CONTAINER_SIZE_CHANGE, data)
}

// Setup listener when the dialog has been presented.
Expand All @@ -92,7 +97,7 @@ class TrueSheetView(context: Context) :
}

// Dispatch onPresent event
eventDispatcher?.dispatchEvent(PresentEvent(surfaceId, id, sheetDialog.getSizeInfoForIndex(currentSizeIndex)))
dispatchEvent(TrueSheetEvent.PRESENT, sizeInfoData(sheetDialog.getSizeInfoForIndex(currentSizeIndex)))
}

// Setup listener when the dialog has been dismissed.
Expand All @@ -106,13 +111,21 @@ class TrueSheetView(context: Context) :
}

// Dispatch onDismiss event
eventDispatcher?.dispatchEvent(DismissEvent(surfaceId, id))
dispatchEvent(TrueSheetEvent.DISMISS)
}

// Configure sheet behavior events
behavior.addBottomSheetCallback(
object : BottomSheetBehavior.BottomSheetCallback() {
override fun onSlide(sheetView: View, slideOffset: Float) {
when (sheetDialog.behavior.state) {
// For consistency with IOS, we consider SETTLING as dragging change.
BottomSheetBehavior.STATE_DRAGGING,
BottomSheetBehavior.STATE_SETTLING -> handleDragChange(sheetView)

else -> { }
}

footerView?.let {
val y = (maxScreenHeight - sheetView.top - footerHeight).toFloat()
if (slideOffset >= 0) {
Expand All @@ -125,23 +138,20 @@ class TrueSheetView(context: Context) :
}
}

override fun onStateChanged(view: View, newState: Int) {
override fun onStateChanged(sheetView: View, newState: Int) {
if (!isShowing) return

val sizeInfo = getSizeInfoForState(newState)
if (sizeInfo == null || sizeInfo.index == currentSizeIndex) return

// Invoke promise when sheet resized programmatically
presentPromise?.let { promise ->
promise()
presentPromise = null
}
when (newState) {
// When changed to dragging, we know that the drag has started
BottomSheetBehavior.STATE_DRAGGING -> handleDragBegin(sheetView)

currentSizeIndex = sizeInfo.index
setupDimmedBackground(sizeInfo.index)
// Either of the following state determines drag end
BottomSheetBehavior.STATE_EXPANDED,
BottomSheetBehavior.STATE_COLLAPSED,
BottomSheetBehavior.STATE_HALF_EXPANDED -> handleDragEnd(newState)

// Dispatch onSizeChange event
eventDispatcher?.dispatchEvent(SizeChangeEvent(surfaceId, id, sizeInfo))
else -> { }
}
}
}
)
Expand Down Expand Up @@ -192,7 +202,7 @@ class TrueSheetView(context: Context) :
}

// Dispatch onMount event
eventDispatcher?.dispatchEvent(MountEvent(surfaceId, id))
dispatchEvent(TrueSheetEvent.MOUNT)
}
}
}
Expand Down Expand Up @@ -239,6 +249,68 @@ class TrueSheetView(context: Context) :
sheetDialog.dismiss()
}

private fun sizeInfoData(sizeInfo: SizeInfo): WritableMap {
val data = Arguments.createMap()
data.putInt("index", sizeInfo.index)
data.putDouble("value", sizeInfo.value.toDouble())

return data
}

private fun getCurrentSizeInfo(sheetView: View): SizeInfo {
val height = sheetDialog.maxScreenHeight - sheetView.top
val currentSizeInfo = SizeInfo(currentSizeIndex, Utils.toDIP(height.toFloat()))

return currentSizeInfo
}

private fun handleDragBegin(sheetView: View) {
// Dispatch drag started event
dispatchEvent(TrueSheetEvent.DRAG_BEGIN, sizeInfoData(getCurrentSizeInfo(sheetView)))
// Flag sheet is being dragged
isDragging = true
}

private fun handleDragChange(sheetView: View) {
if (!isDragging) return

// Dispatch drag change event
dispatchEvent(TrueSheetEvent.DRAG_CHANGE, sizeInfoData(getCurrentSizeInfo(sheetView)))
}

private fun handleDragEnd(state: Int) {
if (!isDragging) return

// For consistency with IOS,
// we only handle state changes after dragging.
//
// Changing size programmatically is handled via the present method.
val sizeInfo = sheetDialog.getSizeInfoForState(state)
sizeInfo?.let {
// Dispatch drag ended after dragging
dispatchEvent(TrueSheetEvent.DRAG_END, sizeInfoData(it))
if (it.index != currentSizeIndex) {
// Invoke promise when sheet resized programmatically
presentPromise?.let { promise ->
promise()
presentPromise = null
}

currentSizeIndex = it.index
sheetDialog.setupDimmedBackground(it.index)

// Dispatch onSizeChange event
dispatchEvent(TrueSheetEvent.SIZE_CHANGE, sizeInfoData(it))
}
}

isDragging = false
}

private fun dispatchEvent(name: String, data: WritableMap? = null) {
eventDispatcher?.dispatchEvent(TrueSheetEvent(surfaceId, id, name, data))
}

private fun configureIfShowing() {
if (sheetDialog.isShowing) {
sheetDialog.configure()
Expand Down Expand Up @@ -322,11 +394,19 @@ class TrueSheetView(context: Context) :
* Present the sheet at given size index.
*/
fun present(sizeIndex: Int, promiseCallback: () -> Unit) {
if (!sheetDialog.isShowing) {
currentSizeIndex = sizeIndex
currentSizeIndex = sizeIndex

if (sheetDialog.isShowing) {
// For consistency with IOS, we are not waiting
// for the state to change before dispatching onSizeChange event.
val sizeInfo = sheetDialog.getSizeInfoForIndex(sizeIndex)
dispatchEvent(TrueSheetEvent.SIZE_CHANGE, sizeInfoData(sizeInfo))

promiseCallback()
} else {
presentPromise = promiseCallback
}

presentPromise = promiseCallback
sheetDialog.present(sizeIndex)
}

Expand All @@ -337,8 +417,4 @@ class TrueSheetView(context: Context) :
dismissPromise = promiseCallback
sheetDialog.dismiss()
}

companion object {
const val TAG = "TrueSheetView"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.ViewGroupManager
import com.facebook.react.uimanager.annotations.ReactProp
import com.lodev09.truesheet.core.Utils
import com.lodev09.truesheet.events.ContainerSizeChangeEvent
import com.lodev09.truesheet.events.DismissEvent
import com.lodev09.truesheet.events.MountEvent
import com.lodev09.truesheet.events.PresentEvent
import com.lodev09.truesheet.events.SizeChangeEvent

class TrueSheetViewManager : ViewGroupManager<TrueSheetView>() {
override fun getName() = TAG
Expand All @@ -29,11 +24,14 @@ class TrueSheetViewManager : ViewGroupManager<TrueSheetView>() {

override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any>? =
MapBuilder.builder<String, Any>()
.put(MountEvent.EVENT_NAME, MapBuilder.of("registrationName", "onMount"))
.put(PresentEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPresent"))
.put(DismissEvent.EVENT_NAME, MapBuilder.of("registrationName", "onDismiss"))
.put(SizeChangeEvent.EVENT_NAME, MapBuilder.of("registrationName", "onSizeChange"))
.put(ContainerSizeChangeEvent.EVENT_NAME, MapBuilder.of("registrationName", "onContainerSizeChange"))
.put(TrueSheetEvent.MOUNT, MapBuilder.of("registrationName", "onMount"))
.put(TrueSheetEvent.PRESENT, MapBuilder.of("registrationName", "onPresent"))
.put(TrueSheetEvent.DISMISS, MapBuilder.of("registrationName", "onDismiss"))
.put(TrueSheetEvent.SIZE_CHANGE, MapBuilder.of("registrationName", "onSizeChange"))
.put(TrueSheetEvent.DRAG_BEGIN, MapBuilder.of("registrationName", "onDragBegin"))
.put(TrueSheetEvent.DRAG_CHANGE, MapBuilder.of("registrationName", "onDragChange"))
.put(TrueSheetEvent.DRAG_END, MapBuilder.of("registrationName", "onDragEnd"))
.put(TrueSheetEvent.CONTAINER_SIZE_CHANGE, MapBuilder.of("registrationName", "onContainerSizeChange"))
.build()

@ReactProp(name = "edgeToEdge")
Expand Down

This file was deleted.

Loading