@onekeyfe/react-native-native-sheet 3.0.125
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/LICENSE +21 -0
- package/NativeSheet.podspec +22 -0
- package/README.md +56 -0
- package/android/build.gradle +82 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/onekey/nativesheet/NativeSheetEvents.kt +37 -0
- package/android/src/main/java/com/onekey/nativesheet/NativeSheetPackage.kt +16 -0
- package/android/src/main/java/com/onekey/nativesheet/NativeSheetView.kt +354 -0
- package/android/src/main/java/com/onekey/nativesheet/NativeSheetViewManager.kt +118 -0
- package/ios/NativeSheetContainerView.swift +441 -0
- package/ios/RCTNativeSheetComponentView.h +11 -0
- package/ios/RCTNativeSheetComponentView.mm +156 -0
- package/lib/module/NativeSheetNativeComponent.ts +35 -0
- package/lib/module/codegen-types.d.js +2 -0
- package/lib/module/codegen-types.d.js.map +1 -0
- package/lib/module/index.js +92 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeSheetNativeComponent.d.ts +25 -0
- package/lib/typescript/src/NativeSheetNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +40 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +171 -0
- package/react-native.config.js +12 -0
- package/src/NativeSheetNativeComponent.ts +35 -0
- package/src/codegen-types.d.ts +9 -0
- package/src/index.tsx +145 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 OneKey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "NativeSheet"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => '16.0' }
|
|
14
|
+
s.source = { :git => "https://github.com/OneKeyHQ/app-modules.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = [
|
|
17
|
+
"ios/**/*.{swift}",
|
|
18
|
+
"ios/**/*.{m,mm}",
|
|
19
|
+
]
|
|
20
|
+
s.swift_version = '5.0'
|
|
21
|
+
install_modules_dependencies(s)
|
|
22
|
+
end
|
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @onekeyfe/react-native-native-sheet
|
|
2
|
+
|
|
3
|
+
A native iOS and Android bottom-sheet host for arbitrary React Native content.
|
|
4
|
+
The platform owns presentation, stacking, gestures, and transitions; React
|
|
5
|
+
Native owns the sheet body. No action-row or icon schema is required.
|
|
6
|
+
|
|
7
|
+
## Behavior
|
|
8
|
+
|
|
9
|
+
- iOS uses `UISheetPresentationController` with a fixed custom detent.
|
|
10
|
+
- Android uses `BottomSheetDialog` and `BottomSheetBehavior`.
|
|
11
|
+
- Multiple sheets form a native presentation stack. Closing a lower sheet also
|
|
12
|
+
closes its descendants in top-to-bottom order.
|
|
13
|
+
- `height` is captured when a sheet opens. Async child updates reflow inside the
|
|
14
|
+
existing surface and do not resize the outer sheet.
|
|
15
|
+
- `NativeSheetSecurityProvider` force-dismisses descendant sheets without an
|
|
16
|
+
animation while the app lock is active, so native windows never cover the
|
|
17
|
+
security surface.
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import {
|
|
23
|
+
NativeSheet,
|
|
24
|
+
NativeSheetSecurityProvider,
|
|
25
|
+
} from '@onekeyfe/react-native-native-sheet';
|
|
26
|
+
|
|
27
|
+
function AppRoot({ isLocked, children }) {
|
|
28
|
+
return (
|
|
29
|
+
<NativeSheetSecurityProvider blocked={isLocked}>
|
|
30
|
+
{children}
|
|
31
|
+
</NativeSheetSecurityProvider>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function RenameSheet({ open, onClose }) {
|
|
36
|
+
return (
|
|
37
|
+
<NativeSheet
|
|
38
|
+
open={open}
|
|
39
|
+
height={360}
|
|
40
|
+
backgroundColor="#fff"
|
|
41
|
+
onDismiss={onClose}
|
|
42
|
+
>
|
|
43
|
+
{/* Any React Native subtree: form fields, lists, QR codes, WebViews, etc. */}
|
|
44
|
+
<RenameForm />
|
|
45
|
+
</NativeSheet>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
The caller owns `open` and must set it to `false` from `onDismiss`. Put scrolling
|
|
51
|
+
or asynchronously loaded content inside a fixed-height layout so it can update
|
|
52
|
+
without changing the native detent.
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
MIT
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.getExtOrDefault = {name ->
|
|
3
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties['NativeSheet_' + name]
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
repositories {
|
|
7
|
+
google()
|
|
8
|
+
mavenCentral()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
dependencies {
|
|
12
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
13
|
+
// noinspection DifferentKotlinGradleVersion
|
|
14
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
apply plugin: "com.android.library"
|
|
19
|
+
apply plugin: "kotlin-android"
|
|
20
|
+
apply plugin: "com.facebook.react"
|
|
21
|
+
|
|
22
|
+
def getExtOrIntegerDefault(name) {
|
|
23
|
+
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["NativeSheet_" + name]).toInteger()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
android {
|
|
27
|
+
namespace "com.onekey.nativesheet"
|
|
28
|
+
|
|
29
|
+
compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
|
|
30
|
+
|
|
31
|
+
defaultConfig {
|
|
32
|
+
minSdkVersion getExtOrIntegerDefault("minSdkVersion")
|
|
33
|
+
targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
buildFeatures {
|
|
37
|
+
buildConfig true
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
buildTypes {
|
|
41
|
+
release {
|
|
42
|
+
minifyEnabled false
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
lintOptions {
|
|
47
|
+
disable "GradleCompatible"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
compileOptions {
|
|
51
|
+
sourceCompatibility JavaVersion.VERSION_1_8
|
|
52
|
+
targetCompatibility JavaVersion.VERSION_1_8
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
sourceSets {
|
|
56
|
+
main {
|
|
57
|
+
java.srcDirs += [
|
|
58
|
+
"generated/java",
|
|
59
|
+
"generated/jni"
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
repositories {
|
|
66
|
+
mavenCentral()
|
|
67
|
+
google()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
def kotlin_version = getExtOrDefault("kotlinVersion")
|
|
71
|
+
|
|
72
|
+
dependencies {
|
|
73
|
+
implementation "com.facebook.react:react-android"
|
|
74
|
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
75
|
+
implementation "com.google.android.material:material:1.14.0-alpha05"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
react {
|
|
79
|
+
jsRootDir = file("../src/")
|
|
80
|
+
libraryName = "RNCNativeSheet"
|
|
81
|
+
codegenJavaPackageName = "com.onekey.nativesheet"
|
|
82
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package com.onekey.nativesheet
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.Arguments
|
|
4
|
+
import com.facebook.react.bridge.WritableMap
|
|
5
|
+
import com.facebook.react.uimanager.events.Event
|
|
6
|
+
|
|
7
|
+
internal class NativeSheetDismissEvent(
|
|
8
|
+
surfaceId: Int,
|
|
9
|
+
viewTag: Int,
|
|
10
|
+
private val reason: String,
|
|
11
|
+
) : Event<NativeSheetDismissEvent>(surfaceId, viewTag) {
|
|
12
|
+
override fun getEventName(): String = EVENT_NAME
|
|
13
|
+
|
|
14
|
+
override fun getEventData(): WritableMap = Arguments.createMap().apply {
|
|
15
|
+
putString("reason", reason)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
companion object {
|
|
19
|
+
const val EVENT_NAME = "topDismiss"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
internal class NativeSheetPresentedEvent(
|
|
24
|
+
surfaceId: Int,
|
|
25
|
+
viewTag: Int,
|
|
26
|
+
private val height: Double,
|
|
27
|
+
) : Event<NativeSheetPresentedEvent>(surfaceId, viewTag) {
|
|
28
|
+
override fun getEventName(): String = EVENT_NAME
|
|
29
|
+
|
|
30
|
+
override fun getEventData(): WritableMap = Arguments.createMap().apply {
|
|
31
|
+
putDouble("height", height)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
companion object {
|
|
35
|
+
const val EVENT_NAME = "topPresented"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package com.onekey.nativesheet
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
class NativeSheetPackage : ReactPackage {
|
|
9
|
+
override fun createViewManagers(
|
|
10
|
+
reactContext: ReactApplicationContext,
|
|
11
|
+
): List<ViewManager<*, *>> = listOf(NativeSheetViewManager(reactContext))
|
|
12
|
+
|
|
13
|
+
override fun createNativeModules(
|
|
14
|
+
reactContext: ReactApplicationContext,
|
|
15
|
+
): List<NativeModule> = emptyList()
|
|
16
|
+
}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
package com.onekey.nativesheet
|
|
2
|
+
|
|
3
|
+
import android.annotation.SuppressLint
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.graphics.drawable.GradientDrawable
|
|
6
|
+
import android.view.Gravity
|
|
7
|
+
import android.view.KeyEvent
|
|
8
|
+
import android.view.MotionEvent
|
|
9
|
+
import android.view.View
|
|
10
|
+
import android.view.ViewGroup
|
|
11
|
+
import android.widget.FrameLayout
|
|
12
|
+
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
|
13
|
+
import com.facebook.react.config.ReactFeatureFlags
|
|
14
|
+
import com.facebook.react.uimanager.JSPointerDispatcher
|
|
15
|
+
import com.facebook.react.uimanager.JSTouchDispatcher
|
|
16
|
+
import com.facebook.react.uimanager.RootView
|
|
17
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
18
|
+
import com.facebook.react.uimanager.events.EventDispatcher
|
|
19
|
+
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
20
|
+
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
21
|
+
import java.lang.ref.WeakReference
|
|
22
|
+
import kotlin.math.roundToInt
|
|
23
|
+
|
|
24
|
+
internal object NativeSheetStack {
|
|
25
|
+
private val active = mutableListOf<WeakReference<NativeSheetView>>()
|
|
26
|
+
|
|
27
|
+
private fun compact() {
|
|
28
|
+
active.removeAll { it.get() == null }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fun present(view: NativeSheetView) {
|
|
32
|
+
compact()
|
|
33
|
+
if (active.any { it.get() === view }) return
|
|
34
|
+
view.showDialogInternal()
|
|
35
|
+
if (view.hasDialog()) active.add(WeakReference(view))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
fun dismiss(view: NativeSheetView, reason: String, animated: Boolean) {
|
|
39
|
+
compact()
|
|
40
|
+
val index = active.indexOfFirst { it.get() === view }
|
|
41
|
+
if (index < 0) {
|
|
42
|
+
view.dismissDialogInternal(reason, animated)
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
val targets = active.subList(index, active.size).mapNotNull { it.get() }.asReversed()
|
|
47
|
+
active.subList(index, active.size).clear()
|
|
48
|
+
targets.forEach { target ->
|
|
49
|
+
val targetReason = if (target === view) reason else if (reason == "security") reason else "system"
|
|
50
|
+
target.dismissDialogInternal(targetReason, animated)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
fun didDismiss(view: NativeSheetView) {
|
|
55
|
+
active.removeAll { it.get() == null || it.get() === view }
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* React children mounted in a Dialog are outside their original ReactRootView.
|
|
61
|
+
* This root mirrors React Native Modal's touch dispatch boundary so Pressability,
|
|
62
|
+
* pointer events, and native gesture cancellation keep working after reparenting.
|
|
63
|
+
*/
|
|
64
|
+
private class NativeSheetDialogRootView(
|
|
65
|
+
private val reactContext: ThemedReactContext,
|
|
66
|
+
) : FrameLayout(reactContext), RootView {
|
|
67
|
+
var eventDispatcher: EventDispatcher? = null
|
|
68
|
+
|
|
69
|
+
private val touchDispatcher = JSTouchDispatcher(this)
|
|
70
|
+
private val pointerDispatcher = if (ReactFeatureFlags.dispatchPointerEvents) {
|
|
71
|
+
JSPointerDispatcher(this)
|
|
72
|
+
} else {
|
|
73
|
+
null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
override fun handleException(t: Throwable) {
|
|
77
|
+
reactContext.reactApplicationContext.handleException(RuntimeException(t))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
|
81
|
+
eventDispatcher?.let { dispatcher ->
|
|
82
|
+
touchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
|
|
83
|
+
pointerDispatcher?.handleMotionEvent(event, dispatcher, true)
|
|
84
|
+
}
|
|
85
|
+
return super.onInterceptTouchEvent(event)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@SuppressLint("ClickableViewAccessibility")
|
|
89
|
+
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
90
|
+
eventDispatcher?.let { dispatcher ->
|
|
91
|
+
touchDispatcher.handleTouchEvent(event, dispatcher, reactContext)
|
|
92
|
+
pointerDispatcher?.handleMotionEvent(event, dispatcher, false)
|
|
93
|
+
}
|
|
94
|
+
super.onTouchEvent(event)
|
|
95
|
+
return true
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
override fun onInterceptHoverEvent(event: MotionEvent): Boolean {
|
|
99
|
+
eventDispatcher?.let { pointerDispatcher?.handleMotionEvent(event, it, true) }
|
|
100
|
+
return super.onInterceptHoverEvent(event)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
override fun onHoverEvent(event: MotionEvent): Boolean {
|
|
104
|
+
eventDispatcher?.let { pointerDispatcher?.handleMotionEvent(event, it, false) }
|
|
105
|
+
return super.onHoverEvent(event)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@OptIn(UnstableReactNativeAPI::class)
|
|
109
|
+
override fun onChildStartedNativeGesture(childView: View?, ev: MotionEvent) {
|
|
110
|
+
eventDispatcher?.let { dispatcher ->
|
|
111
|
+
touchDispatcher.onChildStartedNativeGesture(ev, dispatcher, reactContext)
|
|
112
|
+
pointerDispatcher?.onChildStartedNativeGesture(childView, ev, dispatcher)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
override fun onChildEndedNativeGesture(childView: View, ev: MotionEvent) {
|
|
117
|
+
eventDispatcher?.let { touchDispatcher.onChildEndedNativeGesture(ev, it) }
|
|
118
|
+
pointerDispatcher?.onChildEndedNativeGesture()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) = Unit
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
class NativeSheetView(
|
|
125
|
+
private val reactContext: ThemedReactContext,
|
|
126
|
+
) : FrameLayout(reactContext) {
|
|
127
|
+
var open: Boolean = false
|
|
128
|
+
var sheetHeight: Double = 0.0
|
|
129
|
+
var securityBlocked: Boolean = false
|
|
130
|
+
var dismissOnPanDown: Boolean = true
|
|
131
|
+
var dismissOnBackdropPress: Boolean = false
|
|
132
|
+
var dismissOnBackPress: Boolean = true
|
|
133
|
+
var showHandle: Boolean = true
|
|
134
|
+
var cornerRadius: Double = 32.0
|
|
135
|
+
var dimAmount: Double = 0.4
|
|
136
|
+
var sheetBackgroundColor: Int? = null
|
|
137
|
+
var onDismiss: ((String) -> Unit)? = null
|
|
138
|
+
var onPresented: ((Double) -> Unit)? = null
|
|
139
|
+
var eventDispatcher: EventDispatcher? = null
|
|
140
|
+
set(value) {
|
|
141
|
+
field = value
|
|
142
|
+
dialogContent?.eventDispatcher = value
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
private var contentChild: View? = null
|
|
146
|
+
private var dialog: BottomSheetDialog? = null
|
|
147
|
+
private var dialogContent: NativeSheetDialogRootView? = null
|
|
148
|
+
private var committedOpen = false
|
|
149
|
+
private var dismissedForCurrentOpen = false
|
|
150
|
+
private var pendingDismissReason = "system"
|
|
151
|
+
private var dismissNotified = false
|
|
152
|
+
|
|
153
|
+
init {
|
|
154
|
+
visibility = INVISIBLE
|
|
155
|
+
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fun addReactChild(child: View, index: Int) {
|
|
159
|
+
contentChild?.let { existing ->
|
|
160
|
+
if (existing !== child) removeReactChild(existing)
|
|
161
|
+
}
|
|
162
|
+
contentChild = child
|
|
163
|
+
attachChild(dialogContent ?: this, child, index)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
fun getReactChildCount(): Int = if (contentChild == null) 0 else 1
|
|
167
|
+
|
|
168
|
+
fun getReactChildAt(index: Int): View? = if (index == 0) contentChild else null
|
|
169
|
+
|
|
170
|
+
fun removeReactChild(child: View) {
|
|
171
|
+
if (contentChild !== child) return
|
|
172
|
+
(child.parent as? ViewGroup)?.removeView(child)
|
|
173
|
+
contentChild = null
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
fun removeAllReactChildren() {
|
|
177
|
+
contentChild?.let(::removeReactChild)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
fun commitConfiguration() {
|
|
181
|
+
val openChanged = committedOpen != open
|
|
182
|
+
committedOpen = open
|
|
183
|
+
if (!open) dismissedForCurrentOpen = false
|
|
184
|
+
|
|
185
|
+
if (securityBlocked) {
|
|
186
|
+
dismissedForCurrentOpen = open
|
|
187
|
+
NativeSheetStack.dismiss(this, "security", false)
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!open) {
|
|
192
|
+
NativeSheetStack.dismiss(this, "programmatic", true)
|
|
193
|
+
} else if ((openChanged || dialog == null) && !dismissedForCurrentOpen) {
|
|
194
|
+
NativeSheetStack.present(this)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
fun hasDialog(): Boolean = dialog != null
|
|
199
|
+
|
|
200
|
+
internal fun showDialogInternal() {
|
|
201
|
+
if (dialog != null || securityBlocked || !open) return
|
|
202
|
+
val activity = reactContext.currentActivity ?: return
|
|
203
|
+
if (activity.isFinishing || activity.isDestroyed) return
|
|
204
|
+
|
|
205
|
+
val heightPx = dpToPx(sheetHeight.coerceAtLeast(1.0))
|
|
206
|
+
val content = NativeSheetDialogRootView(reactContext).apply {
|
|
207
|
+
eventDispatcher = this@NativeSheetView.eventDispatcher
|
|
208
|
+
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, heightPx)
|
|
209
|
+
background = GradientDrawable().apply {
|
|
210
|
+
shape = GradientDrawable.RECTANGLE
|
|
211
|
+
setColor(sheetBackgroundColor ?: Color.WHITE)
|
|
212
|
+
cornerRadii = floatArrayOf(
|
|
213
|
+
dpToPx(this@NativeSheetView.cornerRadius).toFloat(),
|
|
214
|
+
dpToPx(this@NativeSheetView.cornerRadius).toFloat(),
|
|
215
|
+
dpToPx(this@NativeSheetView.cornerRadius).toFloat(),
|
|
216
|
+
dpToPx(this@NativeSheetView.cornerRadius).toFloat(),
|
|
217
|
+
0f,
|
|
218
|
+
0f,
|
|
219
|
+
0f,
|
|
220
|
+
0f,
|
|
221
|
+
)
|
|
222
|
+
}
|
|
223
|
+
clipToOutline = true
|
|
224
|
+
}
|
|
225
|
+
contentChild?.let { attachChild(content, it, 0) }
|
|
226
|
+
if (showHandle) content.addView(createHandle(), createHandleLayoutParams())
|
|
227
|
+
|
|
228
|
+
pendingDismissReason = "system"
|
|
229
|
+
dismissNotified = false
|
|
230
|
+
val nextDialog = BottomSheetDialog(activity).apply {
|
|
231
|
+
setContentView(content)
|
|
232
|
+
setCancelable(dismissOnPanDown || dismissOnBackPress || dismissOnBackdropPress)
|
|
233
|
+
setCanceledOnTouchOutside(dismissOnBackdropPress)
|
|
234
|
+
dismissWithAnimation = true
|
|
235
|
+
setOnKeyListener { _, keyCode, event ->
|
|
236
|
+
if (keyCode == KeyEvent.KEYCODE_BACK && event.action == KeyEvent.ACTION_UP) {
|
|
237
|
+
if (!dismissOnBackPress) return@setOnKeyListener true
|
|
238
|
+
pendingDismissReason = "back"
|
|
239
|
+
}
|
|
240
|
+
false
|
|
241
|
+
}
|
|
242
|
+
setOnCancelListener {
|
|
243
|
+
if (pendingDismissReason == "system") pendingDismissReason = "backdrop"
|
|
244
|
+
}
|
|
245
|
+
setOnDismissListener {
|
|
246
|
+
handleDialogDismissed()
|
|
247
|
+
}
|
|
248
|
+
setOnShowListener {
|
|
249
|
+
configureShownDialog(this, heightPx)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
dialogContent = content
|
|
254
|
+
dialog = nextDialog
|
|
255
|
+
nextDialog.show()
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private fun configureShownDialog(sheetDialog: BottomSheetDialog, heightPx: Int) {
|
|
259
|
+
sheetDialog.window?.apply {
|
|
260
|
+
setDimAmount(dimAmount.coerceIn(0.0, 1.0).toFloat())
|
|
261
|
+
setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
|
262
|
+
}
|
|
263
|
+
val bottomSheet = sheetDialog.findViewById<FrameLayout>(
|
|
264
|
+
com.google.android.material.R.id.design_bottom_sheet,
|
|
265
|
+
) ?: return
|
|
266
|
+
bottomSheet.layoutParams = bottomSheet.layoutParams.apply { height = heightPx }
|
|
267
|
+
bottomSheet.setBackgroundColor(Color.TRANSPARENT)
|
|
268
|
+
sheetDialog.behavior.apply {
|
|
269
|
+
peekHeight = heightPx
|
|
270
|
+
isFitToContents = true
|
|
271
|
+
isHideable = dismissOnPanDown
|
|
272
|
+
isDraggable = dismissOnPanDown
|
|
273
|
+
skipCollapsed = true
|
|
274
|
+
state = BottomSheetBehavior.STATE_EXPANDED
|
|
275
|
+
addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
|
|
276
|
+
override fun onStateChanged(bottomSheet: View, newState: Int) {
|
|
277
|
+
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
|
|
278
|
+
pendingDismissReason = "pan"
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
override fun onSlide(bottomSheet: View, slideOffset: Float) = Unit
|
|
283
|
+
})
|
|
284
|
+
}
|
|
285
|
+
onPresented?.invoke(pxToDp(heightPx))
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
internal fun dismissDialogInternal(reason: String, animated: Boolean) {
|
|
289
|
+
val currentDialog = dialog ?: return
|
|
290
|
+
pendingDismissReason = reason
|
|
291
|
+
if (animated) {
|
|
292
|
+
currentDialog.dismissWithAnimation = true
|
|
293
|
+
} else {
|
|
294
|
+
currentDialog.dismissWithAnimation = false
|
|
295
|
+
}
|
|
296
|
+
currentDialog.dismiss()
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private fun handleDialogDismissed() {
|
|
300
|
+
val child = contentChild
|
|
301
|
+
dialogContent = null
|
|
302
|
+
dialog = null
|
|
303
|
+
child?.let { attachChild(this, it, 0) }
|
|
304
|
+
NativeSheetStack.didDismiss(this)
|
|
305
|
+
if (!dismissNotified) {
|
|
306
|
+
dismissNotified = true
|
|
307
|
+
dismissedForCurrentOpen = committedOpen
|
|
308
|
+
onDismiss?.invoke(pendingDismissReason)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
fun onDropViewInstance() {
|
|
313
|
+
NativeSheetStack.dismiss(this, "system", false)
|
|
314
|
+
onDismiss = null
|
|
315
|
+
onPresented = null
|
|
316
|
+
eventDispatcher = null
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private fun createHandle(): View = View(reactContext).apply {
|
|
320
|
+
background = GradientDrawable().apply {
|
|
321
|
+
shape = GradientDrawable.RECTANGLE
|
|
322
|
+
cornerRadius = dpToPx(2.5).toFloat()
|
|
323
|
+
setColor(0x33000000)
|
|
324
|
+
}
|
|
325
|
+
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
private fun createHandleLayoutParams() = LayoutParams(dpToPx(36.0), dpToPx(5.0)).apply {
|
|
329
|
+
gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
|
|
330
|
+
topMargin = dpToPx(8.0)
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private fun attachChild(parent: ViewGroup, child: View, index: Int) {
|
|
334
|
+
if (child.parent === parent) return
|
|
335
|
+
(child.parent as? ViewGroup)?.removeView(child)
|
|
336
|
+
val params = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
|
337
|
+
parent.addView(child, index.coerceIn(0, parent.childCount), params)
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
private fun dpToPx(value: Double): Int =
|
|
341
|
+
(value * resources.displayMetrics.density).roundToInt()
|
|
342
|
+
|
|
343
|
+
private fun pxToDp(value: Int): Double = value / resources.displayMetrics.density.toDouble()
|
|
344
|
+
|
|
345
|
+
override fun onDetachedFromWindow() {
|
|
346
|
+
super.onDetachedFromWindow()
|
|
347
|
+
if (!isAttachedToWindow) NativeSheetStack.dismiss(this, "system", false)
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
override fun onAttachedToWindow() {
|
|
351
|
+
super.onAttachedToWindow()
|
|
352
|
+
commitConfiguration()
|
|
353
|
+
}
|
|
354
|
+
}
|