@lodev09/react-native-true-sheet 3.0.0-beta.8 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -6
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +29 -33
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +3 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +48 -43
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +387 -88
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +22 -4
- package/android/src/main/java/com/lodev09/truesheet/core/RNScreensFragmentObserver.kt +0 -5
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDialogObserver.kt +67 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetGrabberView.kt +44 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetDragEvents.kt +71 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetFocusEvents.kt +65 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetLifecycleEvents.kt +94 -0
- package/android/src/main/java/com/lodev09/truesheet/events/TrueSheetStateEvents.kt +56 -0
- package/android/src/main/java/com/lodev09/truesheet/utils/ScreenUtils.kt +37 -33
- package/android/src/main/res/anim/true_sheet_slide_in.xml +13 -0
- package/android/src/main/res/anim/true_sheet_slide_out.xml +13 -0
- package/android/src/main/res/values/styles.xml +13 -1
- package/ios/TrueSheetContainerView.mm +4 -0
- package/ios/TrueSheetContentView.h +2 -1
- package/ios/TrueSheetContentView.mm +91 -11
- package/ios/TrueSheetView.mm +65 -41
- package/ios/TrueSheetViewController.h +21 -10
- package/ios/TrueSheetViewController.mm +330 -165
- package/ios/core/TrueSheetBlurView.h +24 -0
- package/ios/{utils/ConversionUtil.mm → core/TrueSheetBlurView.mm} +65 -3
- package/ios/events/TrueSheetDragEvents.h +39 -0
- package/ios/events/TrueSheetDragEvents.mm +62 -0
- package/ios/events/{OnPositionChangeEvent.h → TrueSheetFocusEvents.h} +8 -5
- package/ios/events/TrueSheetFocusEvents.mm +49 -0
- package/ios/events/TrueSheetLifecycleEvents.h +40 -0
- package/ios/events/TrueSheetLifecycleEvents.mm +71 -0
- package/ios/events/TrueSheetStateEvents.h +35 -0
- package/ios/events/TrueSheetStateEvents.mm +49 -0
- package/ios/utils/GestureUtil.h +7 -0
- package/ios/utils/GestureUtil.mm +12 -0
- package/lib/module/TrueSheet.js +65 -12
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +15 -5
- package/lib/module/index.js +0 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/reanimated/ReanimatedTrueSheet.js +13 -7
- package/lib/module/reanimated/ReanimatedTrueSheet.js.map +1 -1
- package/lib/module/reanimated/ReanimatedTrueSheetProvider.js +4 -2
- package/lib/module/reanimated/ReanimatedTrueSheetProvider.js.map +1 -1
- package/lib/typescript/src/TrueSheet.d.ts +4 -0
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +58 -6
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +14 -5
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +0 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/reanimated/ReanimatedTrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts +8 -2
- package/lib/typescript/src/reanimated/ReanimatedTrueSheetProvider.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/TrueSheet.tsx +80 -10
- package/src/TrueSheet.types.ts +65 -6
- package/src/__mocks__/index.js +0 -5
- package/src/fabric/TrueSheetViewNativeComponent.ts +15 -5
- package/src/index.ts +0 -1
- package/src/reanimated/ReanimatedTrueSheet.tsx +12 -7
- package/src/reanimated/ReanimatedTrueSheetProvider.tsx +11 -3
- package/android/src/main/java/com/lodev09/truesheet/events/DetentChangeEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DidDismissEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/DidPresentEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragBeginEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragChangeEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/DragEndEvent.kt +0 -26
- package/android/src/main/java/com/lodev09/truesheet/events/MountEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/PositionChangeEvent.kt +0 -32
- package/android/src/main/java/com/lodev09/truesheet/events/WillDismissEvent.kt +0 -20
- package/android/src/main/java/com/lodev09/truesheet/events/WillPresentEvent.kt +0 -26
- package/ios/events/OnDetentChangeEvent.h +0 -28
- package/ios/events/OnDetentChangeEvent.mm +0 -30
- package/ios/events/OnDidDismissEvent.h +0 -26
- package/ios/events/OnDidDismissEvent.mm +0 -25
- package/ios/events/OnDidPresentEvent.h +0 -28
- package/ios/events/OnDidPresentEvent.mm +0 -30
- package/ios/events/OnDragBeginEvent.h +0 -28
- package/ios/events/OnDragBeginEvent.mm +0 -30
- package/ios/events/OnDragChangeEvent.h +0 -28
- package/ios/events/OnDragChangeEvent.mm +0 -30
- package/ios/events/OnDragEndEvent.h +0 -28
- package/ios/events/OnDragEndEvent.mm +0 -30
- package/ios/events/OnMountEvent.h +0 -26
- package/ios/events/OnMountEvent.mm +0 -25
- package/ios/events/OnPositionChangeEvent.mm +0 -32
- package/ios/events/OnWillDismissEvent.h +0 -26
- package/ios/events/OnWillDismissEvent.mm +0 -25
- package/ios/events/OnWillPresentEvent.h +0 -28
- package/ios/events/OnWillPresentEvent.mm +0 -30
- package/ios/utils/ConversionUtil.h +0 -24
- package/lib/module/TrueSheetGrabber.js +0 -51
- package/lib/module/TrueSheetGrabber.js.map +0 -1
- package/lib/typescript/src/TrueSheetGrabber.d.ts +0 -39
- package/lib/typescript/src/TrueSheetGrabber.d.ts.map +0 -1
- package/src/TrueSheetGrabber.tsx +0 -82
|
@@ -2,17 +2,16 @@ package com.lodev09.truesheet
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.graphics.Color
|
|
5
|
-
import android.graphics.drawable.GradientDrawable
|
|
6
5
|
import android.graphics.drawable.ShapeDrawable
|
|
7
6
|
import android.graphics.drawable.shapes.RoundRectShape
|
|
8
7
|
import android.util.TypedValue
|
|
9
|
-
import android.view.Gravity
|
|
10
8
|
import android.view.MotionEvent
|
|
11
9
|
import android.view.View
|
|
12
10
|
import android.view.WindowManager
|
|
13
11
|
import android.view.accessibility.AccessibilityNodeInfo
|
|
14
12
|
import android.widget.FrameLayout
|
|
15
13
|
import androidx.core.view.isNotEmpty
|
|
14
|
+
import androidx.core.view.isVisible
|
|
16
15
|
import com.facebook.react.R
|
|
17
16
|
import com.facebook.react.uimanager.JSPointerDispatcher
|
|
18
17
|
import com.facebook.react.uimanager.JSTouchDispatcher
|
|
@@ -26,21 +25,26 @@ import com.facebook.react.views.view.ReactViewGroup
|
|
|
26
25
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
27
26
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
28
27
|
import com.lodev09.truesheet.core.RNScreensFragmentObserver
|
|
28
|
+
import com.lodev09.truesheet.core.TrueSheetGrabberView
|
|
29
29
|
import com.lodev09.truesheet.utils.ScreenUtils
|
|
30
30
|
|
|
31
31
|
data class DetentInfo(val index: Int, val position: Float)
|
|
32
32
|
|
|
33
33
|
interface TrueSheetViewControllerDelegate {
|
|
34
|
-
fun viewControllerWillPresent(index: Int, position: Float)
|
|
35
|
-
fun viewControllerDidPresent(index: Int, position: Float)
|
|
34
|
+
fun viewControllerWillPresent(index: Int, position: Float, detent: Float)
|
|
35
|
+
fun viewControllerDidPresent(index: Int, position: Float, detent: Float)
|
|
36
36
|
fun viewControllerWillDismiss()
|
|
37
|
-
fun viewControllerDidDismiss()
|
|
38
|
-
fun viewControllerDidChangeDetent(index: Int, position: Float)
|
|
39
|
-
fun viewControllerDidDragBegin(index: Int, position: Float)
|
|
40
|
-
fun viewControllerDidDragChange(index: Int, position: Float)
|
|
41
|
-
fun viewControllerDidDragEnd(index: Int, position: Float)
|
|
42
|
-
fun viewControllerDidChangePosition(index:
|
|
37
|
+
fun viewControllerDidDismiss(hadParent: Boolean)
|
|
38
|
+
fun viewControllerDidChangeDetent(index: Int, position: Float, detent: Float)
|
|
39
|
+
fun viewControllerDidDragBegin(index: Int, position: Float, detent: Float)
|
|
40
|
+
fun viewControllerDidDragChange(index: Int, position: Float, detent: Float)
|
|
41
|
+
fun viewControllerDidDragEnd(index: Int, position: Float, detent: Float)
|
|
42
|
+
fun viewControllerDidChangePosition(index: Float, position: Float, detent: Float, realtime: Boolean)
|
|
43
43
|
fun viewControllerDidChangeSize(width: Int, height: Int)
|
|
44
|
+
fun viewControllerWillFocus()
|
|
45
|
+
fun viewControllerDidFocus()
|
|
46
|
+
fun viewControllerWillBlur()
|
|
47
|
+
fun viewControllerDidBlur()
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
/**
|
|
@@ -58,10 +62,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
58
62
|
private const val GRABBER_TAG = "TrueSheetGrabber"
|
|
59
63
|
private const val DEFAULT_MAX_WIDTH = 640 // dp
|
|
60
64
|
private const val DEFAULT_CORNER_RADIUS = 16 // dp
|
|
61
|
-
private const val GRABBER_WIDTH = 32f // dp
|
|
62
|
-
private const val GRABBER_HEIGHT = 4f // dp
|
|
63
|
-
private const val GRABBER_TOP_MARGIN = 16f // dp
|
|
64
|
-
private val GRABBER_COLOR = Color.argb((0.4 * 255).toInt(), 73, 69, 79) // #49454F @ 40%
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* Gets the effective sheet height by subtracting headerHeight * 2.
|
|
@@ -107,15 +107,26 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
107
107
|
var isPresented = false
|
|
108
108
|
private set
|
|
109
109
|
|
|
110
|
+
var isDialogVisible = false
|
|
111
|
+
private set
|
|
112
|
+
|
|
110
113
|
var currentDetentIndex: Int = -1
|
|
111
114
|
private set
|
|
112
115
|
|
|
116
|
+
// Resolved detent positions (Y coordinate when sheet rests at each detent)
|
|
117
|
+
private val resolvedDetentPositions = mutableListOf<Int>()
|
|
118
|
+
|
|
113
119
|
private var isDragging = false
|
|
120
|
+
private var isReconfiguring = false
|
|
114
121
|
private var windowAnimation: Int = 0
|
|
122
|
+
private var lastEmittedPositionPx: Int = -1
|
|
115
123
|
|
|
116
124
|
var presentPromise: (() -> Unit)? = null
|
|
117
125
|
var dismissPromise: (() -> Unit)? = null
|
|
118
126
|
|
|
127
|
+
// Reference to parent TrueSheetView (if presented from another sheet)
|
|
128
|
+
var parentSheetView: TrueSheetView? = null
|
|
129
|
+
|
|
119
130
|
// ====================================================================
|
|
120
131
|
// MARK: - Configuration Properties
|
|
121
132
|
// ====================================================================
|
|
@@ -142,6 +153,12 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
142
153
|
}
|
|
143
154
|
}
|
|
144
155
|
|
|
156
|
+
var draggable: Boolean = true
|
|
157
|
+
set(value) {
|
|
158
|
+
field = value
|
|
159
|
+
behavior?.isDraggable = value
|
|
160
|
+
}
|
|
161
|
+
|
|
145
162
|
// ====================================================================
|
|
146
163
|
// MARK: - Computed Properties
|
|
147
164
|
// ====================================================================
|
|
@@ -149,6 +166,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
149
166
|
val statusBarHeight: Int
|
|
150
167
|
get() = ScreenUtils.getStatusBarHeight(reactContext)
|
|
151
168
|
|
|
169
|
+
/**
|
|
170
|
+
* The bottom inset (navigation bar height) to add to sheet height.
|
|
171
|
+
* This matches iOS behavior where the system adds bottom safe area inset internally.
|
|
172
|
+
*/
|
|
173
|
+
private val bottomInset: Int
|
|
174
|
+
get() = ScreenUtils.getNavigationBarHeight(reactContext)
|
|
175
|
+
|
|
152
176
|
/**
|
|
153
177
|
* Edge-to-edge is enabled by default on API 36+, or when explicitly configured.
|
|
154
178
|
*/
|
|
@@ -186,8 +210,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
186
210
|
*/
|
|
187
211
|
private var rnScreensObserver: RNScreensFragmentObserver? = null
|
|
188
212
|
|
|
189
|
-
fun hasActiveModals(): Boolean = rnScreensObserver?.hasActiveModals() ?: false
|
|
190
|
-
|
|
191
213
|
// ====================================================================
|
|
192
214
|
// MARK: - Initialization
|
|
193
215
|
// ====================================================================
|
|
@@ -208,7 +230,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
208
230
|
val style = if (edgeToEdgeEnabled) {
|
|
209
231
|
com.lodev09.truesheet.R.style.TrueSheetEdgeToEdgeEnabledDialog
|
|
210
232
|
} else {
|
|
211
|
-
|
|
233
|
+
com.lodev09.truesheet.R.style.TrueSheetDialog
|
|
212
234
|
}
|
|
213
235
|
|
|
214
236
|
dialog = BottomSheetDialog(reactContext, style).apply {
|
|
@@ -225,6 +247,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
225
247
|
setCanceledOnTouchOutside(dismissible)
|
|
226
248
|
setCancelable(dismissible)
|
|
227
249
|
behavior.isHideable = dismissible
|
|
250
|
+
behavior.isDraggable = draggable
|
|
228
251
|
}
|
|
229
252
|
}
|
|
230
253
|
|
|
@@ -241,19 +264,31 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
241
264
|
dialog = null
|
|
242
265
|
isDragging = false
|
|
243
266
|
isPresented = false
|
|
267
|
+
isDialogVisible = false
|
|
268
|
+
lastEmittedPositionPx = -1
|
|
244
269
|
}
|
|
245
270
|
|
|
246
271
|
private fun setupDialogListeners(dialog: BottomSheetDialog) {
|
|
247
272
|
dialog.setOnShowListener {
|
|
248
273
|
isPresented = true
|
|
274
|
+
isDialogVisible = true
|
|
249
275
|
resetAnimation()
|
|
250
276
|
setupBackground()
|
|
251
277
|
setupGrabber()
|
|
252
278
|
|
|
253
279
|
sheetContainer?.post {
|
|
254
280
|
val detentInfo = getDetentInfoForIndex(currentDetentIndex)
|
|
255
|
-
|
|
256
|
-
|
|
281
|
+
val detent = getDetentValueForIndex(detentInfo.index)
|
|
282
|
+
val positionPx = bottomSheetView?.let { ScreenUtils.getScreenY(it) } ?: screenHeight
|
|
283
|
+
|
|
284
|
+
delegate?.viewControllerDidPresent(detentInfo.index, detentInfo.position, detent)
|
|
285
|
+
|
|
286
|
+
// Store resolved position for initial detent
|
|
287
|
+
storeResolvedPosition(detentInfo.index)
|
|
288
|
+
emitChangePositionDelegate(detentInfo.index, positionPx, realtime = false)
|
|
289
|
+
|
|
290
|
+
// Notify parent sheet that it has lost focus (after this sheet appeared)
|
|
291
|
+
parentSheetView?.viewControllerDidBlur()
|
|
257
292
|
|
|
258
293
|
presentPromise?.invoke()
|
|
259
294
|
presentPromise = null
|
|
@@ -263,13 +298,22 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
263
298
|
}
|
|
264
299
|
|
|
265
300
|
dialog.setOnCancelListener {
|
|
301
|
+
// Notify parent sheet that it is about to regain focus
|
|
302
|
+
parentSheetView?.viewControllerWillFocus()
|
|
303
|
+
|
|
266
304
|
delegate?.viewControllerWillDismiss()
|
|
267
305
|
}
|
|
268
306
|
|
|
269
307
|
dialog.setOnDismissListener {
|
|
308
|
+
val hadParent = parentSheetView != null
|
|
309
|
+
|
|
310
|
+
// Notify parent sheet that it has regained focus
|
|
311
|
+
parentSheetView?.viewControllerDidFocus()
|
|
312
|
+
parentSheetView = null
|
|
313
|
+
|
|
270
314
|
dismissPromise?.invoke()
|
|
271
315
|
dismissPromise = null
|
|
272
|
-
delegate?.viewControllerDidDismiss()
|
|
316
|
+
delegate?.viewControllerDidDismiss(hadParent)
|
|
273
317
|
cleanupDialog()
|
|
274
318
|
}
|
|
275
319
|
}
|
|
@@ -279,8 +323,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
279
323
|
object : BottomSheetBehavior.BottomSheetCallback() {
|
|
280
324
|
override fun onSlide(sheetView: View, slideOffset: Float) {
|
|
281
325
|
val behavior = behavior ?: return
|
|
282
|
-
val
|
|
283
|
-
|
|
326
|
+
val positionPx = getCurrentPositionPx(sheetView)
|
|
327
|
+
val detentIndex = getDetentIndexForPosition(positionPx)
|
|
328
|
+
|
|
329
|
+
emitChangePositionDelegate(detentIndex, positionPx, realtime = true)
|
|
284
330
|
|
|
285
331
|
when (behavior.state) {
|
|
286
332
|
BottomSheetBehavior.STATE_DRAGGING,
|
|
@@ -305,7 +351,38 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
305
351
|
|
|
306
352
|
BottomSheetBehavior.STATE_EXPANDED,
|
|
307
353
|
BottomSheetBehavior.STATE_COLLAPSED,
|
|
308
|
-
BottomSheetBehavior.STATE_HALF_EXPANDED ->
|
|
354
|
+
BottomSheetBehavior.STATE_HALF_EXPANDED -> {
|
|
355
|
+
// Ignore state changes triggered by content size reconfiguration
|
|
356
|
+
if (isReconfiguring) return
|
|
357
|
+
|
|
358
|
+
getDetentInfoForState(newState)?.let { detentInfo ->
|
|
359
|
+
// Store resolved position when sheet settles
|
|
360
|
+
storeResolvedPosition(detentInfo.index)
|
|
361
|
+
|
|
362
|
+
if (isDragging) {
|
|
363
|
+
// Handle drag end
|
|
364
|
+
val detent = getDetentValueForIndex(detentInfo.index)
|
|
365
|
+
delegate?.viewControllerDidDragEnd(detentInfo.index, detentInfo.position, detent)
|
|
366
|
+
|
|
367
|
+
if (detentInfo.index != currentDetentIndex) {
|
|
368
|
+
presentPromise?.invoke()
|
|
369
|
+
presentPromise = null
|
|
370
|
+
currentDetentIndex = detentInfo.index
|
|
371
|
+
setupDimmedBackground(detentInfo.index)
|
|
372
|
+
delegate?.viewControllerDidChangeDetent(detentInfo.index, detentInfo.position, detent)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
isDragging = false
|
|
376
|
+
} else {
|
|
377
|
+
// Handle programmatic resize - emit detent change after sheet settles
|
|
378
|
+
if (detentInfo.index != currentDetentIndex) {
|
|
379
|
+
val detent = getDetentValueForIndex(detentInfo.index)
|
|
380
|
+
currentDetentIndex = detentInfo.index
|
|
381
|
+
delegate?.viewControllerDidChangeDetent(detentInfo.index, detentInfo.position, detent)
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
309
386
|
|
|
310
387
|
else -> {}
|
|
311
388
|
}
|
|
@@ -319,12 +396,12 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
319
396
|
reactContext = reactContext,
|
|
320
397
|
onModalPresented = {
|
|
321
398
|
if (isPresented) {
|
|
322
|
-
|
|
399
|
+
hideDialog()
|
|
323
400
|
}
|
|
324
401
|
},
|
|
325
402
|
onModalDismissed = {
|
|
326
403
|
if (isPresented) {
|
|
327
|
-
|
|
404
|
+
showDialog()
|
|
328
405
|
}
|
|
329
406
|
}
|
|
330
407
|
)
|
|
@@ -336,6 +413,61 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
336
413
|
rnScreensObserver = null
|
|
337
414
|
}
|
|
338
415
|
|
|
416
|
+
// ====================================================================
|
|
417
|
+
// MARK: - Dialog Visibility (for stacking)
|
|
418
|
+
// ====================================================================
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Returns true if the sheet's top is at or above the status bar.
|
|
422
|
+
*/
|
|
423
|
+
val isExpanded: Boolean
|
|
424
|
+
get() {
|
|
425
|
+
val sheetTop = bottomSheetView?.top ?: return false
|
|
426
|
+
return sheetTop <= statusBarHeight
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Returns the current top position of the sheet (Y coordinate from screen top).
|
|
431
|
+
* Used for comparing sheet positions during stacking.
|
|
432
|
+
*/
|
|
433
|
+
val currentSheetTop: Int
|
|
434
|
+
get() = bottomSheetView?.let { ScreenUtils.getScreenY(it) } ?: screenHeight
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Returns the expected top position of the sheet when presented at the given detent index.
|
|
438
|
+
* Used for comparing sheet positions before presentation.
|
|
439
|
+
*/
|
|
440
|
+
fun getExpectedSheetTop(detentIndex: Int): Int {
|
|
441
|
+
if (detentIndex < 0 || detentIndex >= detents.size) return screenHeight
|
|
442
|
+
val detentHeight = getDetentHeight(detents[detentIndex])
|
|
443
|
+
return screenHeight - detentHeight
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Hides the dialog without dismissing it.
|
|
448
|
+
* Used when another TrueSheet presents on top or when RN screen is presented.
|
|
449
|
+
*/
|
|
450
|
+
fun hideDialog() {
|
|
451
|
+
isDialogVisible = false
|
|
452
|
+
dialog?.window?.decorView?.visibility = View.INVISIBLE
|
|
453
|
+
|
|
454
|
+
// Emit off-screen position (detent = 0 since sheet is fully hidden)
|
|
455
|
+
emitChangePositionDelegate(currentDetentIndex, screenHeight, realtime = false)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Shows a previously hidden dialog.
|
|
460
|
+
* Used when the sheet on top dismisses.
|
|
461
|
+
*/
|
|
462
|
+
fun showDialog() {
|
|
463
|
+
isDialogVisible = true
|
|
464
|
+
dialog?.window?.decorView?.visibility = View.VISIBLE
|
|
465
|
+
|
|
466
|
+
// Emit current position
|
|
467
|
+
val positionPx = bottomSheetView?.let { ScreenUtils.getScreenY(it) } ?: screenHeight
|
|
468
|
+
emitChangePositionDelegate(currentDetentIndex, positionPx, realtime = false)
|
|
469
|
+
}
|
|
470
|
+
|
|
339
471
|
// ====================================================================
|
|
340
472
|
// MARK: - Presentation
|
|
341
473
|
// ====================================================================
|
|
@@ -346,20 +478,25 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
346
478
|
return
|
|
347
479
|
}
|
|
348
480
|
|
|
349
|
-
currentDetentIndex = detentIndex
|
|
350
481
|
setupDimmedBackground(detentIndex)
|
|
351
482
|
|
|
352
483
|
if (isPresented) {
|
|
353
|
-
|
|
354
|
-
|
|
484
|
+
// Detent change will be emitted when sheet settles in onStateChanged
|
|
485
|
+
// Don't update currentDetentIndex here - it will be updated when sheet settles
|
|
355
486
|
setStateForDetentIndex(detentIndex)
|
|
356
487
|
} else {
|
|
488
|
+
currentDetentIndex = detentIndex
|
|
357
489
|
isDragging = false
|
|
358
490
|
setupSheetDetents()
|
|
359
491
|
setStateForDetentIndex(detentIndex)
|
|
360
492
|
|
|
361
493
|
val detentInfo = getDetentInfoForIndex(detentIndex)
|
|
362
|
-
|
|
494
|
+
val detent = getDetentValueForIndex(detentInfo.index)
|
|
495
|
+
|
|
496
|
+
// Notify parent sheet that it is about to lose focus (before this sheet appears)
|
|
497
|
+
parentSheetView?.viewControllerWillBlur()
|
|
498
|
+
|
|
499
|
+
delegate?.viewControllerWillPresent(detentInfo.index, detentInfo.position, detent)
|
|
363
500
|
|
|
364
501
|
if (!animated) {
|
|
365
502
|
dialog.window?.setWindowAnimations(0)
|
|
@@ -371,8 +508,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
371
508
|
|
|
372
509
|
fun dismiss() {
|
|
373
510
|
this.post {
|
|
374
|
-
|
|
375
|
-
|
|
511
|
+
// Emit off-screen position (detent = 0 since sheet is fully hidden)
|
|
512
|
+
emitChangePositionDelegate(currentDetentIndex, screenHeight, realtime = false)
|
|
376
513
|
}
|
|
377
514
|
dialog?.dismiss()
|
|
378
515
|
}
|
|
@@ -384,39 +521,53 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
384
521
|
fun setupSheetDetents() {
|
|
385
522
|
val behavior = this.behavior ?: return
|
|
386
523
|
|
|
524
|
+
// Reset resolved positions if detents count changed
|
|
525
|
+
if (resolvedDetentPositions.size != detents.size) {
|
|
526
|
+
resolvedDetentPositions.clear()
|
|
527
|
+
repeat(detents.size) { resolvedDetentPositions.add(0) }
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Always update auto detent positions based on current content height
|
|
531
|
+
for (i in detents.indices) {
|
|
532
|
+
if (detents[i] == -1.0) {
|
|
533
|
+
val detentHeight = getDetentHeight(detents[i])
|
|
534
|
+
resolvedDetentPositions[i] = screenHeight - detentHeight
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// Flag to prevent state change callbacks from updating detent index during reconfiguration
|
|
539
|
+
isReconfiguring = true
|
|
540
|
+
|
|
387
541
|
behavior.apply {
|
|
388
542
|
skipCollapsed = false
|
|
389
|
-
isFitToContents =
|
|
543
|
+
isFitToContents = false
|
|
390
544
|
maxWidth = DEFAULT_MAX_WIDTH.dpToPx().toInt()
|
|
391
545
|
|
|
392
546
|
when (detents.size) {
|
|
393
547
|
1 -> {
|
|
394
|
-
|
|
395
|
-
|
|
548
|
+
setPeekHeight(getDetentHeight(detents[0]), isPresented)
|
|
549
|
+
expandedOffset = screenHeight - peekHeight
|
|
396
550
|
}
|
|
397
551
|
|
|
398
552
|
2 -> {
|
|
399
553
|
setPeekHeight(getDetentHeight(detents[0]), isPresented)
|
|
400
|
-
|
|
554
|
+
expandedOffset = screenHeight - getDetentHeight(detents[1])
|
|
401
555
|
}
|
|
402
556
|
|
|
403
557
|
3 -> {
|
|
404
|
-
isFitToContents = false
|
|
405
558
|
setPeekHeight(getDetentHeight(detents[0]), isPresented)
|
|
406
|
-
maxHeight = getDetentHeight(detents[2])
|
|
407
|
-
expandedOffset = sheetTopInset
|
|
408
559
|
halfExpandedRatio = minOf(getDetentHeight(detents[1]).toFloat() / screenHeight.toFloat(), 1.0f)
|
|
560
|
+
expandedOffset = screenHeight - getDetentHeight(detents[2])
|
|
409
561
|
}
|
|
410
562
|
}
|
|
563
|
+
}
|
|
411
564
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
params.height = maxHeight
|
|
416
|
-
layoutParams = params
|
|
417
|
-
}
|
|
418
|
-
}
|
|
565
|
+
// Re-apply current state to update position after config changes
|
|
566
|
+
if (isPresented) {
|
|
567
|
+
setStateForDetentIndex(currentDetentIndex)
|
|
419
568
|
}
|
|
569
|
+
|
|
570
|
+
isReconfiguring = false
|
|
420
571
|
}
|
|
421
572
|
|
|
422
573
|
fun setupGrabber() {
|
|
@@ -426,23 +577,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
426
577
|
bottomSheet.removeView(it)
|
|
427
578
|
}
|
|
428
579
|
|
|
429
|
-
if (!grabber) return
|
|
580
|
+
if (!grabber || !draggable) return
|
|
430
581
|
|
|
431
|
-
val grabberView =
|
|
582
|
+
val grabberView = TrueSheetGrabberView(reactContext).apply {
|
|
432
583
|
tag = GRABBER_TAG
|
|
433
|
-
layoutParams = FrameLayout.LayoutParams(
|
|
434
|
-
GRABBER_WIDTH.dpToPx().toInt(),
|
|
435
|
-
GRABBER_HEIGHT.dpToPx().toInt()
|
|
436
|
-
).apply {
|
|
437
|
-
gravity = Gravity.CENTER_HORIZONTAL or Gravity.TOP
|
|
438
|
-
topMargin = GRABBER_TOP_MARGIN.dpToPx().toInt()
|
|
439
|
-
}
|
|
440
|
-
background = GradientDrawable().apply {
|
|
441
|
-
shape = GradientDrawable.RECTANGLE
|
|
442
|
-
cornerRadius = (GRABBER_HEIGHT / 2).dpToPx()
|
|
443
|
-
setColor(GRABBER_COLOR)
|
|
444
|
-
}
|
|
445
|
-
elevation = 1f
|
|
446
584
|
}
|
|
447
585
|
|
|
448
586
|
bottomSheet.addView(grabberView)
|
|
@@ -538,6 +676,159 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
538
676
|
}
|
|
539
677
|
}
|
|
540
678
|
|
|
679
|
+
// ====================================================================
|
|
680
|
+
// MARK: - Position Change Delegate
|
|
681
|
+
// ====================================================================
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Emits position change to the delegate if the position has changed.
|
|
685
|
+
* @param index The current detent index (discrete, used as fallback)
|
|
686
|
+
* @param positionPx The current position in pixels (screen Y coordinate)
|
|
687
|
+
* @param realtime Whether the position is a real-time value (during drag or animation tracking)
|
|
688
|
+
*/
|
|
689
|
+
private fun emitChangePositionDelegate(index: Int, positionPx: Int, realtime: Boolean) {
|
|
690
|
+
if (positionPx == lastEmittedPositionPx) return
|
|
691
|
+
|
|
692
|
+
lastEmittedPositionPx = positionPx
|
|
693
|
+
val position = positionPx.pxToDp()
|
|
694
|
+
val interpolatedIndex = getInterpolatedIndexForPosition(positionPx)
|
|
695
|
+
val detent = getInterpolatedDetentForPosition(positionPx)
|
|
696
|
+
delegate?.viewControllerDidChangePosition(interpolatedIndex, position, detent, realtime)
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Stores the current Y position as the resolved position for the given detent index.
|
|
701
|
+
* This is called when the sheet settles at a detent to capture the actual position
|
|
702
|
+
* which may differ from the calculated position due to system adjustments.
|
|
703
|
+
*/
|
|
704
|
+
private fun storeResolvedPosition(index: Int) {
|
|
705
|
+
if (index < 0 || index >= resolvedDetentPositions.size) return
|
|
706
|
+
val positionPx = bottomSheetView?.let { ScreenUtils.getScreenY(it) } ?: return
|
|
707
|
+
if (positionPx in 1..<screenHeight) {
|
|
708
|
+
resolvedDetentPositions[index] = positionPx
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Stores the resolved position for the current detent.
|
|
714
|
+
* Called from TrueSheetView when content size changes.
|
|
715
|
+
*/
|
|
716
|
+
fun storeCurrentResolvedPosition() {
|
|
717
|
+
storeResolvedPosition(currentDetentIndex)
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Returns the estimated Y position for a detent index, using stored positions when available.
|
|
722
|
+
*/
|
|
723
|
+
private fun getEstimatedPositionForIndex(index: Int): Int {
|
|
724
|
+
if (index < 0 || index >= resolvedDetentPositions.size) return screenHeight
|
|
725
|
+
|
|
726
|
+
val storedPos = resolvedDetentPositions[index]
|
|
727
|
+
if (storedPos > 0) return storedPos
|
|
728
|
+
|
|
729
|
+
// Estimate based on getDetentHeight which accounts for bottomInset and maxAllowedHeight
|
|
730
|
+
if (index < detents.size) {
|
|
731
|
+
val detentHeight = getDetentHeight(detents[index])
|
|
732
|
+
return screenHeight - detentHeight
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
return screenHeight
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* Finds the segment index and interpolation progress for a given position.
|
|
740
|
+
* Returns a Triple of (fromIndex, toIndex, progress) where progress is 0-1 within that segment.
|
|
741
|
+
* Returns null if position count is less than 2.
|
|
742
|
+
*/
|
|
743
|
+
private fun findSegmentForPosition(positionPx: Int): Triple<Int, Int, Float>? {
|
|
744
|
+
val count = resolvedDetentPositions.size
|
|
745
|
+
if (count < 2) return null
|
|
746
|
+
|
|
747
|
+
val firstPos = getEstimatedPositionForIndex(0)
|
|
748
|
+
val lastPos = getEstimatedPositionForIndex(count - 1)
|
|
749
|
+
|
|
750
|
+
// Below first detent
|
|
751
|
+
if (positionPx > firstPos) {
|
|
752
|
+
val range = screenHeight - firstPos
|
|
753
|
+
val progress = if (range > 0) (positionPx - firstPos).toFloat() / range else 0f
|
|
754
|
+
return Triple(-1, 0, progress) // Special index -1 for below first
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Above last detent
|
|
758
|
+
if (positionPx < lastPos) {
|
|
759
|
+
return Triple(count - 1, count - 1, 0f)
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Find segment (positions decrease as index increases)
|
|
763
|
+
for (i in 0 until count - 1) {
|
|
764
|
+
val pos = getEstimatedPositionForIndex(i)
|
|
765
|
+
val nextPos = getEstimatedPositionForIndex(i + 1)
|
|
766
|
+
|
|
767
|
+
if (positionPx <= pos && positionPx >= nextPos) {
|
|
768
|
+
val range = pos - nextPos
|
|
769
|
+
val progress = if (range > 0) (pos - positionPx).toFloat() / range else 0f
|
|
770
|
+
return Triple(i, i + 1, maxOf(0f, minOf(1f, progress)))
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
return Triple(count - 1, count - 1, 0f)
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/**
|
|
778
|
+
* Calculates the interpolated index based on position.
|
|
779
|
+
* Returns a continuous value (e.g., 0.5 means halfway between detent 0 and 1).
|
|
780
|
+
*/
|
|
781
|
+
private fun getInterpolatedIndexForPosition(positionPx: Int): Float {
|
|
782
|
+
val count = resolvedDetentPositions.size
|
|
783
|
+
if (count == 0) return -1f
|
|
784
|
+
if (count == 1) return 0f
|
|
785
|
+
|
|
786
|
+
val segment = findSegmentForPosition(positionPx) ?: return 0f
|
|
787
|
+
val (fromIndex, _, progress) = segment
|
|
788
|
+
|
|
789
|
+
// Below first detent
|
|
790
|
+
if (fromIndex == -1) return -progress
|
|
791
|
+
|
|
792
|
+
return fromIndex + progress
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Calculates the interpolated detent value based on position.
|
|
797
|
+
* Returns the actual screen fraction, clamped to valid detent range.
|
|
798
|
+
*/
|
|
799
|
+
private fun getInterpolatedDetentForPosition(positionPx: Int): Float {
|
|
800
|
+
val count = resolvedDetentPositions.size
|
|
801
|
+
if (count == 0) return 0f
|
|
802
|
+
|
|
803
|
+
val segment = findSegmentForPosition(positionPx) ?: return getDetentValueForIndex(0)
|
|
804
|
+
val (fromIndex, toIndex, progress) = segment
|
|
805
|
+
|
|
806
|
+
// Below first detent
|
|
807
|
+
if (fromIndex == -1) {
|
|
808
|
+
val firstDetent = getDetentValueForIndex(0)
|
|
809
|
+
return maxOf(0f, firstDetent * (1 - progress))
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
val fromDetent = getDetentValueForIndex(fromIndex)
|
|
813
|
+
val toDetent = getDetentValueForIndex(toIndex)
|
|
814
|
+
return fromDetent + progress * (toDetent - fromDetent)
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
/**
|
|
818
|
+
* Gets the detent value (fraction) for a given index.
|
|
819
|
+
* Returns the raw screen fraction without bottomInset for interpolation calculations.
|
|
820
|
+
* Note: bottomInset is only added in getDetentHeight() for actual sheet sizing.
|
|
821
|
+
*/
|
|
822
|
+
private fun getDetentValueForIndex(index: Int): Float {
|
|
823
|
+
if (index < 0 || index >= detents.size) return 0f
|
|
824
|
+
val value = detents[index]
|
|
825
|
+
return if (value == -1.0) {
|
|
826
|
+
(contentHeight + headerHeight).toFloat() / screenHeight.toFloat()
|
|
827
|
+
} else {
|
|
828
|
+
value.toFloat()
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
541
832
|
// ====================================================================
|
|
542
833
|
// MARK: - Drag Handling
|
|
543
834
|
// ====================================================================
|
|
@@ -547,36 +838,40 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
547
838
|
return DetentInfo(currentDetentIndex, screenY.pxToDp())
|
|
548
839
|
}
|
|
549
840
|
|
|
841
|
+
private fun getCurrentPositionPx(sheetView: View): Int = ScreenUtils.getScreenY(sheetView)
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Returns the detent index for the current position.
|
|
845
|
+
* Only reports a higher index when the sheet has reached that detent's height.
|
|
846
|
+
*/
|
|
847
|
+
private fun getDetentIndexForPosition(positionPx: Int): Int {
|
|
848
|
+
if (detents.isEmpty()) return 0
|
|
849
|
+
|
|
850
|
+
val sheetHeight = screenHeight - positionPx
|
|
851
|
+
|
|
852
|
+
// Find the highest detent index that the sheet has reached
|
|
853
|
+
for (i in detents.indices.reversed()) {
|
|
854
|
+
val detentHeight = getDetentHeight(detents[i])
|
|
855
|
+
if (sheetHeight >= detentHeight) {
|
|
856
|
+
return i
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
return 0
|
|
861
|
+
}
|
|
862
|
+
|
|
550
863
|
private fun handleDragBegin(sheetView: View) {
|
|
551
864
|
val detentInfo = getCurrentDetentInfo(sheetView)
|
|
552
|
-
|
|
865
|
+
val detent = getDetentValueForIndex(detentInfo.index)
|
|
866
|
+
delegate?.viewControllerDidDragBegin(detentInfo.index, detentInfo.position, detent)
|
|
553
867
|
isDragging = true
|
|
554
868
|
}
|
|
555
869
|
|
|
556
870
|
private fun handleDragChange(sheetView: View) {
|
|
557
871
|
if (!isDragging) return
|
|
558
872
|
val detentInfo = getCurrentDetentInfo(sheetView)
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
private fun handleDragEnd(state: Int) {
|
|
563
|
-
if (!isDragging) return
|
|
564
|
-
|
|
565
|
-
val detentInfo = getDetentInfoForState(state)
|
|
566
|
-
detentInfo?.let {
|
|
567
|
-
delegate?.viewControllerDidDragEnd(it.index, it.position)
|
|
568
|
-
|
|
569
|
-
if (it.index != currentDetentIndex) {
|
|
570
|
-
presentPromise?.invoke()
|
|
571
|
-
presentPromise = null
|
|
572
|
-
|
|
573
|
-
currentDetentIndex = it.index
|
|
574
|
-
setupDimmedBackground(it.index)
|
|
575
|
-
delegate?.viewControllerDidChangeDetent(it.index, it.position)
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
isDragging = false
|
|
873
|
+
val detent = getDetentValueForIndex(detentInfo.index)
|
|
874
|
+
delegate?.viewControllerDidDragChange(detentInfo.index, detentInfo.position, detent)
|
|
580
875
|
}
|
|
581
876
|
|
|
582
877
|
// ====================================================================
|
|
@@ -585,12 +880,15 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
585
880
|
|
|
586
881
|
private fun getDetentHeight(detent: Double): Int {
|
|
587
882
|
val height: Int = if (detent == -1.0) {
|
|
588
|
-
|
|
883
|
+
// For auto detent, add bottomInset to match iOS behavior where the system
|
|
884
|
+
// adds bottom safe area inset internally to the sheet height
|
|
885
|
+
contentHeight + headerHeight + bottomInset
|
|
589
886
|
} else {
|
|
590
887
|
if (detent <= 0.0 || detent > 1.0) {
|
|
591
888
|
throw IllegalArgumentException("TrueSheet: detent fraction ($detent) must be between 0 and 1")
|
|
592
889
|
}
|
|
593
|
-
|
|
890
|
+
// For fractional detents, add bottomInset to match iOS behavior
|
|
891
|
+
(detent * screenHeight).toInt() + bottomInset
|
|
594
892
|
}
|
|
595
893
|
|
|
596
894
|
val maxAllowedHeight = screenHeight - sheetTopInset
|
|
@@ -669,7 +967,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
669
967
|
super.onSizeChanged(w, h, oldw, oldh)
|
|
670
968
|
if (w == oldw && h == oldh) return
|
|
671
969
|
|
|
672
|
-
delegate?.viewControllerDidChangeSize(w,
|
|
970
|
+
delegate?.viewControllerDidChangeSize(w, h)
|
|
673
971
|
|
|
674
972
|
val oldScreenHeight = screenHeight
|
|
675
973
|
screenHeight = ScreenUtils.getScreenHeight(reactContext, edgeToEdgeEnabled)
|
|
@@ -678,8 +976,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
678
976
|
setupSheetDetents()
|
|
679
977
|
this.post {
|
|
680
978
|
positionFooter()
|
|
681
|
-
|
|
682
|
-
|
|
979
|
+
storeResolvedPosition(currentDetentIndex)
|
|
980
|
+
val positionPx = bottomSheetView?.let { ScreenUtils.getScreenY(it) } ?: screenHeight
|
|
981
|
+
emitChangePositionDelegate(currentDetentIndex, positionPx, realtime = false)
|
|
683
982
|
}
|
|
684
983
|
}
|
|
685
984
|
}
|
|
@@ -699,7 +998,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
699
998
|
*/
|
|
700
999
|
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
|
701
1000
|
val footer = containerView?.footerView
|
|
702
|
-
if (footer != null && footer.
|
|
1001
|
+
if (footer != null && footer.isVisible) {
|
|
703
1002
|
val footerLocation = ScreenUtils.getScreenLocation(footer)
|
|
704
1003
|
val touchScreenX = event.rawX.toInt()
|
|
705
1004
|
val touchScreenY = event.rawY.toInt()
|