Skip to content

Commit 74ef9b4

Browse files
authored
Feat: Drag events & Reanimated support (#124)
* fix(ios): correct sizeInfo on present * chore(ios): decrease min ios version * feat(ios): add drag events * chore: update builder-bob * chore(android): put back gesture handler and add todo * chore: disable new-arch, clip android background, update example * feat(android): implement drag events * chore: update clean script * feat: implement reanimated (not yet working) * chore: tidy * feat(ios): support reanimated events * refactor(ios): use synthetic data for events * refactor(android): events * fix(ios): correct size when changing size programmatically * refactor(android): consistency with ios * docs: add reanimated guide * refactor(android): normalize event dispatcher * refactor(android): organize code * chore: tidy
1 parent 8247669 commit 74ef9b4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+41942
-3201
lines changed

android/build.gradle

+11-18
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,20 @@
11
buildscript {
2-
// Buildscript is evaluated before everything else so we can't use getExtOrDefault
3-
def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["TrueSheet_kotlinVersion"]
2+
ext.getExtOrDefault = {name ->
3+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['TrueSheet_' + name]
4+
}
45

56
repositories {
67
google()
78
mavenCentral()
89
}
910

1011
dependencies {
11-
classpath "com.android.tools.build:gradle:7.2.1"
12+
classpath "com.android.tools.build:gradle:8.7.2"
1213
// noinspection DifferentKotlinGradleVersion
13-
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
1415
}
1516
}
1617

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

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

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

33-
def getExtOrDefault(name) {
34-
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["TrueSheet_" + name]
35-
}
36-
3733
def getExtOrIntegerDefault(name) {
3834
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["TrueSheet_" + name]).toInteger()
3935
}
@@ -49,7 +45,7 @@ def supportsNamespace() {
4945

5046
android {
5147
if (supportsNamespace()) {
52-
namespace "com.lodev09.truesheet"
48+
namespace "com.truesheet"
5349

5450
sourceSets {
5551
main {
@@ -90,11 +86,8 @@ repositories {
9086
def kotlin_version = getExtOrDefault("kotlinVersion")
9187

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

android/gradle.properties

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
TrueSheet_kotlinVersion=1.7.0
2-
TrueSheet_minSdkVersion=21
3-
TrueSheet_targetSdkVersion=31
4-
TrueSheet_compileSdkVersion=31
5-
TrueSheet_ndkversion=21.4.7075529
1+
TrueSheet_kotlinVersion=2.0.21
2+
TrueSheet_minSdkVersion=24
3+
TrueSheet_targetSdkVersion=34
4+
TrueSheet_compileSdkVersion=35
5+
TrueSheet_ndkversion=27.1.12297006

android/src/main/java/com/lodev09/truesheet/TrueSheetDialog.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ class TrueSheetDialog(private val reactContext: ThemedReactContext, private val
7070
setContentView(rootSheetView)
7171

7272
sheetView = rootSheetView.parent as ViewGroup
73-
sheetView.setBackgroundColor(Color.TRANSPARENT)
73+
74+
sheetView.setBackgroundColor(backgroundColor)
75+
sheetView.clipToOutline = true
7476

7577
// Setup window params to adjust layout based on Keyboard state
7678
window?.apply {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.lodev09.truesheet
2+
3+
import com.facebook.react.bridge.Arguments
4+
import com.facebook.react.bridge.WritableMap
5+
import com.facebook.react.uimanager.events.Event
6+
7+
class TrueSheetEvent(surfaceId: Int, viewId: Int, private val name: String, private val data: WritableMap?) :
8+
Event<TrueSheetEvent>(surfaceId, viewId) {
9+
override fun getEventName() = name
10+
override fun getEventData(): WritableMap = data ?: Arguments.createMap()
11+
12+
companion object {
13+
const val MOUNT = "mount"
14+
const val PRESENT = "present"
15+
const val DISMISS = "dismiss"
16+
const val SIZE_CHANGE = "sizeChange"
17+
const val DRAG_BEGIN = "dragBegin"
18+
const val DRAG_CHANGE = "dragChange"
19+
const val DRAG_END = "dragEnd"
20+
const val CONTAINER_SIZE_CHANGE = "containerSizeChange"
21+
}
22+
}

android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt

+105-29
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,16 @@ import android.view.View
55
import android.view.ViewGroup
66
import android.view.ViewStructure
77
import android.view.accessibility.AccessibilityEvent
8+
import com.facebook.react.bridge.Arguments
89
import com.facebook.react.bridge.LifecycleEventListener
910
import com.facebook.react.bridge.UiThreadUtil
11+
import com.facebook.react.bridge.WritableMap
1012
import com.facebook.react.uimanager.ThemedReactContext
1113
import com.facebook.react.uimanager.UIManagerHelper
1214
import com.facebook.react.uimanager.events.EventDispatcher
1315
import com.google.android.material.bottomsheet.BottomSheetBehavior
1416
import com.lodev09.truesheet.core.RootSheetView
1517
import com.lodev09.truesheet.core.Utils
16-
import com.lodev09.truesheet.events.ContainerSizeChangeEvent
17-
import com.lodev09.truesheet.events.DismissEvent
18-
import com.lodev09.truesheet.events.MountEvent
19-
import com.lodev09.truesheet.events.PresentEvent
20-
import com.lodev09.truesheet.events.SizeChangeEvent
2118

2219
class TrueSheetView(context: Context) :
2320
ViewGroup(context),
@@ -33,6 +30,11 @@ class TrueSheetView(context: Context) :
3330
var initialIndex: Int = -1
3431
var initialIndexAnimated: Boolean = true
3532

33+
/**
34+
* Determines if the sheet is being dragged by the user.
35+
*/
36+
private var isDragging = false
37+
3638
/**
3739
* Current activeIndex.
3840
*/
@@ -70,7 +72,10 @@ class TrueSheetView(context: Context) :
7072
// Configure Sheet Dialog
7173
sheetDialog.apply {
7274
setOnSizeChangeListener { w, h ->
73-
eventDispatcher?.dispatchEvent(ContainerSizeChangeEvent(surfaceId, id, Utils.toDIP(w.toFloat()), Utils.toDIP(h.toFloat())))
75+
val data = Arguments.createMap()
76+
data.putDouble("width", Utils.toDIP(w.toFloat()).toDouble())
77+
data.putDouble("height", Utils.toDIP(h.toFloat()).toDouble())
78+
dispatchEvent(TrueSheetEvent.CONTAINER_SIZE_CHANGE, data)
7479
}
7580

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

9499
// Dispatch onPresent event
95-
eventDispatcher?.dispatchEvent(PresentEvent(surfaceId, id, sheetDialog.getSizeInfoForIndex(currentSizeIndex)))
100+
dispatchEvent(TrueSheetEvent.PRESENT, sizeInfoData(sheetDialog.getSizeInfoForIndex(currentSizeIndex)))
96101
}
97102

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

108113
// Dispatch onDismiss event
109-
eventDispatcher?.dispatchEvent(DismissEvent(surfaceId, id))
114+
dispatchEvent(TrueSheetEvent.DISMISS)
110115
}
111116

112117
// Configure sheet behavior events
113118
behavior.addBottomSheetCallback(
114119
object : BottomSheetBehavior.BottomSheetCallback() {
115120
override fun onSlide(sheetView: View, slideOffset: Float) {
121+
when (sheetDialog.behavior.state) {
122+
// For consistency with IOS, we consider SETTLING as dragging change.
123+
BottomSheetBehavior.STATE_DRAGGING,
124+
BottomSheetBehavior.STATE_SETTLING -> handleDragChange(sheetView)
125+
126+
else -> { }
127+
}
128+
116129
footerView?.let {
117130
val y = (maxScreenHeight - sheetView.top - footerHeight).toFloat()
118131
if (slideOffset >= 0) {
@@ -125,23 +138,20 @@ class TrueSheetView(context: Context) :
125138
}
126139
}
127140

128-
override fun onStateChanged(view: View, newState: Int) {
141+
override fun onStateChanged(sheetView: View, newState: Int) {
129142
if (!isShowing) return
130143

131-
val sizeInfo = getSizeInfoForState(newState)
132-
if (sizeInfo == null || sizeInfo.index == currentSizeIndex) return
133-
134-
// Invoke promise when sheet resized programmatically
135-
presentPromise?.let { promise ->
136-
promise()
137-
presentPromise = null
138-
}
144+
when (newState) {
145+
// When changed to dragging, we know that the drag has started
146+
BottomSheetBehavior.STATE_DRAGGING -> handleDragBegin(sheetView)
139147

140-
currentSizeIndex = sizeInfo.index
141-
setupDimmedBackground(sizeInfo.index)
148+
// Either of the following state determines drag end
149+
BottomSheetBehavior.STATE_EXPANDED,
150+
BottomSheetBehavior.STATE_COLLAPSED,
151+
BottomSheetBehavior.STATE_HALF_EXPANDED -> handleDragEnd(newState)
142152

143-
// Dispatch onSizeChange event
144-
eventDispatcher?.dispatchEvent(SizeChangeEvent(surfaceId, id, sizeInfo))
153+
else -> { }
154+
}
145155
}
146156
}
147157
)
@@ -192,7 +202,7 @@ class TrueSheetView(context: Context) :
192202
}
193203

194204
// Dispatch onMount event
195-
eventDispatcher?.dispatchEvent(MountEvent(surfaceId, id))
205+
dispatchEvent(TrueSheetEvent.MOUNT)
196206
}
197207
}
198208
}
@@ -239,6 +249,68 @@ class TrueSheetView(context: Context) :
239249
sheetDialog.dismiss()
240250
}
241251

252+
private fun sizeInfoData(sizeInfo: SizeInfo): WritableMap {
253+
val data = Arguments.createMap()
254+
data.putInt("index", sizeInfo.index)
255+
data.putDouble("value", sizeInfo.value.toDouble())
256+
257+
return data
258+
}
259+
260+
private fun getCurrentSizeInfo(sheetView: View): SizeInfo {
261+
val height = sheetDialog.maxScreenHeight - sheetView.top
262+
val currentSizeInfo = SizeInfo(currentSizeIndex, Utils.toDIP(height.toFloat()))
263+
264+
return currentSizeInfo
265+
}
266+
267+
private fun handleDragBegin(sheetView: View) {
268+
// Dispatch drag started event
269+
dispatchEvent(TrueSheetEvent.DRAG_BEGIN, sizeInfoData(getCurrentSizeInfo(sheetView)))
270+
// Flag sheet is being dragged
271+
isDragging = true
272+
}
273+
274+
private fun handleDragChange(sheetView: View) {
275+
if (!isDragging) return
276+
277+
// Dispatch drag change event
278+
dispatchEvent(TrueSheetEvent.DRAG_CHANGE, sizeInfoData(getCurrentSizeInfo(sheetView)))
279+
}
280+
281+
private fun handleDragEnd(state: Int) {
282+
if (!isDragging) return
283+
284+
// For consistency with IOS,
285+
// we only handle state changes after dragging.
286+
//
287+
// Changing size programmatically is handled via the present method.
288+
val sizeInfo = sheetDialog.getSizeInfoForState(state)
289+
sizeInfo?.let {
290+
// Dispatch drag ended after dragging
291+
dispatchEvent(TrueSheetEvent.DRAG_END, sizeInfoData(it))
292+
if (it.index != currentSizeIndex) {
293+
// Invoke promise when sheet resized programmatically
294+
presentPromise?.let { promise ->
295+
promise()
296+
presentPromise = null
297+
}
298+
299+
currentSizeIndex = it.index
300+
sheetDialog.setupDimmedBackground(it.index)
301+
302+
// Dispatch onSizeChange event
303+
dispatchEvent(TrueSheetEvent.SIZE_CHANGE, sizeInfoData(it))
304+
}
305+
}
306+
307+
isDragging = false
308+
}
309+
310+
private fun dispatchEvent(name: String, data: WritableMap? = null) {
311+
eventDispatcher?.dispatchEvent(TrueSheetEvent(surfaceId, id, name, data))
312+
}
313+
242314
private fun configureIfShowing() {
243315
if (sheetDialog.isShowing) {
244316
sheetDialog.configure()
@@ -322,11 +394,19 @@ class TrueSheetView(context: Context) :
322394
* Present the sheet at given size index.
323395
*/
324396
fun present(sizeIndex: Int, promiseCallback: () -> Unit) {
325-
if (!sheetDialog.isShowing) {
326-
currentSizeIndex = sizeIndex
397+
currentSizeIndex = sizeIndex
398+
399+
if (sheetDialog.isShowing) {
400+
// For consistency with IOS, we are not waiting
401+
// for the state to change before dispatching onSizeChange event.
402+
val sizeInfo = sheetDialog.getSizeInfoForIndex(sizeIndex)
403+
dispatchEvent(TrueSheetEvent.SIZE_CHANGE, sizeInfoData(sizeInfo))
404+
405+
promiseCallback()
406+
} else {
407+
presentPromise = promiseCallback
327408
}
328409

329-
presentPromise = promiseCallback
330410
sheetDialog.present(sizeIndex)
331411
}
332412

@@ -337,8 +417,4 @@ class TrueSheetView(context: Context) :
337417
dismissPromise = promiseCallback
338418
sheetDialog.dismiss()
339419
}
340-
341-
companion object {
342-
const val TAG = "TrueSheetView"
343-
}
344420
}

android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt

+8-10
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@ import com.facebook.react.uimanager.ThemedReactContext
1111
import com.facebook.react.uimanager.ViewGroupManager
1212
import com.facebook.react.uimanager.annotations.ReactProp
1313
import com.lodev09.truesheet.core.Utils
14-
import com.lodev09.truesheet.events.ContainerSizeChangeEvent
15-
import com.lodev09.truesheet.events.DismissEvent
16-
import com.lodev09.truesheet.events.MountEvent
17-
import com.lodev09.truesheet.events.PresentEvent
18-
import com.lodev09.truesheet.events.SizeChangeEvent
1914

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

3025
override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any>? =
3126
MapBuilder.builder<String, Any>()
32-
.put(MountEvent.EVENT_NAME, MapBuilder.of("registrationName", "onMount"))
33-
.put(PresentEvent.EVENT_NAME, MapBuilder.of("registrationName", "onPresent"))
34-
.put(DismissEvent.EVENT_NAME, MapBuilder.of("registrationName", "onDismiss"))
35-
.put(SizeChangeEvent.EVENT_NAME, MapBuilder.of("registrationName", "onSizeChange"))
36-
.put(ContainerSizeChangeEvent.EVENT_NAME, MapBuilder.of("registrationName", "onContainerSizeChange"))
27+
.put(TrueSheetEvent.MOUNT, MapBuilder.of("registrationName", "onMount"))
28+
.put(TrueSheetEvent.PRESENT, MapBuilder.of("registrationName", "onPresent"))
29+
.put(TrueSheetEvent.DISMISS, MapBuilder.of("registrationName", "onDismiss"))
30+
.put(TrueSheetEvent.SIZE_CHANGE, MapBuilder.of("registrationName", "onSizeChange"))
31+
.put(TrueSheetEvent.DRAG_BEGIN, MapBuilder.of("registrationName", "onDragBegin"))
32+
.put(TrueSheetEvent.DRAG_CHANGE, MapBuilder.of("registrationName", "onDragChange"))
33+
.put(TrueSheetEvent.DRAG_END, MapBuilder.of("registrationName", "onDragEnd"))
34+
.put(TrueSheetEvent.CONTAINER_SIZE_CHANGE, MapBuilder.of("registrationName", "onContainerSizeChange"))
3735
.build()
3836

3937
@ReactProp(name = "edgeToEdge")

android/src/main/java/com/lodev09/truesheet/events/ContainerSizeChangeEvent.kt

-23
This file was deleted.

0 commit comments

Comments
 (0)