@lodev09/react-native-true-sheet 3.5.1-beta.0 → 3.5.1-beta.1
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/TrueSheetView.kt +2 -12
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +172 -214
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetAnimator.kt +7 -5
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDialogFragment.kt +320 -0
- package/package.json +1 -1
|
@@ -43,7 +43,6 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
43
43
|
var eventDispatcher: EventDispatcher? = null
|
|
44
44
|
|
|
45
45
|
// Initial present configuration (set by ViewManager before mount)
|
|
46
|
-
var pendingInitialPresent: Boolean = false
|
|
47
46
|
var initialDetentIndex: Int = -1
|
|
48
47
|
var initialDetentAnimated: Boolean = true
|
|
49
48
|
|
|
@@ -105,7 +104,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
105
104
|
viewController.createDialog()
|
|
106
105
|
|
|
107
106
|
if (initialDetentIndex >= 0) {
|
|
108
|
-
|
|
107
|
+
post { present(initialDetentIndex, initialDetentAnimated) { } }
|
|
109
108
|
}
|
|
110
109
|
|
|
111
110
|
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
@@ -292,16 +291,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
292
291
|
* Uses post() to ensure all layout passes complete before reconfiguring.
|
|
293
292
|
*/
|
|
294
293
|
fun updateSheetIfNeeded() {
|
|
295
|
-
if (!viewController.isPresented)
|
|
296
|
-
// Handle initial present if pending
|
|
297
|
-
if (pendingInitialPresent) {
|
|
298
|
-
viewController.setupSheetDetents()
|
|
299
|
-
present(initialDetentIndex, initialDetentAnimated) { }
|
|
300
|
-
pendingInitialPresent = false
|
|
301
|
-
}
|
|
302
|
-
return
|
|
303
|
-
}
|
|
304
|
-
|
|
294
|
+
if (!viewController.isPresented) return
|
|
305
295
|
if (isSheetUpdatePending) return
|
|
306
296
|
|
|
307
297
|
isSheetUpdatePending = true
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
package com.lodev09.truesheet
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
|
-
import android.
|
|
5
|
-
import android.graphics.drawable.ShapeDrawable
|
|
6
|
-
import android.graphics.drawable.shapes.RoundRectShape
|
|
7
|
-
import android.util.TypedValue
|
|
4
|
+
import android.util.Log
|
|
8
5
|
import android.view.MotionEvent
|
|
9
6
|
import android.view.View
|
|
10
7
|
import android.view.WindowManager
|
|
11
8
|
import android.view.accessibility.AccessibilityNodeInfo
|
|
12
9
|
import android.widget.FrameLayout
|
|
10
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
13
11
|
import androidx.core.view.isNotEmpty
|
|
14
12
|
import androidx.core.view.isVisible
|
|
15
13
|
import com.facebook.react.R
|
|
@@ -30,9 +28,10 @@ import com.lodev09.truesheet.core.TrueSheetAnimator
|
|
|
30
28
|
import com.lodev09.truesheet.core.TrueSheetAnimatorProvider
|
|
31
29
|
import com.lodev09.truesheet.core.TrueSheetDetentCalculator
|
|
32
30
|
import com.lodev09.truesheet.core.TrueSheetDetentMeasurements
|
|
31
|
+
import com.lodev09.truesheet.core.TrueSheetDialogFragment
|
|
32
|
+
import com.lodev09.truesheet.core.TrueSheetDialogFragmentDelegate
|
|
33
33
|
import com.lodev09.truesheet.core.TrueSheetDialogObserver
|
|
34
34
|
import com.lodev09.truesheet.core.TrueSheetDimView
|
|
35
|
-
import com.lodev09.truesheet.core.TrueSheetGrabberView
|
|
36
35
|
import com.lodev09.truesheet.core.TrueSheetKeyboardObserver
|
|
37
36
|
import com.lodev09.truesheet.core.TrueSheetKeyboardObserverDelegate
|
|
38
37
|
import com.lodev09.truesheet.utils.ScreenUtils
|
|
@@ -66,7 +65,7 @@ interface TrueSheetViewControllerDelegate {
|
|
|
66
65
|
// =============================================================================
|
|
67
66
|
|
|
68
67
|
/**
|
|
69
|
-
* Manages the bottom sheet dialog and its presentation lifecycle.
|
|
68
|
+
* Manages the bottom sheet dialog fragment and its presentation lifecycle.
|
|
70
69
|
* Acts as a RootView to properly dispatch touch events to React Native.
|
|
71
70
|
*/
|
|
72
71
|
@SuppressLint("ClickableViewAccessibility", "ViewConstructor")
|
|
@@ -74,13 +73,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
74
73
|
ReactViewGroup(reactContext),
|
|
75
74
|
RootView,
|
|
76
75
|
TrueSheetDetentMeasurements,
|
|
77
|
-
TrueSheetAnimatorProvider
|
|
76
|
+
TrueSheetAnimatorProvider,
|
|
77
|
+
TrueSheetDialogFragmentDelegate {
|
|
78
78
|
|
|
79
79
|
companion object {
|
|
80
80
|
const val TAG_NAME = "TrueSheet"
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
private const val GRABBER_TAG = "TrueSheetGrabber"
|
|
82
|
+
private const val FRAGMENT_TAG = "TrueSheetDialogFragment"
|
|
84
83
|
private const val DEFAULT_MAX_WIDTH = 640 // dp
|
|
85
84
|
private const val DEFAULT_CORNER_RADIUS = 16 // dp
|
|
86
85
|
private const val TRANSLATE_ANIMATION_DURATION = 200L
|
|
@@ -102,8 +101,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
102
101
|
|
|
103
102
|
var delegate: TrueSheetViewControllerDelegate? = null
|
|
104
103
|
|
|
105
|
-
// Dialog
|
|
106
|
-
private var
|
|
104
|
+
// Dialog Fragment
|
|
105
|
+
private var dialogFragment: TrueSheetDialogFragment? = null
|
|
107
106
|
private var dimView: TrueSheetDimView? = null
|
|
108
107
|
private var parentDimView: TrueSheetDimView? = null
|
|
109
108
|
|
|
@@ -164,23 +163,20 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
164
163
|
var sheetCornerRadius: Float = DEFAULT_CORNER_RADIUS.dpToPx()
|
|
165
164
|
set(value) {
|
|
166
165
|
field = if (value < 0) DEFAULT_CORNER_RADIUS.dpToPx() else value
|
|
167
|
-
|
|
166
|
+
dialogFragment?.sheetCornerRadius = field
|
|
167
|
+
if (isPresented) dialogFragment?.setupBackground()
|
|
168
168
|
}
|
|
169
169
|
|
|
170
170
|
var dismissible: Boolean = true
|
|
171
171
|
set(value) {
|
|
172
172
|
field = value
|
|
173
|
-
|
|
174
|
-
setCanceledOnTouchOutside(value)
|
|
175
|
-
setCancelable(value)
|
|
176
|
-
behavior.isHideable = value
|
|
177
|
-
}
|
|
173
|
+
dialogFragment?.dismissible = value
|
|
178
174
|
}
|
|
179
175
|
|
|
180
176
|
var draggable: Boolean = true
|
|
181
177
|
set(value) {
|
|
182
178
|
field = value
|
|
183
|
-
|
|
179
|
+
dialogFragment?.updateDraggable(value)
|
|
184
180
|
}
|
|
185
181
|
|
|
186
182
|
// =============================================================================
|
|
@@ -188,14 +184,17 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
188
184
|
// =============================================================================
|
|
189
185
|
|
|
190
186
|
// Dialog
|
|
187
|
+
private val dialog: BottomSheetDialog?
|
|
188
|
+
get() = dialogFragment?.bottomSheetDialog
|
|
189
|
+
|
|
191
190
|
private val behavior: BottomSheetBehavior<FrameLayout>?
|
|
192
|
-
get() =
|
|
191
|
+
get() = dialogFragment?.behavior
|
|
193
192
|
|
|
194
193
|
private val sheetContainer: FrameLayout?
|
|
195
194
|
get() = this.parent as? FrameLayout
|
|
196
195
|
|
|
197
196
|
override val bottomSheetView: FrameLayout?
|
|
198
|
-
get() =
|
|
197
|
+
get() = dialogFragment?.bottomSheetView
|
|
199
198
|
|
|
200
199
|
private val containerView: TrueSheetContainerView?
|
|
201
200
|
get() = if (this.isNotEmpty()) getChildAt(0) as? TrueSheetContainerView else null
|
|
@@ -267,53 +266,29 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
267
266
|
}
|
|
268
267
|
|
|
269
268
|
// =============================================================================
|
|
270
|
-
// MARK: -
|
|
269
|
+
// MARK: - Fragment Creation & Cleanup
|
|
271
270
|
// =============================================================================
|
|
272
271
|
|
|
273
272
|
fun createDialog() {
|
|
274
|
-
if (
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
273
|
+
if (dialogFragment != null) return
|
|
274
|
+
|
|
275
|
+
dialogFragment = TrueSheetDialogFragment.newInstance().apply {
|
|
276
|
+
this.delegate = this@TrueSheetViewController
|
|
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
|
|
280
286
|
}
|
|
281
287
|
|
|
282
|
-
|
|
283
|
-
setContentView(this@TrueSheetViewController)
|
|
284
|
-
|
|
285
|
-
window?.apply {
|
|
286
|
-
setWindowAnimations(0)
|
|
287
|
-
setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
setupModalObserver()
|
|
291
|
-
setupDialogListeners(this)
|
|
292
|
-
setupBottomSheetBehavior(this)
|
|
293
|
-
|
|
294
|
-
setCanceledOnTouchOutside(dismissible)
|
|
295
|
-
setCancelable(dismissible)
|
|
296
|
-
behavior.isHideable = dismissible
|
|
297
|
-
behavior.isDraggable = draggable
|
|
298
|
-
|
|
299
|
-
onBackPressedDispatcher.addCallback(object : androidx.activity.OnBackPressedCallback(true) {
|
|
300
|
-
override fun handleOnBackPressed() {
|
|
301
|
-
this@TrueSheetViewController.delegate?.viewControllerDidBackPress()
|
|
302
|
-
if (dismissible) {
|
|
303
|
-
dismiss()
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
})
|
|
307
|
-
}
|
|
288
|
+
setupModalObserver()
|
|
308
289
|
}
|
|
309
290
|
|
|
310
291
|
private fun cleanupDialog() {
|
|
311
|
-
dialog?.apply {
|
|
312
|
-
setOnShowListener(null)
|
|
313
|
-
setOnCancelListener(null)
|
|
314
|
-
setOnDismissListener(null)
|
|
315
|
-
}
|
|
316
|
-
|
|
317
292
|
cleanupKeyboardObserver()
|
|
318
293
|
cleanupModalObserver()
|
|
319
294
|
sheetAnimator.cancel()
|
|
@@ -323,7 +298,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
323
298
|
parentDimView = null
|
|
324
299
|
sheetContainer?.removeView(this)
|
|
325
300
|
|
|
326
|
-
|
|
301
|
+
dialogFragment = null
|
|
327
302
|
interactionState = InteractionState.Idle
|
|
328
303
|
isDismissing = false
|
|
329
304
|
isPresented = false
|
|
@@ -334,94 +309,94 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
334
309
|
}
|
|
335
310
|
|
|
336
311
|
// =============================================================================
|
|
337
|
-
// MARK: -
|
|
312
|
+
// MARK: - TrueSheetDialogFragmentDelegate
|
|
338
313
|
// =============================================================================
|
|
339
314
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
bottomSheetView?.visibility = VISIBLE
|
|
315
|
+
override fun onDialogShow() {
|
|
316
|
+
bottomSheetView?.visibility = VISIBLE
|
|
343
317
|
|
|
344
|
-
|
|
345
|
-
|
|
318
|
+
isPresented = true
|
|
319
|
+
isDialogVisible = true
|
|
346
320
|
|
|
347
|
-
|
|
321
|
+
emitWillPresentEvents()
|
|
348
322
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
setupKeyboardObserver()
|
|
353
|
-
|
|
354
|
-
if (shouldAnimatePresent) {
|
|
355
|
-
val toTop = getExpectedSheetTop(currentDetentIndex)
|
|
356
|
-
sheetAnimator.animatePresent(
|
|
357
|
-
toTop = toTop,
|
|
358
|
-
onUpdate = { effectiveTop ->
|
|
359
|
-
emitChangePositionDelegate(effectiveTop)
|
|
360
|
-
positionFooter()
|
|
361
|
-
updateDimAmount(effectiveTop)
|
|
362
|
-
},
|
|
363
|
-
onEnd = { finishPresent() }
|
|
364
|
-
)
|
|
365
|
-
} else {
|
|
366
|
-
val toTop = getExpectedSheetTop(currentDetentIndex)
|
|
367
|
-
emitChangePositionDelegate(toTop)
|
|
368
|
-
positionFooter()
|
|
369
|
-
finishPresent()
|
|
370
|
-
}
|
|
371
|
-
}
|
|
323
|
+
setupSheetDetents()
|
|
324
|
+
setupDimmedBackground(currentDetentIndex)
|
|
325
|
+
setupKeyboardObserver()
|
|
372
326
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
327
|
+
if (shouldAnimatePresent) {
|
|
328
|
+
val toTop = getExpectedSheetTop(currentDetentIndex)
|
|
329
|
+
sheetAnimator.animatePresent(
|
|
330
|
+
toTop = toTop,
|
|
331
|
+
onUpdate = { effectiveTop ->
|
|
332
|
+
emitChangePositionDelegate(effectiveTop)
|
|
333
|
+
positionFooter()
|
|
334
|
+
updateDimAmount(effectiveTop)
|
|
335
|
+
},
|
|
336
|
+
onEnd = { finishPresent() }
|
|
337
|
+
)
|
|
338
|
+
} else {
|
|
339
|
+
val toTop = getExpectedSheetTop(currentDetentIndex)
|
|
340
|
+
emitChangePositionDelegate(toTop)
|
|
341
|
+
positionFooter()
|
|
342
|
+
finishPresent()
|
|
376
343
|
}
|
|
377
344
|
}
|
|
378
345
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
val behavior = behavior ?: return
|
|
346
|
+
override fun onDialogDismiss() {
|
|
347
|
+
emitDidDismissEvents()
|
|
348
|
+
cleanupDialog()
|
|
349
|
+
}
|
|
384
350
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
351
|
+
override fun onDialogCancel() {
|
|
352
|
+
// Cancel is called before dismiss for user-initiated cancellation
|
|
353
|
+
}
|
|
388
354
|
|
|
389
|
-
|
|
390
|
-
|
|
355
|
+
override fun onStateChanged(sheetView: View, newState: Int) {
|
|
356
|
+
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
|
|
357
|
+
if (isDismissing) return
|
|
358
|
+
isDismissing = true
|
|
359
|
+
emitWillDismissEvents()
|
|
360
|
+
dialogFragment?.dismiss()
|
|
361
|
+
return
|
|
362
|
+
}
|
|
391
363
|
|
|
392
|
-
|
|
393
|
-
emitChangePositionDelegate(sheetView.top)
|
|
364
|
+
if (!isPresented) return
|
|
394
365
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
updateDimAmount(sheetView.top)
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
}
|
|
366
|
+
when (newState) {
|
|
367
|
+
BottomSheetBehavior.STATE_DRAGGING -> handleDragBegin(sheetView)
|
|
401
368
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
isDismissing = true
|
|
406
|
-
emitWillDismissEvents()
|
|
407
|
-
dialog.dismiss()
|
|
408
|
-
return
|
|
409
|
-
}
|
|
369
|
+
BottomSheetBehavior.STATE_EXPANDED,
|
|
370
|
+
BottomSheetBehavior.STATE_COLLAPSED,
|
|
371
|
+
BottomSheetBehavior.STATE_HALF_EXPANDED -> handleStateSettled(sheetView, newState)
|
|
410
372
|
|
|
411
|
-
|
|
373
|
+
else -> {}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
412
376
|
|
|
413
|
-
|
|
414
|
-
|
|
377
|
+
override fun onSlide(sheetView: View, slideOffset: Float) {
|
|
378
|
+
// Skip if our custom animator is handling the animation
|
|
379
|
+
if (sheetAnimator.isAnimating) return
|
|
415
380
|
|
|
416
|
-
|
|
417
|
-
BottomSheetBehavior.STATE_COLLAPSED,
|
|
418
|
-
BottomSheetBehavior.STATE_HALF_EXPANDED -> handleStateSettled(sheetView, newState)
|
|
381
|
+
val behavior = behavior ?: return
|
|
419
382
|
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
383
|
+
when (behavior.state) {
|
|
384
|
+
BottomSheetBehavior.STATE_DRAGGING,
|
|
385
|
+
BottomSheetBehavior.STATE_SETTLING -> handleDragChange(sheetView)
|
|
386
|
+
|
|
387
|
+
else -> { }
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
emitChangePositionDelegate(sheetView.top)
|
|
391
|
+
|
|
392
|
+
if (!isKeyboardTransitioning) {
|
|
393
|
+
positionFooter(slideOffset)
|
|
394
|
+
updateDimAmount(sheetView.top)
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
override fun onBackPressed() {
|
|
399
|
+
delegate?.viewControllerDidBackPress()
|
|
425
400
|
}
|
|
426
401
|
|
|
427
402
|
private fun handleStateSettled(sheetView: View, newState: Int) {
|
|
@@ -526,24 +501,33 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
526
501
|
// =============================================================================
|
|
527
502
|
|
|
528
503
|
fun present(detentIndex: Int, animated: Boolean = true) {
|
|
529
|
-
val
|
|
530
|
-
RNLog.w(reactContext, "TrueSheet: No dialog available. Ensure the sheet is mounted before presenting.")
|
|
504
|
+
val fragment = this.dialogFragment ?: run {
|
|
505
|
+
RNLog.w(reactContext, "TrueSheet: No dialog fragment available. Ensure the sheet is mounted before presenting.")
|
|
531
506
|
return
|
|
532
507
|
}
|
|
533
508
|
|
|
534
|
-
|
|
535
|
-
|
|
509
|
+
val activity = reactContext.currentActivity as? AppCompatActivity ?: run {
|
|
510
|
+
RNLog.w(reactContext, "TrueSheet: No AppCompatActivity available for fragment transaction.")
|
|
511
|
+
return
|
|
512
|
+
}
|
|
536
513
|
|
|
537
|
-
if (
|
|
514
|
+
if (isPresented) {
|
|
515
|
+
setupDimmedBackground(detentIndex)
|
|
516
|
+
setStateForDetentIndex(detentIndex)
|
|
517
|
+
} else {
|
|
538
518
|
shouldAnimatePresent = animated
|
|
539
519
|
currentDetentIndex = detentIndex
|
|
540
520
|
interactionState = InteractionState.Idle
|
|
541
521
|
|
|
542
|
-
//
|
|
522
|
+
// Show the fragment - detents are configured in onDialogShow
|
|
523
|
+
if (!fragment.isAdded) {
|
|
524
|
+
fragment.show(activity.supportFragmentManager, FRAGMENT_TAG)
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Execute pending transactions to ensure fragment is added
|
|
528
|
+
activity.supportFragmentManager.executePendingTransactions()
|
|
543
529
|
bottomSheetView?.translationY = realScreenHeight.toFloat()
|
|
544
530
|
bottomSheetView?.visibility = INVISIBLE
|
|
545
|
-
|
|
546
|
-
dialog.show()
|
|
547
531
|
}
|
|
548
532
|
}
|
|
549
533
|
|
|
@@ -560,11 +544,11 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
560
544
|
positionFooter()
|
|
561
545
|
updateDimAmount(effectiveTop)
|
|
562
546
|
},
|
|
563
|
-
onEnd = {
|
|
547
|
+
onEnd = { dialogFragment?.dismiss() }
|
|
564
548
|
)
|
|
565
549
|
} else {
|
|
566
550
|
emitChangePositionDelegate(realScreenHeight)
|
|
567
|
-
|
|
551
|
+
dialogFragment?.dismiss()
|
|
568
552
|
}
|
|
569
553
|
}
|
|
570
554
|
|
|
@@ -583,51 +567,57 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
583
567
|
// =============================================================================
|
|
584
568
|
|
|
585
569
|
fun setupSheetDetents() {
|
|
570
|
+
val fragment = this.dialogFragment ?: return
|
|
586
571
|
val behavior = this.behavior ?: return
|
|
587
572
|
|
|
588
573
|
interactionState = InteractionState.Reconfiguring
|
|
589
574
|
val edgeToEdgeTopInset: Int = if (!edgeToEdgeFullScreen) topInset else 0
|
|
590
575
|
|
|
591
|
-
behavior.
|
|
592
|
-
isFitToContents = false
|
|
593
|
-
maxWidth = DEFAULT_MAX_WIDTH.dpToPx().toInt()
|
|
576
|
+
behavior.isFitToContents = false
|
|
594
577
|
|
|
595
|
-
|
|
578
|
+
val maxAvailableHeight = realScreenHeight - edgeToEdgeTopInset
|
|
596
579
|
|
|
597
|
-
|
|
580
|
+
val peekHeight = detentCalculator.getDetentHeight(detents[0])
|
|
598
581
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
582
|
+
val halfExpandedDetentHeight = when (detents.size) {
|
|
583
|
+
1 -> peekHeight
|
|
584
|
+
else -> detentCalculator.getDetentHeight(detents[1])
|
|
585
|
+
}
|
|
603
586
|
|
|
604
|
-
|
|
587
|
+
val maxDetentHeight = detentCalculator.getDetentHeight(detents.last())
|
|
605
588
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
589
|
+
val adjustedHalfExpandedHeight = minOf(halfExpandedDetentHeight, maxAvailableHeight)
|
|
590
|
+
val halfExpandedRatio = (adjustedHalfExpandedHeight.toFloat() / realScreenHeight.toFloat())
|
|
591
|
+
.coerceIn(0f, 0.999f)
|
|
609
592
|
|
|
610
|
-
|
|
593
|
+
val expandedOffset = maxOf(edgeToEdgeTopInset, realScreenHeight - maxDetentHeight)
|
|
611
594
|
|
|
612
|
-
|
|
613
|
-
|
|
595
|
+
// fitToContents works better with <= 2 detents when no expanded offset
|
|
596
|
+
val fitToContents = detents.size < 3 && expandedOffset == 0
|
|
614
597
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
598
|
+
fragment.configureDetents(
|
|
599
|
+
peekHeight = peekHeight,
|
|
600
|
+
halfExpandedRatio = halfExpandedRatio,
|
|
601
|
+
expandedOffset = expandedOffset,
|
|
602
|
+
fitToContents = fitToContents,
|
|
603
|
+
animate = isPresented
|
|
604
|
+
)
|
|
618
605
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
delegate?.viewControllerDidChangeSize(newWidth, newHeight)
|
|
623
|
-
}
|
|
606
|
+
val offset = if (expandedOffset == 0) topInset else 0
|
|
607
|
+
val newHeight = realScreenHeight - expandedOffset - offset
|
|
608
|
+
val newWidth = minOf(screenWidth, DEFAULT_MAX_WIDTH.dpToPx().toInt())
|
|
624
609
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
610
|
+
if (lastStateWidth != newWidth || lastStateHeight != newHeight) {
|
|
611
|
+
lastStateWidth = newWidth
|
|
612
|
+
lastStateHeight = newHeight
|
|
613
|
+
delegate?.viewControllerDidChangeSize(newWidth, newHeight)
|
|
614
|
+
}
|
|
628
615
|
|
|
629
|
-
|
|
616
|
+
if (isPresented) {
|
|
617
|
+
setStateForDetentIndex(currentDetentIndex)
|
|
630
618
|
}
|
|
619
|
+
|
|
620
|
+
interactionState = InteractionState.Idle
|
|
631
621
|
}
|
|
632
622
|
|
|
633
623
|
fun setupSheetDetentsForSizeChange() {
|
|
@@ -636,7 +626,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
636
626
|
}
|
|
637
627
|
|
|
638
628
|
fun setStateForDetentIndex(index: Int) {
|
|
639
|
-
|
|
629
|
+
dialogFragment?.setState(detentCalculator.getStateForDetentIndex(index))
|
|
640
630
|
}
|
|
641
631
|
|
|
642
632
|
// =============================================================================
|
|
@@ -644,19 +634,11 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
644
634
|
// =============================================================================
|
|
645
635
|
|
|
646
636
|
fun setupGrabber() {
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
if (!grabber || !draggable) return
|
|
654
|
-
|
|
655
|
-
val grabberView = TrueSheetGrabberView(reactContext, grabberOptions).apply {
|
|
656
|
-
tag = GRABBER_TAG
|
|
637
|
+
dialogFragment?.apply {
|
|
638
|
+
grabberEnabled = this@TrueSheetViewController.grabber
|
|
639
|
+
grabberOptions = this@TrueSheetViewController.grabberOptions
|
|
640
|
+
setupGrabber()
|
|
657
641
|
}
|
|
658
|
-
|
|
659
|
-
bottomSheet.addView(grabberView)
|
|
660
642
|
}
|
|
661
643
|
|
|
662
644
|
// =============================================================================
|
|
@@ -664,25 +646,11 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
664
646
|
// =============================================================================
|
|
665
647
|
|
|
666
648
|
fun setupBackground() {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
sheetCornerRadius,
|
|
672
|
-
sheetCornerRadius,
|
|
673
|
-
sheetCornerRadius,
|
|
674
|
-
sheetCornerRadius,
|
|
675
|
-
0f,
|
|
676
|
-
0f,
|
|
677
|
-
0f,
|
|
678
|
-
0f
|
|
679
|
-
)
|
|
680
|
-
val backgroundColor = sheetBackgroundColor ?: getDefaultBackgroundColor()
|
|
681
|
-
|
|
682
|
-
bottomSheet.background = ShapeDrawable(RoundRectShape(outerRadii, null, null)).apply {
|
|
683
|
-
paint.color = backgroundColor
|
|
649
|
+
dialogFragment?.apply {
|
|
650
|
+
sheetCornerRadius = this@TrueSheetViewController.sheetCornerRadius
|
|
651
|
+
sheetBackgroundColor = this@TrueSheetViewController.sheetBackgroundColor
|
|
652
|
+
setupBackground()
|
|
684
653
|
}
|
|
685
|
-
bottomSheet.clipToOutline = true
|
|
686
654
|
}
|
|
687
655
|
|
|
688
656
|
fun setupDimmedBackground(detentIndex: Int) {
|
|
@@ -754,25 +722,12 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
754
722
|
}
|
|
755
723
|
}
|
|
756
724
|
|
|
757
|
-
fun getDefaultBackgroundColor(): Int {
|
|
758
|
-
val typedValue = TypedValue()
|
|
759
|
-
return if (reactContext.theme.resolveAttribute(
|
|
760
|
-
com.google.android.material.R.attr.colorSurfaceContainerLow,
|
|
761
|
-
typedValue,
|
|
762
|
-
true
|
|
763
|
-
)
|
|
764
|
-
) {
|
|
765
|
-
typedValue.data
|
|
766
|
-
} else {
|
|
767
|
-
Color.WHITE
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
|
|
771
725
|
// =============================================================================
|
|
772
726
|
// MARK: - Footer Positioning
|
|
773
727
|
// =============================================================================
|
|
774
728
|
|
|
775
729
|
fun positionFooter(slideOffset: Float? = null) {
|
|
730
|
+
if (!isPresented) return
|
|
776
731
|
val footerView = containerView?.footerView ?: return
|
|
777
732
|
val bottomSheet = bottomSheetView ?: return
|
|
778
733
|
|
|
@@ -827,7 +782,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
827
782
|
isKeyboardTransitioning = false
|
|
828
783
|
}
|
|
829
784
|
|
|
830
|
-
override fun keyboardDidChangeHeight(height: Int) {
|
|
785
|
+
override fun keyboardDidChangeHeight(height: Int) {
|
|
786
|
+
if (!shouldHandleKeyboard()) return
|
|
787
|
+
positionFooter()
|
|
788
|
+
}
|
|
831
789
|
}
|
|
832
790
|
start()
|
|
833
791
|
}
|
|
@@ -44,18 +44,20 @@ class TrueSheetAnimator(private val provider: TrueSheetAnimatorProvider) {
|
|
|
44
44
|
return
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
val
|
|
47
|
+
val fromTop = provider.realScreenHeight
|
|
48
|
+
val distance = fromTop - toTop
|
|
48
49
|
|
|
49
50
|
presentAnimator?.cancel()
|
|
50
|
-
presentAnimator = ValueAnimator.ofFloat(
|
|
51
|
+
presentAnimator = ValueAnimator.ofFloat(0f, 1f).apply {
|
|
51
52
|
duration = PRESENT_DURATION
|
|
52
53
|
interpolator = DecelerateInterpolator()
|
|
53
54
|
|
|
54
55
|
addUpdateListener { animator ->
|
|
55
56
|
val fraction = animator.animatedValue as Float
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
// Calculate effective top based on animation progress
|
|
58
|
+
val effectiveTop = fromTop - (distance * fraction).toInt()
|
|
59
|
+
// Adjust translationY to compensate for bottomSheet.top position
|
|
60
|
+
bottomSheet.translationY = (effectiveTop - bottomSheet.top).toFloat()
|
|
59
61
|
onUpdate(effectiveTop)
|
|
60
62
|
}
|
|
61
63
|
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
package com.lodev09.truesheet.core
|
|
2
|
+
|
|
3
|
+
import android.app.Dialog
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.graphics.drawable.ShapeDrawable
|
|
6
|
+
import android.graphics.drawable.shapes.RoundRectShape
|
|
7
|
+
import android.os.Bundle
|
|
8
|
+
import android.util.TypedValue
|
|
9
|
+
import android.view.LayoutInflater
|
|
10
|
+
import android.view.View
|
|
11
|
+
import android.view.ViewGroup
|
|
12
|
+
import android.view.WindowManager
|
|
13
|
+
import android.widget.FrameLayout
|
|
14
|
+
import androidx.activity.OnBackPressedCallback
|
|
15
|
+
import com.facebook.react.uimanager.PixelUtil.dpToPx
|
|
16
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
17
|
+
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
18
|
+
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
19
|
+
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
|
20
|
+
import com.lodev09.truesheet.BuildConfig
|
|
21
|
+
import com.lodev09.truesheet.R
|
|
22
|
+
import com.lodev09.truesheet.utils.ScreenUtils
|
|
23
|
+
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// MARK: - Delegate Protocol
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
interface TrueSheetDialogFragmentDelegate {
|
|
29
|
+
fun onDialogShow()
|
|
30
|
+
fun onDialogDismiss()
|
|
31
|
+
fun onDialogCancel()
|
|
32
|
+
fun onStateChanged(sheetView: View, newState: Int)
|
|
33
|
+
fun onSlide(sheetView: View, slideOffset: Float)
|
|
34
|
+
fun onBackPressed()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// MARK: - TrueSheetDialogFragment
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Custom BottomSheetDialogFragment for TrueSheet.
|
|
43
|
+
* Provides a Material Design bottom sheet with proper lifecycle management.
|
|
44
|
+
*
|
|
45
|
+
* This fragment handles:
|
|
46
|
+
* - Dialog creation with proper theming (edge-to-edge support)
|
|
47
|
+
* - BottomSheetBehavior configuration and callbacks
|
|
48
|
+
* - Background styling with corner radius
|
|
49
|
+
* - Grabber view management
|
|
50
|
+
* - Back press handling
|
|
51
|
+
*
|
|
52
|
+
* The parent TrueSheetViewController handles:
|
|
53
|
+
* - React Native touch dispatching
|
|
54
|
+
* - Detent calculations
|
|
55
|
+
* - Animations
|
|
56
|
+
* - Keyboard/modal observers
|
|
57
|
+
* - Stacking and dimming
|
|
58
|
+
*/
|
|
59
|
+
class TrueSheetDialogFragment : BottomSheetDialogFragment() {
|
|
60
|
+
|
|
61
|
+
companion object {
|
|
62
|
+
private const val GRABBER_TAG = "TrueSheetGrabber"
|
|
63
|
+
private const val DEFAULT_MAX_WIDTH = 640 // dp
|
|
64
|
+
private const val DEFAULT_CORNER_RADIUS = 16f // dp
|
|
65
|
+
|
|
66
|
+
fun newInstance(): TrueSheetDialogFragment = TrueSheetDialogFragment()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// =============================================================================
|
|
70
|
+
// MARK: - Properties
|
|
71
|
+
// =============================================================================
|
|
72
|
+
|
|
73
|
+
var delegate: TrueSheetDialogFragmentDelegate? = null
|
|
74
|
+
|
|
75
|
+
// Content view provided by the controller
|
|
76
|
+
var contentView: View? = null
|
|
77
|
+
|
|
78
|
+
// React context for theme resolution and screen utils
|
|
79
|
+
var reactContext: ThemedReactContext? = null
|
|
80
|
+
|
|
81
|
+
// Configuration
|
|
82
|
+
var sheetCornerRadius: Float = DEFAULT_CORNER_RADIUS.dpToPx()
|
|
83
|
+
var sheetBackgroundColor: Int? = null
|
|
84
|
+
var edgeToEdgeFullScreen: Boolean = false
|
|
85
|
+
var grabberEnabled: Boolean = true
|
|
86
|
+
var grabberOptions: GrabberOptions? = null
|
|
87
|
+
var draggable: Boolean = true
|
|
88
|
+
|
|
89
|
+
var dismissible: Boolean = true
|
|
90
|
+
set(value) {
|
|
91
|
+
field = value
|
|
92
|
+
(dialog as? BottomSheetDialog)?.apply {
|
|
93
|
+
setCanceledOnTouchOutside(value)
|
|
94
|
+
setCancelable(value)
|
|
95
|
+
behavior.isHideable = value
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// =============================================================================
|
|
100
|
+
// MARK: - Computed Properties
|
|
101
|
+
// =============================================================================
|
|
102
|
+
|
|
103
|
+
val bottomSheetDialog: BottomSheetDialog?
|
|
104
|
+
get() = dialog as? BottomSheetDialog
|
|
105
|
+
|
|
106
|
+
val behavior: BottomSheetBehavior<FrameLayout>?
|
|
107
|
+
get() = bottomSheetDialog?.behavior
|
|
108
|
+
|
|
109
|
+
val bottomSheetView: FrameLayout?
|
|
110
|
+
get() = dialog?.findViewById(com.google.android.material.R.id.design_bottom_sheet)
|
|
111
|
+
|
|
112
|
+
private val edgeToEdgeEnabled: Boolean
|
|
113
|
+
get() {
|
|
114
|
+
val defaultEnabled = android.os.Build.VERSION.SDK_INT >= 36
|
|
115
|
+
return BuildConfig.EDGE_TO_EDGE_ENABLED || bottomSheetDialog?.edgeToEdgeEnabled == true || defaultEnabled
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
val topInset: Int
|
|
119
|
+
get() = reactContext?.let { if (edgeToEdgeEnabled) ScreenUtils.getInsets(it).top else 0 } ?: 0
|
|
120
|
+
|
|
121
|
+
// =============================================================================
|
|
122
|
+
// MARK: - Lifecycle
|
|
123
|
+
// =============================================================================
|
|
124
|
+
|
|
125
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
126
|
+
super.onCreate(savedInstanceState)
|
|
127
|
+
// Prevent dialog from being recreated on configuration change
|
|
128
|
+
// The controller manages state separately
|
|
129
|
+
retainInstance = true
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
|
133
|
+
val ctx = reactContext ?: requireContext()
|
|
134
|
+
|
|
135
|
+
val style = if (edgeToEdgeEnabled) {
|
|
136
|
+
R.style.TrueSheetEdgeToEdgeEnabledDialog
|
|
137
|
+
} else {
|
|
138
|
+
R.style.TrueSheetDialog
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
val dialog = BottomSheetDialog(ctx, style)
|
|
142
|
+
|
|
143
|
+
dialog.window?.apply {
|
|
144
|
+
setWindowAnimations(0)
|
|
145
|
+
setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING)
|
|
146
|
+
// Clear default dim - TrueSheet uses custom TrueSheetDimView for dimming
|
|
147
|
+
clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
dialog.setOnShowListener {
|
|
151
|
+
setupBottomSheetBehavior()
|
|
152
|
+
setupBackground()
|
|
153
|
+
setupGrabber()
|
|
154
|
+
// Re-apply dismissible after show since behavior may reset it
|
|
155
|
+
dialog.behavior.isHideable = dismissible
|
|
156
|
+
delegate?.onDialogShow()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
dialog.setCanceledOnTouchOutside(dismissible)
|
|
160
|
+
dialog.setCancelable(dismissible)
|
|
161
|
+
dialog.behavior.isHideable = dismissible
|
|
162
|
+
dialog.behavior.isDraggable = draggable
|
|
163
|
+
dialog.behavior.maxWidth = DEFAULT_MAX_WIDTH.dpToPx().toInt()
|
|
164
|
+
|
|
165
|
+
// Handle back press
|
|
166
|
+
dialog.onBackPressedDispatcher.addCallback(
|
|
167
|
+
this,
|
|
168
|
+
object : OnBackPressedCallback(true) {
|
|
169
|
+
override fun handleOnBackPressed() {
|
|
170
|
+
delegate?.onBackPressed()
|
|
171
|
+
if (dismissible) {
|
|
172
|
+
dismiss()
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
return dialog
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = contentView
|
|
182
|
+
|
|
183
|
+
override fun onCancel(dialog: android.content.DialogInterface) {
|
|
184
|
+
super.onCancel(dialog)
|
|
185
|
+
delegate?.onDialogCancel()
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
override fun onDismiss(dialog: android.content.DialogInterface) {
|
|
189
|
+
super.onDismiss(dialog)
|
|
190
|
+
delegate?.onDialogDismiss()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
override fun onDestroyView() {
|
|
194
|
+
// Detach content view to prevent it from being destroyed with the fragment
|
|
195
|
+
(contentView?.parent as? ViewGroup)?.removeView(contentView)
|
|
196
|
+
super.onDestroyView()
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// =============================================================================
|
|
200
|
+
// MARK: - Setup
|
|
201
|
+
// =============================================================================
|
|
202
|
+
|
|
203
|
+
private fun setupBottomSheetBehavior() {
|
|
204
|
+
val behavior = this.behavior ?: return
|
|
205
|
+
|
|
206
|
+
behavior.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
|
|
207
|
+
override fun onSlide(sheetView: View, slideOffset: Float) {
|
|
208
|
+
delegate?.onSlide(sheetView, slideOffset)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
override fun onStateChanged(sheetView: View, newState: Int) {
|
|
212
|
+
delegate?.onStateChanged(sheetView, newState)
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
fun setupBackground() {
|
|
218
|
+
val bottomSheet = bottomSheetView ?: return
|
|
219
|
+
val ctx = reactContext ?: return
|
|
220
|
+
|
|
221
|
+
val radius = if (sheetCornerRadius < 0) DEFAULT_CORNER_RADIUS.dpToPx() else sheetCornerRadius
|
|
222
|
+
|
|
223
|
+
// Rounded corners only on top
|
|
224
|
+
val outerRadii = floatArrayOf(
|
|
225
|
+
radius,
|
|
226
|
+
radius,
|
|
227
|
+
radius,
|
|
228
|
+
radius,
|
|
229
|
+
0f,
|
|
230
|
+
0f,
|
|
231
|
+
0f,
|
|
232
|
+
0f
|
|
233
|
+
)
|
|
234
|
+
val backgroundColor = sheetBackgroundColor ?: getDefaultBackgroundColor(ctx)
|
|
235
|
+
|
|
236
|
+
bottomSheet.background = ShapeDrawable(RoundRectShape(outerRadii, null, null)).apply {
|
|
237
|
+
paint.color = backgroundColor
|
|
238
|
+
}
|
|
239
|
+
bottomSheet.clipToOutline = true
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
fun setupGrabber() {
|
|
243
|
+
val bottomSheet = bottomSheetView ?: return
|
|
244
|
+
val ctx = reactContext ?: return
|
|
245
|
+
|
|
246
|
+
// Remove existing grabber
|
|
247
|
+
bottomSheet.findViewWithTag<View>(GRABBER_TAG)?.let {
|
|
248
|
+
bottomSheet.removeView(it)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!grabberEnabled || !draggable) return
|
|
252
|
+
|
|
253
|
+
val grabberView = TrueSheetGrabberView(ctx, grabberOptions).apply {
|
|
254
|
+
tag = GRABBER_TAG
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
bottomSheet.addView(grabberView)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// =============================================================================
|
|
261
|
+
// MARK: - Configuration
|
|
262
|
+
// =============================================================================
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Configure detent-related behavior settings.
|
|
266
|
+
* Called by the controller when detents change.
|
|
267
|
+
*/
|
|
268
|
+
fun configureDetents(
|
|
269
|
+
peekHeight: Int,
|
|
270
|
+
halfExpandedRatio: Float,
|
|
271
|
+
expandedOffset: Int,
|
|
272
|
+
fitToContents: Boolean,
|
|
273
|
+
skipCollapsed: Boolean = false,
|
|
274
|
+
animate: Boolean = false
|
|
275
|
+
) {
|
|
276
|
+
val behavior = this.behavior ?: return
|
|
277
|
+
|
|
278
|
+
behavior.apply {
|
|
279
|
+
isFitToContents = fitToContents
|
|
280
|
+
this.skipCollapsed = skipCollapsed
|
|
281
|
+
setPeekHeight(peekHeight, animate)
|
|
282
|
+
this.halfExpandedRatio = halfExpandedRatio.coerceIn(0f, 0.999f)
|
|
283
|
+
this.expandedOffset = expandedOffset
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Set the behavior state.
|
|
289
|
+
*/
|
|
290
|
+
fun setState(state: Int) {
|
|
291
|
+
behavior?.state = state
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Update draggable state.
|
|
296
|
+
*/
|
|
297
|
+
fun updateDraggable(enabled: Boolean) {
|
|
298
|
+
draggable = enabled
|
|
299
|
+
behavior?.isDraggable = enabled
|
|
300
|
+
if (isAdded) setupGrabber()
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// =============================================================================
|
|
304
|
+
// MARK: - Helpers
|
|
305
|
+
// =============================================================================
|
|
306
|
+
|
|
307
|
+
private fun getDefaultBackgroundColor(context: ThemedReactContext): Int {
|
|
308
|
+
val typedValue = TypedValue()
|
|
309
|
+
return if (context.theme.resolveAttribute(
|
|
310
|
+
com.google.android.material.R.attr.colorSurfaceContainerLow,
|
|
311
|
+
typedValue,
|
|
312
|
+
true
|
|
313
|
+
)
|
|
314
|
+
) {
|
|
315
|
+
typedValue.data
|
|
316
|
+
} else {
|
|
317
|
+
Color.WHITE
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
package/package.json
CHANGED