@lodev09/react-native-true-sheet 0.9.7 → 0.9.8
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 +30 -10
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetDialog.kt +191 -28
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +35 -45
- package/android/src/main/java/com/lodev09/truesheet/core/{RootViewGroup.kt → RootSheetView.kt} +3 -8
- package/package.json +1 -1
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetBehavior.kt +0 -230
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ npm i @lodev09/react-native-true-sheet
|
|
|
31
31
|
|
|
32
32
|
## Usage
|
|
33
33
|
|
|
34
|
-
```
|
|
34
|
+
```tsx
|
|
35
35
|
import { TrueSheet } from "@lodev09/react-native-true-sheet"
|
|
36
36
|
|
|
37
37
|
// ...
|
|
@@ -78,7 +78,7 @@ Extends `ViewProps`
|
|
|
78
78
|
|
|
79
79
|
## Methods
|
|
80
80
|
|
|
81
|
-
```
|
|
81
|
+
```tsx
|
|
82
82
|
const sheet = useRef<TrueSheet>(null)
|
|
83
83
|
|
|
84
84
|
const resize = () => {
|
|
@@ -108,7 +108,7 @@ return (
|
|
|
108
108
|
|
|
109
109
|
## Events
|
|
110
110
|
|
|
111
|
-
```
|
|
111
|
+
```tsx
|
|
112
112
|
const handleSizeChange = (info: SizeInfo) => {
|
|
113
113
|
console.log(info)
|
|
114
114
|
}
|
|
@@ -130,7 +130,7 @@ return (
|
|
|
130
130
|
|
|
131
131
|
### `SheetSize`
|
|
132
132
|
|
|
133
|
-
```
|
|
133
|
+
```tsx
|
|
134
134
|
<TrueSheet sizes={['auto', '80%', 'large']}>
|
|
135
135
|
// ...
|
|
136
136
|
</TrueSheet>
|
|
@@ -166,7 +166,7 @@ Grabber props to be used for android grabber or handle.
|
|
|
166
166
|
|
|
167
167
|
Blur tint that is mapped into native values in iOS.
|
|
168
168
|
|
|
169
|
-
```
|
|
169
|
+
```tsx
|
|
170
170
|
<TrueSheet blurTint="dark">
|
|
171
171
|
// ...
|
|
172
172
|
</TrueSheet>
|
|
@@ -200,7 +200,7 @@ Blur tint that is mapped into native values in iOS.
|
|
|
200
200
|
|
|
201
201
|
`Object` that comes with some events.
|
|
202
202
|
|
|
203
|
-
```
|
|
203
|
+
```tsx
|
|
204
204
|
{
|
|
205
205
|
index: 1,
|
|
206
206
|
value: 69
|
|
@@ -212,21 +212,41 @@ Blur tint that is mapped into native values in iOS.
|
|
|
212
212
|
| index | `number` | The size index from the provided sizes. See `sizes` prop. |
|
|
213
213
|
| value | `number` | The actual height value of the size. |
|
|
214
214
|
|
|
215
|
+
## Testing
|
|
216
|
+
|
|
217
|
+
When using `jest`, simply mock the entire package.
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
jest.mock('@lodev09/react-native-true-sheet')
|
|
221
|
+
```
|
|
222
|
+
|
|
215
223
|
## Troubleshooting
|
|
216
224
|
|
|
225
|
+
### Handling `ScrollView` on **Android**
|
|
226
|
+
|
|
227
|
+
On Android, `nestedScrollEnabled` needs to be enabled so that scrolling works when the sheet is expanded to its `maxHeight`.
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
<TrueSheet ref={sheet}>
|
|
231
|
+
<ScrollView nestedScrollEnabled>
|
|
232
|
+
// ...
|
|
233
|
+
</ScrollView>
|
|
234
|
+
</TrueSheet>
|
|
235
|
+
```
|
|
236
|
+
|
|
217
237
|
### Using `react-native-gesture-handler` on **Android**
|
|
218
238
|
|
|
219
239
|
On Android, RNGH does not work by default because modals are not located under React Native Root view in native hierarchy. To fix that, components need to be wrapped with `GestureHandlerRootView`.
|
|
220
240
|
|
|
221
241
|
Example:
|
|
222
|
-
```
|
|
242
|
+
```tsx
|
|
223
243
|
import { GestureHandlerRootView } from 'react-native-gesture-handler'
|
|
224
244
|
```
|
|
225
|
-
```
|
|
245
|
+
```tsx
|
|
226
246
|
return (
|
|
227
247
|
<TrueSheet ref={sheet}>
|
|
228
248
|
<GestureHandlerRootView>
|
|
229
|
-
|
|
249
|
+
// ...
|
|
230
250
|
</GestureHandlerRootView>
|
|
231
251
|
</TrueSheet>
|
|
232
252
|
)
|
|
@@ -237,7 +257,7 @@ return (
|
|
|
237
257
|
On iOS, navigating to a [React Navigation](https://reactnavigation.org) screen from within the Sheet can cause issues. To resolve this, dismiss the sheet before navigating!
|
|
238
258
|
|
|
239
259
|
Example:
|
|
240
|
-
```
|
|
260
|
+
```tsx
|
|
241
261
|
const sheet = useRef<TrueSheet>(null)
|
|
242
262
|
|
|
243
263
|
const navigate = async () => {
|
|
@@ -3,38 +3,34 @@ package com.lodev09.truesheet
|
|
|
3
3
|
import android.graphics.Color
|
|
4
4
|
import android.view.ViewGroup
|
|
5
5
|
import android.view.WindowManager
|
|
6
|
-
import android.widget.LinearLayout
|
|
7
|
-
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
8
6
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
7
|
+
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
9
8
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
10
9
|
import com.lodev09.truesheet.core.KeyboardManager
|
|
11
|
-
import com.lodev09.truesheet.core.
|
|
10
|
+
import com.lodev09.truesheet.core.RootSheetView
|
|
12
11
|
import com.lodev09.truesheet.core.Utils
|
|
13
12
|
|
|
14
|
-
class
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
) : BottomSheetDialog(reactContext) {
|
|
13
|
+
data class SizeInfo(val index: Int, val value: Float)
|
|
14
|
+
|
|
15
|
+
class TrueSheetDialog(private val reactContext: ThemedReactContext, private val rootSheetView: RootSheetView) :
|
|
16
|
+
BottomSheetDialog(reactContext) {
|
|
19
17
|
|
|
20
18
|
private var keyboardManager = KeyboardManager(reactContext)
|
|
21
19
|
|
|
22
|
-
var
|
|
20
|
+
var maxScreenHeight: Int = 0
|
|
21
|
+
var maxSheetHeight: Int? = null
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
addView(rootViewGroup)
|
|
27
|
-
setContentView(this)
|
|
23
|
+
var contentView: ViewGroup? = null
|
|
24
|
+
var footerView: ViewGroup? = null
|
|
28
25
|
|
|
29
|
-
|
|
26
|
+
var sizes: Array<Any> = arrayOf("medium", "large")
|
|
30
27
|
|
|
31
|
-
|
|
32
|
-
sheetView.setBackgroundColor(Color.TRANSPARENT)
|
|
28
|
+
var sheetView: ViewGroup
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
init {
|
|
31
|
+
setContentView(rootSheetView)
|
|
32
|
+
sheetView = rootSheetView.parent as ViewGroup
|
|
33
|
+
sheetView.setBackgroundColor(Color.TRANSPARENT)
|
|
38
34
|
|
|
39
35
|
// Setup window params to adjust layout based on Keyboard state.
|
|
40
36
|
window?.apply {
|
|
@@ -47,15 +43,95 @@ class TrueSheetDialog(
|
|
|
47
43
|
|
|
48
44
|
fun show(sizeIndex: Int) {
|
|
49
45
|
if (isShowing) {
|
|
50
|
-
|
|
46
|
+
setStateForSizeIndex(sizeIndex)
|
|
51
47
|
} else {
|
|
52
|
-
|
|
53
|
-
|
|
48
|
+
configure()
|
|
49
|
+
setStateForSizeIndex(sizeIndex)
|
|
54
50
|
|
|
55
51
|
this.show()
|
|
56
52
|
}
|
|
57
53
|
}
|
|
58
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Set the state based on the given size index.
|
|
57
|
+
*/
|
|
58
|
+
private fun setStateForSizeIndex(index: Int) {
|
|
59
|
+
behavior.state = getStateForSizeIndex(index)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get the height value based on the size config value.
|
|
64
|
+
*/
|
|
65
|
+
private fun getSizeHeight(size: Any, contentHeight: Int): Int {
|
|
66
|
+
val height =
|
|
67
|
+
when (size) {
|
|
68
|
+
is Double -> Utils.toPixel(size)
|
|
69
|
+
|
|
70
|
+
is Int -> Utils.toPixel(size.toDouble())
|
|
71
|
+
|
|
72
|
+
is String -> {
|
|
73
|
+
when (size) {
|
|
74
|
+
"auto" -> contentHeight
|
|
75
|
+
|
|
76
|
+
"large" -> maxScreenHeight
|
|
77
|
+
|
|
78
|
+
"medium" -> (maxScreenHeight * 0.50).toInt()
|
|
79
|
+
|
|
80
|
+
"small" -> (maxScreenHeight * 0.25).toInt()
|
|
81
|
+
|
|
82
|
+
else -> {
|
|
83
|
+
if (size.endsWith('%')) {
|
|
84
|
+
val percent = size.trim('%').toDoubleOrNull()
|
|
85
|
+
if (percent == null) {
|
|
86
|
+
0
|
|
87
|
+
} else {
|
|
88
|
+
((percent / 100) * maxScreenHeight).toInt()
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
val fixedHeight = size.toDoubleOrNull()
|
|
92
|
+
if (fixedHeight == null) {
|
|
93
|
+
0
|
|
94
|
+
} else {
|
|
95
|
+
Utils.toPixel(fixedHeight)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
else -> (maxScreenHeight * 0.5).toInt()
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return minOf(height, maxSheetHeight ?: maxScreenHeight)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Determines the state based on the given size index.
|
|
110
|
+
*/
|
|
111
|
+
private fun getStateForSizeIndex(index: Int) =
|
|
112
|
+
when (sizes.size) {
|
|
113
|
+
1 -> BottomSheetBehavior.STATE_EXPANDED
|
|
114
|
+
|
|
115
|
+
2 -> {
|
|
116
|
+
when (index) {
|
|
117
|
+
0 -> BottomSheetBehavior.STATE_COLLAPSED
|
|
118
|
+
1 -> BottomSheetBehavior.STATE_EXPANDED
|
|
119
|
+
else -> BottomSheetBehavior.STATE_HIDDEN
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
3 -> {
|
|
124
|
+
when (index) {
|
|
125
|
+
0 -> BottomSheetBehavior.STATE_COLLAPSED
|
|
126
|
+
1 -> BottomSheetBehavior.STATE_HALF_EXPANDED
|
|
127
|
+
2 -> BottomSheetBehavior.STATE_EXPANDED
|
|
128
|
+
else -> BottomSheetBehavior.STATE_HIDDEN
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
else -> BottomSheetBehavior.STATE_HIDDEN
|
|
133
|
+
}
|
|
134
|
+
|
|
59
135
|
/**
|
|
60
136
|
* Handle keyboard state changes and adjust maxScreenHeight (sheet max height) accordingly.
|
|
61
137
|
* Also update footer's Y position.
|
|
@@ -63,13 +139,13 @@ class TrueSheetDialog(
|
|
|
63
139
|
fun registerKeyboardManager() {
|
|
64
140
|
keyboardManager.registerKeyboardListener(object : KeyboardManager.OnKeyboardListener {
|
|
65
141
|
override fun onKeyboardStateChange(isVisible: Boolean, visibleHeight: Int?) {
|
|
66
|
-
when (isVisible) {
|
|
67
|
-
true ->
|
|
68
|
-
else ->
|
|
142
|
+
maxScreenHeight = when (isVisible) {
|
|
143
|
+
true -> visibleHeight ?: 0
|
|
144
|
+
else -> Utils.screenHeight(reactContext)
|
|
69
145
|
}
|
|
70
146
|
|
|
71
|
-
|
|
72
|
-
y = (
|
|
147
|
+
footerView?.apply {
|
|
148
|
+
y = (maxScreenHeight - (sheetView.top ?: 0) - height).toFloat()
|
|
73
149
|
}
|
|
74
150
|
}
|
|
75
151
|
})
|
|
@@ -82,6 +158,93 @@ class TrueSheetDialog(
|
|
|
82
158
|
keyboardManager.unregisterKeyboardListener()
|
|
83
159
|
}
|
|
84
160
|
|
|
161
|
+
/**
|
|
162
|
+
* Configure the sheet based on size preferences.
|
|
163
|
+
*/
|
|
164
|
+
fun configure() {
|
|
165
|
+
// Update the usable sheet height
|
|
166
|
+
maxScreenHeight = Utils.screenHeight(reactContext)
|
|
167
|
+
|
|
168
|
+
var contentHeight = 0
|
|
169
|
+
|
|
170
|
+
contentView?.let { contentHeight = it.height }
|
|
171
|
+
footerView?.let { contentHeight += it.height }
|
|
172
|
+
|
|
173
|
+
// Configure sheet sizes
|
|
174
|
+
behavior.apply {
|
|
175
|
+
skipCollapsed = false
|
|
176
|
+
isFitToContents = true
|
|
177
|
+
|
|
178
|
+
// m3 max width 640dp
|
|
179
|
+
maxWidth = Utils.toPixel(640.0)
|
|
180
|
+
|
|
181
|
+
when (sizes.size) {
|
|
182
|
+
1 -> {
|
|
183
|
+
maxHeight = getSizeHeight(sizes[0], contentHeight)
|
|
184
|
+
peekHeight = maxHeight
|
|
185
|
+
skipCollapsed = true
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
2 -> {
|
|
189
|
+
peekHeight = getSizeHeight(sizes[0], contentHeight)
|
|
190
|
+
maxHeight = getSizeHeight(sizes[1], contentHeight)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
3 -> {
|
|
194
|
+
// Enables half expanded
|
|
195
|
+
isFitToContents = false
|
|
196
|
+
|
|
197
|
+
peekHeight = getSizeHeight(sizes[0], contentHeight)
|
|
198
|
+
halfExpandedRatio = getSizeHeight(sizes[1], contentHeight).toFloat() / maxScreenHeight.toFloat()
|
|
199
|
+
maxHeight = getSizeHeight(sizes[2], contentHeight)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Get the SizeInfo data by state.
|
|
207
|
+
*/
|
|
208
|
+
fun getSizeInfoForState(state: Int): SizeInfo? =
|
|
209
|
+
when (sizes.size) {
|
|
210
|
+
1 -> {
|
|
211
|
+
when (state) {
|
|
212
|
+
BottomSheetBehavior.STATE_EXPANDED -> SizeInfo(0, Utils.toDIP(behavior.maxHeight))
|
|
213
|
+
else -> null
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
2 -> {
|
|
218
|
+
when (state) {
|
|
219
|
+
BottomSheetBehavior.STATE_COLLAPSED -> SizeInfo(0, Utils.toDIP(behavior.peekHeight))
|
|
220
|
+
BottomSheetBehavior.STATE_EXPANDED -> SizeInfo(1, Utils.toDIP(behavior.maxHeight))
|
|
221
|
+
else -> null
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
3 -> {
|
|
226
|
+
when (state) {
|
|
227
|
+
BottomSheetBehavior.STATE_COLLAPSED -> SizeInfo(0, Utils.toDIP(behavior.peekHeight))
|
|
228
|
+
|
|
229
|
+
BottomSheetBehavior.STATE_HALF_EXPANDED -> {
|
|
230
|
+
val height = behavior.halfExpandedRatio * maxScreenHeight
|
|
231
|
+
SizeInfo(1, Utils.toDIP(height.toInt()))
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
BottomSheetBehavior.STATE_EXPANDED -> SizeInfo(2, Utils.toDIP(behavior.maxHeight))
|
|
235
|
+
|
|
236
|
+
else -> null
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
else -> null
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Get SizeInfo data for given size index.
|
|
245
|
+
*/
|
|
246
|
+
fun getSizeInfoForIndex(index: Int) = getSizeInfoForState(getStateForSizeIndex(index)) ?: SizeInfo(0, 0f)
|
|
247
|
+
|
|
85
248
|
companion object {
|
|
86
249
|
const val TAG = "TrueSheetView"
|
|
87
250
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package com.lodev09.truesheet
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
+
import android.util.Log
|
|
4
5
|
import android.view.View
|
|
5
6
|
import android.view.ViewGroup
|
|
6
7
|
import android.view.ViewStructure
|
|
@@ -13,7 +14,7 @@ import com.facebook.react.uimanager.events.EventDispatcher
|
|
|
13
14
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
14
15
|
import com.lodev09.truesheet.core.DismissEvent
|
|
15
16
|
import com.lodev09.truesheet.core.PresentEvent
|
|
16
|
-
import com.lodev09.truesheet.core.
|
|
17
|
+
import com.lodev09.truesheet.core.RootSheetView
|
|
17
18
|
import com.lodev09.truesheet.core.SizeChangeEvent
|
|
18
19
|
|
|
19
20
|
class TrueSheetView(context: Context) :
|
|
@@ -47,15 +48,10 @@ class TrueSheetView(context: Context) :
|
|
|
47
48
|
*/
|
|
48
49
|
private val sheetDialog: TrueSheetDialog
|
|
49
50
|
|
|
50
|
-
/**
|
|
51
|
-
* The custom BottomSheetDialogBehavior instance.
|
|
52
|
-
*/
|
|
53
|
-
private val sheetBehavior: TrueSheetBehavior
|
|
54
|
-
|
|
55
51
|
/**
|
|
56
52
|
* React root view placeholder.
|
|
57
53
|
*/
|
|
58
|
-
private val
|
|
54
|
+
private val rootSheetView: RootSheetView
|
|
59
55
|
|
|
60
56
|
/**
|
|
61
57
|
* 1st child of the container view.
|
|
@@ -71,11 +67,10 @@ class TrueSheetView(context: Context) :
|
|
|
71
67
|
reactContext.addLifecycleEventListener(this)
|
|
72
68
|
eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
|
|
73
69
|
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
rootSheetView = RootSheetView(context)
|
|
71
|
+
rootSheetView.eventDispatcher = eventDispatcher
|
|
76
72
|
|
|
77
|
-
|
|
78
|
-
sheetDialog = TrueSheetDialog(reactContext, sheetBehavior, sheetRootView)
|
|
73
|
+
sheetDialog = TrueSheetDialog(reactContext, rootSheetView)
|
|
79
74
|
|
|
80
75
|
// Configure Sheet Dialog
|
|
81
76
|
sheetDialog.apply {
|
|
@@ -86,7 +81,7 @@ class TrueSheetView(context: Context) :
|
|
|
86
81
|
// Initialize footer y
|
|
87
82
|
footerView?.apply {
|
|
88
83
|
UiThreadUtil.runOnUiThread {
|
|
89
|
-
y = (
|
|
84
|
+
y = (sheetDialog.maxScreenHeight - sheetView.top - height).toFloat()
|
|
90
85
|
}
|
|
91
86
|
}
|
|
92
87
|
|
|
@@ -96,11 +91,12 @@ class TrueSheetView(context: Context) :
|
|
|
96
91
|
}
|
|
97
92
|
|
|
98
93
|
// dispatch onPresent event
|
|
99
|
-
eventDispatcher?.dispatchEvent(PresentEvent(surfaceId, id,
|
|
94
|
+
eventDispatcher?.dispatchEvent(PresentEvent(surfaceId, id, sheetDialog.getSizeInfoForIndex(activeIndex)))
|
|
100
95
|
}
|
|
101
96
|
|
|
102
97
|
// Setup listener when the dialog has been dismissed.
|
|
103
98
|
setOnDismissListener {
|
|
99
|
+
Log.d(TAG, "dismissed")
|
|
104
100
|
unregisterKeyboardManager()
|
|
105
101
|
dismissPromise?.let { promise ->
|
|
106
102
|
promise()
|
|
@@ -113,8 +109,8 @@ class TrueSheetView(context: Context) :
|
|
|
113
109
|
}
|
|
114
110
|
|
|
115
111
|
// Configure sheet behavior events
|
|
116
|
-
|
|
117
|
-
addBottomSheetCallback(
|
|
112
|
+
sheetDialog.apply {
|
|
113
|
+
behavior.addBottomSheetCallback(
|
|
118
114
|
object : BottomSheetBehavior.BottomSheetCallback() {
|
|
119
115
|
override fun onSlide(sheetView: View, slideOffset: Float) {
|
|
120
116
|
footerView?.let {
|
|
@@ -128,24 +124,18 @@ class TrueSheetView(context: Context) :
|
|
|
128
124
|
}
|
|
129
125
|
|
|
130
126
|
override fun onStateChanged(view: View, newState: Int) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// Invoke promise when sheet resized programmatically
|
|
138
|
-
presentPromise?.let { promise ->
|
|
139
|
-
promise()
|
|
140
|
-
presentPromise = null
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
activeIndex = sizeInfo.index
|
|
144
|
-
|
|
145
|
-
// dispatch onSizeChange event
|
|
146
|
-
eventDispatcher?.dispatchEvent(SizeChangeEvent(surfaceId, id, sizeInfo))
|
|
147
|
-
}
|
|
127
|
+
val sizeInfo = getSizeInfoForState(newState)
|
|
128
|
+
if (sizeInfo != null && sizeInfo.index != activeIndex) {
|
|
129
|
+
// Invoke promise when sheet resized programmatically
|
|
130
|
+
presentPromise?.let { promise ->
|
|
131
|
+
promise()
|
|
132
|
+
presentPromise = null
|
|
148
133
|
}
|
|
134
|
+
|
|
135
|
+
activeIndex = sizeInfo.index
|
|
136
|
+
|
|
137
|
+
// dispatch onSizeChange event
|
|
138
|
+
eventDispatcher?.dispatchEvent(SizeChangeEvent(surfaceId, id, sizeInfo))
|
|
149
139
|
}
|
|
150
140
|
}
|
|
151
141
|
}
|
|
@@ -154,7 +144,7 @@ class TrueSheetView(context: Context) :
|
|
|
154
144
|
}
|
|
155
145
|
|
|
156
146
|
override fun dispatchProvideStructure(structure: ViewStructure) {
|
|
157
|
-
|
|
147
|
+
rootSheetView.dispatchProvideStructure(structure)
|
|
158
148
|
}
|
|
159
149
|
|
|
160
150
|
override fun onLayout(
|
|
@@ -181,29 +171,29 @@ class TrueSheetView(context: Context) :
|
|
|
181
171
|
contentView = it.getChildAt(0) as ViewGroup
|
|
182
172
|
footerView = it.getChildAt(1) as ViewGroup
|
|
183
173
|
|
|
184
|
-
|
|
185
|
-
|
|
174
|
+
sheetDialog.contentView = contentView
|
|
175
|
+
sheetDialog.footerView = footerView
|
|
186
176
|
|
|
187
177
|
// rootView's first child is the Container View
|
|
188
|
-
|
|
178
|
+
rootSheetView.addView(it, index)
|
|
189
179
|
}
|
|
190
180
|
}
|
|
191
181
|
|
|
192
182
|
override fun getChildCount(): Int {
|
|
193
183
|
// This method may be called by the parent constructor
|
|
194
184
|
// before rootView is initialized.
|
|
195
|
-
return
|
|
185
|
+
return rootSheetView.childCount
|
|
196
186
|
}
|
|
197
187
|
|
|
198
|
-
override fun getChildAt(index: Int): View =
|
|
188
|
+
override fun getChildAt(index: Int): View = rootSheetView.getChildAt(index)
|
|
199
189
|
|
|
200
190
|
override fun removeView(child: View) {
|
|
201
|
-
|
|
191
|
+
rootSheetView.removeView(child)
|
|
202
192
|
}
|
|
203
193
|
|
|
204
194
|
override fun removeViewAt(index: Int) {
|
|
205
195
|
val child = getChildAt(index)
|
|
206
|
-
|
|
196
|
+
rootSheetView.removeView(child)
|
|
207
197
|
}
|
|
208
198
|
|
|
209
199
|
override fun addChildrenForAccessibility(outChildren: ArrayList<View>) {
|
|
@@ -232,18 +222,18 @@ class TrueSheetView(context: Context) :
|
|
|
232
222
|
}
|
|
233
223
|
|
|
234
224
|
fun setMaxHeight(height: Int) {
|
|
235
|
-
|
|
236
|
-
|
|
225
|
+
sheetDialog.maxSheetHeight = height
|
|
226
|
+
sheetDialog.configure()
|
|
237
227
|
}
|
|
238
228
|
|
|
239
229
|
fun setDismissible(dismissible: Boolean) {
|
|
240
|
-
|
|
230
|
+
sheetDialog.behavior.isHideable = dismissible
|
|
241
231
|
sheetDialog.setCancelable(dismissible)
|
|
242
232
|
}
|
|
243
233
|
|
|
244
234
|
fun setSizes(newSizes: Array<Any>) {
|
|
245
|
-
|
|
246
|
-
|
|
235
|
+
sheetDialog.sizes = newSizes
|
|
236
|
+
sheetDialog.configure()
|
|
247
237
|
}
|
|
248
238
|
|
|
249
239
|
/**
|
package/android/src/main/java/com/lodev09/truesheet/core/{RootViewGroup.kt → RootSheetView.kt}
RENAMED
|
@@ -15,8 +15,8 @@ import com.facebook.react.uimanager.events.EventDispatcher
|
|
|
15
15
|
import com.facebook.react.views.view.ReactViewGroup
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
* child information forwarded from
|
|
18
|
+
* RootSheetView is the ViewGroup which contains all the children of a Modal. It gets all
|
|
19
|
+
* child information forwarded from TrueSheetView and uses that to create children. It is
|
|
20
20
|
* also responsible for acting as a RootView and handling touch events. It does this the same way
|
|
21
21
|
* as ReactRootView.
|
|
22
22
|
*
|
|
@@ -26,7 +26,7 @@ import com.facebook.react.views.view.ReactViewGroup
|
|
|
26
26
|
* styleHeight on the LayoutShadowNode to be the window size. This is done through the
|
|
27
27
|
* UIManagerModule, and will then cause the children to layout as if they can fill the window.
|
|
28
28
|
*/
|
|
29
|
-
class
|
|
29
|
+
class RootSheetView(context: Context?) :
|
|
30
30
|
ReactViewGroup(context),
|
|
31
31
|
RootView {
|
|
32
32
|
private var hasAdjustedSize = false
|
|
@@ -34,7 +34,6 @@ class RootViewGroup(context: Context?) :
|
|
|
34
34
|
private var viewHeight = 0
|
|
35
35
|
|
|
36
36
|
private val mJSTouchDispatcher = JSTouchDispatcher(this)
|
|
37
|
-
|
|
38
37
|
private var mJSPointerDispatcher: JSPointerDispatcher? = null
|
|
39
38
|
|
|
40
39
|
var eventDispatcher: EventDispatcher? = null
|
|
@@ -134,8 +133,4 @@ class RootViewGroup(context: Context?) :
|
|
|
134
133
|
// No-op - override in order to still receive events to onInterceptTouchEvent
|
|
135
134
|
// even when some other view disallow that
|
|
136
135
|
}
|
|
137
|
-
|
|
138
|
-
companion object {
|
|
139
|
-
const val TAG = "TrueSheetView"
|
|
140
|
-
}
|
|
141
136
|
}
|
package/package.json
CHANGED
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
package com.lodev09.truesheet
|
|
2
|
-
|
|
3
|
-
import android.view.MotionEvent
|
|
4
|
-
import android.view.ViewGroup
|
|
5
|
-
import android.widget.ScrollView
|
|
6
|
-
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
7
|
-
import com.facebook.react.bridge.ReactContext
|
|
8
|
-
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
9
|
-
import com.lodev09.truesheet.core.Utils
|
|
10
|
-
|
|
11
|
-
data class SizeInfo(val index: Int, val value: Float)
|
|
12
|
-
|
|
13
|
-
class TrueSheetBehavior(private val reactContext: ReactContext) : BottomSheetBehavior<ViewGroup>() {
|
|
14
|
-
var maxScreenHeight: Int = 0
|
|
15
|
-
var maxSheetHeight: Int? = null
|
|
16
|
-
|
|
17
|
-
var contentView: ViewGroup? = null
|
|
18
|
-
var footerView: ViewGroup? = null
|
|
19
|
-
|
|
20
|
-
var sizes: Array<Any> = arrayOf("medium", "large")
|
|
21
|
-
|
|
22
|
-
override fun onInterceptTouchEvent(parent: CoordinatorLayout, child: ViewGroup, event: MotionEvent): Boolean {
|
|
23
|
-
contentView?.let {
|
|
24
|
-
val isDownEvent = (event.actionMasked == MotionEvent.ACTION_DOWN)
|
|
25
|
-
val expanded = state == STATE_EXPANDED
|
|
26
|
-
|
|
27
|
-
if (isDownEvent && expanded) {
|
|
28
|
-
for (i in 0 until it.childCount) {
|
|
29
|
-
val contentChild = it.getChildAt(i)
|
|
30
|
-
val scrolled = (contentChild is ScrollView && contentChild.scrollY > 0)
|
|
31
|
-
|
|
32
|
-
if (!scrolled) continue
|
|
33
|
-
if (isInsideSheet(contentChild as ScrollView, event)) {
|
|
34
|
-
return false
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return super.onInterceptTouchEvent(parent, child, event)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
private fun isInsideSheet(scrollView: ScrollView, event: MotionEvent): Boolean {
|
|
44
|
-
val x = event.x
|
|
45
|
-
val y = event.y
|
|
46
|
-
|
|
47
|
-
val position = IntArray(2)
|
|
48
|
-
scrollView.getLocationOnScreen(position)
|
|
49
|
-
|
|
50
|
-
val nestedX = position[0]
|
|
51
|
-
val nestedY = position[1]
|
|
52
|
-
|
|
53
|
-
val boundRight = nestedX + scrollView.width
|
|
54
|
-
val boundBottom = nestedY + scrollView.height
|
|
55
|
-
|
|
56
|
-
return (x > nestedX && x < boundRight && y > nestedY && y < boundBottom) ||
|
|
57
|
-
event.action == MotionEvent.ACTION_CANCEL
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Get the height value based on the size config value.
|
|
62
|
-
*/
|
|
63
|
-
private fun getSizeHeight(size: Any, contentHeight: Int): Int {
|
|
64
|
-
val height =
|
|
65
|
-
when (size) {
|
|
66
|
-
is Double -> Utils.toPixel(size)
|
|
67
|
-
|
|
68
|
-
is Int -> Utils.toPixel(size.toDouble())
|
|
69
|
-
|
|
70
|
-
is String -> {
|
|
71
|
-
when (size) {
|
|
72
|
-
"auto" -> contentHeight
|
|
73
|
-
|
|
74
|
-
"large" -> maxScreenHeight
|
|
75
|
-
|
|
76
|
-
"medium" -> (maxScreenHeight * 0.50).toInt()
|
|
77
|
-
|
|
78
|
-
"small" -> (maxScreenHeight * 0.25).toInt()
|
|
79
|
-
|
|
80
|
-
else -> {
|
|
81
|
-
if (size.endsWith('%')) {
|
|
82
|
-
val percent = size.trim('%').toDoubleOrNull()
|
|
83
|
-
if (percent == null) {
|
|
84
|
-
0
|
|
85
|
-
} else {
|
|
86
|
-
((percent / 100) * maxScreenHeight).toInt()
|
|
87
|
-
}
|
|
88
|
-
} else {
|
|
89
|
-
val fixedHeight = size.toDoubleOrNull()
|
|
90
|
-
if (fixedHeight == null) {
|
|
91
|
-
0
|
|
92
|
-
} else {
|
|
93
|
-
Utils.toPixel(fixedHeight)
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
else -> (maxScreenHeight * 0.5).toInt()
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return minOf(height, maxSheetHeight ?: maxScreenHeight)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Determines the state based on the given size index.
|
|
108
|
-
*/
|
|
109
|
-
fun getStateForSizeIndex(index: Int) =
|
|
110
|
-
when (sizes.size) {
|
|
111
|
-
1 -> STATE_EXPANDED
|
|
112
|
-
|
|
113
|
-
2 -> {
|
|
114
|
-
when (index) {
|
|
115
|
-
0 -> STATE_COLLAPSED
|
|
116
|
-
1 -> STATE_EXPANDED
|
|
117
|
-
else -> STATE_HIDDEN
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
3 -> {
|
|
122
|
-
when (index) {
|
|
123
|
-
0 -> STATE_COLLAPSED
|
|
124
|
-
1 -> STATE_HALF_EXPANDED
|
|
125
|
-
2 -> STATE_EXPANDED
|
|
126
|
-
else -> STATE_HIDDEN
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
else -> STATE_HIDDEN
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Configure the sheet based on size preferences.
|
|
135
|
-
*/
|
|
136
|
-
fun configure() {
|
|
137
|
-
// Update the usable sheet height
|
|
138
|
-
maxScreenHeight = Utils.screenHeight(reactContext)
|
|
139
|
-
|
|
140
|
-
var contentHeight = 0
|
|
141
|
-
|
|
142
|
-
contentView?.let { contentHeight = it.height }
|
|
143
|
-
footerView?.let { contentHeight += it.height }
|
|
144
|
-
|
|
145
|
-
// Configure sheet sizes
|
|
146
|
-
apply {
|
|
147
|
-
skipCollapsed = false
|
|
148
|
-
isFitToContents = true
|
|
149
|
-
|
|
150
|
-
// m3 max width 640dp
|
|
151
|
-
maxWidth = Utils.toPixel(640.0)
|
|
152
|
-
|
|
153
|
-
when (sizes.size) {
|
|
154
|
-
1 -> {
|
|
155
|
-
maxHeight = getSizeHeight(sizes[0], contentHeight)
|
|
156
|
-
peekHeight = maxHeight
|
|
157
|
-
skipCollapsed = true
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
2 -> {
|
|
161
|
-
peekHeight = getSizeHeight(sizes[0], contentHeight)
|
|
162
|
-
maxHeight = getSizeHeight(sizes[1], contentHeight)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
3 -> {
|
|
166
|
-
// Enables half expanded
|
|
167
|
-
isFitToContents = false
|
|
168
|
-
|
|
169
|
-
peekHeight = getSizeHeight(sizes[0], contentHeight)
|
|
170
|
-
halfExpandedRatio = getSizeHeight(sizes[1], contentHeight).toFloat() / maxScreenHeight.toFloat()
|
|
171
|
-
maxHeight = getSizeHeight(sizes[2], contentHeight)
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Get the SizeInfo data by state.
|
|
179
|
-
*/
|
|
180
|
-
fun getSizeInfoForState(state: Int): SizeInfo? =
|
|
181
|
-
when (sizes.size) {
|
|
182
|
-
1 -> {
|
|
183
|
-
when (state) {
|
|
184
|
-
STATE_EXPANDED -> SizeInfo(0, Utils.toDIP(maxHeight))
|
|
185
|
-
else -> null
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
2 -> {
|
|
190
|
-
when (state) {
|
|
191
|
-
STATE_COLLAPSED -> SizeInfo(0, Utils.toDIP(peekHeight))
|
|
192
|
-
STATE_EXPANDED -> SizeInfo(1, Utils.toDIP(maxHeight))
|
|
193
|
-
else -> null
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
3 -> {
|
|
198
|
-
when (state) {
|
|
199
|
-
STATE_COLLAPSED -> SizeInfo(0, Utils.toDIP(peekHeight))
|
|
200
|
-
|
|
201
|
-
STATE_HALF_EXPANDED -> {
|
|
202
|
-
val height = halfExpandedRatio * maxScreenHeight
|
|
203
|
-
SizeInfo(1, Utils.toDIP(height.toInt()))
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
STATE_EXPANDED -> SizeInfo(2, Utils.toDIP(maxHeight))
|
|
207
|
-
|
|
208
|
-
else -> null
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
else -> null
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Get SizeInfo data for given size index.
|
|
217
|
-
*/
|
|
218
|
-
fun getSizeInfoForIndex(index: Int) = getSizeInfoForState(getStateForSizeIndex(index)) ?: SizeInfo(0, 0f)
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* Set the state based on the given size index.
|
|
222
|
-
*/
|
|
223
|
-
fun setStateForSizeIndex(index: Int) {
|
|
224
|
-
state = getStateForSizeIndex(index)
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
companion object {
|
|
228
|
-
const val TAG = "TrueSheetView"
|
|
229
|
-
}
|
|
230
|
-
}
|