@lodev09/react-native-true-sheet 3.5.0 → 3.5.1-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/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +162 -138
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +20 -22
- package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetAnimator.kt +15 -6
- package/android/src/main/res/values/styles.xml +2 -8
- package/package.json +1 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package com.lodev09.truesheet
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
|
-
import android.util.Log
|
|
5
4
|
import android.view.View
|
|
6
5
|
import android.view.ViewStructure
|
|
7
6
|
import android.view.accessibility.AccessibilityEvent
|
|
@@ -30,6 +29,12 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
30
29
|
TrueSheetViewControllerDelegate,
|
|
31
30
|
TrueSheetContainerViewDelegate {
|
|
32
31
|
|
|
32
|
+
companion object {
|
|
33
|
+
const val TAG_NAME = "TrueSheet"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ==================== Properties ====================
|
|
37
|
+
|
|
33
38
|
internal val viewController: TrueSheetViewController = TrueSheetViewController(reactContext)
|
|
34
39
|
|
|
35
40
|
private val containerView: TrueSheetContainerView?
|
|
@@ -37,26 +42,29 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
37
42
|
|
|
38
43
|
var eventDispatcher: EventDispatcher? = null
|
|
39
44
|
|
|
45
|
+
// Initial present configuration (set by ViewManager before mount)
|
|
46
|
+
var pendingInitialPresent: Boolean = false
|
|
40
47
|
var initialDetentIndex: Int = -1
|
|
41
48
|
var initialDetentAnimated: Boolean = true
|
|
42
49
|
|
|
43
50
|
var stateWrapper: StateWrapper? = null
|
|
44
51
|
set(value) {
|
|
45
|
-
//
|
|
46
|
-
// This ensures
|
|
52
|
+
// On first state wrapper assignment, immediately update state with screen dimensions.
|
|
53
|
+
// This ensures Yoga has initial width/height for content layout before presenting.
|
|
47
54
|
if (field == null && value != null) {
|
|
48
55
|
updateState(viewController.screenWidth, viewController.screenHeight)
|
|
49
56
|
}
|
|
50
57
|
field = value
|
|
51
58
|
}
|
|
52
59
|
|
|
53
|
-
// Track last dimensions to avoid unnecessary state updates
|
|
54
60
|
private var lastContainerWidth: Int = 0
|
|
55
61
|
private var lastContainerHeight: Int = 0
|
|
56
62
|
|
|
57
|
-
//
|
|
63
|
+
// Debounce flag to coalesce rapid layout changes into a single sheet update
|
|
58
64
|
private var isSheetUpdatePending: Boolean = false
|
|
59
65
|
|
|
66
|
+
// ==================== Initialization ====================
|
|
67
|
+
|
|
60
68
|
init {
|
|
61
69
|
reactContext.addLifecycleEventListener(this)
|
|
62
70
|
viewController.delegate = this
|
|
@@ -65,6 +73,8 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
65
73
|
visibility = GONE
|
|
66
74
|
}
|
|
67
75
|
|
|
76
|
+
// ==================== ReactViewGroup Overrides ====================
|
|
77
|
+
|
|
68
78
|
override fun dispatchProvideStructure(structure: ViewStructure) {
|
|
69
79
|
super.dispatchProvideStructure(structure)
|
|
70
80
|
}
|
|
@@ -76,7 +86,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
76
86
|
right: Int,
|
|
77
87
|
bottom: Int
|
|
78
88
|
) {
|
|
79
|
-
//
|
|
89
|
+
// No-op: layout is managed by React Native's UIManager
|
|
80
90
|
}
|
|
81
91
|
|
|
82
92
|
override fun setId(id: Int) {
|
|
@@ -85,19 +95,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
85
95
|
TrueSheetModule.registerView(this, id)
|
|
86
96
|
}
|
|
87
97
|
|
|
88
|
-
|
|
89
|
-
* Called by the manager after all properties are set.
|
|
90
|
-
* Reconfigures the sheet if it's currently presented.
|
|
91
|
-
*/
|
|
92
|
-
fun finalizeUpdates() {
|
|
93
|
-
if (viewController.isPresented) {
|
|
94
|
-
viewController.setupBackground()
|
|
95
|
-
viewController.setupGrabber()
|
|
96
|
-
updateSheetIfNeeded()
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ==================== View Management ====================
|
|
98
|
+
// ==================== View Hierarchy Management ====================
|
|
101
99
|
|
|
102
100
|
override fun addView(child: View?, index: Int) {
|
|
103
101
|
viewController.addView(child, index)
|
|
@@ -106,9 +104,8 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
106
104
|
child.delegate = this
|
|
107
105
|
viewController.createDialog()
|
|
108
106
|
|
|
109
|
-
// Present at initial detent after layout pass when content height is available
|
|
110
107
|
if (initialDetentIndex >= 0) {
|
|
111
|
-
|
|
108
|
+
pendingInitialPresent = true
|
|
112
109
|
}
|
|
113
110
|
|
|
114
111
|
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
@@ -117,6 +114,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
117
114
|
}
|
|
118
115
|
|
|
119
116
|
override fun getChildCount(): Int = viewController.childCount
|
|
117
|
+
|
|
120
118
|
override fun getChildAt(index: Int): View? = viewController.getChildAt(index)
|
|
121
119
|
|
|
122
120
|
override fun removeViewAt(index: Int) {
|
|
@@ -127,119 +125,46 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
127
125
|
viewController.removeView(child)
|
|
128
126
|
}
|
|
129
127
|
|
|
130
|
-
// Accessibility
|
|
128
|
+
// Accessibility: delegate to dialog's host view since this view is hidden
|
|
131
129
|
override fun addChildrenForAccessibility(outChildren: ArrayList<View>) {}
|
|
132
130
|
override fun dispatchPopulateAccessibilityEvent(event: AccessibilityEvent): Boolean = false
|
|
133
131
|
|
|
134
|
-
|
|
135
|
-
reactContext.removeLifecycleEventListener(this)
|
|
136
|
-
TrueSheetModule.unregisterView(id)
|
|
137
|
-
TrueSheetDialogObserver.removeSheet(this)
|
|
138
|
-
|
|
139
|
-
if (viewController.isPresented) {
|
|
140
|
-
viewController.dismiss()
|
|
141
|
-
}
|
|
142
|
-
viewController.delegate = null
|
|
143
|
-
}
|
|
132
|
+
// ==================== Lifecycle ====================
|
|
144
133
|
|
|
145
134
|
override fun onHostResume() {
|
|
146
135
|
finalizeUpdates()
|
|
147
136
|
}
|
|
148
137
|
|
|
149
|
-
override fun onHostPause() {
|
|
150
|
-
}
|
|
138
|
+
override fun onHostPause() {}
|
|
151
139
|
|
|
152
140
|
override fun onHostDestroy() {
|
|
153
141
|
onDropInstance()
|
|
154
142
|
}
|
|
155
143
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
eventDispatcher?.dispatchEvent(WillPresentEvent(surfaceId, id, index, position, detent))
|
|
161
|
-
|
|
162
|
-
// Enable touch event dispatching to React Native
|
|
163
|
-
viewController.eventDispatcher = eventDispatcher
|
|
164
|
-
containerView?.footerView?.eventDispatcher = eventDispatcher
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
override fun viewControllerDidPresent(index: Int, position: Float, detent: Float) {
|
|
168
|
-
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
169
|
-
eventDispatcher?.dispatchEvent(DidPresentEvent(surfaceId, id, index, position, detent))
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
override fun viewControllerWillDismiss() {
|
|
173
|
-
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
174
|
-
eventDispatcher?.dispatchEvent(WillDismissEvent(surfaceId, id))
|
|
175
|
-
|
|
176
|
-
// Disable touch event dispatching
|
|
177
|
-
viewController.eventDispatcher = null
|
|
178
|
-
containerView?.footerView?.eventDispatcher = null
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
override fun viewControllerDidDismiss(hadParent: Boolean) {
|
|
182
|
-
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
183
|
-
eventDispatcher?.dispatchEvent(DidDismissEvent(surfaceId, id))
|
|
184
|
-
|
|
185
|
-
TrueSheetDialogObserver.onSheetDidDismiss(this, hadParent)
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
override fun viewControllerDidChangeDetent(index: Int, position: Float, detent: Float) {
|
|
189
|
-
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
190
|
-
eventDispatcher?.dispatchEvent(DetentChangeEvent(surfaceId, id, index, position, detent))
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
override fun viewControllerDidDragBegin(index: Int, position: Float, detent: Float) {
|
|
194
|
-
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
195
|
-
eventDispatcher?.dispatchEvent(DragBeginEvent(surfaceId, id, index, position, detent))
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
override fun viewControllerDidDragChange(index: Int, position: Float, detent: Float) {
|
|
199
|
-
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
200
|
-
eventDispatcher?.dispatchEvent(DragChangeEvent(surfaceId, id, index, position, detent))
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
override fun viewControllerDidDragEnd(index: Int, position: Float, detent: Float) {
|
|
204
|
-
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
205
|
-
eventDispatcher?.dispatchEvent(DragEndEvent(surfaceId, id, index, position, detent))
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
override fun viewControllerDidChangePosition(index: Float, position: Float, detent: Float, realtime: Boolean) {
|
|
209
|
-
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
210
|
-
eventDispatcher?.dispatchEvent(PositionChangeEvent(surfaceId, id, index, position, detent, realtime))
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
override fun viewControllerDidChangeSize(width: Int, height: Int) {
|
|
214
|
-
updateState(width, height)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
override fun viewControllerWillFocus() {
|
|
218
|
-
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
219
|
-
eventDispatcher?.dispatchEvent(WillFocusEvent(surfaceId, id))
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
override fun viewControllerDidFocus() {
|
|
223
|
-
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
224
|
-
eventDispatcher?.dispatchEvent(FocusEvent(surfaceId, id))
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
override fun viewControllerWillBlur() {
|
|
228
|
-
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
229
|
-
eventDispatcher?.dispatchEvent(WillBlurEvent(surfaceId, id))
|
|
230
|
-
}
|
|
144
|
+
fun onDropInstance() {
|
|
145
|
+
reactContext.removeLifecycleEventListener(this)
|
|
146
|
+
TrueSheetModule.unregisterView(id)
|
|
147
|
+
TrueSheetDialogObserver.removeSheet(this)
|
|
231
148
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
149
|
+
if (viewController.isPresented) {
|
|
150
|
+
viewController.dismiss()
|
|
151
|
+
}
|
|
152
|
+
viewController.delegate = null
|
|
235
153
|
}
|
|
236
154
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Called by the ViewManager after all properties are set.
|
|
157
|
+
* Reconfigures the sheet if it's currently presented.
|
|
158
|
+
*/
|
|
159
|
+
fun finalizeUpdates() {
|
|
160
|
+
if (viewController.isPresented) {
|
|
161
|
+
viewController.setupBackground()
|
|
162
|
+
viewController.setupGrabber()
|
|
163
|
+
updateSheetIfNeeded()
|
|
164
|
+
}
|
|
240
165
|
}
|
|
241
166
|
|
|
242
|
-
// ==================== Property Setters
|
|
167
|
+
// ==================== Property Setters ====================
|
|
243
168
|
|
|
244
169
|
fun setMaxHeight(height: Int) {
|
|
245
170
|
if (viewController.maxSheetHeight == height) return
|
|
@@ -293,6 +218,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
293
218
|
fun setDetents(newDetents: MutableList<Double>) {
|
|
294
219
|
viewController.detents = newDetents
|
|
295
220
|
}
|
|
221
|
+
|
|
296
222
|
fun setEdgeToEdgeFullScreen(edgeToEdgeFullScreen: Boolean) {
|
|
297
223
|
viewController.edgeToEdgeFullScreen = edgeToEdgeFullScreen
|
|
298
224
|
}
|
|
@@ -305,6 +231,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
305
231
|
|
|
306
232
|
/**
|
|
307
233
|
* Updates the Fabric state with container dimensions for Yoga layout.
|
|
234
|
+
* Converts pixel values to density-independent pixels (dp).
|
|
308
235
|
*/
|
|
309
236
|
fun updateState(width: Int, height: Int) {
|
|
310
237
|
if (width == lastContainerWidth && height == lastContainerHeight) return
|
|
@@ -319,9 +246,12 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
319
246
|
sw.updateState(newStateData)
|
|
320
247
|
}
|
|
321
248
|
|
|
249
|
+
// ==================== Sheet Actions ====================
|
|
250
|
+
|
|
322
251
|
@UiThread
|
|
323
252
|
fun present(detentIndex: Int, animated: Boolean = true, promiseCallback: () -> Unit) {
|
|
324
253
|
if (!viewController.isPresented) {
|
|
254
|
+
// Register with observer to track sheet stack hierarchy
|
|
325
255
|
viewController.parentSheetView = TrueSheetDialogObserver.onSheetWillPresent(this, detentIndex)
|
|
326
256
|
}
|
|
327
257
|
viewController.presentPromise = promiseCallback
|
|
@@ -359,10 +289,19 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
359
289
|
|
|
360
290
|
/**
|
|
361
291
|
* Debounced sheet update to handle rapid content/header size changes.
|
|
362
|
-
* Uses post to ensure all layout passes complete before reconfiguring.
|
|
292
|
+
* Uses post() to ensure all layout passes complete before reconfiguring.
|
|
363
293
|
*/
|
|
364
294
|
fun updateSheetIfNeeded() {
|
|
365
|
-
if (!viewController.isPresented)
|
|
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
|
+
|
|
366
305
|
if (isSheetUpdatePending) return
|
|
367
306
|
|
|
368
307
|
isSheetUpdatePending = true
|
|
@@ -373,25 +312,12 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
373
312
|
}
|
|
374
313
|
}
|
|
375
314
|
|
|
376
|
-
// ====================
|
|
377
|
-
|
|
378
|
-
override fun containerViewContentDidChangeSize(width: Int, height: Int) {
|
|
379
|
-
updateSheetIfNeeded()
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
override fun containerViewHeaderDidChangeSize(width: Int, height: Int) {
|
|
383
|
-
updateSheetIfNeeded()
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
override fun containerViewFooterDidChangeSize(width: Int, height: Int) {
|
|
387
|
-
viewController.positionFooter()
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
// ==================== Stack Translation ====================
|
|
315
|
+
// ==================== Sheet Stack Translation ====================
|
|
391
316
|
|
|
392
317
|
/**
|
|
393
|
-
* Updates this sheet's translation based on its
|
|
394
|
-
*
|
|
318
|
+
* Updates this sheet's translation based on its child sheet's position.
|
|
319
|
+
* When a child sheet is presented, parent sheets slide down to create a stacked appearance.
|
|
320
|
+
* Propagates additional translation to parent so the entire stack stays visually consistent.
|
|
395
321
|
*/
|
|
396
322
|
fun updateTranslationForChild(childSheetTop: Int) {
|
|
397
323
|
if (!viewController.isDialogVisible || viewController.isExpanded) return
|
|
@@ -402,13 +328,14 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
402
328
|
|
|
403
329
|
viewController.translateDialog(newTranslation)
|
|
404
330
|
|
|
331
|
+
// Propagate any additional translation up the stack
|
|
405
332
|
if (additionalTranslation > 0) {
|
|
406
333
|
TrueSheetDialogObserver.getParentSheet(this)?.addTranslation(additionalTranslation)
|
|
407
334
|
}
|
|
408
335
|
}
|
|
409
336
|
|
|
410
337
|
/**
|
|
411
|
-
*
|
|
338
|
+
* Recursively adds translation to this sheet and all parent sheets.
|
|
412
339
|
*/
|
|
413
340
|
private fun addTranslation(amount: Int) {
|
|
414
341
|
if (!viewController.isDialogVisible || viewController.isExpanded) return
|
|
@@ -417,7 +344,104 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
|
|
|
417
344
|
TrueSheetDialogObserver.getParentSheet(this)?.addTranslation(amount)
|
|
418
345
|
}
|
|
419
346
|
|
|
420
|
-
|
|
421
|
-
|
|
347
|
+
// ==================== TrueSheetViewControllerDelegate ====================
|
|
348
|
+
|
|
349
|
+
override fun viewControllerWillPresent(index: Int, position: Float, detent: Float) {
|
|
350
|
+
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
351
|
+
eventDispatcher?.dispatchEvent(WillPresentEvent(surfaceId, id, index, position, detent))
|
|
352
|
+
|
|
353
|
+
// Enable touch event dispatching to React Native while sheet is visible
|
|
354
|
+
viewController.eventDispatcher = eventDispatcher
|
|
355
|
+
containerView?.footerView?.eventDispatcher = eventDispatcher
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
override fun viewControllerDidPresent(index: Int, position: Float, detent: Float) {
|
|
359
|
+
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
360
|
+
eventDispatcher?.dispatchEvent(DidPresentEvent(surfaceId, id, index, position, detent))
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
override fun viewControllerWillDismiss() {
|
|
364
|
+
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
365
|
+
eventDispatcher?.dispatchEvent(WillDismissEvent(surfaceId, id))
|
|
366
|
+
|
|
367
|
+
// Disable touch event dispatching when sheet is dismissing
|
|
368
|
+
viewController.eventDispatcher = null
|
|
369
|
+
containerView?.footerView?.eventDispatcher = null
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
override fun viewControllerDidDismiss(hadParent: Boolean) {
|
|
373
|
+
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
374
|
+
eventDispatcher?.dispatchEvent(DidDismissEvent(surfaceId, id))
|
|
375
|
+
|
|
376
|
+
TrueSheetDialogObserver.onSheetDidDismiss(this, hadParent)
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
override fun viewControllerDidChangeDetent(index: Int, position: Float, detent: Float) {
|
|
380
|
+
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
381
|
+
eventDispatcher?.dispatchEvent(DetentChangeEvent(surfaceId, id, index, position, detent))
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
override fun viewControllerDidDragBegin(index: Int, position: Float, detent: Float) {
|
|
385
|
+
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
386
|
+
eventDispatcher?.dispatchEvent(DragBeginEvent(surfaceId, id, index, position, detent))
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
override fun viewControllerDidDragChange(index: Int, position: Float, detent: Float) {
|
|
390
|
+
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
391
|
+
eventDispatcher?.dispatchEvent(DragChangeEvent(surfaceId, id, index, position, detent))
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
override fun viewControllerDidDragEnd(index: Int, position: Float, detent: Float) {
|
|
395
|
+
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
396
|
+
eventDispatcher?.dispatchEvent(DragEndEvent(surfaceId, id, index, position, detent))
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
override fun viewControllerDidChangePosition(index: Float, position: Float, detent: Float, realtime: Boolean) {
|
|
400
|
+
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
401
|
+
eventDispatcher?.dispatchEvent(PositionChangeEvent(surfaceId, id, index, position, detent, realtime))
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
override fun viewControllerDidChangeSize(width: Int, height: Int) {
|
|
405
|
+
updateState(width, height)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
override fun viewControllerWillFocus() {
|
|
409
|
+
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
410
|
+
eventDispatcher?.dispatchEvent(WillFocusEvent(surfaceId, id))
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
override fun viewControllerDidFocus() {
|
|
414
|
+
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
415
|
+
eventDispatcher?.dispatchEvent(FocusEvent(surfaceId, id))
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
override fun viewControllerWillBlur() {
|
|
419
|
+
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
420
|
+
eventDispatcher?.dispatchEvent(WillBlurEvent(surfaceId, id))
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
override fun viewControllerDidBlur() {
|
|
424
|
+
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
425
|
+
eventDispatcher?.dispatchEvent(BlurEvent(surfaceId, id))
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
override fun viewControllerDidBackPress() {
|
|
429
|
+
val surfaceId = UIManagerHelper.getSurfaceId(this)
|
|
430
|
+
eventDispatcher?.dispatchEvent(BackPressEvent(surfaceId, id))
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ==================== TrueSheetContainerViewDelegate ====================
|
|
434
|
+
|
|
435
|
+
override fun containerViewContentDidChangeSize(width: Int, height: Int) {
|
|
436
|
+
updateSheetIfNeeded()
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
override fun containerViewHeaderDidChangeSize(width: Int, height: Int) {
|
|
440
|
+
updateSheetIfNeeded()
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
override fun containerViewFooterDidChangeSize(width: Int, height: Int) {
|
|
444
|
+
// Footer changes don't affect detents, only reposition it
|
|
445
|
+
viewController.positionFooter()
|
|
422
446
|
}
|
|
423
447
|
}
|
|
@@ -80,7 +80,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
80
80
|
const val TAG_NAME = "TrueSheet"
|
|
81
81
|
|
|
82
82
|
// Prevents fully expanded ratio which causes behavior issues
|
|
83
|
-
private const val MAX_HALF_EXPANDED_RATIO = 0.999f
|
|
84
83
|
private const val GRABBER_TAG = "TrueSheetGrabber"
|
|
85
84
|
private const val DEFAULT_MAX_WIDTH = 640 // dp
|
|
86
85
|
private const val DEFAULT_CORNER_RADIUS = 16 // dp
|
|
@@ -341,9 +340,15 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
341
340
|
private fun setupDialogListeners(dialog: BottomSheetDialog) {
|
|
342
341
|
dialog.setOnShowListener {
|
|
343
342
|
bottomSheetView?.visibility = VISIBLE
|
|
343
|
+
|
|
344
344
|
isPresented = true
|
|
345
345
|
isDialogVisible = true
|
|
346
346
|
|
|
347
|
+
emitWillPresentEvents()
|
|
348
|
+
|
|
349
|
+
setupSheetDetents()
|
|
350
|
+
setupBackground()
|
|
351
|
+
setupGrabber()
|
|
347
352
|
setupKeyboardObserver()
|
|
348
353
|
|
|
349
354
|
if (shouldAnimatePresent) {
|
|
@@ -377,8 +382,6 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
377
382
|
override fun onSlide(sheetView: View, slideOffset: Float) {
|
|
378
383
|
val behavior = behavior ?: return
|
|
379
384
|
|
|
380
|
-
emitChangePositionDelegate(sheetView.top)
|
|
381
|
-
|
|
382
385
|
when (behavior.state) {
|
|
383
386
|
BottomSheetBehavior.STATE_DRAGGING,
|
|
384
387
|
BottomSheetBehavior.STATE_SETTLING -> handleDragChange(sheetView)
|
|
@@ -386,9 +389,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
386
389
|
else -> { }
|
|
387
390
|
}
|
|
388
391
|
|
|
389
|
-
if (!
|
|
390
|
-
|
|
391
|
-
|
|
392
|
+
if (!sheetAnimator.isAnimating) {
|
|
393
|
+
emitChangePositionDelegate(sheetView.top)
|
|
394
|
+
|
|
395
|
+
if (!isKeyboardTransitioning) {
|
|
396
|
+
positionFooter(slideOffset)
|
|
397
|
+
updateDimAmount(sheetView.top)
|
|
398
|
+
}
|
|
392
399
|
}
|
|
393
400
|
}
|
|
394
401
|
|
|
@@ -525,22 +532,15 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
525
532
|
}
|
|
526
533
|
|
|
527
534
|
setupDimmedBackground(detentIndex)
|
|
535
|
+
setStateForDetentIndex(detentIndex)
|
|
528
536
|
|
|
529
|
-
if (isPresented) {
|
|
530
|
-
setStateForDetentIndex(detentIndex)
|
|
531
|
-
} else {
|
|
537
|
+
if (!isPresented) {
|
|
532
538
|
shouldAnimatePresent = animated
|
|
533
539
|
currentDetentIndex = detentIndex
|
|
534
540
|
interactionState = InteractionState.Idle
|
|
535
541
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
setupSheetDetents()
|
|
539
|
-
setStateForDetentIndex(detentIndex)
|
|
540
|
-
setupBackground()
|
|
541
|
-
setupGrabber()
|
|
542
|
-
|
|
543
|
-
// Hide until animation starts
|
|
542
|
+
// Position off-screen until animation starts
|
|
543
|
+
bottomSheetView?.translationY = realScreenHeight.toFloat()
|
|
544
544
|
bottomSheetView?.visibility = INVISIBLE
|
|
545
545
|
|
|
546
546
|
dialog.show()
|
|
@@ -604,10 +604,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
604
604
|
val maxDetentHeight = detentCalculator.getDetentHeight(detents.last())
|
|
605
605
|
|
|
606
606
|
val adjustedHalfExpandedHeight = minOf(halfExpandedDetentHeight, maxAvailableHeight)
|
|
607
|
-
halfExpandedRatio =
|
|
608
|
-
|
|
609
|
-
MAX_HALF_EXPANDED_RATIO
|
|
610
|
-
)
|
|
607
|
+
halfExpandedRatio = (adjustedHalfExpandedHeight.toFloat() / realScreenHeight.toFloat())
|
|
608
|
+
.coerceIn(0f, 0.999f)
|
|
611
609
|
|
|
612
610
|
expandedOffset = maxOf(edgeToEdgeTopInset, realScreenHeight - maxDetentHeight)
|
|
613
611
|
|
|
@@ -961,7 +959,7 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
|
|
|
961
959
|
// Skip reconfiguration if expanded and only height changed (e.g., keyboard)
|
|
962
960
|
if (h + topInset >= screenHeight && isExpanded && oldw == w) return
|
|
963
961
|
|
|
964
|
-
|
|
962
|
+
post {
|
|
965
963
|
setupSheetDetents()
|
|
966
964
|
positionFooter()
|
|
967
965
|
bottomSheetView?.let { emitChangePositionDelegate(it.top, realtime = false) }
|
|
@@ -3,6 +3,7 @@ package com.lodev09.truesheet.core
|
|
|
3
3
|
import android.animation.Animator
|
|
4
4
|
import android.animation.AnimatorListenerAdapter
|
|
5
5
|
import android.animation.ValueAnimator
|
|
6
|
+
import android.util.Log
|
|
6
7
|
import android.view.animation.AccelerateInterpolator
|
|
7
8
|
import android.view.animation.DecelerateInterpolator
|
|
8
9
|
import android.widget.FrameLayout
|
|
@@ -29,6 +30,8 @@ class TrueSheetAnimator(private val provider: TrueSheetAnimatorProvider) {
|
|
|
29
30
|
private var presentAnimator: ValueAnimator? = null
|
|
30
31
|
private var dismissAnimator: ValueAnimator? = null
|
|
31
32
|
|
|
33
|
+
var isAnimating: Boolean = false
|
|
34
|
+
|
|
32
35
|
/**
|
|
33
36
|
* Animate the sheet presenting from bottom of screen to target position.
|
|
34
37
|
* @param toTop The target top position of the sheet
|
|
@@ -57,14 +60,20 @@ class TrueSheetAnimator(private val provider: TrueSheetAnimatorProvider) {
|
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
addListener(object : AnimatorListenerAdapter() {
|
|
63
|
+
override fun onAnimationStart(animation: Animator) {
|
|
64
|
+
isAnimating = true
|
|
65
|
+
}
|
|
66
|
+
|
|
60
67
|
override fun onAnimationEnd(animation: Animator) {
|
|
61
68
|
bottomSheet.translationY = 0f
|
|
62
69
|
presentAnimator = null
|
|
70
|
+
isAnimating = false
|
|
63
71
|
onEnd()
|
|
64
72
|
}
|
|
65
73
|
|
|
66
74
|
override fun onAnimationCancel(animation: Animator) {
|
|
67
75
|
presentAnimator = null
|
|
76
|
+
isAnimating = false
|
|
68
77
|
onEnd()
|
|
69
78
|
}
|
|
70
79
|
})
|
|
@@ -101,13 +110,19 @@ class TrueSheetAnimator(private val provider: TrueSheetAnimatorProvider) {
|
|
|
101
110
|
}
|
|
102
111
|
|
|
103
112
|
addListener(object : AnimatorListenerAdapter() {
|
|
113
|
+
override fun onAnimationStart(animation: Animator) {
|
|
114
|
+
isAnimating = true
|
|
115
|
+
}
|
|
116
|
+
|
|
104
117
|
override fun onAnimationEnd(animation: Animator) {
|
|
105
118
|
dismissAnimator = null
|
|
119
|
+
isAnimating = false
|
|
106
120
|
onEnd()
|
|
107
121
|
}
|
|
108
122
|
|
|
109
123
|
override fun onAnimationCancel(animation: Animator) {
|
|
110
124
|
dismissAnimator = null
|
|
125
|
+
isAnimating = false
|
|
111
126
|
onEnd()
|
|
112
127
|
}
|
|
113
128
|
})
|
|
@@ -125,10 +140,4 @@ class TrueSheetAnimator(private val provider: TrueSheetAnimatorProvider) {
|
|
|
125
140
|
dismissAnimator?.cancel()
|
|
126
141
|
dismissAnimator = null
|
|
127
142
|
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Check if any animation is currently running.
|
|
131
|
-
*/
|
|
132
|
-
val isAnimating: Boolean
|
|
133
|
-
get() = presentAnimator?.isRunning == true || dismissAnimator?.isRunning == true
|
|
134
143
|
}
|
|
@@ -1,11 +1,5 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="utf-8"?>
|
|
2
2
|
<resources>
|
|
3
|
-
<!-- No animation style - animations handled programmatically -->
|
|
4
|
-
<style name="TrueSheetNoAnimation" parent="Animation.AppCompat.Dialog">
|
|
5
|
-
<item name="android:windowEnterAnimation">@null</item>
|
|
6
|
-
<item name="android:windowExitAnimation">@null</item>
|
|
7
|
-
</style>
|
|
8
|
-
|
|
9
3
|
<!-- Fast fade out animation for hiding sheet when rn-screen is presented -->
|
|
10
4
|
<style name="TrueSheetFastFadeOut" parent="Animation.AppCompat.Dialog">
|
|
11
5
|
<item name="android:windowEnterAnimation">@null</item>
|
|
@@ -14,7 +8,7 @@
|
|
|
14
8
|
|
|
15
9
|
<!-- Default BottomSheetDialog style with programmatic animations -->
|
|
16
10
|
<style name="TrueSheetDialog" parent="Theme.Design.Light.BottomSheetDialog">
|
|
17
|
-
<item name="android:windowAnimationStyle">@
|
|
11
|
+
<item name="android:windowAnimationStyle">@null</item>
|
|
18
12
|
</style>
|
|
19
13
|
|
|
20
14
|
<!-- BottomSheetDialog style with edge-to-edge and programmatic animations -->
|
|
@@ -22,6 +16,6 @@
|
|
|
22
16
|
<item name="android:windowIsFloating">false</item>
|
|
23
17
|
<item name="enableEdgeToEdge">true</item>
|
|
24
18
|
<item name="android:navigationBarColor">@android:color/transparent</item>
|
|
25
|
-
<item name="android:windowAnimationStyle">@
|
|
19
|
+
<item name="android:windowAnimationStyle">@null</item>
|
|
26
20
|
</style>
|
|
27
21
|
</resources>
|
package/package.json
CHANGED