@lodev09/react-native-true-sheet 3.3.4 → 3.4.0-beta.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 +1 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +2 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +184 -120
- package/android/src/main/java/com/lodev09/truesheet/core/RNScreensFragmentObserver.kt +2 -2
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDialogObserver.kt +4 -3
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDimView.kt +72 -0
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetGrabberView.kt +0 -3
- package/android/src/main/res/values/styles.xml +2 -3
- package/package.json +2 -2
- package/android/src/main/res/anim/true_sheet_fade_in.xml +0 -6
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
The true native bottom sheet experience for your React Native Apps. 💩
|
|
10
10
|
|
|
11
|
-
<img alt="React Native True Sheet - IOS" src="docs/static/img/preview-ios.gif" width="
|
|
11
|
+
<img alt="React Native True Sheet - IOS" src="docs/static/img/preview-ios.gif" width="248" height="500" /><img alt="React Native True Sheet - Android" src="docs/static/img/preview-android.gif" width="248" height="500" /><img alt="React Native True Sheet - Web" src="docs/static/img/preview-web.gif" width="248" height="500" />
|
|
12
12
|
|
|
13
13
|
## Features
|
|
14
14
|
|
|
@@ -251,6 +251,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
251
251
|
viewController.dimmed = dimmed
|
|
252
252
|
if (viewController.isPresented) {
|
|
253
253
|
viewController.setupDimmedBackground(viewController.currentDetentIndex)
|
|
254
|
+
viewController.updateDimAmount()
|
|
254
255
|
}
|
|
255
256
|
}
|
|
256
257
|
|
|
@@ -259,6 +260,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
259
260
|
viewController.dimmedDetentIndex = index
|
|
260
261
|
if (viewController.isPresented) {
|
|
261
262
|
viewController.setupDimmedBackground(viewController.currentDetentIndex)
|
|
263
|
+
viewController.updateDimAmount()
|
|
262
264
|
}
|
|
263
265
|
}
|
|
264
266
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package com.lodev09.truesheet
|
|
2
2
|
|
|
3
|
+
import android.animation.ValueAnimator
|
|
3
4
|
import android.annotation.SuppressLint
|
|
4
5
|
import android.graphics.Color
|
|
5
6
|
import android.graphics.drawable.ShapeDrawable
|
|
@@ -28,6 +29,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
|
28
29
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
29
30
|
import com.lodev09.truesheet.core.GrabberOptions
|
|
30
31
|
import com.lodev09.truesheet.core.RNScreensFragmentObserver
|
|
32
|
+
import com.lodev09.truesheet.core.TrueSheetDimView
|
|
31
33
|
import com.lodev09.truesheet.core.TrueSheetGrabberView
|
|
32
34
|
import com.lodev09.truesheet.core.TrueSheetKeyboardObserver
|
|
33
35
|
import com.lodev09.truesheet.core.TrueSheetKeyboardObserverDelegate
|
|
@@ -72,7 +74,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
72
74
|
private const val DEFAULT_CORNER_RADIUS = 16 // dp
|
|
73
75
|
|
|
74
76
|
// Animation durations from res/anim/true_sheet_slide_in.xml and true_sheet_slide_out.xml
|
|
75
|
-
private const val PRESENT_ANIMATION_DURATION =
|
|
77
|
+
private const val PRESENT_ANIMATION_DURATION = 250L
|
|
76
78
|
private const val DISMISS_ANIMATION_DURATION = 150L
|
|
77
79
|
}
|
|
78
80
|
|
|
@@ -87,6 +89,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
87
89
|
// ====================================================================
|
|
88
90
|
|
|
89
91
|
private var dialog: BottomSheetDialog? = null
|
|
92
|
+
private var dimView: TrueSheetDimView? = null
|
|
93
|
+
private var parentDimView: TrueSheetDimView? = null
|
|
90
94
|
|
|
91
95
|
private val behavior: BottomSheetBehavior<FrameLayout>?
|
|
92
96
|
get() = dialog?.behavior
|
|
@@ -144,6 +148,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
144
148
|
get() = ScreenUtils.getScreenHeight(reactContext)
|
|
145
149
|
val screenWidth: Int
|
|
146
150
|
get() = ScreenUtils.getScreenWidth(reactContext)
|
|
151
|
+
val realScreenHeight: Int
|
|
152
|
+
get() = ScreenUtils.getRealScreenHeight(reactContext)
|
|
147
153
|
|
|
148
154
|
var maxSheetHeight: Int? = null
|
|
149
155
|
var detents = mutableListOf(0.5, 1.0)
|
|
@@ -152,7 +158,11 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
152
158
|
var dimmedDetentIndex = 0
|
|
153
159
|
var grabber: Boolean = true
|
|
154
160
|
var grabberOptions: GrabberOptions? = null
|
|
155
|
-
var sheetCornerRadius: Float =
|
|
161
|
+
var sheetCornerRadius: Float = DEFAULT_CORNER_RADIUS.dpToPx()
|
|
162
|
+
set(value) {
|
|
163
|
+
field = if (value < 0) DEFAULT_CORNER_RADIUS.dpToPx() else value
|
|
164
|
+
setupBackground()
|
|
165
|
+
}
|
|
156
166
|
var sheetBackgroundColor: Int? = null
|
|
157
167
|
var edgeToEdgeFullScreen: Boolean = false
|
|
158
168
|
|
|
@@ -261,6 +271,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
261
271
|
|
|
262
272
|
cleanupKeyboardObserver()
|
|
263
273
|
cleanupModalObserver()
|
|
274
|
+
dimView?.detach()
|
|
275
|
+
dimView = null
|
|
276
|
+
parentDimView?.detach()
|
|
277
|
+
parentDimView = null
|
|
264
278
|
sheetContainer?.removeView(this)
|
|
265
279
|
|
|
266
280
|
dialog = null
|
|
@@ -281,16 +295,18 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
281
295
|
setupGrabber()
|
|
282
296
|
setupKeyboardObserver()
|
|
283
297
|
|
|
298
|
+
val toTop = getExpectedSheetTop(currentDetentIndex)
|
|
299
|
+
setupTransitionTracker(realScreenHeight, toTop, PRESENT_ANIMATION_DURATION)
|
|
300
|
+
animateDimAlpha(show = true)
|
|
301
|
+
|
|
284
302
|
sheetContainer?.post {
|
|
285
|
-
bottomSheetView?.let { emitChangePositionDelegate(it, realtime = false) }
|
|
286
303
|
positionFooter()
|
|
287
304
|
}
|
|
288
305
|
|
|
289
306
|
sheetContainer?.postDelayed({
|
|
290
|
-
val
|
|
291
|
-
val detent = getDetentValueForIndex(detentInfo.index)
|
|
307
|
+
val (index, position, detent) = getDetentInfoWithValue(currentDetentIndex)
|
|
292
308
|
|
|
293
|
-
delegate?.viewControllerDidPresent(
|
|
309
|
+
delegate?.viewControllerDidPresent(index, position, detent)
|
|
294
310
|
parentSheetView?.viewControllerDidBlur()
|
|
295
311
|
delegate?.viewControllerDidFocus()
|
|
296
312
|
|
|
@@ -303,8 +319,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
303
319
|
if (isDismissing) return@setOnCancelListener
|
|
304
320
|
|
|
305
321
|
isDismissing = true
|
|
322
|
+
val fromTop = bottomSheetView?.top ?: getExpectedSheetTop(currentDetentIndex)
|
|
323
|
+
setupTransitionTracker(fromTop, realScreenHeight, DISMISS_ANIMATION_DURATION)
|
|
324
|
+
animateDimAlpha(show = false)
|
|
306
325
|
emitWillDismissEvents()
|
|
307
|
-
emitDismissedPosition()
|
|
308
326
|
}
|
|
309
327
|
|
|
310
328
|
dialog.setOnDismissListener {
|
|
@@ -321,7 +339,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
321
339
|
override fun onSlide(sheetView: View, slideOffset: Float) {
|
|
322
340
|
val behavior = behavior ?: return
|
|
323
341
|
|
|
324
|
-
emitChangePositionDelegate(sheetView
|
|
342
|
+
emitChangePositionDelegate(sheetView.top)
|
|
325
343
|
|
|
326
344
|
when (behavior.state) {
|
|
327
345
|
BottomSheetBehavior.STATE_DRAGGING,
|
|
@@ -331,6 +349,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
331
349
|
}
|
|
332
350
|
|
|
333
351
|
positionFooter(slideOffset)
|
|
352
|
+
updateDimAmount()
|
|
334
353
|
}
|
|
335
354
|
|
|
336
355
|
override fun onStateChanged(sheetView: View, newState: Int) {
|
|
@@ -386,14 +405,21 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
386
405
|
reactContext = reactContext,
|
|
387
406
|
onModalPresented = {
|
|
388
407
|
if (isPresented && isDialogVisible) {
|
|
389
|
-
|
|
408
|
+
isDialogVisible = false
|
|
409
|
+
dialog?.window?.setWindowAnimations(com.lodev09.truesheet.R.style.TrueSheetFadeOutAnimation)
|
|
410
|
+
dialog?.window?.decorView?.visibility = INVISIBLE
|
|
390
411
|
wasHiddenByModal = true
|
|
391
412
|
}
|
|
392
413
|
},
|
|
393
414
|
onModalDismissed = {
|
|
394
415
|
// Only show if we were the one hidden by modal, not by sheet stacking
|
|
395
416
|
if (isPresented && wasHiddenByModal) {
|
|
396
|
-
|
|
417
|
+
isDialogVisible = true
|
|
418
|
+
dialog?.window?.decorView?.visibility = VISIBLE
|
|
419
|
+
// Restore animation after visibility change to avoid slide animation
|
|
420
|
+
sheetContainer?.post {
|
|
421
|
+
dialog?.window?.setWindowAnimations(windowAnimation)
|
|
422
|
+
}
|
|
397
423
|
wasHiddenByModal = false
|
|
398
424
|
}
|
|
399
425
|
}
|
|
@@ -424,6 +450,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
424
450
|
dismissPromise = null
|
|
425
451
|
}
|
|
426
452
|
|
|
453
|
+
/** Helper to get detent info with its screen fraction value. */
|
|
454
|
+
private fun getDetentInfoWithValue(index: Int): Triple<Int, Float, Float> {
|
|
455
|
+
val detentInfo = getDetentInfoForIndex(index)
|
|
456
|
+
val detent = getDetentValueForIndex(detentInfo.index)
|
|
457
|
+
return Triple(detentInfo.index, detentInfo.position, detent)
|
|
458
|
+
}
|
|
459
|
+
|
|
427
460
|
// ====================================================================
|
|
428
461
|
// MARK: - Dialog Visibility (for stacking)
|
|
429
462
|
// ====================================================================
|
|
@@ -439,35 +472,22 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
439
472
|
|
|
440
473
|
fun getExpectedSheetTop(detentIndex: Int): Int {
|
|
441
474
|
if (detentIndex < 0 || detentIndex >= detents.size) return screenHeight
|
|
442
|
-
|
|
443
|
-
return screenHeight - detentHeight
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
/** Hides without dismissing. Used for sheet stacking and RN Screens modals. */
|
|
447
|
-
fun hideDialog(emitPosition: Boolean = false, animated: Boolean = false) {
|
|
448
|
-
isDialogVisible = false
|
|
449
|
-
if (animated) {
|
|
450
|
-
dialog?.window?.setWindowAnimations(com.lodev09.truesheet.R.style.TrueSheetFadeAnimation)
|
|
451
|
-
}
|
|
452
|
-
dialog?.window?.decorView?.visibility = INVISIBLE
|
|
453
|
-
if (emitPosition) {
|
|
454
|
-
emitDismissedPosition()
|
|
455
|
-
}
|
|
475
|
+
return realScreenHeight - getDetentHeight(detents[detentIndex])
|
|
456
476
|
}
|
|
457
477
|
|
|
458
|
-
/**
|
|
459
|
-
fun
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
470
|
-
|
|
478
|
+
/** Translates the sheet when stacking. Pass 0 to reset. */
|
|
479
|
+
fun translateDialog(translationY: Int) {
|
|
480
|
+
val bottomSheet = bottomSheetView ?: return
|
|
481
|
+
val duration = if (translationY > 0) PRESENT_ANIMATION_DURATION else DISMISS_ANIMATION_DURATION
|
|
482
|
+
|
|
483
|
+
bottomSheet.animate()
|
|
484
|
+
.translationY(translationY.toFloat())
|
|
485
|
+
.setDuration(duration)
|
|
486
|
+
.setUpdateListener {
|
|
487
|
+
val effectiveTop = bottomSheet.top + bottomSheet.translationY.toInt()
|
|
488
|
+
emitChangePositionDelegate(effectiveTop)
|
|
489
|
+
}
|
|
490
|
+
.start()
|
|
471
491
|
}
|
|
472
492
|
|
|
473
493
|
// ====================================================================
|
|
@@ -490,11 +510,10 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
490
510
|
setupSheetDetents()
|
|
491
511
|
setStateForDetentIndex(detentIndex)
|
|
492
512
|
|
|
493
|
-
val
|
|
494
|
-
val detent = getDetentValueForIndex(detentInfo.index)
|
|
513
|
+
val (index, position, detent) = getDetentInfoWithValue(detentIndex)
|
|
495
514
|
|
|
496
515
|
parentSheetView?.viewControllerWillBlur()
|
|
497
|
-
delegate?.viewControllerWillPresent(
|
|
516
|
+
delegate?.viewControllerWillPresent(index, position, detent)
|
|
498
517
|
delegate?.viewControllerWillFocus()
|
|
499
518
|
|
|
500
519
|
if (!animated) {
|
|
@@ -509,8 +528,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
509
528
|
if (isDismissing) return
|
|
510
529
|
|
|
511
530
|
isDismissing = true
|
|
531
|
+
val fromTop = bottomSheetView?.top ?: getExpectedSheetTop(currentDetentIndex)
|
|
532
|
+
setupTransitionTracker(fromTop, realScreenHeight, DISMISS_ANIMATION_DURATION)
|
|
512
533
|
emitWillDismissEvents()
|
|
513
|
-
emitDismissedPosition()
|
|
514
534
|
|
|
515
535
|
if (!animated) {
|
|
516
536
|
dialog?.window?.setWindowAnimations(0)
|
|
@@ -528,14 +548,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
528
548
|
val behavior = this.behavior ?: return
|
|
529
549
|
|
|
530
550
|
isReconfiguring = true
|
|
531
|
-
val realHeight = ScreenUtils.getRealScreenHeight(reactContext)
|
|
532
551
|
val edgeToEdgeTopInset: Int = if (!edgeToEdgeFullScreen) topInset else 0
|
|
533
552
|
|
|
534
553
|
behavior.apply {
|
|
535
554
|
isFitToContents = false
|
|
536
555
|
maxWidth = DEFAULT_MAX_WIDTH.dpToPx().toInt()
|
|
537
556
|
|
|
538
|
-
val maxAvailableHeight =
|
|
557
|
+
val maxAvailableHeight = realScreenHeight - edgeToEdgeTopInset
|
|
539
558
|
|
|
540
559
|
setPeekHeight(getDetentHeight(detents[0]), isPresented)
|
|
541
560
|
|
|
@@ -547,13 +566,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
547
566
|
val maxDetentHeight = getDetentHeight(detents.last())
|
|
548
567
|
|
|
549
568
|
val adjustedHalfExpandedHeight = minOf(halfExpandedDetentHeight, maxAvailableHeight)
|
|
550
|
-
halfExpandedRatio = minOf(adjustedHalfExpandedHeight.toFloat() /
|
|
569
|
+
halfExpandedRatio = minOf(adjustedHalfExpandedHeight.toFloat() / realScreenHeight.toFloat(), MAX_HALF_EXPANDED_RATIO)
|
|
551
570
|
|
|
552
|
-
expandedOffset = maxOf(edgeToEdgeTopInset,
|
|
571
|
+
expandedOffset = maxOf(edgeToEdgeTopInset, realScreenHeight - maxDetentHeight)
|
|
553
572
|
isFitToContents = detents.size < 3 && expandedOffset == 0
|
|
554
573
|
|
|
555
574
|
val offset = if (expandedOffset == 0) topInset else 0
|
|
556
|
-
val newHeight =
|
|
575
|
+
val newHeight = realScreenHeight - expandedOffset - offset
|
|
557
576
|
val newWidth = minOf(screenWidth, maxWidth)
|
|
558
577
|
|
|
559
578
|
if (lastStateWidth != newWidth || lastStateHeight != newHeight) {
|
|
@@ -587,6 +606,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
587
606
|
}
|
|
588
607
|
|
|
589
608
|
private var keyboardObserver: TrueSheetKeyboardObserver? = null
|
|
609
|
+
private var positionAnimator: ValueAnimator? = null
|
|
590
610
|
|
|
591
611
|
fun setupKeyboardObserver() {
|
|
592
612
|
val bottomSheet = bottomSheetView ?: return
|
|
@@ -605,11 +625,51 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
605
625
|
keyboardObserver = null
|
|
606
626
|
}
|
|
607
627
|
|
|
628
|
+
private fun setupTransitionTracker(fromTop: Int, toTop: Int, duration: Long) {
|
|
629
|
+
positionAnimator?.cancel()
|
|
630
|
+
positionAnimator = ValueAnimator.ofInt(fromTop, toTop).apply {
|
|
631
|
+
this.duration = duration
|
|
632
|
+
interpolator = if (fromTop > toTop) {
|
|
633
|
+
android.view.animation.DecelerateInterpolator(2f) // present
|
|
634
|
+
} else {
|
|
635
|
+
android.view.animation.AccelerateInterpolator(2f) // dismiss
|
|
636
|
+
}
|
|
637
|
+
addUpdateListener { animator ->
|
|
638
|
+
emitChangePositionDelegate(animator.animatedValue as Int)
|
|
639
|
+
}
|
|
640
|
+
addListener(object : android.animation.AnimatorListenerAdapter() {
|
|
641
|
+
override fun onAnimationEnd(animation: android.animation.Animator) {
|
|
642
|
+
positionAnimator = null
|
|
643
|
+
}
|
|
644
|
+
})
|
|
645
|
+
start()
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
private fun emitChangePositionDelegate(currentTop: Int, realtime: Boolean = true) {
|
|
650
|
+
if (currentTop == lastEmittedPositionPx) return
|
|
651
|
+
|
|
652
|
+
lastEmittedPositionPx = currentTop
|
|
653
|
+
val visibleHeight = realScreenHeight - currentTop
|
|
654
|
+
val position = getPositionDp(visibleHeight)
|
|
655
|
+
val interpolatedIndex = getInterpolatedIndexForPosition(currentTop)
|
|
656
|
+
val detent = getInterpolatedDetentForPosition(currentTop)
|
|
657
|
+
delegate?.viewControllerDidChangePosition(interpolatedIndex, position, detent, realtime)
|
|
658
|
+
}
|
|
659
|
+
|
|
608
660
|
fun setupBackground() {
|
|
609
661
|
val bottomSheet = bottomSheetView ?: return
|
|
610
662
|
|
|
611
|
-
val
|
|
612
|
-
|
|
663
|
+
val outerRadii = floatArrayOf(
|
|
664
|
+
sheetCornerRadius,
|
|
665
|
+
sheetCornerRadius,
|
|
666
|
+
sheetCornerRadius,
|
|
667
|
+
sheetCornerRadius,
|
|
668
|
+
0f,
|
|
669
|
+
0f,
|
|
670
|
+
0f,
|
|
671
|
+
0f
|
|
672
|
+
)
|
|
613
673
|
val backgroundColor = sheetBackgroundColor ?: getDefaultBackgroundColor()
|
|
614
674
|
|
|
615
675
|
bottomSheet.background = ShapeDrawable(RoundRectShape(outerRadii, null, null)).apply {
|
|
@@ -618,24 +678,46 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
618
678
|
bottomSheet.clipToOutline = true
|
|
619
679
|
}
|
|
620
680
|
|
|
621
|
-
/** Configures dim and touch-through behavior based on detent index. */
|
|
622
681
|
fun setupDimmedBackground(detentIndex: Int) {
|
|
623
682
|
val dialog = this.dialog ?: return
|
|
683
|
+
|
|
624
684
|
dialog.window?.apply {
|
|
625
|
-
val
|
|
685
|
+
val touchOutside = findViewById<View>(com.google.android.material.R.id.touch_outside)
|
|
686
|
+
clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
|
687
|
+
|
|
688
|
+
val shouldDimAtDetent = dimmed && detentIndex >= dimmedDetentIndex
|
|
689
|
+
|
|
690
|
+
if (dimmed) {
|
|
691
|
+
val parentDimVisible = (parentSheetView?.viewController?.dimView?.alpha ?: 0f) > 0f
|
|
626
692
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
693
|
+
if (dimView == null) dimView = TrueSheetDimView(reactContext)
|
|
694
|
+
if (!parentDimVisible) dimView?.attach(null)
|
|
695
|
+
|
|
696
|
+
val parentController = parentSheetView?.viewController
|
|
697
|
+
val parentBottomSheet = parentController?.bottomSheetView
|
|
698
|
+
if (parentBottomSheet != null) {
|
|
699
|
+
if (parentDimView == null) parentDimView = TrueSheetDimView(reactContext)
|
|
700
|
+
parentDimView?.attach(parentBottomSheet, parentController.sheetCornerRadius)
|
|
701
|
+
}
|
|
702
|
+
} else {
|
|
703
|
+
dimView?.detach()
|
|
704
|
+
dimView = null
|
|
705
|
+
parentDimView?.detach()
|
|
706
|
+
parentDimView = null
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (shouldDimAtDetent) {
|
|
710
|
+
touchOutside.setOnTouchListener(null)
|
|
631
711
|
dialog.setCanceledOnTouchOutside(dismissible)
|
|
632
712
|
} else {
|
|
633
|
-
|
|
713
|
+
touchOutside.setOnTouchListener { v, event ->
|
|
634
714
|
event.setLocation(event.rawX - v.x, event.rawY - v.y)
|
|
635
|
-
|
|
715
|
+
(
|
|
716
|
+
parentSheetView?.viewController?.dialog?.window?.decorView
|
|
717
|
+
?: reactContext.currentActivity?.window?.decorView
|
|
718
|
+
)?.dispatchTouchEvent(event)
|
|
636
719
|
false
|
|
637
720
|
}
|
|
638
|
-
clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
|
639
721
|
dialog.setCanceledOnTouchOutside(false)
|
|
640
722
|
}
|
|
641
723
|
}
|
|
@@ -645,6 +727,20 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
645
727
|
dialog?.window?.setWindowAnimations(windowAnimation)
|
|
646
728
|
}
|
|
647
729
|
|
|
730
|
+
private fun animateDimAlpha(show: Boolean) {
|
|
731
|
+
if (!dimmed) return
|
|
732
|
+
val duration = if (show) PRESENT_ANIMATION_DURATION else DISMISS_ANIMATION_DURATION
|
|
733
|
+
dimView?.animateAlpha(show, duration, dimmedDetentIndex, currentDetentIndex)
|
|
734
|
+
parentDimView?.animateAlpha(show, duration, dimmedDetentIndex, currentDetentIndex)
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
fun updateDimAmount() {
|
|
738
|
+
if (!dimmed) return
|
|
739
|
+
val sheetTop = bottomSheetView?.top ?: return
|
|
740
|
+
dimView?.interpolateAlpha(sheetTop, dimmedDetentIndex, ::getSheetTopForDetentIndex)
|
|
741
|
+
parentDimView?.interpolateAlpha(sheetTop, dimmedDetentIndex, ::getSheetTopForDetentIndex)
|
|
742
|
+
}
|
|
743
|
+
|
|
648
744
|
/** Positions footer at bottom of sheet, adjusting during drag via slideOffset. */
|
|
649
745
|
fun positionFooter(slideOffset: Float? = null) {
|
|
650
746
|
val footerView = containerView?.footerView ?: return
|
|
@@ -692,37 +788,16 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
692
788
|
* Calculate the visible sheet height from a sheet view.
|
|
693
789
|
* Uses real screen height for consistency across API levels.
|
|
694
790
|
*/
|
|
695
|
-
private fun getVisibleSheetHeight(sheetView: View): Int
|
|
696
|
-
val realHeight = ScreenUtils.getRealScreenHeight(reactContext)
|
|
697
|
-
return realHeight - sheetView.top
|
|
698
|
-
}
|
|
791
|
+
private fun getVisibleSheetHeight(sheetView: View): Int = realScreenHeight - sheetView.top
|
|
699
792
|
|
|
700
793
|
private fun getPositionDp(visibleSheetHeight: Int): Float = (screenHeight - visibleSheetHeight).pxToDp()
|
|
701
794
|
|
|
702
|
-
private fun emitChangePositionDelegate(sheetView: View, realtime: Boolean) {
|
|
703
|
-
if (sheetView.top == lastEmittedPositionPx) return
|
|
704
|
-
|
|
705
|
-
lastEmittedPositionPx = sheetView.top
|
|
706
|
-
val position = getPositionDp(getVisibleSheetHeight(sheetView))
|
|
707
|
-
val interpolatedIndex = getInterpolatedIndexForPosition(sheetView.top)
|
|
708
|
-
val detent = getInterpolatedDetentForPosition(sheetView.top)
|
|
709
|
-
delegate?.viewControllerDidChangePosition(interpolatedIndex, position, detent, realtime)
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
private fun emitDismissedPosition() {
|
|
713
|
-
val position = screenHeight.pxToDp()
|
|
714
|
-
lastEmittedPositionPx = -1
|
|
715
|
-
delegate?.viewControllerDidChangePosition(-1f, position, 0f, false)
|
|
716
|
-
}
|
|
717
|
-
|
|
718
795
|
/**
|
|
719
796
|
* Get the expected sheetTop position for a detent index.
|
|
720
797
|
*/
|
|
721
798
|
private fun getSheetTopForDetentIndex(index: Int): Int {
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
val detentHeight = getDetentHeight(detents[index])
|
|
725
|
-
return realHeight - detentHeight
|
|
799
|
+
if (index < 0 || index >= detents.size) return realScreenHeight
|
|
800
|
+
return realScreenHeight - getDetentHeight(detents[index])
|
|
726
801
|
}
|
|
727
802
|
|
|
728
803
|
/** Returns (fromIndex, toIndex, progress) for interpolation, or null if < 2 detents. */
|
|
@@ -730,12 +805,11 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
730
805
|
val count = detents.size
|
|
731
806
|
if (count == 0) return null
|
|
732
807
|
|
|
733
|
-
val realHeight = ScreenUtils.getRealScreenHeight(reactContext)
|
|
734
808
|
val firstPos = getSheetTopForDetentIndex(0)
|
|
735
809
|
|
|
736
810
|
// Above first detent - interpolating toward closed
|
|
737
811
|
if (positionPx > firstPos) {
|
|
738
|
-
val range =
|
|
812
|
+
val range = realScreenHeight - firstPos
|
|
739
813
|
val progress = if (range > 0) (positionPx - firstPos).toFloat() / range else 0f
|
|
740
814
|
return Triple(-1, 0, progress)
|
|
741
815
|
}
|
|
@@ -846,47 +920,37 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
846
920
|
return maxSheetHeight?.let { minOf(height, it, maxAllowedHeight) } ?: minOf(height, maxAllowedHeight)
|
|
847
921
|
}
|
|
848
922
|
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
0 -> BottomSheetBehavior.STATE_COLLAPSED
|
|
855
|
-
1 -> BottomSheetBehavior.STATE_EXPANDED
|
|
856
|
-
else -> BottomSheetBehavior.STATE_HIDDEN
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
3 -> when (index) {
|
|
860
|
-
0 -> BottomSheetBehavior.STATE_COLLAPSED
|
|
861
|
-
1 -> BottomSheetBehavior.STATE_HALF_EXPANDED
|
|
862
|
-
2 -> BottomSheetBehavior.STATE_EXPANDED
|
|
863
|
-
else -> BottomSheetBehavior.STATE_HIDDEN
|
|
864
|
-
}
|
|
923
|
+
/** Maps detent index to BottomSheetBehavior state based on detent count. */
|
|
924
|
+
private fun getStateForDetentIndex(index: Int): Int {
|
|
925
|
+
val stateMap = getDetentStateMap() ?: return BottomSheetBehavior.STATE_HIDDEN
|
|
926
|
+
return stateMap.entries.find { it.value == index }?.key ?: BottomSheetBehavior.STATE_HIDDEN
|
|
927
|
+
}
|
|
865
928
|
|
|
866
|
-
|
|
867
|
-
|
|
929
|
+
/** Maps BottomSheetBehavior state to DetentInfo based on detent count. */
|
|
930
|
+
fun getDetentInfoForState(state: Int): DetentInfo? {
|
|
931
|
+
val stateMap = getDetentStateMap() ?: return null
|
|
932
|
+
val index = stateMap[state] ?: return null
|
|
933
|
+
return DetentInfo(index, getPositionForDetentIndex(index))
|
|
934
|
+
}
|
|
868
935
|
|
|
869
|
-
|
|
936
|
+
/** Returns state-to-index mapping based on detent count. */
|
|
937
|
+
private fun getDetentStateMap(): Map<Int, Int>? =
|
|
870
938
|
when (detents.size) {
|
|
871
|
-
1 ->
|
|
872
|
-
BottomSheetBehavior.STATE_COLLAPSED,
|
|
873
|
-
BottomSheetBehavior.STATE_EXPANDED
|
|
874
|
-
|
|
875
|
-
else -> null
|
|
876
|
-
}
|
|
939
|
+
1 -> mapOf(
|
|
940
|
+
BottomSheetBehavior.STATE_COLLAPSED to 0,
|
|
941
|
+
BottomSheetBehavior.STATE_EXPANDED to 0
|
|
942
|
+
)
|
|
877
943
|
|
|
878
|
-
2 ->
|
|
879
|
-
BottomSheetBehavior.STATE_COLLAPSED
|
|
880
|
-
BottomSheetBehavior.STATE_EXPANDED
|
|
881
|
-
|
|
882
|
-
}
|
|
944
|
+
2 -> mapOf(
|
|
945
|
+
BottomSheetBehavior.STATE_COLLAPSED to 0,
|
|
946
|
+
BottomSheetBehavior.STATE_EXPANDED to 1
|
|
947
|
+
)
|
|
883
948
|
|
|
884
|
-
3 ->
|
|
885
|
-
BottomSheetBehavior.STATE_COLLAPSED
|
|
886
|
-
BottomSheetBehavior.STATE_HALF_EXPANDED
|
|
887
|
-
BottomSheetBehavior.STATE_EXPANDED
|
|
888
|
-
|
|
889
|
-
}
|
|
949
|
+
3 -> mapOf(
|
|
950
|
+
BottomSheetBehavior.STATE_COLLAPSED to 0,
|
|
951
|
+
BottomSheetBehavior.STATE_HALF_EXPANDED to 1,
|
|
952
|
+
BottomSheetBehavior.STATE_EXPANDED to 2
|
|
953
|
+
)
|
|
890
954
|
|
|
891
955
|
else -> null
|
|
892
956
|
}
|
|
@@ -928,7 +992,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
928
992
|
this.post {
|
|
929
993
|
setupSheetDetents()
|
|
930
994
|
positionFooter()
|
|
931
|
-
bottomSheetView?.let { emitChangePositionDelegate(it, realtime = false) }
|
|
995
|
+
bottomSheetView?.let { emitChangePositionDelegate(it.top, realtime = false) }
|
|
932
996
|
}
|
|
933
997
|
}
|
|
934
998
|
|
|
@@ -27,8 +27,8 @@ class RNScreensFragmentObserver(
|
|
|
27
27
|
val fragmentManager = activity.supportFragmentManager
|
|
28
28
|
|
|
29
29
|
fragmentLifecycleCallback = object : FragmentManager.FragmentLifecycleCallbacks() {
|
|
30
|
-
override fun
|
|
31
|
-
super.
|
|
30
|
+
override fun onFragmentAttached(fm: FragmentManager, fragment: Fragment, context: android.content.Context) {
|
|
31
|
+
super.onFragmentAttached(fm, fragment, context)
|
|
32
32
|
|
|
33
33
|
if (isModalFragment(fragment) && !activeModalFragments.contains(fragment)) {
|
|
34
34
|
activeModalFragments.add(fragment)
|
|
@@ -20,7 +20,7 @@ object TrueSheetDialogObserver {
|
|
|
20
20
|
val parentSheet = presentedSheetStack.lastOrNull()
|
|
21
21
|
?.takeIf { it.viewController.isPresented && it.viewController.isDialogVisible }
|
|
22
22
|
|
|
23
|
-
//
|
|
23
|
+
// Translate parent sheets down to match the new sheet's position
|
|
24
24
|
val newSheetTop = sheetView.viewController.getExpectedSheetTop(detentIndex)
|
|
25
25
|
for (sheet in presentedSheetStack) {
|
|
26
26
|
if (!sheet.viewController.isDialogVisible) continue
|
|
@@ -28,7 +28,8 @@ object TrueSheetDialogObserver {
|
|
|
28
28
|
|
|
29
29
|
val sheetTop = sheet.viewController.currentSheetTop
|
|
30
30
|
if (sheetTop < newSheetTop) {
|
|
31
|
-
|
|
31
|
+
val translationY = newSheetTop - sheetTop
|
|
32
|
+
sheet.viewController.translateDialog(translationY)
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -49,7 +50,7 @@ object TrueSheetDialogObserver {
|
|
|
49
50
|
synchronized(presentedSheetStack) {
|
|
50
51
|
presentedSheetStack.remove(sheetView)
|
|
51
52
|
if (hadParent) {
|
|
52
|
-
presentedSheetStack.lastOrNull()?.viewController?.
|
|
53
|
+
presentedSheetStack.lastOrNull()?.viewController?.translateDialog(0)
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
package com.lodev09.truesheet.core
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.graphics.Outline
|
|
6
|
+
import android.view.View
|
|
7
|
+
import android.view.ViewGroup
|
|
8
|
+
import android.view.ViewOutlineProvider
|
|
9
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
10
|
+
import com.lodev09.truesheet.utils.ScreenUtils
|
|
11
|
+
|
|
12
|
+
@SuppressLint("ViewConstructor")
|
|
13
|
+
class TrueSheetDimView(private val reactContext: ThemedReactContext) : View(reactContext) {
|
|
14
|
+
|
|
15
|
+
companion object {
|
|
16
|
+
private const val MAX_ALPHA = 0.4f
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private var targetView: ViewGroup? = null
|
|
20
|
+
|
|
21
|
+
init {
|
|
22
|
+
layoutParams = ViewGroup.LayoutParams(
|
|
23
|
+
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
24
|
+
ViewGroup.LayoutParams.MATCH_PARENT
|
|
25
|
+
)
|
|
26
|
+
setBackgroundColor(Color.BLACK)
|
|
27
|
+
alpha = 0f
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
fun attach(view: ViewGroup? = null, cornerRadius: Float = 0f) {
|
|
31
|
+
if (parent != null) return
|
|
32
|
+
targetView = view ?: reactContext.currentActivity?.window?.decorView as? ViewGroup
|
|
33
|
+
|
|
34
|
+
if (cornerRadius > 0f) {
|
|
35
|
+
outlineProvider = object : ViewOutlineProvider() {
|
|
36
|
+
override fun getOutline(v: View, outline: Outline) {
|
|
37
|
+
outline.setRoundRect(0, 0, v.width, v.height, cornerRadius)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
clipToOutline = true
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
targetView?.addView(this)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
fun detach() {
|
|
47
|
+
targetView?.removeView(this)
|
|
48
|
+
targetView = null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fun animateAlpha(show: Boolean, duration: Long, dimmedDetentIndex: Int, currentDetentIndex: Int) {
|
|
52
|
+
val targetAlpha = if (show && currentDetentIndex >= dimmedDetentIndex) MAX_ALPHA else 0f
|
|
53
|
+
animate().alpha(targetAlpha).setDuration(duration).start()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fun interpolateAlpha(sheetTop: Int, dimmedDetentIndex: Int, getSheetTopForDetentIndex: (Int) -> Int) {
|
|
57
|
+
val realHeight = ScreenUtils.getRealScreenHeight(reactContext)
|
|
58
|
+
val dimmedDetentTop = getSheetTopForDetentIndex(dimmedDetentIndex)
|
|
59
|
+
val belowDimmedTop = if (dimmedDetentIndex > 0) getSheetTopForDetentIndex(dimmedDetentIndex - 1) else realHeight
|
|
60
|
+
|
|
61
|
+
alpha = when {
|
|
62
|
+
sheetTop <= dimmedDetentTop -> MAX_ALPHA
|
|
63
|
+
|
|
64
|
+
sheetTop >= belowDimmedTop -> 0f
|
|
65
|
+
|
|
66
|
+
else -> {
|
|
67
|
+
val progress = 1f - (sheetTop - dimmedDetentTop).toFloat() / (belowDimmedTop - dimmedDetentTop)
|
|
68
|
+
(progress * MAX_ALPHA).coerceIn(0f, MAX_ALPHA)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -70,9 +70,6 @@ class TrueSheetGrabberView(context: Context, private val options: GrabberOptions
|
|
|
70
70
|
cornerRadius = grabberCornerRadius.dpToPx()
|
|
71
71
|
setColor(grabberColor)
|
|
72
72
|
}
|
|
73
|
-
|
|
74
|
-
// High elevation to ensure grabber appears above content views
|
|
75
|
-
elevation = 100f
|
|
76
73
|
}
|
|
77
74
|
|
|
78
75
|
private fun getAdaptiveColor(baseColor: Int? = null): Int {
|
|
@@ -6,9 +6,8 @@
|
|
|
6
6
|
<item name="android:windowExitAnimation">@anim/true_sheet_slide_out</item>
|
|
7
7
|
</style>
|
|
8
8
|
|
|
9
|
-
<!--
|
|
10
|
-
<style name="
|
|
11
|
-
<item name="android:windowEnterAnimation">@anim/true_sheet_fade_in</item>
|
|
9
|
+
<!-- Fade out only - used when hiding sheet for RN Screens modal -->
|
|
10
|
+
<style name="TrueSheetFadeOutAnimation" parent="Animation.AppCompat.Dialog">
|
|
12
11
|
<item name="android:windowExitAnimation">@anim/true_sheet_fade_out</item>
|
|
13
12
|
</style>
|
|
14
13
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lodev09/react-native-true-sheet",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0-beta.0",
|
|
4
4
|
"description": "The true native bottom sheet experience for your React Native Apps.",
|
|
5
5
|
"source": "./src/index.ts",
|
|
6
6
|
"main": "./lib/module/index.js",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"clean": "scripts/clean.sh",
|
|
54
54
|
"prepare": "bob build",
|
|
55
55
|
"release": "release-it",
|
|
56
|
-
"release:beta": "yarn release
|
|
56
|
+
"release:beta": "yarn release --preRelease=beta"
|
|
57
57
|
},
|
|
58
58
|
"keywords": [
|
|
59
59
|
"react-native",
|