@lodev09/react-native-true-sheet 0.7.0 → 0.8.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 +34 -18
- package/android/src/main/java/com/lodev09/truesheet/{core/TrueSheetBehavior.kt → TrueSheetBehavior.kt} +69 -39
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetDialog.kt +84 -0
- package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +82 -61
- package/android/src/main/java/com/lodev09/truesheet/core/Events.kt +1 -0
- package/android/src/main/java/com/lodev09/truesheet/core/KeyboardManager.kt +53 -0
- package/android/src/main/java/com/lodev09/truesheet/core/RootViewGroup.kt +1 -1
- package/android/src/main/java/com/lodev09/truesheet/core/Utils.kt +5 -37
- package/ios/Extensions/UIView+pinTo.swift +54 -10
- package/ios/TrueSheetView.swift +56 -22
- package/ios/TrueSheetViewController.swift +36 -0
- package/lib/commonjs/TrueSheet.js +4 -1
- package/lib/commonjs/TrueSheet.js.map +1 -1
- package/lib/commonjs/TrueSheetGrabber.js +56 -0
- package/lib/commonjs/TrueSheetGrabber.js.map +1 -0
- package/lib/commonjs/index.js +11 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/TrueSheet.js +4 -1
- package/lib/module/TrueSheet.js.map +1 -1
- package/lib/module/TrueSheetGrabber.js +48 -0
- package/lib/module/TrueSheetGrabber.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
- package/lib/typescript/src/TrueSheetGrabber.d.ts +31 -0
- package/lib/typescript/src/TrueSheetGrabber.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/TrueSheet.tsx +2 -0
- package/src/TrueSheetGrabber.tsx +74 -0
- package/src/index.ts +1 -0
package/README.md
CHANGED
|
@@ -9,10 +9,10 @@ The true native bottom sheet 💩
|
|
|
9
9
|

|
|
10
10
|
|
|
11
11
|
## Features
|
|
12
|
-
* ✅ Implemented
|
|
13
|
-
* ✅ **_NOT_** your pure JS, (re)animated
|
|
14
|
-
* ✅ Clean, fast and lightweight.
|
|
15
|
-
* ✅ Handles your
|
|
12
|
+
* ✅ Implemented in the native realm.
|
|
13
|
+
* ✅ **_NOT_** your pure JS, (re)animated view (might integrate in the future 👀)
|
|
14
|
+
* ✅ Clean, fast, and lightweight.
|
|
15
|
+
* ✅ Handles your scrolling needs, easy.
|
|
16
16
|
* ✅ Asynchronus `ref` methods.
|
|
17
17
|
|
|
18
18
|
## Installation
|
|
@@ -21,6 +21,10 @@ The true native bottom sheet 💩
|
|
|
21
21
|
yarn add @lodev09/react-native-true-sheet
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
```sh
|
|
25
|
+
npm i @lodev09/react-native-true-sheet
|
|
26
|
+
```
|
|
27
|
+
|
|
24
28
|
## Usage
|
|
25
29
|
|
|
26
30
|
```ts
|
|
@@ -54,14 +58,14 @@ Extended from `ViewProps`
|
|
|
54
58
|
|
|
55
59
|
| Prop | Type | Default | Description | 🍎 | 🤖 |
|
|
56
60
|
| - | - | - | - | - | - |
|
|
57
|
-
| sizes | [`SheetSize`](#sheetsize) | `['medium', 'large']` | The sizes you want the
|
|
61
|
+
| sizes | [`SheetSize`](#sheetsize) | `['medium', 'large']` | The sizes you want the sheet to support. Maximum of _**3 sizes**_ only! **_collapsed_**, **_half-expanded_**, and **_expanded_**. Example: `size={['auto', '60%', 'large']}`| ✅ | ✅ |
|
|
58
62
|
| backgroundColor | `ColorValue` | `white` | Main sheet background color. | ✅ | ✅ |
|
|
59
|
-
| cornerRadius | `number` | - |
|
|
63
|
+
| cornerRadius | `number` | - | the sheet corner radius. | ✅ | ✅ |
|
|
60
64
|
| maxHeight | `number` | - | Overrides `large` or `100%` height. | ✅ | ✅ |
|
|
61
65
|
| contentContainerStyle | `StyleProp<ViewStyle>` | - | Optional content container styles. | ✅ | ✅ |
|
|
62
|
-
| FooterComponent | `ReactNode` | - | A component that floats at the bottom of the
|
|
63
|
-
| dismissible | `boolean` | `true` |
|
|
64
|
-
| grabber | `boolean` | `true` | Shows
|
|
66
|
+
| FooterComponent | `ReactNode` | - | A component that floats at the bottom of the sheet. | ✅ | ✅ |
|
|
67
|
+
| dismissible | `boolean` | `true` | If set to `false`, the sheet will prevent interactive dismissal via dragging or clicking outside of it. | ✅ | ✅ |
|
|
68
|
+
| grabber | `boolean` | `true` | Shows a grabber (or handle). Native on IOS and styled `View` on Android. | ✅ | ✅ |
|
|
65
69
|
| blurTint | [`BlurTint`](#blurTint) | - | The blur effect style on iOS. Overrides `backgroundColor` if set. Example: `light`, `dark`, etc. | ✅ | |
|
|
66
70
|
| scrollRef | `RefObject<...>` | - | The main scrollable ref that Sheet should handle on iOS. | ✅ | |
|
|
67
71
|
|
|
@@ -92,8 +96,8 @@ return (
|
|
|
92
96
|
| Name | Parameters | Description |
|
|
93
97
|
| - | - | - |
|
|
94
98
|
| present | `index: number = 0` | Present the modal sheet. Optionally accepts a size `index`. See `sizes` prop. |
|
|
95
|
-
| resize | `index: number` | Resizes the
|
|
96
|
-
| dismiss | - | Dismisses the
|
|
99
|
+
| resize | `index: number` | Resizes the sheet programmatically by `index`. This is an alias of the `present(index)` method. |
|
|
100
|
+
| dismiss | - | Dismisses the sheet. |
|
|
97
101
|
|
|
98
102
|
## Events
|
|
99
103
|
|
|
@@ -111,8 +115,8 @@ return (
|
|
|
111
115
|
|
|
112
116
|
| Name | Parameters | Description |
|
|
113
117
|
| - | - | - |
|
|
114
|
-
| onPresent | [`SizeInfo`](#sizeinfo) | Called when the
|
|
115
|
-
| onDismiss | - | Called when the
|
|
118
|
+
| onPresent | [`SizeInfo`](#sizeinfo) | Called when the sheet has been presented. Comes with the size index and value. |
|
|
119
|
+
| onDismiss | - | Called when the sheet has been dismissed. |
|
|
116
120
|
| onSizeChange | [`SizeInfo`](#sizeinfo) | Called when the size of the sheet has changed. Either by dragging or presenting programatically. Comes with the size index and value. |
|
|
117
121
|
|
|
118
122
|
## Types
|
|
@@ -127,12 +131,17 @@ return (
|
|
|
127
131
|
|
|
128
132
|
| Value | Description | 🍎 | 🤖 |
|
|
129
133
|
| - | - | - | - |
|
|
130
|
-
| `large` | Translates to 100% | ✅ | ✅ |
|
|
131
|
-
| `medium` | Translates to 50% | **_15+_** | ✅ |
|
|
132
|
-
| `auto` | Auto resize based on content height. | **_16+_** | ✅ |
|
|
133
|
-
| `number` | Fixed height | **_16+_** | ✅ |
|
|
134
|
+
| `"large"` | Translates to 100% | ✅ | ✅ |
|
|
135
|
+
| `"medium"` | Translates to 50% | **_15+_** | ✅ |
|
|
136
|
+
| `"auto"` | Auto resize based on content height. | **_16+_** | ✅ |
|
|
137
|
+
| `"number"` | Fixed height | **_16+_** | ✅ |
|
|
134
138
|
| `${number}%` | Fixed height in % | **_16+_** | ✅ |
|
|
135
|
-
| `small` | Translates to 25% | **_16+_** | ✅ |
|
|
139
|
+
| `"small"` | Translates to 25% | **_16+_** | ✅ |
|
|
140
|
+
|
|
141
|
+
> [!NOTE]
|
|
142
|
+
> `auto` is not guaranteed to be accurate if your content depends on various rendering logic. Experiment with it and try to keep your content size as fixed as possible.
|
|
143
|
+
>
|
|
144
|
+
> Alternatively, you can programmatically call [`resize`](#methods) to adjust the sheet size on-the-fly.
|
|
136
145
|
|
|
137
146
|
### `BlurTint`
|
|
138
147
|
|
|
@@ -184,6 +193,13 @@ Blur tint that is mapped into native values in iOS.
|
|
|
184
193
|
| index | `number` | The size index from the provided sizes. See `sizes` prop. |
|
|
185
194
|
| value | `number` | The actual height value of the size. |
|
|
186
195
|
|
|
196
|
+
## v1 Roadmap
|
|
197
|
+
|
|
198
|
+
- [ ] Android: grabber
|
|
199
|
+
- [ ] Inline sheet
|
|
200
|
+
- [ ] Reanimated integration(?)
|
|
201
|
+
- [ ] Any ideas?
|
|
202
|
+
|
|
187
203
|
## Contributing
|
|
188
204
|
|
|
189
205
|
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository and the development workflow.
|
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
package com.lodev09.truesheet
|
|
1
|
+
package com.lodev09.truesheet
|
|
2
2
|
|
|
3
|
-
import android.graphics.Point
|
|
4
3
|
import android.view.MotionEvent
|
|
5
4
|
import android.view.ViewGroup
|
|
6
5
|
import android.widget.ScrollView
|
|
7
6
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
7
|
+
import com.facebook.react.bridge.ReactContext
|
|
8
8
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
9
|
+
import com.lodev09.truesheet.core.Utils
|
|
9
10
|
|
|
10
11
|
data class SizeInfo(val index: Int, val value: Float)
|
|
11
12
|
|
|
12
|
-
class TrueSheetBehavior : BottomSheetBehavior<ViewGroup>() {
|
|
13
|
-
var
|
|
13
|
+
class TrueSheetBehavior(private val reactContext: ReactContext) : BottomSheetBehavior<ViewGroup>() {
|
|
14
|
+
var maxScreenHeight: Int = 0
|
|
14
15
|
var maxSheetHeight: Int? = null
|
|
15
16
|
|
|
16
17
|
var contentView: ViewGroup? = null
|
|
@@ -56,6 +57,9 @@ class TrueSheetBehavior : BottomSheetBehavior<ViewGroup>() {
|
|
|
56
57
|
event.action == MotionEvent.ACTION_CANCEL
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Get the height value based on the size config value.
|
|
62
|
+
*/
|
|
59
63
|
private fun getSizeHeight(size: Any, contentHeight: Int): Int {
|
|
60
64
|
val height =
|
|
61
65
|
when (size) {
|
|
@@ -67,11 +71,11 @@ class TrueSheetBehavior : BottomSheetBehavior<ViewGroup>() {
|
|
|
67
71
|
when (size) {
|
|
68
72
|
"auto" -> contentHeight
|
|
69
73
|
|
|
70
|
-
"large" ->
|
|
74
|
+
"large" -> maxScreenHeight
|
|
71
75
|
|
|
72
|
-
"medium" -> (
|
|
76
|
+
"medium" -> (maxScreenHeight * 0.50).toInt()
|
|
73
77
|
|
|
74
|
-
"small" -> (
|
|
78
|
+
"small" -> (maxScreenHeight * 0.25).toInt()
|
|
75
79
|
|
|
76
80
|
else -> {
|
|
77
81
|
if (size.endsWith('%')) {
|
|
@@ -79,7 +83,7 @@ class TrueSheetBehavior : BottomSheetBehavior<ViewGroup>() {
|
|
|
79
83
|
if (percent == null) {
|
|
80
84
|
0
|
|
81
85
|
} else {
|
|
82
|
-
((percent / 100) *
|
|
86
|
+
((percent / 100) * maxScreenHeight).toInt()
|
|
83
87
|
}
|
|
84
88
|
} else {
|
|
85
89
|
val fixedHeight = size.toDoubleOrNull()
|
|
@@ -93,13 +97,46 @@ class TrueSheetBehavior : BottomSheetBehavior<ViewGroup>() {
|
|
|
93
97
|
}
|
|
94
98
|
}
|
|
95
99
|
|
|
96
|
-
else -> (
|
|
100
|
+
else -> (maxScreenHeight * 0.5).toInt()
|
|
97
101
|
}
|
|
98
102
|
|
|
99
|
-
return minOf(height, maxSheetHeight ?:
|
|
103
|
+
return minOf(height, maxSheetHeight ?: maxScreenHeight)
|
|
100
104
|
}
|
|
101
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
|
+
*/
|
|
102
136
|
fun configure() {
|
|
137
|
+
// Update the usable sheet height
|
|
138
|
+
maxScreenHeight = Utils.activityView(reactContext)?.height ?: 0
|
|
139
|
+
|
|
103
140
|
var contentHeight = 0
|
|
104
141
|
|
|
105
142
|
contentView?.let { contentHeight = it.height }
|
|
@@ -107,12 +144,16 @@ class TrueSheetBehavior : BottomSheetBehavior<ViewGroup>() {
|
|
|
107
144
|
|
|
108
145
|
// Configure sheet sizes
|
|
109
146
|
apply {
|
|
110
|
-
isFitToContents = true
|
|
111
147
|
skipCollapsed = false
|
|
148
|
+
isFitToContents = true
|
|
149
|
+
|
|
150
|
+
// m3 max width 640dp
|
|
151
|
+
maxWidth = Utils.toPixel(640.0)
|
|
112
152
|
|
|
113
153
|
when (sizes.size) {
|
|
114
154
|
1 -> {
|
|
115
155
|
maxHeight = getSizeHeight(sizes[0], contentHeight)
|
|
156
|
+
peekHeight = maxHeight
|
|
116
157
|
skipCollapsed = true
|
|
117
158
|
}
|
|
118
159
|
|
|
@@ -126,37 +167,16 @@ class TrueSheetBehavior : BottomSheetBehavior<ViewGroup>() {
|
|
|
126
167
|
isFitToContents = false
|
|
127
168
|
|
|
128
169
|
peekHeight = getSizeHeight(sizes[0], contentHeight)
|
|
129
|
-
halfExpandedRatio = getSizeHeight(sizes[1], contentHeight).toFloat() /
|
|
170
|
+
halfExpandedRatio = getSizeHeight(sizes[1], contentHeight).toFloat() / maxScreenHeight.toFloat()
|
|
130
171
|
maxHeight = getSizeHeight(sizes[2], contentHeight)
|
|
131
172
|
}
|
|
132
173
|
}
|
|
133
174
|
}
|
|
134
175
|
}
|
|
135
176
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
2 -> {
|
|
141
|
-
when (index) {
|
|
142
|
-
0 -> STATE_COLLAPSED
|
|
143
|
-
1 -> STATE_EXPANDED
|
|
144
|
-
else -> STATE_HIDDEN
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
3 -> {
|
|
149
|
-
when (index) {
|
|
150
|
-
0 -> STATE_COLLAPSED
|
|
151
|
-
1 -> STATE_HALF_EXPANDED
|
|
152
|
-
2 -> STATE_EXPANDED
|
|
153
|
-
else -> STATE_HIDDEN
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
else -> STATE_HIDDEN
|
|
158
|
-
}
|
|
159
|
-
|
|
177
|
+
/**
|
|
178
|
+
* Get the SizeInfo data by state.
|
|
179
|
+
*/
|
|
160
180
|
fun getSizeInfoForState(state: Int): SizeInfo? =
|
|
161
181
|
when (sizes.size) {
|
|
162
182
|
1 -> {
|
|
@@ -179,7 +199,7 @@ class TrueSheetBehavior : BottomSheetBehavior<ViewGroup>() {
|
|
|
179
199
|
STATE_COLLAPSED -> SizeInfo(0, Utils.toDIP(peekHeight))
|
|
180
200
|
|
|
181
201
|
STATE_HALF_EXPANDED -> {
|
|
182
|
-
val height = halfExpandedRatio *
|
|
202
|
+
val height = halfExpandedRatio * maxScreenHeight
|
|
183
203
|
SizeInfo(1, Utils.toDIP(height.toInt()))
|
|
184
204
|
}
|
|
185
205
|
|
|
@@ -192,9 +212,19 @@ class TrueSheetBehavior : BottomSheetBehavior<ViewGroup>() {
|
|
|
192
212
|
else -> null
|
|
193
213
|
}
|
|
194
214
|
|
|
195
|
-
|
|
215
|
+
/**
|
|
216
|
+
* Get SizeInfo data for given size index.
|
|
217
|
+
*/
|
|
218
|
+
fun getSizeInfoForIndex(index: Int) = getSizeInfoForState(getStateForSizeIndex(index)) ?: SizeInfo(0, 0f)
|
|
196
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Set the state based on the given size index.
|
|
222
|
+
*/
|
|
197
223
|
fun setStateForSizeIndex(index: Int) {
|
|
198
|
-
state =
|
|
224
|
+
state = getStateForSizeIndex(index)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
companion object {
|
|
228
|
+
const val TAG = "TrueSheetView"
|
|
199
229
|
}
|
|
200
230
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
package com.lodev09.truesheet
|
|
2
|
+
|
|
3
|
+
import android.graphics.Color
|
|
4
|
+
import android.view.ViewGroup
|
|
5
|
+
import android.view.WindowManager
|
|
6
|
+
import android.widget.LinearLayout
|
|
7
|
+
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
8
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
9
|
+
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
10
|
+
import com.lodev09.truesheet.core.KeyboardManager
|
|
11
|
+
import com.lodev09.truesheet.core.RootViewGroup
|
|
12
|
+
import com.lodev09.truesheet.core.Utils
|
|
13
|
+
|
|
14
|
+
class TrueSheetDialog(
|
|
15
|
+
private val reactContext: ThemedReactContext,
|
|
16
|
+
private val behavior: TrueSheetBehavior,
|
|
17
|
+
private val rootViewGroup: RootViewGroup
|
|
18
|
+
) : BottomSheetDialog(reactContext) {
|
|
19
|
+
|
|
20
|
+
private var keyboardManager = KeyboardManager(reactContext)
|
|
21
|
+
|
|
22
|
+
var sheetView: ViewGroup
|
|
23
|
+
|
|
24
|
+
init {
|
|
25
|
+
LinearLayout(reactContext).apply {
|
|
26
|
+
addView(rootViewGroup)
|
|
27
|
+
setContentView(this)
|
|
28
|
+
|
|
29
|
+
sheetView = parent as ViewGroup
|
|
30
|
+
|
|
31
|
+
// Set to transparent background to support corner radius
|
|
32
|
+
sheetView.setBackgroundColor(Color.TRANSPARENT)
|
|
33
|
+
|
|
34
|
+
// Assign our main BottomSheetBehavior
|
|
35
|
+
val sheetViewParams = sheetView.layoutParams as CoordinatorLayout.LayoutParams
|
|
36
|
+
sheetViewParams.behavior = behavior
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Setup window params to adjust layout based on Keyboard state.
|
|
40
|
+
window?.apply {
|
|
41
|
+
// SOFT_INPUT_ADJUST_RESIZE to resize the sheet above the keyboard
|
|
42
|
+
setSoftInputMode(
|
|
43
|
+
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fun show(sizeIndex: Int) {
|
|
49
|
+
if (isShowing) {
|
|
50
|
+
behavior.setStateForSizeIndex(sizeIndex)
|
|
51
|
+
} else {
|
|
52
|
+
behavior.configure()
|
|
53
|
+
behavior.setStateForSizeIndex(sizeIndex)
|
|
54
|
+
|
|
55
|
+
this.show()
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Handle keyboard state changes and adjust maxScreenHeight (sheet max height) accordingly.
|
|
61
|
+
* Also update footer's Y position.
|
|
62
|
+
*/
|
|
63
|
+
fun registerKeyboardManager() {
|
|
64
|
+
keyboardManager.registerKeyboardListener(object : KeyboardManager.OnKeyboardListener {
|
|
65
|
+
override fun onKeyboardStateChange(isVisible: Boolean) {
|
|
66
|
+
behavior.maxScreenHeight = Utils.activityView(reactContext)?.height ?: 0
|
|
67
|
+
behavior.footerView?.apply {
|
|
68
|
+
y = (behavior.maxScreenHeight - (sheetView.top ?: 0) - height).toFloat()
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Remove keyboard listener.
|
|
76
|
+
*/
|
|
77
|
+
fun unregisterKeyboardManager() {
|
|
78
|
+
keyboardManager.unregisterKeyboardListener()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
companion object {
|
|
82
|
+
const val TAG = "TrueSheetView"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -1,26 +1,20 @@
|
|
|
1
1
|
package com.lodev09.truesheet
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
|
-
import android.graphics.Color
|
|
5
4
|
import android.view.View
|
|
6
5
|
import android.view.ViewGroup
|
|
7
6
|
import android.view.ViewStructure
|
|
8
7
|
import android.view.accessibility.AccessibilityEvent
|
|
9
|
-
import android.widget.LinearLayout
|
|
10
|
-
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
11
8
|
import com.facebook.react.bridge.LifecycleEventListener
|
|
12
9
|
import com.facebook.react.bridge.UiThreadUtil
|
|
13
10
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
14
11
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
15
12
|
import com.facebook.react.uimanager.events.EventDispatcher
|
|
16
13
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
17
|
-
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
18
14
|
import com.lodev09.truesheet.core.DismissEvent
|
|
19
15
|
import com.lodev09.truesheet.core.PresentEvent
|
|
20
16
|
import com.lodev09.truesheet.core.RootViewGroup
|
|
21
17
|
import com.lodev09.truesheet.core.SizeChangeEvent
|
|
22
|
-
import com.lodev09.truesheet.core.TrueSheetBehavior
|
|
23
|
-
import com.lodev09.truesheet.core.Utils
|
|
24
18
|
|
|
25
19
|
class TrueSheetView(context: Context) :
|
|
26
20
|
ViewGroup(context),
|
|
@@ -33,22 +27,44 @@ class TrueSheetView(context: Context) :
|
|
|
33
27
|
private val surfaceId: Int
|
|
34
28
|
get() = UIManagerHelper.getSurfaceId(this)
|
|
35
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Current activeIndex.
|
|
32
|
+
*/
|
|
36
33
|
private var activeIndex: Int = 0
|
|
37
34
|
|
|
35
|
+
/**
|
|
36
|
+
* Promise callback to be invoked after `present` is called.
|
|
37
|
+
*/
|
|
38
38
|
private var presentPromise: (() -> Unit)? = null
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Promise callback to be invoked after `dismiss` is called.
|
|
42
|
+
*/
|
|
39
43
|
private var dismissPromise: (() -> Unit)? = null
|
|
40
44
|
|
|
41
|
-
|
|
45
|
+
/**
|
|
46
|
+
* The main BottomSheetDialog instance.
|
|
47
|
+
*/
|
|
48
|
+
private val sheetDialog: TrueSheetDialog
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The custom BottomSheetDialogBehavior instance.
|
|
52
|
+
*/
|
|
42
53
|
private val sheetBehavior: TrueSheetBehavior
|
|
43
|
-
private val sheetView: ViewGroup
|
|
44
54
|
|
|
45
|
-
|
|
55
|
+
/**
|
|
56
|
+
* React root view placeholder.
|
|
57
|
+
*/
|
|
46
58
|
private val sheetRootView: RootViewGroup
|
|
47
59
|
|
|
48
|
-
|
|
60
|
+
/**
|
|
61
|
+
* 1st child of the container view.
|
|
62
|
+
*/
|
|
49
63
|
private var contentView: ViewGroup? = null
|
|
50
64
|
|
|
51
|
-
|
|
65
|
+
/**
|
|
66
|
+
* 2nd child of the container view.
|
|
67
|
+
*/
|
|
52
68
|
private var footerView: ViewGroup? = null
|
|
53
69
|
|
|
54
70
|
init {
|
|
@@ -58,73 +74,78 @@ class TrueSheetView(context: Context) :
|
|
|
58
74
|
sheetRootView = RootViewGroup(context)
|
|
59
75
|
sheetRootView.eventDispatcher = eventDispatcher
|
|
60
76
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
// Configure the sheet layout view
|
|
65
|
-
LinearLayout(context).apply {
|
|
66
|
-
addView(sheetRootView)
|
|
67
|
-
sheetDialog.setContentView(this)
|
|
68
|
-
|
|
69
|
-
sheetView = parent as ViewGroup
|
|
70
|
-
sheetView.setBackgroundColor(Color.TRANSPARENT)
|
|
71
|
-
|
|
72
|
-
// Assign our main BottomSheetBehavior
|
|
73
|
-
val sheetViewParams = sheetView.layoutParams as CoordinatorLayout.LayoutParams
|
|
74
|
-
sheetViewParams.behavior = sheetBehavior
|
|
75
|
-
}
|
|
77
|
+
sheetBehavior = TrueSheetBehavior(reactContext)
|
|
78
|
+
sheetDialog = TrueSheetDialog(reactContext, sheetBehavior, sheetRootView)
|
|
76
79
|
|
|
77
80
|
// Configure Sheet Dialog
|
|
78
81
|
sheetDialog.apply {
|
|
82
|
+
// Setup listener when the dialog has been presented.
|
|
79
83
|
setOnShowListener {
|
|
84
|
+
registerKeyboardManager()
|
|
85
|
+
|
|
80
86
|
// Initialize footer y
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
footerView?.apply {
|
|
88
|
+
UiThreadUtil.runOnUiThread {
|
|
89
|
+
y = (sheetBehavior.maxScreenHeight - sheetView.top - height).toFloat()
|
|
84
90
|
}
|
|
85
91
|
}
|
|
86
92
|
|
|
87
|
-
presentPromise?.
|
|
88
|
-
|
|
93
|
+
presentPromise?.let { promise ->
|
|
94
|
+
promise()
|
|
95
|
+
presentPromise = null
|
|
96
|
+
}
|
|
89
97
|
|
|
90
98
|
// dispatch onPresent event
|
|
91
99
|
eventDispatcher?.dispatchEvent(PresentEvent(surfaceId, id, sheetBehavior.getSizeInfoForIndex(activeIndex)))
|
|
92
100
|
}
|
|
93
101
|
|
|
102
|
+
// Setup listener when the dialog has been dismissed.
|
|
94
103
|
setOnDismissListener {
|
|
95
|
-
|
|
96
|
-
dismissPromise
|
|
104
|
+
unregisterKeyboardManager()
|
|
105
|
+
dismissPromise?.let { promise ->
|
|
106
|
+
promise()
|
|
107
|
+
dismissPromise = null
|
|
108
|
+
}
|
|
97
109
|
|
|
98
110
|
// dispatch onDismiss event
|
|
99
111
|
eventDispatcher?.dispatchEvent(DismissEvent(surfaceId, id))
|
|
100
112
|
}
|
|
101
113
|
}
|
|
102
114
|
|
|
103
|
-
// Configure
|
|
115
|
+
// Configure sheet behavior events
|
|
104
116
|
sheetBehavior.apply {
|
|
105
|
-
|
|
106
|
-
// Set our default max height
|
|
107
|
-
maxSheetSize = Utils.maxSize(context)
|
|
108
|
-
|
|
109
117
|
addBottomSheetCallback(
|
|
110
118
|
object : BottomSheetBehavior.BottomSheetCallback() {
|
|
111
119
|
override fun onSlide(sheetView: View, slideOffset: Float) {
|
|
112
120
|
footerView?.let {
|
|
113
|
-
|
|
121
|
+
val y = (maxScreenHeight - sheetView.top - it.height).toFloat()
|
|
122
|
+
if (slideOffset >= 0) {
|
|
123
|
+
it.y = y
|
|
124
|
+
} else {
|
|
125
|
+
it.y = y - it.height * slideOffset
|
|
126
|
+
}
|
|
114
127
|
}
|
|
115
128
|
}
|
|
116
129
|
|
|
117
130
|
override fun onStateChanged(view: View, newState: Int) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
when (newState) {
|
|
132
|
+
BottomSheetBehavior.STATE_HIDDEN -> sheetDialog.dismiss()
|
|
133
|
+
|
|
134
|
+
else -> {
|
|
135
|
+
val sizeInfo = getSizeInfoForState(newState)
|
|
136
|
+
if (sizeInfo != null && sizeInfo.index != activeIndex) {
|
|
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
|
+
}
|
|
148
|
+
}
|
|
128
149
|
}
|
|
129
150
|
}
|
|
130
151
|
}
|
|
@@ -225,21 +246,21 @@ class TrueSheetView(context: Context) :
|
|
|
225
246
|
sheetBehavior.configure()
|
|
226
247
|
}
|
|
227
248
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
activeIndex = index
|
|
236
|
-
sheetBehavior.setStateForSizeIndex(index)
|
|
237
|
-
|
|
238
|
-
presentPromise = promiseCallback
|
|
239
|
-
sheetDialog.show()
|
|
249
|
+
/**
|
|
250
|
+
* Present the sheet at given size index.
|
|
251
|
+
*/
|
|
252
|
+
fun present(sizeIndex: Int, promiseCallback: () -> Unit) {
|
|
253
|
+
if (!sheetDialog.isShowing) {
|
|
254
|
+
activeIndex = sizeIndex
|
|
240
255
|
}
|
|
256
|
+
|
|
257
|
+
presentPromise = promiseCallback
|
|
258
|
+
sheetDialog.show(sizeIndex)
|
|
241
259
|
}
|
|
242
260
|
|
|
261
|
+
/**
|
|
262
|
+
* Dismisses the sheet.
|
|
263
|
+
*/
|
|
243
264
|
fun dismiss(promiseCallback: () -> Unit) {
|
|
244
265
|
dismissPromise = promiseCallback
|
|
245
266
|
sheetDialog.dismiss()
|
|
@@ -3,6 +3,7 @@ package com.lodev09.truesheet.core
|
|
|
3
3
|
import com.facebook.react.bridge.Arguments
|
|
4
4
|
import com.facebook.react.bridge.WritableMap
|
|
5
5
|
import com.facebook.react.uimanager.events.Event
|
|
6
|
+
import com.lodev09.truesheet.SizeInfo
|
|
6
7
|
|
|
7
8
|
// onPresent
|
|
8
9
|
class PresentEvent(surfaceId: Int, viewId: Int, private val sizeInfo: SizeInfo) : Event<PresentEvent>(surfaceId, viewId) {
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
package com.lodev09.truesheet.core
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.view.View
|
|
5
|
+
import android.view.ViewTreeObserver.OnGlobalLayoutListener
|
|
6
|
+
import android.view.inputmethod.InputMethodManager
|
|
7
|
+
import com.facebook.react.bridge.ReactContext
|
|
8
|
+
|
|
9
|
+
class KeyboardManager(reactContext: ReactContext) {
|
|
10
|
+
interface OnKeyboardListener {
|
|
11
|
+
fun onKeyboardStateChange(isVisible: Boolean)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
private var screenView: View? = Utils.activityView(reactContext)
|
|
15
|
+
private var onGlobalLayoutListener: OnGlobalLayoutListener? = null
|
|
16
|
+
private var isKeyboardVisible = false
|
|
17
|
+
|
|
18
|
+
fun registerKeyboardListener(listener: OnKeyboardListener?) {
|
|
19
|
+
screenView?.apply {
|
|
20
|
+
unregisterKeyboardListener()
|
|
21
|
+
|
|
22
|
+
onGlobalLayoutListener = object : OnGlobalLayoutListener {
|
|
23
|
+
private var previousHeight = 0
|
|
24
|
+
|
|
25
|
+
override fun onGlobalLayout() {
|
|
26
|
+
val heightDiff = rootView.height - height
|
|
27
|
+
if (heightDiff > Utils.toPixel(200.0)) {
|
|
28
|
+
// Will ask InputMethodManager.isAcceptingText() to detect if keyboard appeared or not.
|
|
29
|
+
val inputManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
30
|
+
if (height != previousHeight && inputManager.isAcceptingText()) {
|
|
31
|
+
listener?.onKeyboardStateChange(true)
|
|
32
|
+
|
|
33
|
+
previousHeight = height
|
|
34
|
+
isKeyboardVisible = true
|
|
35
|
+
}
|
|
36
|
+
} else if (isKeyboardVisible) {
|
|
37
|
+
listener?.onKeyboardStateChange(false)
|
|
38
|
+
previousHeight = 0
|
|
39
|
+
isKeyboardVisible = false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
fun unregisterKeyboardListener() {
|
|
49
|
+
onGlobalLayoutListener?.let {
|
|
50
|
+
screenView?.getViewTreeObserver()?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -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
|
-
|
|
29
|
+
class RootViewGroup(context: Context?) :
|
|
30
30
|
ReactViewGroup(context),
|
|
31
31
|
RootView {
|
|
32
32
|
private var hasAdjustedSize = false
|