react-native-screens 4.10.0-beta.2 → 4.10.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/RNScreens.podspec +2 -2
- package/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledHeaderConfigViewGroup.kt +3 -8
- package/android/src/fabric/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt +9 -6
- package/android/src/fabric/java/com/swmansion/rnscreens/NativeProxy.kt +2 -1
- package/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt +111 -15
- package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +54 -17
- package/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt +1 -1
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +125 -48
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +46 -286
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +94 -54
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigShadowNode.kt +3 -0
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubview.kt +19 -2
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.kt +2 -1
- package/android/src/main/java/com/swmansion/rnscreens/bottomsheet/SheetDelegate.kt +262 -3
- package/android/src/main/java/com/swmansion/rnscreens/ext/ViewExt.kt +2 -0
- package/android/src/main/java/com/swmansion/rnscreens/utils/InsetsKt.kt +31 -0
- package/android/src/main/java/com/swmansion/rnscreens/utils/PaddingBundle.kt +1 -0
- package/android/src/paper/java/com/swmansion/rnscreens/FabricEnabledHeaderConfigViewGroup.kt +14 -5
- package/android/src/versioned/pointerevents/77/com/swmansion/rnscreens/{ScreensCoordinatorLayoutPointerEventsImpl.kt → PointerEventsBoxNoneImpl.kt} +1 -1
- package/android/src/versioned/pointerevents/latest/com/swmansion/rnscreens/{ScreensCoordinatorLayoutPointerEventsImpl.kt → PointerEventsBoxNoneImpl.kt} +1 -1
- package/common/cpp/react/renderer/components/rnscreens/RNSModalScreenShadowNode.cpp +1 -3
- package/common/cpp/react/renderer/components/rnscreens/RNSScreenStackHeaderConfigComponentDescriptor.h +7 -3
- package/common/cpp/react/renderer/components/rnscreens/RNSScreenStackHeaderSubviewComponentDescriptor.h +1 -1
- package/cpp/RNSScreenRemovalListener.cpp +3 -1
- package/ios/RNSFullWindowOverlay.mm +6 -6
- package/ios/RNSScreen.mm +12 -0
- package/ios/RNSScreenStackHeaderConfig.h +1 -1
- package/ios/RNSScreenStackHeaderConfig.mm +27 -22
- package/lib/commonjs/components/Screen.js +13 -4
- package/lib/commonjs/components/Screen.js.map +1 -1
- package/lib/module/components/Screen.js +13 -4
- package/lib/module/components/Screen.js.map +1 -1
- package/lib/typescript/components/Screen.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/Screen.tsx +23 -13
- package/android/src/main/java/com/swmansion/rnscreens/NativeDismissalObserver.kt +0 -12
|
@@ -7,7 +7,6 @@ import android.annotation.SuppressLint
|
|
|
7
7
|
import android.content.Context
|
|
8
8
|
import android.graphics.Color
|
|
9
9
|
import android.graphics.drawable.ColorDrawable
|
|
10
|
-
import android.os.Build
|
|
11
10
|
import android.os.Bundle
|
|
12
11
|
import android.view.LayoutInflater
|
|
13
12
|
import android.view.Menu
|
|
@@ -16,33 +15,23 @@ import android.view.MenuItem
|
|
|
16
15
|
import android.view.View
|
|
17
16
|
import android.view.ViewGroup
|
|
18
17
|
import android.view.WindowInsets
|
|
19
|
-
import android.view.WindowManager
|
|
20
18
|
import android.view.animation.Animation
|
|
21
19
|
import android.view.animation.AnimationSet
|
|
22
20
|
import android.view.animation.Transformation
|
|
23
|
-
import android.view.inputmethod.InputMethodManager
|
|
24
21
|
import android.widget.LinearLayout
|
|
25
|
-
import androidx.annotation.RequiresApi
|
|
26
22
|
import androidx.appcompat.widget.Toolbar
|
|
27
23
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
28
|
-
import androidx.core.view.WindowInsetsCompat
|
|
29
24
|
import com.facebook.react.uimanager.PixelUtil
|
|
30
25
|
import com.facebook.react.uimanager.ReactPointerEventsView
|
|
31
26
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
32
27
|
import com.google.android.material.appbar.AppBarLayout
|
|
33
28
|
import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior
|
|
34
29
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
35
|
-
import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback
|
|
36
30
|
import com.google.android.material.shape.CornerFamily
|
|
37
31
|
import com.google.android.material.shape.MaterialShapeDrawable
|
|
38
32
|
import com.google.android.material.shape.ShapeAppearanceModel
|
|
39
33
|
import com.swmansion.rnscreens.bottomsheet.DimmingViewManager
|
|
40
34
|
import com.swmansion.rnscreens.bottomsheet.SheetDelegate
|
|
41
|
-
import com.swmansion.rnscreens.bottomsheet.SheetUtils
|
|
42
|
-
import com.swmansion.rnscreens.bottomsheet.isSheetFitToContents
|
|
43
|
-
import com.swmansion.rnscreens.bottomsheet.useSingleDetent
|
|
44
|
-
import com.swmansion.rnscreens.bottomsheet.useThreeDetents
|
|
45
|
-
import com.swmansion.rnscreens.bottomsheet.useTwoDetents
|
|
46
35
|
import com.swmansion.rnscreens.bottomsheet.usesFormSheetPresentation
|
|
47
36
|
import com.swmansion.rnscreens.events.ScreenAnimationDelegate
|
|
48
37
|
import com.swmansion.rnscreens.events.ScreenDismissedEvent
|
|
@@ -65,7 +54,6 @@ class KeyboardVisible(
|
|
|
65
54
|
class ScreenStackFragment :
|
|
66
55
|
ScreenFragment,
|
|
67
56
|
ScreenStackFragmentWrapper {
|
|
68
|
-
public var nativeDismissalObserver: NativeDismissalObserver? = null
|
|
69
57
|
private var appBarLayout: AppBarLayout? = null
|
|
70
58
|
private var toolbar: Toolbar? = null
|
|
71
59
|
private var isToolbarShadowHidden = false
|
|
@@ -159,51 +147,6 @@ class ScreenStackFragment :
|
|
|
159
147
|
}
|
|
160
148
|
}
|
|
161
149
|
|
|
162
|
-
// If the Screen has `formSheet` presentation this callback is attached to its behavior.
|
|
163
|
-
// It is responsible for firing detent changed events & removing the sheet from the container
|
|
164
|
-
// once it is hidden by user gesture.
|
|
165
|
-
private val bottomSheetStateCallback =
|
|
166
|
-
object : BottomSheetCallback() {
|
|
167
|
-
private var lastStableState: Int =
|
|
168
|
-
SheetUtils.sheetStateFromDetentIndex(
|
|
169
|
-
screen.sheetInitialDetentIndex,
|
|
170
|
-
screen.sheetDetents.count(),
|
|
171
|
-
)
|
|
172
|
-
|
|
173
|
-
override fun onStateChanged(
|
|
174
|
-
bottomSheet: View,
|
|
175
|
-
newState: Int,
|
|
176
|
-
) {
|
|
177
|
-
if (SheetUtils.isStateStable(newState)) {
|
|
178
|
-
lastStableState = newState
|
|
179
|
-
screen.notifySheetDetentChange(
|
|
180
|
-
SheetUtils.detentIndexFromSheetState(
|
|
181
|
-
lastStableState,
|
|
182
|
-
screen.sheetDetents.count(),
|
|
183
|
-
),
|
|
184
|
-
true,
|
|
185
|
-
)
|
|
186
|
-
} else if (newState == BottomSheetBehavior.STATE_DRAGGING) {
|
|
187
|
-
screen.notifySheetDetentChange(
|
|
188
|
-
SheetUtils.detentIndexFromSheetState(
|
|
189
|
-
lastStableState,
|
|
190
|
-
screen.sheetDetents.count(),
|
|
191
|
-
),
|
|
192
|
-
false,
|
|
193
|
-
)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
|
|
197
|
-
dismissSelf()
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
override fun onSlide(
|
|
202
|
-
bottomSheet: View,
|
|
203
|
-
slideOffset: Float,
|
|
204
|
-
) = Unit
|
|
205
|
-
}
|
|
206
|
-
|
|
207
150
|
/**
|
|
208
151
|
* Currently this method dispatches event to JS where state is recomputed and fragment
|
|
209
152
|
* gets removed in the result of incoming state update.
|
|
@@ -241,7 +184,7 @@ class ScreenStackFragment :
|
|
|
241
184
|
).apply {
|
|
242
185
|
behavior =
|
|
243
186
|
if (screen.usesFormSheetPresentation()) {
|
|
244
|
-
|
|
187
|
+
createBottomSheetBehaviour()
|
|
245
188
|
} else if (isToolbarTranslucent) {
|
|
246
189
|
null
|
|
247
190
|
} else {
|
|
@@ -249,13 +192,8 @@ class ScreenStackFragment :
|
|
|
249
192
|
}
|
|
250
193
|
}
|
|
251
194
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
// TODO(@kkafar): without this line there is no drawable / outline & nothing shows...? Determine what's going on here
|
|
255
|
-
attachShapeToScreen(screen)
|
|
256
|
-
screen.elevation = screen.sheetElevation
|
|
257
|
-
}
|
|
258
|
-
|
|
195
|
+
// This must be called before further sheet configuration.
|
|
196
|
+
// Otherwise there is no enter animation -> dunno why, just observed it.
|
|
259
197
|
coordinatorLayout.addView(screen.recycle())
|
|
260
198
|
|
|
261
199
|
if (!screen.usesFormSheetPresentation()) {
|
|
@@ -279,7 +217,28 @@ class ScreenStackFragment :
|
|
|
279
217
|
}
|
|
280
218
|
toolbar?.let { appBarLayout?.addView(it.recycle()) }
|
|
281
219
|
setHasOptionsMenu(true)
|
|
220
|
+
} else {
|
|
221
|
+
screen.clipToOutline = true
|
|
222
|
+
// TODO(@kkafar): without this line there is no drawable / outline & nothing shows...? Determine what's going on here
|
|
223
|
+
attachShapeToScreen(screen)
|
|
224
|
+
screen.elevation = screen.sheetElevation
|
|
225
|
+
|
|
226
|
+
// Lifecycle of sheet delegate is tied to fragment.
|
|
227
|
+
val sheetDelegate = requireSheetDelegate()
|
|
228
|
+
sheetDelegate.configureBottomSheetBehaviour(screen.sheetBehavior!!)
|
|
229
|
+
|
|
230
|
+
val dimmingDelegate = requireDimmingDelegate(forceCreation = true)
|
|
231
|
+
dimmingDelegate.onViewHierarchyCreated(screen, coordinatorLayout)
|
|
232
|
+
dimmingDelegate.onBehaviourAttached(screen, screen.sheetBehavior!!)
|
|
233
|
+
|
|
234
|
+
val container = screen.container!!
|
|
235
|
+
coordinatorLayout.measure(
|
|
236
|
+
View.MeasureSpec.makeMeasureSpec(container.width, View.MeasureSpec.EXACTLY),
|
|
237
|
+
View.MeasureSpec.makeMeasureSpec(container.height, View.MeasureSpec.EXACTLY),
|
|
238
|
+
)
|
|
239
|
+
coordinatorLayout.layout(0, 0, container.width, container.height)
|
|
282
240
|
}
|
|
241
|
+
|
|
283
242
|
return coordinatorLayout
|
|
284
243
|
}
|
|
285
244
|
|
|
@@ -288,24 +247,6 @@ class ScreenStackFragment :
|
|
|
288
247
|
savedInstanceState: Bundle?,
|
|
289
248
|
) {
|
|
290
249
|
super.onViewCreated(view, savedInstanceState)
|
|
291
|
-
|
|
292
|
-
if (!screen.usesFormSheetPresentation()) {
|
|
293
|
-
return
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
sheetDelegate = SheetDelegate(screen)
|
|
297
|
-
|
|
298
|
-
assert(view == coordinatorLayout)
|
|
299
|
-
val dimmingDelegate = requireDimmingDelegate(forceCreation = true)
|
|
300
|
-
dimmingDelegate.onViewHierarchyCreated(screen, coordinatorLayout)
|
|
301
|
-
dimmingDelegate.onBehaviourAttached(screen, screen.sheetBehavior!!)
|
|
302
|
-
|
|
303
|
-
val container = screen.container!!
|
|
304
|
-
coordinatorLayout.measure(
|
|
305
|
-
View.MeasureSpec.makeMeasureSpec(container.width, View.MeasureSpec.EXACTLY),
|
|
306
|
-
View.MeasureSpec.makeMeasureSpec(container.height, View.MeasureSpec.EXACTLY),
|
|
307
|
-
)
|
|
308
|
-
coordinatorLayout.layout(0, 0, container.width, container.height)
|
|
309
250
|
}
|
|
310
251
|
|
|
311
252
|
override fun onCreateAnimation(
|
|
@@ -387,210 +328,8 @@ class ScreenStackFragment :
|
|
|
387
328
|
return animatorSet
|
|
388
329
|
}
|
|
389
330
|
|
|
390
|
-
/**
|
|
391
|
-
* This method might return slightly different values depending on code path,
|
|
392
|
-
* but during testing I've found this effect negligible. For practical purposes
|
|
393
|
-
* this is acceptable.
|
|
394
|
-
*/
|
|
395
|
-
private fun tryResolveContainerHeight(): Int? {
|
|
396
|
-
if (screen.container != null) {
|
|
397
|
-
return screenStack.height
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
context
|
|
401
|
-
?.resources
|
|
402
|
-
?.displayMetrics
|
|
403
|
-
?.heightPixels
|
|
404
|
-
?.let { return it }
|
|
405
|
-
|
|
406
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
407
|
-
(context?.getSystemService(Context.WINDOW_SERVICE) as? WindowManager)
|
|
408
|
-
?.currentWindowMetrics
|
|
409
|
-
?.bounds
|
|
410
|
-
?.height()
|
|
411
|
-
?.let { return it }
|
|
412
|
-
}
|
|
413
|
-
return null
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
private val keyboardSheetCallback =
|
|
417
|
-
object : BottomSheetCallback() {
|
|
418
|
-
@RequiresApi(Build.VERSION_CODES.M)
|
|
419
|
-
override fun onStateChanged(
|
|
420
|
-
bottomSheet: View,
|
|
421
|
-
newState: Int,
|
|
422
|
-
) {
|
|
423
|
-
if (newState == BottomSheetBehavior.STATE_COLLAPSED) {
|
|
424
|
-
val isImeVisible =
|
|
425
|
-
WindowInsetsCompat
|
|
426
|
-
.toWindowInsetsCompat(bottomSheet.rootWindowInsets)
|
|
427
|
-
.isVisible(WindowInsetsCompat.Type.ime())
|
|
428
|
-
if (isImeVisible) {
|
|
429
|
-
// Does it not interfere with React Native focus mechanism? In any case I'm not aware
|
|
430
|
-
// of different way of hiding the keyboard.
|
|
431
|
-
// https://stackoverflow.com/questions/1109022/how-can-i-close-hide-the-android-soft-keyboard-programmatically
|
|
432
|
-
// https://developer.android.com/develop/ui/views/touch-and-input/keyboard-input/visibility
|
|
433
|
-
|
|
434
|
-
// I want to be polite here and request focus before dismissing the keyboard,
|
|
435
|
-
// however even if it fails I want to try to hide the keyboard. This sometimes works...
|
|
436
|
-
bottomSheet.requestFocus()
|
|
437
|
-
val imm = requireContext().getSystemService(InputMethodManager::class.java)
|
|
438
|
-
imm.hideSoftInputFromWindow(bottomSheet.windowToken, 0)
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
override fun onSlide(
|
|
444
|
-
bottomSheet: View,
|
|
445
|
-
slideOffset: Float,
|
|
446
|
-
) = Unit
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
internal fun configureBottomSheetBehaviour(
|
|
450
|
-
behavior: BottomSheetBehavior<Screen>,
|
|
451
|
-
keyboardState: KeyboardState = KeyboardNotVisible,
|
|
452
|
-
): BottomSheetBehavior<Screen> {
|
|
453
|
-
val containerHeight = tryResolveContainerHeight()
|
|
454
|
-
check(containerHeight != null) {
|
|
455
|
-
"[RNScreens] Failed to find window height during bottom sheet behaviour configuration"
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
behavior.apply {
|
|
459
|
-
isHideable = true
|
|
460
|
-
isDraggable = true
|
|
461
|
-
|
|
462
|
-
// It seems that there is a guard in material implementation that will prevent
|
|
463
|
-
// this callback from being registered multiple times.
|
|
464
|
-
addBottomSheetCallback(bottomSheetStateCallback)
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
screen.footer?.registerWithSheetBehavior(behavior)
|
|
468
|
-
|
|
469
|
-
return when (keyboardState) {
|
|
470
|
-
is KeyboardNotVisible -> {
|
|
471
|
-
when (screen.sheetDetents.count()) {
|
|
472
|
-
1 ->
|
|
473
|
-
behavior.apply {
|
|
474
|
-
val height =
|
|
475
|
-
if (screen.isSheetFitToContents()) {
|
|
476
|
-
screen.contentWrapper
|
|
477
|
-
.get()
|
|
478
|
-
?.height
|
|
479
|
-
.takeIf { screen.contentWrapper.get()?.isLaidOut == true }
|
|
480
|
-
} else {
|
|
481
|
-
(screen.sheetDetents.first() * containerHeight).toInt()
|
|
482
|
-
}
|
|
483
|
-
useSingleDetent(height = height)
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
2 ->
|
|
487
|
-
behavior.useTwoDetents(
|
|
488
|
-
state =
|
|
489
|
-
SheetUtils.sheetStateFromDetentIndex(
|
|
490
|
-
screen.sheetInitialDetentIndex,
|
|
491
|
-
screen.sheetDetents.count(),
|
|
492
|
-
),
|
|
493
|
-
firstHeight = (screen.sheetDetents[0] * containerHeight).toInt(),
|
|
494
|
-
secondHeight = (screen.sheetDetents[1] * containerHeight).toInt(),
|
|
495
|
-
)
|
|
496
|
-
|
|
497
|
-
3 ->
|
|
498
|
-
behavior.useThreeDetents(
|
|
499
|
-
state =
|
|
500
|
-
SheetUtils.sheetStateFromDetentIndex(
|
|
501
|
-
screen.sheetInitialDetentIndex,
|
|
502
|
-
screen.sheetDetents.count(),
|
|
503
|
-
),
|
|
504
|
-
firstHeight = (screen.sheetDetents[0] * containerHeight).toInt(),
|
|
505
|
-
halfExpandedRatio = (screen.sheetDetents[1] / screen.sheetDetents[2]).toFloat(),
|
|
506
|
-
expandedOffsetFromTop = ((1 - screen.sheetDetents[2]) * containerHeight).toInt(),
|
|
507
|
-
)
|
|
508
|
-
|
|
509
|
-
else -> throw IllegalStateException(
|
|
510
|
-
"[RNScreens] Invalid detent count ${screen.sheetDetents.count()}. Expected at most 3.",
|
|
511
|
-
)
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
is KeyboardVisible -> {
|
|
516
|
-
val newMaxHeight =
|
|
517
|
-
if (behavior.maxHeight - keyboardState.height > 1) {
|
|
518
|
-
behavior.maxHeight - keyboardState.height
|
|
519
|
-
} else {
|
|
520
|
-
behavior.maxHeight
|
|
521
|
-
}
|
|
522
|
-
when (screen.sheetDetents.count()) {
|
|
523
|
-
1 ->
|
|
524
|
-
behavior.apply {
|
|
525
|
-
useSingleDetent(height = newMaxHeight)
|
|
526
|
-
addBottomSheetCallback(keyboardSheetCallback)
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
2 ->
|
|
530
|
-
behavior.apply {
|
|
531
|
-
useTwoDetents(
|
|
532
|
-
state = BottomSheetBehavior.STATE_EXPANDED,
|
|
533
|
-
secondHeight = newMaxHeight,
|
|
534
|
-
)
|
|
535
|
-
addBottomSheetCallback(keyboardSheetCallback)
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
3 ->
|
|
539
|
-
behavior.apply {
|
|
540
|
-
useThreeDetents(
|
|
541
|
-
state = BottomSheetBehavior.STATE_EXPANDED,
|
|
542
|
-
)
|
|
543
|
-
maxHeight = newMaxHeight
|
|
544
|
-
addBottomSheetCallback(keyboardSheetCallback)
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
else -> throw IllegalStateException(
|
|
548
|
-
"[RNScreens] Invalid detent count ${screen.sheetDetents.count()}. Expected at most 3.",
|
|
549
|
-
)
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
is KeyboardDidHide -> {
|
|
554
|
-
// Here we assume that the keyboard was either closed explicitly by user,
|
|
555
|
-
// or the user dragged the sheet down. In any case the state should
|
|
556
|
-
// stay unchanged.
|
|
557
|
-
|
|
558
|
-
behavior.removeBottomSheetCallback(keyboardSheetCallback)
|
|
559
|
-
when (screen.sheetDetents.count()) {
|
|
560
|
-
1 ->
|
|
561
|
-
behavior.useSingleDetent(
|
|
562
|
-
height = (screen.sheetDetents.first() * containerHeight).toInt(),
|
|
563
|
-
forceExpandedState = false,
|
|
564
|
-
)
|
|
565
|
-
|
|
566
|
-
2 ->
|
|
567
|
-
behavior.useTwoDetents(
|
|
568
|
-
firstHeight = (screen.sheetDetents[0] * containerHeight).toInt(),
|
|
569
|
-
secondHeight = (screen.sheetDetents[1] * containerHeight).toInt(),
|
|
570
|
-
)
|
|
571
|
-
|
|
572
|
-
3 ->
|
|
573
|
-
behavior.useThreeDetents(
|
|
574
|
-
firstHeight = (screen.sheetDetents[0] * containerHeight).toInt(),
|
|
575
|
-
halfExpandedRatio = (screen.sheetDetents[1] / screen.sheetDetents[2]).toFloat(),
|
|
576
|
-
expandedOffsetFromTop = ((1 - screen.sheetDetents[2]) * containerHeight).toInt(),
|
|
577
|
-
)
|
|
578
|
-
|
|
579
|
-
else -> throw IllegalStateException(
|
|
580
|
-
"[RNScreens] Invalid detent count ${screen.sheetDetents.count()}. Expected at most 3.",
|
|
581
|
-
)
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
331
|
private fun createBottomSheetBehaviour(): BottomSheetBehavior<Screen> = BottomSheetBehavior<Screen>()
|
|
588
332
|
|
|
589
|
-
// In general it would be great to create BottomSheetBehaviour only via this method as it runs some
|
|
590
|
-
// side effects.
|
|
591
|
-
private fun createAndConfigureBottomSheetBehaviour(): BottomSheetBehavior<Screen> =
|
|
592
|
-
configureBottomSheetBehaviour(createBottomSheetBehaviour())
|
|
593
|
-
|
|
594
333
|
private fun resolveBackgroundColor(screen: Screen): Int? {
|
|
595
334
|
val screenColor =
|
|
596
335
|
(screen.background as? ColorDrawable?)?.color
|
|
@@ -724,6 +463,13 @@ class ScreenStackFragment :
|
|
|
724
463
|
return dimmingDelegate!!
|
|
725
464
|
}
|
|
726
465
|
|
|
466
|
+
private fun requireSheetDelegate(): SheetDelegate {
|
|
467
|
+
if (sheetDelegate == null) {
|
|
468
|
+
sheetDelegate = SheetDelegate(screen)
|
|
469
|
+
}
|
|
470
|
+
return sheetDelegate!!
|
|
471
|
+
}
|
|
472
|
+
|
|
727
473
|
private class ScreensCoordinatorLayout(
|
|
728
474
|
context: Context,
|
|
729
475
|
private val fragment: ScreenStackFragment,
|
|
@@ -734,7 +480,7 @@ class ScreenStackFragment :
|
|
|
734
480
|
constructor(context: Context, fragment: ScreenStackFragment) : this(
|
|
735
481
|
context,
|
|
736
482
|
fragment,
|
|
737
|
-
|
|
483
|
+
PointerEventsBoxNoneImpl(),
|
|
738
484
|
)
|
|
739
485
|
|
|
740
486
|
override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets = super.onApplyWindowInsets(insets)
|
|
@@ -797,6 +543,20 @@ class ScreenStackFragment :
|
|
|
797
543
|
}
|
|
798
544
|
}
|
|
799
545
|
|
|
546
|
+
override fun onLayout(
|
|
547
|
+
changed: Boolean,
|
|
548
|
+
l: Int,
|
|
549
|
+
t: Int,
|
|
550
|
+
r: Int,
|
|
551
|
+
b: Int,
|
|
552
|
+
) {
|
|
553
|
+
super.onLayout(changed, l, t, r, b)
|
|
554
|
+
|
|
555
|
+
if (fragment.screen.usesFormSheetPresentation()) {
|
|
556
|
+
fragment.screen.onBottomSheetBehaviorDidLayout(changed)
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
800
560
|
// override fun reactTagForTouch(touchX: Float, touchY: Float): Int {
|
|
801
561
|
// throw IllegalStateException("Screen wrapper should never be asked for the view tag")
|
|
802
562
|
// }
|
|
@@ -3,12 +3,10 @@ package com.swmansion.rnscreens
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.graphics.PorterDuff
|
|
5
5
|
import android.graphics.PorterDuffColorFilter
|
|
6
|
-
import android.os.Build
|
|
7
6
|
import android.text.TextUtils
|
|
8
7
|
import android.util.TypedValue
|
|
9
8
|
import android.view.Gravity
|
|
10
9
|
import android.view.View.OnClickListener
|
|
11
|
-
import android.view.WindowInsets
|
|
12
10
|
import android.widget.ImageView
|
|
13
11
|
import android.widget.TextView
|
|
14
12
|
import androidx.appcompat.app.AppCompatActivity
|
|
@@ -17,19 +15,25 @@ import androidx.fragment.app.Fragment
|
|
|
17
15
|
import com.facebook.react.ReactApplication
|
|
18
16
|
import com.facebook.react.bridge.JSApplicationIllegalArgumentException
|
|
19
17
|
import com.facebook.react.bridge.ReactContext
|
|
18
|
+
import com.facebook.react.uimanager.ReactPointerEventsView
|
|
20
19
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
21
20
|
import com.facebook.react.views.text.ReactTypefaceUtils
|
|
22
21
|
import com.swmansion.rnscreens.events.HeaderAttachedEvent
|
|
23
22
|
import com.swmansion.rnscreens.events.HeaderDetachedEvent
|
|
23
|
+
import kotlin.math.max
|
|
24
24
|
|
|
25
25
|
class ScreenStackHeaderConfig(
|
|
26
26
|
context: Context,
|
|
27
|
-
|
|
27
|
+
private val pointerEventsImpl: ReactPointerEventsView
|
|
28
|
+
) : FabricEnabledHeaderConfigViewGroup(context), ReactPointerEventsView by pointerEventsImpl {
|
|
29
|
+
|
|
30
|
+
constructor(context: Context): this(context, pointerEventsImpl = PointerEventsBoxNoneImpl())
|
|
31
|
+
|
|
28
32
|
private val configSubviews = ArrayList<ScreenStackHeaderSubview>(3)
|
|
29
33
|
val toolbar: CustomToolbar
|
|
30
34
|
var isHeaderHidden = false // named this way to avoid conflict with platform's isHidden
|
|
31
|
-
var isHeaderTranslucent =
|
|
32
|
-
|
|
35
|
+
var isHeaderTranslucent =
|
|
36
|
+
false // named this way to avoid conflict with platform's isTranslucent
|
|
33
37
|
private var title: String? = null
|
|
34
38
|
private var titleColor = 0
|
|
35
39
|
private var titleFontFamily: String? = null
|
|
@@ -41,7 +45,9 @@ class ScreenStackHeaderConfig(
|
|
|
41
45
|
private var isShadowHidden = false
|
|
42
46
|
private var isDestroyed = false
|
|
43
47
|
private var backButtonInCustomView = false
|
|
44
|
-
|
|
48
|
+
var isTopInsetEnabled = true
|
|
49
|
+
private set
|
|
50
|
+
|
|
45
51
|
private var tintColor = 0
|
|
46
52
|
private var isAttachedToWindow = false
|
|
47
53
|
private val defaultStartInset: Int
|
|
@@ -69,19 +75,76 @@ class ScreenStackHeaderConfig(
|
|
|
69
75
|
}
|
|
70
76
|
}
|
|
71
77
|
|
|
78
|
+
var isTitleEmpty: Boolean = false
|
|
79
|
+
|
|
80
|
+
val preferredContentInsetStart
|
|
81
|
+
get() = defaultStartInset
|
|
82
|
+
|
|
83
|
+
val preferredContentInsetEnd
|
|
84
|
+
get() = defaultStartInset
|
|
85
|
+
|
|
86
|
+
val preferredContentInsetStartWithNavigation
|
|
87
|
+
get() =
|
|
88
|
+
// Reset toolbar insets. By default we set symmetric inset for start and end to match iOS
|
|
89
|
+
// implementation where both right and left icons are offset from the edge by default. We also
|
|
90
|
+
// reset startWithNavigation inset which corresponds to the distance between navigation icon and
|
|
91
|
+
// title. If title isn't set we clear that value few lines below to give more space to custom
|
|
92
|
+
// center-mounted views.
|
|
93
|
+
if (isTitleEmpty) {
|
|
94
|
+
0
|
|
95
|
+
} else {
|
|
96
|
+
defaultStartInsetWithNavigation
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
fun destroy() {
|
|
100
|
+
isDestroyed = true
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Native toolbar should notify the header config component that it has completed its layout.
|
|
105
|
+
*/
|
|
106
|
+
fun onNativeToolbarLayout(
|
|
107
|
+
toolbar: Toolbar,
|
|
108
|
+
shouldUpdateShadowStateHint: Boolean,
|
|
109
|
+
) {
|
|
110
|
+
if (!shouldUpdateShadowStateHint) {
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
val isBackButtonDisplayed = toolbar.navigationIcon != null
|
|
115
|
+
|
|
116
|
+
val contentInsetStartEstimation =
|
|
117
|
+
if (isBackButtonDisplayed) {
|
|
118
|
+
toolbar.currentContentInsetStart + toolbar.paddingStart
|
|
119
|
+
} else {
|
|
120
|
+
max(toolbar.currentContentInsetStart, toolbar.paddingStart)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Assuming that there is nothing to the left of back button here, the content
|
|
124
|
+
// offset we're interested in in ShadowTree is the `left` of the subview left.
|
|
125
|
+
// In case it is not available we fallback to approximation.
|
|
126
|
+
val contentInsetStart =
|
|
127
|
+
configSubviews.firstOrNull { it.type === ScreenStackHeaderSubview.Type.LEFT }?.left
|
|
128
|
+
?: contentInsetStartEstimation
|
|
129
|
+
|
|
130
|
+
val contentInsetEnd = toolbar.currentContentInsetEnd + toolbar.paddingEnd
|
|
131
|
+
|
|
132
|
+
// Note that implementation of the callee differs between architectures.
|
|
133
|
+
updateHeaderConfigState(
|
|
134
|
+
toolbar.width,
|
|
135
|
+
toolbar.height,
|
|
136
|
+
contentInsetStart,
|
|
137
|
+
contentInsetEnd,
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
72
141
|
override fun onLayout(
|
|
73
142
|
changed: Boolean,
|
|
74
143
|
l: Int,
|
|
75
144
|
t: Int,
|
|
76
145
|
r: Int,
|
|
77
146
|
b: Int,
|
|
78
|
-
)
|
|
79
|
-
// no-op
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
fun destroy() {
|
|
83
|
-
isDestroyed = true
|
|
84
|
-
}
|
|
147
|
+
) = Unit
|
|
85
148
|
|
|
86
149
|
override fun onAttachedToWindow() {
|
|
87
150
|
super.onAttachedToWindow()
|
|
@@ -90,19 +153,6 @@ class ScreenStackHeaderConfig(
|
|
|
90
153
|
UIManagerHelper
|
|
91
154
|
.getEventDispatcherForReactTag(context as ReactContext, id)
|
|
92
155
|
?.dispatchEvent(HeaderAttachedEvent(surfaceId, id))
|
|
93
|
-
// we want to save the top inset before the status bar can be hidden, which would resolve in
|
|
94
|
-
// inset being 0
|
|
95
|
-
if (headerTopInset == null) {
|
|
96
|
-
headerTopInset =
|
|
97
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
98
|
-
rootWindowInsets.getInsets(WindowInsets.Type.systemBars()).top
|
|
99
|
-
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
100
|
-
rootWindowInsets.systemWindowInsetTop
|
|
101
|
-
} else {
|
|
102
|
-
// Hacky fallback for old android. Before Marshmallow, the status bar height was always 25
|
|
103
|
-
(25 * resources.displayMetrics.density).toInt()
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
156
|
onUpdate()
|
|
107
157
|
}
|
|
108
158
|
|
|
@@ -176,32 +226,27 @@ class ScreenStackHeaderConfig(
|
|
|
176
226
|
screenFragment?.setToolbar(toolbar)
|
|
177
227
|
}
|
|
178
228
|
|
|
179
|
-
if (isTopInsetEnabled) {
|
|
180
|
-
headerTopInset.let {
|
|
181
|
-
toolbar.setPadding(0, it ?: 0, 0, 0)
|
|
182
|
-
}
|
|
183
|
-
} else {
|
|
184
|
-
if (toolbar.paddingTop > 0) {
|
|
185
|
-
toolbar.setPadding(0, 0, 0, 0)
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
229
|
activity.setSupportActionBar(toolbar)
|
|
190
230
|
// non-null toolbar is set in the line above and it is used here
|
|
191
231
|
val actionBar = requireNotNull(activity.supportActionBar)
|
|
192
232
|
|
|
233
|
+
// hide back button
|
|
234
|
+
actionBar.setDisplayHomeAsUpEnabled(
|
|
235
|
+
screenFragment?.canNavigateBack() == true && !isBackButtonHidden,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
// title
|
|
239
|
+
actionBar.title = title
|
|
240
|
+
if (TextUtils.isEmpty(title)) {
|
|
241
|
+
isTitleEmpty = true
|
|
242
|
+
}
|
|
243
|
+
|
|
193
244
|
// Reset toolbar insets. By default we set symmetric inset for start and end to match iOS
|
|
194
245
|
// implementation where both right and left icons are offset from the edge by default. We also
|
|
195
246
|
// reset startWithNavigation inset which corresponds to the distance between navigation icon and
|
|
196
247
|
// title. If title isn't set we clear that value few lines below to give more space to custom
|
|
197
248
|
// center-mounted views.
|
|
198
|
-
toolbar.
|
|
199
|
-
toolbar.setContentInsetsRelative(defaultStartInset, defaultStartInset)
|
|
200
|
-
|
|
201
|
-
// hide back button
|
|
202
|
-
actionBar.setDisplayHomeAsUpEnabled(
|
|
203
|
-
screenFragment?.canNavigateBack() == true && !isBackButtonHidden,
|
|
204
|
-
)
|
|
249
|
+
toolbar.updateContentInsets()
|
|
205
250
|
|
|
206
251
|
// when setSupportActionBar is called a toolbar wrapper gets initialized that overwrites
|
|
207
252
|
// navigation click listener. The default behavior set in the wrapper is to call into
|
|
@@ -214,15 +259,6 @@ class ScreenStackHeaderConfig(
|
|
|
214
259
|
// translucent
|
|
215
260
|
screenFragment?.setToolbarTranslucent(isHeaderTranslucent)
|
|
216
261
|
|
|
217
|
-
// title
|
|
218
|
-
actionBar.title = title
|
|
219
|
-
if (TextUtils.isEmpty(title)) {
|
|
220
|
-
// if title is empty we set start navigation inset to 0 to give more space to custom rendered
|
|
221
|
-
// views. When it is set to default it'd take up additional distance from the back button
|
|
222
|
-
// which would impact the position of custom header views rendered at the center.
|
|
223
|
-
toolbar.contentInsetStartWithNavigation = 0
|
|
224
|
-
}
|
|
225
|
-
|
|
226
262
|
val titleTextView = findTitleTextViewInToolbar(toolbar)
|
|
227
263
|
if (titleColor != 0) {
|
|
228
264
|
toolbar.setTitleTextColor(titleColor)
|
|
@@ -250,7 +286,8 @@ class ScreenStackHeaderConfig(
|
|
|
250
286
|
|
|
251
287
|
// color
|
|
252
288
|
if (tintColor != 0) {
|
|
253
|
-
toolbar.navigationIcon?.colorFilter =
|
|
289
|
+
toolbar.navigationIcon?.colorFilter =
|
|
290
|
+
PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_ATOP)
|
|
254
291
|
}
|
|
255
292
|
|
|
256
293
|
// subviews
|
|
@@ -288,12 +325,14 @@ class ScreenStackHeaderConfig(
|
|
|
288
325
|
toolbar.title = null
|
|
289
326
|
params.gravity = Gravity.START
|
|
290
327
|
}
|
|
328
|
+
|
|
291
329
|
ScreenStackHeaderSubview.Type.RIGHT -> params.gravity = Gravity.END
|
|
292
330
|
ScreenStackHeaderSubview.Type.CENTER -> {
|
|
293
331
|
params.width = LayoutParams.MATCH_PARENT
|
|
294
332
|
params.gravity = Gravity.CENTER_HORIZONTAL
|
|
295
333
|
toolbar.title = null
|
|
296
334
|
}
|
|
335
|
+
|
|
297
336
|
else -> {}
|
|
298
337
|
}
|
|
299
338
|
view.layoutParams = params
|
|
@@ -402,7 +441,8 @@ class ScreenStackHeaderConfig(
|
|
|
402
441
|
|
|
403
442
|
init {
|
|
404
443
|
visibility = GONE
|
|
405
|
-
toolbar =
|
|
444
|
+
toolbar =
|
|
445
|
+
if (BuildConfig.DEBUG) DebugMenuToolbar(context, this) else CustomToolbar(context, this)
|
|
406
446
|
defaultStartInset = toolbar.contentInsetStart
|
|
407
447
|
defaultStartInsetWithNavigation = toolbar.contentInsetStartWithNavigation
|
|
408
448
|
|