@lodev09/react-native-true-sheet 3.5.1-beta.2 → 3.5.1-beta.4
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/android/src/main/java/com/lodev09/truesheet/TrueSheetModule.kt +2 -2
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +32 -28
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +378 -253
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +0 -5
- package/android/src/main/java/com/lodev09/truesheet/core/RNScreensFragmentObserver.kt +36 -14
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +150 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +55 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +18 -12
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +91 -2
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetKeyboardObserver.kt +21 -11
- package/android/src/main/java/com/lodev09/truesheet/core/{TrueSheetDialogObserver.kt → TrueSheetStackManager.kt} +7 -5
- package/ios/TrueSheetViewController.h +2 -3
- package/ios/TrueSheetViewController.mm +11 -4
- package/ios/core/TrueSheetDetentCalculator.h +2 -3
- package/ios/core/TrueSheetDetentCalculator.mm +7 -9
- package/lib/module/TrueSheet.js +1 -16
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/fabric/TrueSheetViewNativeComponent.ts +0 -1
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheet.types.d.ts +0 -8
- package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +0 -1
- package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
- package/lib/typescript/src/navigation/types.d.ts +1 -1
- package/lib/typescript/src/navigation/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/TrueSheet.tsx +1 -16
- package/src/TrueSheet.types.ts +0 -9
- package/src/fabric/TrueSheetViewNativeComponent.ts +0 -1
- package/src/navigation/types.ts +0 -1
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetAnimator.kt +0 -145
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDialogFragment.kt +0 -320
- package/android/src/main/res/anim/fast_fade_out.xml +0 -6
- package/android/src/main/res/values/styles.xml +0 -21
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
package com.lodev09.truesheet
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
|
-
import android.
|
|
4
|
+
import android.os.Build
|
|
5
5
|
import android.view.MotionEvent
|
|
6
6
|
import android.view.View
|
|
7
|
-
import android.view.
|
|
7
|
+
import android.view.ViewGroup
|
|
8
8
|
import android.view.accessibility.AccessibilityNodeInfo
|
|
9
|
-
import
|
|
9
|
+
import androidx.activity.OnBackPressedCallback
|
|
10
10
|
import androidx.appcompat.app.AppCompatActivity
|
|
11
|
+
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
11
12
|
import androidx.core.view.isNotEmpty
|
|
12
13
|
import androidx.core.view.isVisible
|
|
13
14
|
import com.facebook.react.R
|
|
@@ -21,19 +22,19 @@ import com.facebook.react.uimanager.events.EventDispatcher
|
|
|
21
22
|
import com.facebook.react.util.RNLog
|
|
22
23
|
import com.facebook.react.views.view.ReactViewGroup
|
|
23
24
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
24
|
-
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
25
25
|
import com.lodev09.truesheet.core.GrabberOptions
|
|
26
26
|
import com.lodev09.truesheet.core.RNScreensFragmentObserver
|
|
27
|
-
import com.lodev09.truesheet.core.
|
|
28
|
-
import com.lodev09.truesheet.core.
|
|
27
|
+
import com.lodev09.truesheet.core.TrueSheetBottomSheetView
|
|
28
|
+
import com.lodev09.truesheet.core.TrueSheetBottomSheetViewDelegate
|
|
29
|
+
import com.lodev09.truesheet.core.TrueSheetCoordinatorLayout
|
|
30
|
+
import com.lodev09.truesheet.core.TrueSheetCoordinatorLayoutDelegate
|
|
29
31
|
import com.lodev09.truesheet.core.TrueSheetDetentCalculator
|
|
30
|
-
import com.lodev09.truesheet.core.
|
|
31
|
-
import com.lodev09.truesheet.core.TrueSheetDialogFragment
|
|
32
|
-
import com.lodev09.truesheet.core.TrueSheetDialogFragmentDelegate
|
|
33
|
-
import com.lodev09.truesheet.core.TrueSheetDialogObserver
|
|
32
|
+
import com.lodev09.truesheet.core.TrueSheetDetentCalculatorDelegate
|
|
34
33
|
import com.lodev09.truesheet.core.TrueSheetDimView
|
|
34
|
+
import com.lodev09.truesheet.core.TrueSheetDimViewDelegate
|
|
35
35
|
import com.lodev09.truesheet.core.TrueSheetKeyboardObserver
|
|
36
36
|
import com.lodev09.truesheet.core.TrueSheetKeyboardObserverDelegate
|
|
37
|
+
import com.lodev09.truesheet.core.TrueSheetStackManager
|
|
37
38
|
import com.lodev09.truesheet.utils.ScreenUtils
|
|
38
39
|
|
|
39
40
|
// =============================================================================
|
|
@@ -65,24 +66,29 @@ interface TrueSheetViewControllerDelegate {
|
|
|
65
66
|
// =============================================================================
|
|
66
67
|
|
|
67
68
|
/**
|
|
68
|
-
* Manages the bottom sheet
|
|
69
|
-
*
|
|
69
|
+
* Manages the bottom sheet using CoordinatorLayout + BottomSheetBehavior.
|
|
70
|
+
*
|
|
71
|
+
* This approach keeps the sheet in the same activity window (no separate dialog window),
|
|
72
|
+
* which allows touch events to pass through to underlying views when the sheet is not
|
|
73
|
+
* covering them. This solves the touch lag issue when sheets are presented over
|
|
74
|
+
* interactive components like Maps.
|
|
70
75
|
*/
|
|
71
76
|
@SuppressLint("ClickableViewAccessibility", "ViewConstructor")
|
|
72
77
|
class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
73
78
|
ReactViewGroup(reactContext),
|
|
74
79
|
RootView,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
80
|
+
TrueSheetDetentCalculatorDelegate,
|
|
81
|
+
TrueSheetDimViewDelegate,
|
|
82
|
+
TrueSheetCoordinatorLayoutDelegate,
|
|
83
|
+
TrueSheetBottomSheetViewDelegate {
|
|
78
84
|
|
|
79
85
|
companion object {
|
|
80
86
|
const val TAG_NAME = "TrueSheet"
|
|
81
87
|
|
|
82
|
-
private const val FRAGMENT_TAG = "TrueSheetDialogFragment"
|
|
83
88
|
private const val DEFAULT_MAX_WIDTH = 640 // dp
|
|
84
89
|
private const val DEFAULT_CORNER_RADIUS = 16 // dp
|
|
85
90
|
private const val TRANSLATE_ANIMATION_DURATION = 200L
|
|
91
|
+
private const val DISMISS_DURATION = 200L
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
// =============================================================================
|
|
@@ -101,16 +107,20 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
101
107
|
|
|
102
108
|
var delegate: TrueSheetViewControllerDelegate? = null
|
|
103
109
|
|
|
104
|
-
//
|
|
105
|
-
|
|
110
|
+
// CoordinatorLayout components (replaces DialogFragment)
|
|
111
|
+
internal var sheetView: TrueSheetBottomSheetView? = null
|
|
112
|
+
private var coordinatorLayout: TrueSheetCoordinatorLayout? = null
|
|
106
113
|
private var dimView: TrueSheetDimView? = null
|
|
107
114
|
private var parentDimView: TrueSheetDimView? = null
|
|
108
115
|
|
|
116
|
+
// Back button handling
|
|
117
|
+
private var backCallback: OnBackPressedCallback? = null
|
|
118
|
+
|
|
109
119
|
// Presentation State
|
|
110
120
|
var isPresented = false
|
|
111
121
|
private set
|
|
112
122
|
|
|
113
|
-
var
|
|
123
|
+
var isSheetVisible = false
|
|
114
124
|
private set
|
|
115
125
|
|
|
116
126
|
var currentDetentIndex: Int = -1
|
|
@@ -119,7 +129,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
119
129
|
private var interactionState: InteractionState = InteractionState.Idle
|
|
120
130
|
private var isDismissing = false
|
|
121
131
|
private var wasHiddenByModal = false
|
|
122
|
-
private var shouldAnimatePresent =
|
|
132
|
+
private var shouldAnimatePresent = false
|
|
133
|
+
private var isPresentAnimating = false
|
|
123
134
|
|
|
124
135
|
private var lastStateWidth: Int = 0
|
|
125
136
|
private var lastStateHeight: Int = 0
|
|
@@ -137,15 +148,16 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
137
148
|
var parentSheetView: TrueSheetView? = null
|
|
138
149
|
|
|
139
150
|
// Helper Objects
|
|
140
|
-
private val sheetAnimator = TrueSheetAnimator(this)
|
|
141
151
|
private var keyboardObserver: TrueSheetKeyboardObserver? = null
|
|
142
152
|
private var rnScreensObserver: RNScreensFragmentObserver? = null
|
|
143
|
-
|
|
153
|
+
internal val detentCalculator = TrueSheetDetentCalculator(reactContext).apply {
|
|
154
|
+
delegate = this@TrueSheetViewController
|
|
155
|
+
}
|
|
144
156
|
|
|
145
157
|
// Touch Dispatchers
|
|
146
158
|
internal var eventDispatcher: EventDispatcher? = null
|
|
147
|
-
private val
|
|
148
|
-
private var
|
|
159
|
+
private val jsTouchDispatcher = JSTouchDispatcher(this)
|
|
160
|
+
private var jsPointerDispatcher: JSPointerDispatcher? = null
|
|
149
161
|
|
|
150
162
|
// Detent Configuration
|
|
151
163
|
override var maxSheetHeight: Int? = null
|
|
@@ -154,47 +166,37 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
154
166
|
// Appearance Configuration
|
|
155
167
|
var dimmed = true
|
|
156
168
|
var dimmedDetentIndex = 0
|
|
157
|
-
var grabber: Boolean = true
|
|
158
|
-
var grabberOptions: GrabberOptions? = null
|
|
159
|
-
var sheetBackgroundColor: Int? = null
|
|
160
|
-
var edgeToEdgeFullScreen: Boolean = false
|
|
169
|
+
override var grabber: Boolean = true
|
|
170
|
+
override var grabberOptions: GrabberOptions? = null
|
|
171
|
+
override var sheetBackgroundColor: Int? = null
|
|
161
172
|
var insetAdjustment: String = "automatic"
|
|
162
173
|
|
|
163
|
-
var sheetCornerRadius: Float = DEFAULT_CORNER_RADIUS.dpToPx()
|
|
174
|
+
override var sheetCornerRadius: Float = DEFAULT_CORNER_RADIUS.dpToPx()
|
|
164
175
|
set(value) {
|
|
165
176
|
field = if (value < 0) DEFAULT_CORNER_RADIUS.dpToPx() else value
|
|
166
|
-
|
|
167
|
-
if (isPresented) dialogFragment?.setupBackground()
|
|
177
|
+
if (isPresented) sheetView?.setupBackground()
|
|
168
178
|
}
|
|
169
179
|
|
|
170
180
|
var dismissible: Boolean = true
|
|
171
181
|
set(value) {
|
|
172
182
|
field = value
|
|
173
|
-
|
|
183
|
+
behavior?.isHideable = value
|
|
174
184
|
}
|
|
175
185
|
|
|
176
186
|
var draggable: Boolean = true
|
|
177
187
|
set(value) {
|
|
178
188
|
field = value
|
|
179
|
-
|
|
189
|
+
behavior?.isDraggable = value
|
|
190
|
+
if (isPresented) sheetView?.setupGrabber()
|
|
180
191
|
}
|
|
181
192
|
|
|
182
193
|
// =============================================================================
|
|
183
194
|
// MARK: - Computed Properties
|
|
184
195
|
// =============================================================================
|
|
185
196
|
|
|
186
|
-
//
|
|
187
|
-
private val
|
|
188
|
-
get() =
|
|
189
|
-
|
|
190
|
-
private val behavior: BottomSheetBehavior<FrameLayout>?
|
|
191
|
-
get() = dialogFragment?.behavior
|
|
192
|
-
|
|
193
|
-
private val sheetContainer: FrameLayout?
|
|
194
|
-
get() = this.parent as? FrameLayout
|
|
195
|
-
|
|
196
|
-
override val bottomSheetView: FrameLayout?
|
|
197
|
-
get() = dialogFragment?.bottomSheetView
|
|
197
|
+
// Behavior
|
|
198
|
+
private val behavior: BottomSheetBehavior<TrueSheetBottomSheetView>?
|
|
199
|
+
get() = sheetView?.behavior
|
|
198
200
|
|
|
199
201
|
private val containerView: TrueSheetContainerView?
|
|
200
202
|
get() = if (this.isNotEmpty()) getChildAt(0) as? TrueSheetContainerView else null
|
|
@@ -238,126 +240,174 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
238
240
|
private val edgeToEdgeEnabled: Boolean
|
|
239
241
|
get() {
|
|
240
242
|
val defaultEnabled = android.os.Build.VERSION.SDK_INT >= 36
|
|
241
|
-
return BuildConfig.EDGE_TO_EDGE_ENABLED ||
|
|
243
|
+
return BuildConfig.EDGE_TO_EDGE_ENABLED || defaultEnabled
|
|
242
244
|
}
|
|
243
245
|
|
|
244
246
|
// Sheet State
|
|
245
247
|
val isExpanded: Boolean
|
|
246
248
|
get() {
|
|
247
|
-
val sheetTop =
|
|
249
|
+
val sheetTop = sheetView?.top ?: return false
|
|
248
250
|
return sheetTop <= topInset
|
|
249
251
|
}
|
|
250
252
|
|
|
251
253
|
val currentTranslationY: Int
|
|
252
|
-
get() =
|
|
254
|
+
get() = sheetView?.translationY?.toInt() ?: 0
|
|
253
255
|
|
|
254
|
-
|
|
256
|
+
override val isTopmostSheet: Boolean
|
|
255
257
|
get() {
|
|
256
258
|
val hostView = delegate as? TrueSheetView ?: return true
|
|
257
|
-
return
|
|
259
|
+
return TrueSheetStackManager.isTopmostSheet(hostView)
|
|
258
260
|
}
|
|
259
261
|
|
|
262
|
+
private val dimViews: List<TrueSheetDimView>
|
|
263
|
+
get() = listOfNotNull(dimView, parentDimView)
|
|
264
|
+
|
|
260
265
|
// =============================================================================
|
|
261
266
|
// MARK: - Initialization
|
|
262
267
|
// =============================================================================
|
|
263
268
|
|
|
264
269
|
init {
|
|
265
|
-
|
|
270
|
+
jsPointerDispatcher = JSPointerDispatcher(this)
|
|
266
271
|
}
|
|
267
272
|
|
|
268
273
|
// =============================================================================
|
|
269
|
-
// MARK: -
|
|
274
|
+
// MARK: - Sheet Creation & Cleanup
|
|
270
275
|
// =============================================================================
|
|
271
276
|
|
|
272
|
-
fun
|
|
273
|
-
if (
|
|
277
|
+
fun createSheet() {
|
|
278
|
+
if (coordinatorLayout != null) return
|
|
279
|
+
|
|
280
|
+
// Create coordinator layout
|
|
281
|
+
coordinatorLayout = TrueSheetCoordinatorLayout(reactContext).apply {
|
|
282
|
+
delegate = this@TrueSheetViewController
|
|
283
|
+
}
|
|
274
284
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
this.contentView = this@TrueSheetViewController
|
|
278
|
-
this.reactContext = this@TrueSheetViewController.reactContext
|
|
279
|
-
this.sheetCornerRadius = this@TrueSheetViewController.sheetCornerRadius
|
|
280
|
-
this.sheetBackgroundColor = this@TrueSheetViewController.sheetBackgroundColor
|
|
281
|
-
this.edgeToEdgeFullScreen = this@TrueSheetViewController.edgeToEdgeFullScreen
|
|
282
|
-
this.grabberEnabled = this@TrueSheetViewController.grabber
|
|
283
|
-
this.grabberOptions = this@TrueSheetViewController.grabberOptions
|
|
284
|
-
this.dismissible = this@TrueSheetViewController.dismissible
|
|
285
|
-
this.draggable = this@TrueSheetViewController.draggable
|
|
285
|
+
sheetView = TrueSheetBottomSheetView(reactContext).apply {
|
|
286
|
+
delegate = this@TrueSheetViewController
|
|
286
287
|
}
|
|
287
288
|
|
|
288
289
|
setupModalObserver()
|
|
289
290
|
}
|
|
290
291
|
|
|
291
|
-
private fun
|
|
292
|
+
private fun cleanupSheet() {
|
|
292
293
|
cleanupKeyboardObserver()
|
|
293
294
|
cleanupModalObserver()
|
|
294
|
-
|
|
295
|
+
cleanupBackCallback()
|
|
296
|
+
sheetView?.animate()?.cancel()
|
|
297
|
+
|
|
298
|
+
// Remove from activity
|
|
299
|
+
removeFromActivity()
|
|
300
|
+
|
|
301
|
+
// Cleanup dim views
|
|
295
302
|
dimView?.detach()
|
|
296
303
|
dimView = null
|
|
297
304
|
parentDimView?.detach()
|
|
298
305
|
parentDimView = null
|
|
299
|
-
sheetContainer?.removeView(this)
|
|
300
306
|
|
|
301
|
-
|
|
307
|
+
// Detach content from sheet
|
|
308
|
+
sheetView?.removeView(this)
|
|
309
|
+
|
|
310
|
+
coordinatorLayout = null
|
|
311
|
+
sheetView = null
|
|
312
|
+
|
|
302
313
|
interactionState = InteractionState.Idle
|
|
303
314
|
isDismissing = false
|
|
304
315
|
isPresented = false
|
|
305
|
-
|
|
316
|
+
isSheetVisible = false
|
|
306
317
|
wasHiddenByModal = false
|
|
318
|
+
isPresentAnimating = false
|
|
307
319
|
lastEmittedPositionPx = -1
|
|
308
320
|
shouldAnimatePresent = true
|
|
309
321
|
}
|
|
310
322
|
|
|
323
|
+
private fun removeFromActivity() {
|
|
324
|
+
val coordinator = coordinatorLayout ?: return
|
|
325
|
+
val contentView = reactContext.currentActivity?.findViewById<ViewGroup>(android.R.id.content)
|
|
326
|
+
contentView?.removeView(coordinator)
|
|
327
|
+
}
|
|
328
|
+
|
|
311
329
|
// =============================================================================
|
|
312
|
-
// MARK: -
|
|
330
|
+
// MARK: - Back Button Handling
|
|
313
331
|
// =============================================================================
|
|
314
332
|
|
|
315
|
-
|
|
316
|
-
|
|
333
|
+
private fun setupBackCallback() {
|
|
334
|
+
val activity = reactContext.currentActivity as? AppCompatActivity ?: return
|
|
317
335
|
|
|
318
|
-
|
|
319
|
-
|
|
336
|
+
backCallback = object : OnBackPressedCallback(true) {
|
|
337
|
+
override fun handleOnBackPressed() {
|
|
338
|
+
delegate?.viewControllerDidBackPress()
|
|
339
|
+
if (dismissible) {
|
|
340
|
+
dismiss(animated = true)
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
320
344
|
|
|
321
|
-
|
|
345
|
+
activity.onBackPressedDispatcher.addCallback(backCallback!!)
|
|
346
|
+
}
|
|
322
347
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
348
|
+
private fun cleanupBackCallback() {
|
|
349
|
+
backCallback?.remove()
|
|
350
|
+
backCallback = null
|
|
351
|
+
}
|
|
326
352
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
updateDimAmount(effectiveTop)
|
|
335
|
-
},
|
|
336
|
-
onEnd = { finishPresent() }
|
|
337
|
-
)
|
|
338
|
-
} else {
|
|
339
|
-
val toTop = getExpectedSheetTop(currentDetentIndex)
|
|
340
|
-
emitChangePositionDelegate(toTop)
|
|
353
|
+
// =============================================================================
|
|
354
|
+
// MARK: - TrueSheetCoordinatorLayout.Delegate
|
|
355
|
+
// =============================================================================
|
|
356
|
+
|
|
357
|
+
override fun coordinatorLayoutDidLayout(changed: Boolean) {
|
|
358
|
+
// Reposition footer when layout changes
|
|
359
|
+
if (isPresented && changed) {
|
|
341
360
|
positionFooter()
|
|
342
|
-
finishPresent()
|
|
343
361
|
}
|
|
344
362
|
}
|
|
345
363
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
364
|
+
// =============================================================================
|
|
365
|
+
// MARK: - TrueSheetDimViewDelegate
|
|
366
|
+
// =============================================================================
|
|
367
|
+
|
|
368
|
+
override fun dimViewDidTap() {
|
|
369
|
+
val hostView = delegate as? TrueSheetView
|
|
370
|
+
if (hostView == null) {
|
|
371
|
+
RNLog.e(reactContext, "TrueSheet: Expected delegate to be TrueSheetView")
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// If there's a child sheet on top, handle it instead
|
|
376
|
+
val topmostChild = TrueSheetStackManager.getSheetsAbove(hostView).firstOrNull()
|
|
377
|
+
if (topmostChild != null) {
|
|
378
|
+
if (topmostChild.viewController.dismissible) {
|
|
379
|
+
topmostChild.viewController.dismiss(animated = true)
|
|
380
|
+
}
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (dismissible) {
|
|
385
|
+
dismiss(animated = true)
|
|
386
|
+
} else if (parentSheetView == null && currentDetentIndex > 0) {
|
|
387
|
+
setStateForDetentIndex(0)
|
|
388
|
+
}
|
|
349
389
|
}
|
|
350
390
|
|
|
351
|
-
|
|
352
|
-
|
|
391
|
+
// =============================================================================
|
|
392
|
+
// MARK: - BottomSheetCallback
|
|
393
|
+
// =============================================================================
|
|
394
|
+
|
|
395
|
+
private val sheetCallback = object : BottomSheetBehavior.BottomSheetCallback() {
|
|
396
|
+
override fun onStateChanged(sheetView: View, newState: Int) {
|
|
397
|
+
handleStateChanged(sheetView, newState)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
override fun onSlide(sheetView: View, slideOffset: Float) {
|
|
401
|
+
handleSlide(sheetView, slideOffset)
|
|
402
|
+
}
|
|
353
403
|
}
|
|
354
404
|
|
|
355
|
-
|
|
405
|
+
private fun handleStateChanged(sheetView: View, newState: Int) {
|
|
356
406
|
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
|
|
357
407
|
if (isDismissing) return
|
|
358
408
|
isDismissing = true
|
|
359
409
|
emitWillDismissEvents()
|
|
360
|
-
|
|
410
|
+
finishDismiss()
|
|
361
411
|
return
|
|
362
412
|
}
|
|
363
413
|
|
|
@@ -374,9 +424,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
374
424
|
}
|
|
375
425
|
}
|
|
376
426
|
|
|
377
|
-
|
|
378
|
-
// Skip
|
|
379
|
-
if (
|
|
427
|
+
private fun handleSlide(sheetView: View, slideOffset: Float) {
|
|
428
|
+
// Skip during dismiss animation
|
|
429
|
+
if (isDismissing) return
|
|
380
430
|
|
|
381
431
|
val behavior = behavior ?: return
|
|
382
432
|
|
|
@@ -389,23 +439,31 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
389
439
|
|
|
390
440
|
emitChangePositionDelegate(sheetView.top)
|
|
391
441
|
|
|
392
|
-
|
|
442
|
+
// On older APIs, use onSlide for footer positioning during keyboard transitions
|
|
443
|
+
val useLegacyKeyboardHandling = Build.VERSION.SDK_INT < Build.VERSION_CODES.R
|
|
444
|
+
if (!isKeyboardTransitioning || useLegacyKeyboardHandling) {
|
|
393
445
|
positionFooter(slideOffset)
|
|
394
|
-
updateDimAmount(sheetView.top)
|
|
395
446
|
}
|
|
396
|
-
}
|
|
397
447
|
|
|
398
|
-
|
|
399
|
-
|
|
448
|
+
if (!isKeyboardTransitioning) {
|
|
449
|
+
updateDimAmount(sheetView.top)
|
|
450
|
+
}
|
|
400
451
|
}
|
|
401
452
|
|
|
402
453
|
private fun handleStateSettled(sheetView: View, newState: Int) {
|
|
403
454
|
if (interactionState is InteractionState.Reconfiguring) return
|
|
404
455
|
|
|
405
456
|
val index = detentCalculator.getDetentIndexForState(newState) ?: return
|
|
406
|
-
val position =
|
|
457
|
+
val position = getPositionDpForView(sheetView)
|
|
407
458
|
val detentInfo = DetentInfo(index, position)
|
|
408
459
|
|
|
460
|
+
// Handle present animation completion
|
|
461
|
+
if (isPresentAnimating) {
|
|
462
|
+
isPresentAnimating = false
|
|
463
|
+
finishPresent()
|
|
464
|
+
return
|
|
465
|
+
}
|
|
466
|
+
|
|
409
467
|
when (interactionState) {
|
|
410
468
|
is InteractionState.Dragging -> {
|
|
411
469
|
val detent = detentCalculator.getDetentValueForIndex(detentInfo.index)
|
|
@@ -442,7 +500,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
442
500
|
rnScreensObserver = RNScreensFragmentObserver(
|
|
443
501
|
reactContext = reactContext,
|
|
444
502
|
onModalPresented = {
|
|
445
|
-
if (isPresented &&
|
|
503
|
+
if (isPresented && isSheetVisible && isTopmostSheet) {
|
|
446
504
|
hideForModal()
|
|
447
505
|
}
|
|
448
506
|
},
|
|
@@ -469,45 +527,51 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
469
527
|
rnScreensObserver = null
|
|
470
528
|
}
|
|
471
529
|
|
|
530
|
+
private fun setSheetVisibility(visible: Boolean) {
|
|
531
|
+
coordinatorLayout?.visibility = if (visible) VISIBLE else GONE
|
|
532
|
+
dimViews.forEach { it.visibility = if (visible) VISIBLE else INVISIBLE }
|
|
533
|
+
}
|
|
534
|
+
|
|
472
535
|
private fun hideForModal() {
|
|
473
|
-
|
|
536
|
+
isSheetVisible = false
|
|
474
537
|
wasHiddenByModal = true
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
dimView?.alpha = 0f
|
|
478
|
-
parentDimView?.alpha = 0f
|
|
479
|
-
|
|
480
|
-
dialog?.window?.setWindowAnimations(com.lodev09.truesheet.R.style.TrueSheetFastFadeOut)
|
|
481
|
-
dialog?.window?.decorView?.visibility = GONE
|
|
482
|
-
dimView?.visibility = INVISIBLE
|
|
483
|
-
parentDimView?.visibility = INVISIBLE
|
|
484
|
-
|
|
538
|
+
dimViews.forEach { it.alpha = 0f }
|
|
539
|
+
setSheetVisibility(false)
|
|
485
540
|
parentSheetView?.viewController?.hideForModal()
|
|
486
541
|
}
|
|
487
542
|
|
|
488
543
|
private fun showAfterModal() {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
dialog?.window?.setWindowAnimations(0)
|
|
492
|
-
dialog?.window?.decorView?.visibility = VISIBLE
|
|
493
|
-
dimView?.visibility = VISIBLE
|
|
494
|
-
parentDimView?.visibility = VISIBLE
|
|
495
|
-
|
|
544
|
+
isSheetVisible = true
|
|
545
|
+
setSheetVisibility(true)
|
|
496
546
|
updateDimAmount(animated = true)
|
|
497
547
|
}
|
|
498
548
|
|
|
549
|
+
/**
|
|
550
|
+
* Re-applies hidden state after returning from background.
|
|
551
|
+
* Android may restore visibility on activity resume, so we need to hide it again.
|
|
552
|
+
*/
|
|
553
|
+
fun reapplyHiddenState() {
|
|
554
|
+
if (!wasHiddenByModal) return
|
|
555
|
+
setSheetVisibility(false)
|
|
556
|
+
}
|
|
557
|
+
|
|
499
558
|
// =============================================================================
|
|
500
559
|
// MARK: - Presentation
|
|
501
560
|
// =============================================================================
|
|
502
561
|
|
|
503
562
|
fun present(detentIndex: Int, animated: Boolean = true) {
|
|
504
|
-
val
|
|
505
|
-
RNLog.w(reactContext, "TrueSheet: No
|
|
563
|
+
val coordinator = this.coordinatorLayout ?: run {
|
|
564
|
+
RNLog.w(reactContext, "TrueSheet: No coordinator layout available. Ensure the sheet is mounted before presenting.")
|
|
565
|
+
return
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
val sheet = this.sheetView ?: run {
|
|
569
|
+
RNLog.w(reactContext, "TrueSheet: No sheet view available.")
|
|
506
570
|
return
|
|
507
571
|
}
|
|
508
572
|
|
|
509
|
-
val activity = reactContext.currentActivity
|
|
510
|
-
RNLog.w(reactContext, "TrueSheet: No
|
|
573
|
+
val activity = reactContext.currentActivity ?: run {
|
|
574
|
+
RNLog.w(reactContext, "TrueSheet: No activity available for presentation.")
|
|
511
575
|
return
|
|
512
576
|
}
|
|
513
577
|
|
|
@@ -519,15 +583,66 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
519
583
|
currentDetentIndex = detentIndex
|
|
520
584
|
interactionState = InteractionState.Idle
|
|
521
585
|
|
|
522
|
-
//
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
586
|
+
// Setup sheet in coordinator layout
|
|
587
|
+
setupSheetInCoordinator(coordinator, sheet)
|
|
588
|
+
|
|
589
|
+
// Add coordinator to activity
|
|
590
|
+
val contentView = activity.findViewById<ViewGroup>(android.R.id.content)
|
|
591
|
+
contentView?.addView(coordinator)
|
|
592
|
+
|
|
593
|
+
// Setup back button handling
|
|
594
|
+
setupBackCallback()
|
|
595
|
+
|
|
596
|
+
// Start presentation
|
|
597
|
+
onSheetShow()
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
private fun setupSheetInCoordinator(coordinator: TrueSheetCoordinatorLayout, sheet: TrueSheetBottomSheetView) {
|
|
602
|
+
// Add this controller as content to the sheet
|
|
603
|
+
(parent as? ViewGroup)?.removeView(this)
|
|
604
|
+
sheet.addView(this)
|
|
605
|
+
|
|
606
|
+
// Create layout params with behavior
|
|
607
|
+
val params = sheet.createLayoutParams()
|
|
608
|
+
val behavior = params.behavior as BottomSheetBehavior<TrueSheetBottomSheetView>
|
|
609
|
+
|
|
610
|
+
// Configure behavior
|
|
611
|
+
behavior.isHideable = true
|
|
612
|
+
behavior.isDraggable = draggable
|
|
613
|
+
behavior.state = BottomSheetBehavior.STATE_HIDDEN
|
|
614
|
+
behavior.addBottomSheetCallback(sheetCallback)
|
|
615
|
+
|
|
616
|
+
// Add sheet to coordinator
|
|
617
|
+
coordinator.addView(sheet, params)
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
private fun onSheetShow() {
|
|
621
|
+
val sheet = sheetView ?: run {
|
|
622
|
+
RNLog.e(reactContext, "TrueSheet: sheetView is null in onSheetShow")
|
|
623
|
+
return
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
emitWillPresentEvents()
|
|
627
|
+
|
|
628
|
+
setupSheetDetents()
|
|
629
|
+
setupDimmedBackground(currentDetentIndex)
|
|
630
|
+
setupKeyboardObserver()
|
|
631
|
+
sheet.setupBackground()
|
|
632
|
+
sheet.setupGrabber()
|
|
526
633
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
634
|
+
if (shouldAnimatePresent) {
|
|
635
|
+
isPresentAnimating = true
|
|
636
|
+
post { setStateForDetentIndex(currentDetentIndex) }
|
|
637
|
+
} else {
|
|
638
|
+
setStateForDetentIndex(currentDetentIndex)
|
|
639
|
+
emitChangePositionDelegate(detentCalculator.getSheetTopForDetentIndex(currentDetentIndex))
|
|
640
|
+
updateDimAmount()
|
|
641
|
+
finishPresent()
|
|
530
642
|
}
|
|
643
|
+
|
|
644
|
+
isPresented = true
|
|
645
|
+
isSheetVisible = true
|
|
531
646
|
}
|
|
532
647
|
|
|
533
648
|
fun dismiss(animated: Boolean = true) {
|
|
@@ -537,21 +652,32 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
537
652
|
emitWillDismissEvents()
|
|
538
653
|
|
|
539
654
|
if (animated) {
|
|
540
|
-
|
|
541
|
-
onUpdate = { effectiveTop ->
|
|
542
|
-
emitChangePositionDelegate(effectiveTop)
|
|
543
|
-
positionFooter()
|
|
544
|
-
updateDimAmount(effectiveTop)
|
|
545
|
-
},
|
|
546
|
-
onEnd = { dialogFragment?.dismiss() }
|
|
547
|
-
)
|
|
655
|
+
animateDismiss()
|
|
548
656
|
} else {
|
|
549
657
|
emitChangePositionDelegate(realScreenHeight)
|
|
550
|
-
|
|
658
|
+
finishDismiss()
|
|
551
659
|
}
|
|
552
660
|
}
|
|
553
661
|
|
|
662
|
+
private fun animateDismiss() {
|
|
663
|
+
val sheet = sheetView ?: run {
|
|
664
|
+
finishDismiss()
|
|
665
|
+
return
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
sheet.animate()
|
|
669
|
+
.y(realScreenHeight.toFloat())
|
|
670
|
+
.setDuration(DISMISS_DURATION)
|
|
671
|
+
.setInterpolator(android.view.animation.AccelerateInterpolator())
|
|
672
|
+
.setUpdateListener { updateSheetVisuals(sheet.y.toInt()) }
|
|
673
|
+
.withEndAction { finishDismiss() }
|
|
674
|
+
.start()
|
|
675
|
+
}
|
|
676
|
+
|
|
554
677
|
private fun finishPresent() {
|
|
678
|
+
// Restore isHideable to actual value after present animation
|
|
679
|
+
behavior?.isHideable = dismissible
|
|
680
|
+
|
|
555
681
|
val (index, position, detent) = getDetentInfoWithValue(currentDetentIndex)
|
|
556
682
|
delegate?.viewControllerDidPresent(index, position, detent)
|
|
557
683
|
parentSheetView?.viewControllerDidBlur()
|
|
@@ -561,40 +687,47 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
561
687
|
presentPromise = null
|
|
562
688
|
}
|
|
563
689
|
|
|
690
|
+
private fun finishDismiss() {
|
|
691
|
+
emitDidDismissEvents()
|
|
692
|
+
cleanupSheet()
|
|
693
|
+
}
|
|
694
|
+
|
|
564
695
|
// =============================================================================
|
|
565
696
|
// MARK: - Sheet Configuration
|
|
566
697
|
// =============================================================================
|
|
567
698
|
|
|
568
699
|
fun setupSheetDetents() {
|
|
569
|
-
val
|
|
570
|
-
|
|
700
|
+
val behavior = this.behavior ?: run {
|
|
701
|
+
RNLog.e(reactContext, "TrueSheet: behavior is null in setupSheetDetents")
|
|
702
|
+
return
|
|
703
|
+
}
|
|
571
704
|
|
|
572
705
|
interactionState = InteractionState.Reconfiguring
|
|
573
|
-
val edgeToEdgeTopInset: Int = if (!edgeToEdgeFullScreen) topInset else 0
|
|
574
706
|
|
|
575
707
|
behavior.isFitToContents = false
|
|
576
708
|
|
|
577
|
-
val maxAvailableHeight = realScreenHeight -
|
|
709
|
+
val maxAvailableHeight = realScreenHeight - topInset
|
|
578
710
|
|
|
579
|
-
val peekHeight = detentCalculator.getDetentHeight(detents[0])
|
|
711
|
+
val peekHeight = minOf(detentCalculator.getDetentHeight(detents[0]), maxAvailableHeight)
|
|
580
712
|
|
|
581
713
|
val halfExpandedDetentHeight = when (detents.size) {
|
|
582
714
|
1 -> peekHeight
|
|
583
715
|
else -> detentCalculator.getDetentHeight(detents[1])
|
|
584
716
|
}
|
|
585
717
|
|
|
586
|
-
val maxDetentHeight = detentCalculator.getDetentHeight(detents.last())
|
|
718
|
+
val maxDetentHeight = minOf(detentCalculator.getDetentHeight(detents.last()), maxAvailableHeight)
|
|
587
719
|
|
|
588
720
|
val adjustedHalfExpandedHeight = minOf(halfExpandedDetentHeight, maxAvailableHeight)
|
|
589
721
|
val halfExpandedRatio = (adjustedHalfExpandedHeight.toFloat() / realScreenHeight.toFloat())
|
|
590
722
|
.coerceIn(0f, 0.999f)
|
|
591
723
|
|
|
592
|
-
val expandedOffset =
|
|
724
|
+
val expandedOffset = realScreenHeight - maxDetentHeight
|
|
593
725
|
|
|
594
726
|
// fitToContents works better with <= 2 detents when no expanded offset
|
|
595
727
|
val fitToContents = detents.size < 3 && expandedOffset == 0
|
|
596
728
|
|
|
597
|
-
|
|
729
|
+
configureDetents(
|
|
730
|
+
behavior = behavior,
|
|
598
731
|
peekHeight = peekHeight,
|
|
599
732
|
halfExpandedRatio = halfExpandedRatio,
|
|
600
733
|
expandedOffset = expandedOffset,
|
|
@@ -619,93 +752,76 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
619
752
|
interactionState = InteractionState.Idle
|
|
620
753
|
}
|
|
621
754
|
|
|
755
|
+
private fun configureDetents(
|
|
756
|
+
behavior: BottomSheetBehavior<TrueSheetBottomSheetView>,
|
|
757
|
+
peekHeight: Int,
|
|
758
|
+
halfExpandedRatio: Float,
|
|
759
|
+
expandedOffset: Int,
|
|
760
|
+
fitToContents: Boolean,
|
|
761
|
+
animate: Boolean
|
|
762
|
+
) {
|
|
763
|
+
behavior.apply {
|
|
764
|
+
isFitToContents = fitToContents
|
|
765
|
+
skipCollapsed = false
|
|
766
|
+
setPeekHeight(peekHeight, animate)
|
|
767
|
+
this.halfExpandedRatio = halfExpandedRatio.coerceIn(0f, 0.999f)
|
|
768
|
+
this.expandedOffset = expandedOffset
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
|
|
622
772
|
fun setupSheetDetentsForSizeChange() {
|
|
623
773
|
setupSheetDetents()
|
|
624
774
|
positionFooter()
|
|
625
775
|
}
|
|
626
776
|
|
|
627
777
|
fun setStateForDetentIndex(index: Int) {
|
|
628
|
-
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// =============================================================================
|
|
632
|
-
// MARK: - Grabber
|
|
633
|
-
// =============================================================================
|
|
634
|
-
|
|
635
|
-
fun setupGrabber() {
|
|
636
|
-
dialogFragment?.apply {
|
|
637
|
-
grabberEnabled = this@TrueSheetViewController.grabber
|
|
638
|
-
grabberOptions = this@TrueSheetViewController.grabberOptions
|
|
639
|
-
setupGrabber()
|
|
640
|
-
}
|
|
778
|
+
behavior?.state = detentCalculator.getStateForDetentIndex(index)
|
|
641
779
|
}
|
|
642
780
|
|
|
643
781
|
// =============================================================================
|
|
644
|
-
// MARK: - Background
|
|
782
|
+
// MARK: - Dimmed Background
|
|
645
783
|
// =============================================================================
|
|
646
784
|
|
|
647
|
-
fun setupBackground() {
|
|
648
|
-
dialogFragment?.apply {
|
|
649
|
-
sheetCornerRadius = this@TrueSheetViewController.sheetCornerRadius
|
|
650
|
-
sheetBackgroundColor = this@TrueSheetViewController.sheetBackgroundColor
|
|
651
|
-
setupBackground()
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
785
|
fun setupDimmedBackground(detentIndex: Int) {
|
|
656
|
-
val
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
|
661
|
-
|
|
662
|
-
val shouldDimAtDetent = dimmed && detentIndex >= dimmedDetentIndex
|
|
663
|
-
|
|
664
|
-
if (dimmed) {
|
|
665
|
-
val parentDimVisible = (parentSheetView?.viewController?.dimView?.alpha ?: 0f) > 0f
|
|
786
|
+
val coordinator = this.coordinatorLayout ?: run {
|
|
787
|
+
RNLog.e(reactContext, "TrueSheet: coordinatorLayout is null in setupDimmedBackground")
|
|
788
|
+
return
|
|
789
|
+
}
|
|
666
790
|
|
|
667
|
-
|
|
668
|
-
|
|
791
|
+
if (dimmed) {
|
|
792
|
+
val parentDimVisible = (parentSheetView?.viewController?.dimView?.alpha ?: 0f) > 0f
|
|
669
793
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
if (parentBottomSheet != null) {
|
|
674
|
-
if (parentDimView == null) parentDimView = TrueSheetDimView(reactContext)
|
|
675
|
-
parentDimView?.attach(parentBottomSheet, parentController.sheetCornerRadius)
|
|
794
|
+
if (dimView == null) {
|
|
795
|
+
dimView = TrueSheetDimView(reactContext).apply {
|
|
796
|
+
delegate = this@TrueSheetViewController
|
|
676
797
|
}
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
dimView
|
|
680
|
-
parentDimView?.detach()
|
|
681
|
-
parentDimView = null
|
|
798
|
+
}
|
|
799
|
+
if (!parentDimVisible) {
|
|
800
|
+
dimView?.attachToCoordinator(coordinator)
|
|
682
801
|
}
|
|
683
802
|
|
|
684
|
-
if
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
803
|
+
// Attach dim view to parent sheet if stacked
|
|
804
|
+
val parentController = parentSheetView?.viewController
|
|
805
|
+
val parentBottomSheet = parentController?.sheetView
|
|
806
|
+
if (parentBottomSheet != null) {
|
|
807
|
+
if (parentDimView == null) {
|
|
808
|
+
parentDimView = TrueSheetDimView(reactContext).apply {
|
|
809
|
+
delegate = this@TrueSheetViewController
|
|
688
810
|
}
|
|
689
|
-
true
|
|
690
|
-
}
|
|
691
|
-
} else {
|
|
692
|
-
// Pass through touches to parent or activity when not dimmed
|
|
693
|
-
touchOutside.setOnTouchListener { v, event ->
|
|
694
|
-
event.setLocation(event.rawX - v.x, event.rawY - v.y)
|
|
695
|
-
(
|
|
696
|
-
parentSheetView?.viewController?.dialog?.window?.decorView
|
|
697
|
-
?: reactContext.currentActivity?.window?.decorView
|
|
698
|
-
)?.dispatchTouchEvent(event)
|
|
699
|
-
false
|
|
700
811
|
}
|
|
701
|
-
|
|
812
|
+
parentDimView?.attach(parentBottomSheet, parentController.sheetCornerRadius)
|
|
702
813
|
}
|
|
814
|
+
} else {
|
|
815
|
+
dimView?.detach()
|
|
816
|
+
dimView = null
|
|
817
|
+
parentDimView?.detach()
|
|
818
|
+
parentDimView = null
|
|
703
819
|
}
|
|
704
820
|
}
|
|
705
821
|
|
|
706
822
|
fun updateDimAmount(sheetTop: Int? = null, animated: Boolean = false) {
|
|
707
823
|
if (!dimmed) return
|
|
708
|
-
val top = (sheetTop ?:
|
|
824
|
+
val top = (sheetTop ?: sheetView?.top ?: return) + currentKeyboardInset
|
|
709
825
|
|
|
710
826
|
if (animated) {
|
|
711
827
|
val targetAlpha = dimView?.calculateAlpha(
|
|
@@ -713,11 +829,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
713
829
|
dimmedDetentIndex,
|
|
714
830
|
detentCalculator::getSheetTopForDetentIndex
|
|
715
831
|
) ?: 0f
|
|
716
|
-
|
|
717
|
-
parentDimView?.animate()?.alpha(targetAlpha)?.setDuration(200)?.start()
|
|
832
|
+
dimViews.forEach { it.animate().alpha(targetAlpha).setDuration(200).start() }
|
|
718
833
|
} else {
|
|
719
|
-
|
|
720
|
-
parentDimView?.interpolateAlpha(top, dimmedDetentIndex, detentCalculator::getSheetTopForDetentIndex)
|
|
834
|
+
dimViews.forEach { it.interpolateAlpha(top, dimmedDetentIndex, detentCalculator::getSheetTopForDetentIndex) }
|
|
721
835
|
}
|
|
722
836
|
}
|
|
723
837
|
|
|
@@ -728,11 +842,11 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
728
842
|
fun positionFooter(slideOffset: Float? = null) {
|
|
729
843
|
if (!isPresented) return
|
|
730
844
|
val footerView = containerView?.footerView ?: return
|
|
731
|
-
val
|
|
845
|
+
val sheet = sheetView ?: return
|
|
732
846
|
|
|
733
847
|
val footerHeight = footerView.height
|
|
734
|
-
val sheetHeight =
|
|
735
|
-
val sheetTop =
|
|
848
|
+
val sheetHeight = sheet.height
|
|
849
|
+
val sheetTop = sheet.top
|
|
736
850
|
|
|
737
851
|
var footerY = (sheetHeight - sheetTop - footerHeight - currentKeyboardInset).toFloat()
|
|
738
852
|
|
|
@@ -756,13 +870,17 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
756
870
|
}
|
|
757
871
|
|
|
758
872
|
fun setupKeyboardObserver() {
|
|
759
|
-
val
|
|
760
|
-
|
|
873
|
+
val coordinator = coordinatorLayout ?: run {
|
|
874
|
+
RNLog.e(reactContext, "TrueSheet: coordinatorLayout is null in setupKeyboardObserver")
|
|
875
|
+
return
|
|
876
|
+
}
|
|
877
|
+
cleanupKeyboardObserver()
|
|
878
|
+
keyboardObserver = TrueSheetKeyboardObserver(coordinator, reactContext).apply {
|
|
761
879
|
delegate = object : TrueSheetKeyboardObserverDelegate {
|
|
762
880
|
override fun keyboardWillShow(height: Int) {
|
|
881
|
+
isKeyboardTransitioning = true
|
|
763
882
|
if (!shouldHandleKeyboard()) return
|
|
764
883
|
detentIndexBeforeKeyboard = currentDetentIndex
|
|
765
|
-
isKeyboardTransitioning = true
|
|
766
884
|
setupSheetDetents()
|
|
767
885
|
setStateForDetentIndex(detents.size - 1)
|
|
768
886
|
}
|
|
@@ -777,7 +895,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
777
895
|
}
|
|
778
896
|
|
|
779
897
|
override fun keyboardDidHide() {
|
|
780
|
-
if (!shouldHandleKeyboard()) return
|
|
781
898
|
isKeyboardTransitioning = false
|
|
782
899
|
}
|
|
783
900
|
|
|
@@ -799,8 +916,11 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
799
916
|
// MARK: - Drag Handling
|
|
800
917
|
// =============================================================================
|
|
801
918
|
|
|
919
|
+
private fun getPositionDpForView(sheetView: View): Float =
|
|
920
|
+
detentCalculator.getPositionDp(detentCalculator.getVisibleSheetHeight(sheetView.top))
|
|
921
|
+
|
|
802
922
|
private fun handleDragBegin(sheetView: View) {
|
|
803
|
-
val position =
|
|
923
|
+
val position = getPositionDpForView(sheetView)
|
|
804
924
|
val detent = detentCalculator.getDetentValueForIndex(currentDetentIndex)
|
|
805
925
|
delegate?.viewControllerDidDragBegin(currentDetentIndex, position, detent)
|
|
806
926
|
interactionState = InteractionState.Dragging(startTop = sheetView.top)
|
|
@@ -808,7 +928,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
808
928
|
|
|
809
929
|
private fun handleDragChange(sheetView: View) {
|
|
810
930
|
if (interactionState !is InteractionState.Dragging) return
|
|
811
|
-
val position =
|
|
931
|
+
val position = getPositionDpForView(sheetView)
|
|
812
932
|
val detent = detentCalculator.getDetentValueForIndex(currentDetentIndex)
|
|
813
933
|
delegate?.viewControllerDidDragChange(currentDetentIndex, position, detent)
|
|
814
934
|
}
|
|
@@ -854,23 +974,28 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
854
974
|
delegate?.viewControllerDidChangePosition(interpolatedIndex, position, detent, realtime)
|
|
855
975
|
}
|
|
856
976
|
|
|
977
|
+
/**
|
|
978
|
+
* Updates position emission, footer, and dim amount together.
|
|
979
|
+
* This pattern is commonly used during animations and state changes.
|
|
980
|
+
*/
|
|
981
|
+
private fun updateSheetVisuals(effectiveTop: Int, slideOffset: Float? = null) {
|
|
982
|
+
emitChangePositionDelegate(effectiveTop)
|
|
983
|
+
positionFooter(slideOffset)
|
|
984
|
+
updateDimAmount(effectiveTop)
|
|
985
|
+
}
|
|
986
|
+
|
|
857
987
|
// =============================================================================
|
|
858
988
|
// MARK: - Detent Helpers
|
|
859
989
|
// =============================================================================
|
|
860
990
|
|
|
861
|
-
fun
|
|
862
|
-
|
|
863
|
-
return realScreenHeight - detentCalculator.getDetentHeight(detents[detentIndex])
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
fun translateDialog(translationY: Int) {
|
|
867
|
-
val bottomSheet = bottomSheetView ?: return
|
|
991
|
+
fun translateSheet(translationY: Int) {
|
|
992
|
+
val sheet = sheetView ?: return
|
|
868
993
|
|
|
869
|
-
|
|
994
|
+
sheet.animate()
|
|
870
995
|
.translationY(translationY.toFloat())
|
|
871
996
|
.setDuration(TRANSLATE_ANIMATION_DURATION)
|
|
872
997
|
.setUpdateListener {
|
|
873
|
-
val effectiveTop =
|
|
998
|
+
val effectiveTop = sheet.top + sheet.translationY.toInt()
|
|
874
999
|
emitChangePositionDelegate(effectiveTop)
|
|
875
1000
|
}
|
|
876
1001
|
.start()
|
|
@@ -887,7 +1012,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
887
1012
|
private fun getPositionForDetentIndex(index: Int): Float {
|
|
888
1013
|
if (index < 0 || index >= detents.size) return screenHeight.pxToDp()
|
|
889
1014
|
|
|
890
|
-
|
|
1015
|
+
sheetView?.let {
|
|
891
1016
|
val visibleSheetHeight = detentCalculator.getVisibleSheetHeight(it.top)
|
|
892
1017
|
if (visibleSheetHeight in 1..<realScreenHeight) {
|
|
893
1018
|
return detentCalculator.getPositionDp(visibleSheetHeight)
|
|
@@ -919,7 +1044,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
919
1044
|
post {
|
|
920
1045
|
setupSheetDetents()
|
|
921
1046
|
positionFooter()
|
|
922
|
-
|
|
1047
|
+
sheetView?.let { emitChangePositionDelegate(it.top, realtime = false) }
|
|
923
1048
|
}
|
|
924
1049
|
}
|
|
925
1050
|
|
|
@@ -960,41 +1085,41 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
960
1085
|
|
|
961
1086
|
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
|
962
1087
|
eventDispatcher?.let {
|
|
963
|
-
|
|
964
|
-
|
|
1088
|
+
jsTouchDispatcher.handleTouchEvent(event, it, reactContext)
|
|
1089
|
+
jsPointerDispatcher?.handleMotionEvent(event, it, true)
|
|
965
1090
|
}
|
|
966
1091
|
return super.onInterceptTouchEvent(event)
|
|
967
1092
|
}
|
|
968
1093
|
|
|
969
1094
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
970
1095
|
eventDispatcher?.let {
|
|
971
|
-
|
|
972
|
-
|
|
1096
|
+
jsTouchDispatcher.handleTouchEvent(event, it, reactContext)
|
|
1097
|
+
jsPointerDispatcher?.handleMotionEvent(event, it, false)
|
|
973
1098
|
}
|
|
974
1099
|
super.onTouchEvent(event)
|
|
975
1100
|
return true
|
|
976
1101
|
}
|
|
977
1102
|
|
|
978
1103
|
override fun onInterceptHoverEvent(event: MotionEvent): Boolean {
|
|
979
|
-
eventDispatcher?.let {
|
|
1104
|
+
eventDispatcher?.let { jsPointerDispatcher?.handleMotionEvent(event, it, true) }
|
|
980
1105
|
return super.onHoverEvent(event)
|
|
981
1106
|
}
|
|
982
1107
|
|
|
983
1108
|
override fun onHoverEvent(event: MotionEvent): Boolean {
|
|
984
|
-
eventDispatcher?.let {
|
|
1109
|
+
eventDispatcher?.let { jsPointerDispatcher?.handleMotionEvent(event, it, false) }
|
|
985
1110
|
return super.onHoverEvent(event)
|
|
986
1111
|
}
|
|
987
1112
|
|
|
988
1113
|
override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) {
|
|
989
1114
|
eventDispatcher?.let {
|
|
990
|
-
|
|
991
|
-
|
|
1115
|
+
jsTouchDispatcher.onChildStartedNativeGesture(ev, it)
|
|
1116
|
+
jsPointerDispatcher?.onChildStartedNativeGesture(childView, ev, it)
|
|
992
1117
|
}
|
|
993
1118
|
}
|
|
994
1119
|
|
|
995
1120
|
override fun onChildEndedNativeGesture(childView: View, ev: MotionEvent) {
|
|
996
|
-
eventDispatcher?.let {
|
|
997
|
-
|
|
1121
|
+
eventDispatcher?.let { jsTouchDispatcher.onChildEndedNativeGesture(ev, it) }
|
|
1122
|
+
jsPointerDispatcher?.onChildEndedNativeGesture()
|
|
998
1123
|
}
|
|
999
1124
|
|
|
1000
1125
|
override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
|