react-native-unified-action-sheet 0.1.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/CHANGELOG.md +41 -0
- package/LICENSE +20 -0
- package/README.md +167 -0
- package/android/build.gradle +51 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/unifiedactionsheet/ActionSheetOptions.kt +134 -0
- package/android/src/main/java/com/unifiedactionsheet/SheetContentBuilder.kt +159 -0
- package/android/src/main/java/com/unifiedactionsheet/SheetPresenters.kt +214 -0
- package/android/src/main/java/com/unifiedactionsheet/SheetTheming.kt +56 -0
- package/android/src/main/java/com/unifiedactionsheet/UnifiedActionSheetModule.kt +126 -0
- package/android/src/main/java/com/unifiedactionsheet/UnifiedActionSheetPackage.kt +30 -0
- package/ios/UnifiedActionSheet.h +5 -0
- package/ios/UnifiedActionSheet.mm +96 -0
- package/ios/UnifiedActionSheetImpl.swift +240 -0
- package/jest/index.d.ts +12 -0
- package/jest/index.js +46 -0
- package/lib/commonjs/NativeUnifiedActionSheet.js +9 -0
- package/lib/commonjs/NativeUnifiedActionSheet.js.map +1 -0
- package/lib/commonjs/action-sheet-options.interface.js +2 -0
- package/lib/commonjs/action-sheet-options.interface.js.map +1 -0
- package/lib/commonjs/index.js +104 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/module/NativeUnifiedActionSheet.js +5 -0
- package/lib/module/NativeUnifiedActionSheet.js.map +1 -0
- package/lib/module/action-sheet-options.interface.js +2 -0
- package/lib/module/action-sheet-options.interface.js.map +1 -0
- package/lib/module/index.js +97 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/src/NativeUnifiedActionSheet.d.ts +29 -0
- package/lib/typescript/commonjs/src/NativeUnifiedActionSheet.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/action-sheet-options.interface.d.ts +29 -0
- package/lib/typescript/commonjs/src/action-sheet-options.interface.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/index.d.ts +6 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/src/NativeUnifiedActionSheet.d.ts +29 -0
- package/lib/typescript/module/src/NativeUnifiedActionSheet.d.ts.map +1 -0
- package/lib/typescript/module/src/action-sheet-options.interface.d.ts +29 -0
- package/lib/typescript/module/src/action-sheet-options.interface.d.ts.map +1 -0
- package/lib/typescript/module/src/index.d.ts +6 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -0
- package/package.json +155 -0
- package/react-native-unified-action-sheet.podspec +23 -0
- package/src/NativeUnifiedActionSheet.ts +29 -0
- package/src/action-sheet-options.interface.ts +43 -0
- package/src/index.tsx +131 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
package com.unifiedactionsheet
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.app.Dialog
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.graphics.Color
|
|
7
|
+
import android.graphics.Rect
|
|
8
|
+
import android.graphics.drawable.ColorDrawable
|
|
9
|
+
import android.graphics.drawable.GradientDrawable
|
|
10
|
+
import android.view.Gravity
|
|
11
|
+
import android.view.View
|
|
12
|
+
import android.view.View.MeasureSpec
|
|
13
|
+
import android.view.Window
|
|
14
|
+
import android.view.WindowManager
|
|
15
|
+
import android.widget.FrameLayout
|
|
16
|
+
import androidx.appcompat.app.AppCompatDialog
|
|
17
|
+
import androidx.core.graphics.Insets
|
|
18
|
+
import androidx.core.view.ViewCompat
|
|
19
|
+
import androidx.core.view.WindowInsetsCompat
|
|
20
|
+
|
|
21
|
+
private const val MAX_HEIGHT_PERCENT = 90
|
|
22
|
+
|
|
23
|
+
internal interface SheetPresenter {
|
|
24
|
+
fun build(activity: Activity, options: ActionSheetOptions, onSelect: (Dialog, Int) -> Unit): Dialog
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
internal object CenteredDialogPresenter : SheetPresenter {
|
|
28
|
+
private const val CENTERED_CORNER_RADIUS_DP = 28
|
|
29
|
+
private const val CENTERED_MARGIN_DP = 24
|
|
30
|
+
private const val CENTERED_MIN_WIDTH_DP = 280
|
|
31
|
+
private const val CENTERED_MAX_WIDTH_DP = 560
|
|
32
|
+
|
|
33
|
+
override fun build(
|
|
34
|
+
activity: Activity,
|
|
35
|
+
options: ActionSheetOptions,
|
|
36
|
+
onSelect: (Dialog, Int) -> Unit,
|
|
37
|
+
): Dialog {
|
|
38
|
+
val isDark = isDarkAppearance(activity, options.userInterfaceStyle)
|
|
39
|
+
val palette = paletteFor(isDark)
|
|
40
|
+
val dialog = AppCompatDialog(activity, dialogTheme(isDark))
|
|
41
|
+
dialog.supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
|
|
42
|
+
|
|
43
|
+
val context: Context = dialog.context
|
|
44
|
+
val container = buildContent(context, options, palette) { index -> onSelect(dialog, index) }
|
|
45
|
+
|
|
46
|
+
dialog.setContentView(container)
|
|
47
|
+
dialog.setCanceledOnTouchOutside(true)
|
|
48
|
+
|
|
49
|
+
val background = GradientDrawable().apply {
|
|
50
|
+
cornerRadius = dp(context, CENTERED_CORNER_RADIUS_DP).toFloat()
|
|
51
|
+
setColor(palette.surface)
|
|
52
|
+
}
|
|
53
|
+
dialog.window?.setBackgroundDrawable(background)
|
|
54
|
+
container.background = background.constantState?.newDrawable() ?: background
|
|
55
|
+
container.clipToOutline = true
|
|
56
|
+
|
|
57
|
+
val metrics = context.resources.displayMetrics
|
|
58
|
+
val width = (metrics.widthPixels - 2 * dp(context, CENTERED_MARGIN_DP))
|
|
59
|
+
.coerceAtMost(dp(context, CENTERED_MAX_WIDTH_DP))
|
|
60
|
+
.coerceAtLeast(minOf(dp(context, CENTERED_MIN_WIDTH_DP), metrics.widthPixels))
|
|
61
|
+
dialog.window?.setLayout(width, WindowManager.LayoutParams.WRAP_CONTENT)
|
|
62
|
+
|
|
63
|
+
val maxHeight = (metrics.heightPixels * MAX_HEIGHT_PERCENT) / 100
|
|
64
|
+
container.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
|
|
65
|
+
override fun onLayoutChange(
|
|
66
|
+
view: View,
|
|
67
|
+
left: Int,
|
|
68
|
+
top: Int,
|
|
69
|
+
right: Int,
|
|
70
|
+
bottom: Int,
|
|
71
|
+
oldLeft: Int,
|
|
72
|
+
oldTop: Int,
|
|
73
|
+
oldRight: Int,
|
|
74
|
+
oldBottom: Int,
|
|
75
|
+
) {
|
|
76
|
+
if (container.height <= maxHeight) return
|
|
77
|
+
container.removeOnLayoutChangeListener(this)
|
|
78
|
+
dialog.window?.setLayout(width, maxHeight)
|
|
79
|
+
}
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
return dialog
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
internal class AnchoredDialogPresenter(private val anchorRect: Rect) : SheetPresenter {
|
|
87
|
+
override fun build(
|
|
88
|
+
activity: Activity,
|
|
89
|
+
options: ActionSheetOptions,
|
|
90
|
+
onSelect: (Dialog, Int) -> Unit,
|
|
91
|
+
): Dialog {
|
|
92
|
+
val isDark = isDarkAppearance(activity, options.userInterfaceStyle)
|
|
93
|
+
val palette = paletteFor(isDark)
|
|
94
|
+
val dialog = AppCompatDialog(activity, dialogTheme(isDark))
|
|
95
|
+
dialog.supportRequestWindowFeature(Window.FEATURE_NO_TITLE)
|
|
96
|
+
|
|
97
|
+
val context: Context = dialog.context
|
|
98
|
+
val container = buildContent(context, options, palette, includeCancelRow = false) { index ->
|
|
99
|
+
onSelect(dialog, index)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
container.background = GradientDrawable().apply {
|
|
103
|
+
cornerRadius = dp(context, ANCHORED_CORNER_RADIUS_DP).toFloat()
|
|
104
|
+
setColor(palette.surface)
|
|
105
|
+
}
|
|
106
|
+
container.clipToOutline = true
|
|
107
|
+
container.elevation = dp(context, ANCHORED_ELEVATION_DP).toFloat()
|
|
108
|
+
|
|
109
|
+
val pad = dp(context, SHADOW_PADDING_DP)
|
|
110
|
+
val wrapper = FrameLayout(context).apply {
|
|
111
|
+
setPadding(pad, pad, pad, pad)
|
|
112
|
+
clipChildren = false
|
|
113
|
+
clipToPadding = false
|
|
114
|
+
addView(
|
|
115
|
+
container,
|
|
116
|
+
FrameLayout.LayoutParams(
|
|
117
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
118
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
119
|
+
),
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
dialog.setContentView(wrapper)
|
|
123
|
+
dialog.setCanceledOnTouchOutside(true)
|
|
124
|
+
|
|
125
|
+
val window = dialog.window ?: return dialog
|
|
126
|
+
window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
|
|
127
|
+
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
|
128
|
+
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
|
|
129
|
+
window.setGravity(Gravity.TOP or Gravity.LEFT)
|
|
130
|
+
|
|
131
|
+
// The rect arrives in the activity content view's space, which sits below
|
|
132
|
+
// the status bar unless the app is edge to edge, while this window is laid
|
|
133
|
+
// out in screen space. Offset by the content view's position to line up.
|
|
134
|
+
val anchorOnScreen = Rect(anchorRect).apply {
|
|
135
|
+
val offset = IntArray(2)
|
|
136
|
+
activity.findViewById<View>(android.R.id.content)?.getLocationOnScreen(offset)
|
|
137
|
+
offset(offset[0], offset[1])
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
val decor = activity.window.decorView
|
|
141
|
+
val insets = ViewCompat.getRootWindowInsets(decor)?.getInsets(
|
|
142
|
+
WindowInsetsCompat.Type.systemBars() or
|
|
143
|
+
WindowInsetsCompat.Type.displayCutout() or
|
|
144
|
+
WindowInsetsCompat.Type.ime(),
|
|
145
|
+
) ?: Insets.NONE
|
|
146
|
+
val margin = dp(context, EDGE_MARGIN_DP)
|
|
147
|
+
val usable = Rect(
|
|
148
|
+
insets.left + margin,
|
|
149
|
+
insets.top + margin,
|
|
150
|
+
decor.width - insets.right - margin,
|
|
151
|
+
decor.height - insets.bottom - margin,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
val maxWidth = minOf(dp(context, ANCHORED_MAX_WIDTH_DP), usable.width())
|
|
155
|
+
container.measure(
|
|
156
|
+
MeasureSpec.makeMeasureSpec(maxWidth, MeasureSpec.AT_MOST),
|
|
157
|
+
MeasureSpec.makeMeasureSpec(usable.height(), MeasureSpec.AT_MOST),
|
|
158
|
+
)
|
|
159
|
+
val width = container.measuredWidth
|
|
160
|
+
.coerceIn(minOf(dp(context, ANCHORED_MIN_WIDTH_DP), maxWidth), maxWidth)
|
|
161
|
+
container.measure(
|
|
162
|
+
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
|
163
|
+
MeasureSpec.makeMeasureSpec(usable.height(), MeasureSpec.AT_MOST),
|
|
164
|
+
)
|
|
165
|
+
val desiredHeight = container.measuredHeight
|
|
166
|
+
|
|
167
|
+
val gap = dp(context, ANCHOR_GAP_DP)
|
|
168
|
+
val spaceBelow = usable.bottom - (anchorOnScreen.bottom + gap)
|
|
169
|
+
val spaceAbove = (anchorOnScreen.top - gap) - usable.top
|
|
170
|
+
var height = desiredHeight
|
|
171
|
+
var y = anchorOnScreen.bottom + gap
|
|
172
|
+
if (desiredHeight > spaceBelow) {
|
|
173
|
+
if (desiredHeight <= spaceAbove) {
|
|
174
|
+
y = anchorOnScreen.top - gap - desiredHeight
|
|
175
|
+
} else {
|
|
176
|
+
height = minOf(desiredHeight, maxOf(spaceBelow, spaceAbove))
|
|
177
|
+
.coerceAtLeast(minOf(desiredHeight, dp(context, ANCHORED_MIN_HEIGHT_DP)))
|
|
178
|
+
y = if (spaceBelow >= spaceAbove) {
|
|
179
|
+
anchorOnScreen.bottom + gap
|
|
180
|
+
} else {
|
|
181
|
+
anchorOnScreen.top - gap - height
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
y = y.coerceIn(usable.top, maxOf(usable.top, usable.bottom - height))
|
|
186
|
+
|
|
187
|
+
val rtl =
|
|
188
|
+
context.resources.configuration.layoutDirection == View.LAYOUT_DIRECTION_RTL
|
|
189
|
+
val x = when {
|
|
190
|
+
options.anchorAlignment == AnchorAlignment.CENTER -> anchorOnScreen.centerX() - width / 2
|
|
191
|
+
rtl -> anchorOnScreen.right - width
|
|
192
|
+
else -> anchorOnScreen.left
|
|
193
|
+
}.coerceIn(usable.left, maxOf(usable.left, usable.right - width))
|
|
194
|
+
|
|
195
|
+
val attributes = window.attributes
|
|
196
|
+
attributes.x = x - pad
|
|
197
|
+
attributes.y = y - pad
|
|
198
|
+
window.attributes = attributes
|
|
199
|
+
window.setLayout(width + 2 * pad, height + 2 * pad)
|
|
200
|
+
|
|
201
|
+
return dialog
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private companion object {
|
|
205
|
+
const val ANCHORED_MIN_WIDTH_DP = 180
|
|
206
|
+
const val ANCHORED_MAX_WIDTH_DP = 280
|
|
207
|
+
const val ANCHORED_MIN_HEIGHT_DP = 96
|
|
208
|
+
const val ANCHORED_CORNER_RADIUS_DP = 8
|
|
209
|
+
const val ANCHORED_ELEVATION_DP = 3
|
|
210
|
+
const val SHADOW_PADDING_DP = 8
|
|
211
|
+
const val EDGE_MARGIN_DP = 8
|
|
212
|
+
const val ANCHOR_GAP_DP = 4
|
|
213
|
+
}
|
|
214
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
package com.unifiedactionsheet
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.content.Context
|
|
5
|
+
import android.content.res.Configuration
|
|
6
|
+
import android.util.TypedValue
|
|
7
|
+
import androidx.appcompat.R as AppCompatR
|
|
8
|
+
|
|
9
|
+
internal data class SheetPalette(
|
|
10
|
+
val primaryText: Int,
|
|
11
|
+
val secondaryText: Int,
|
|
12
|
+
val error: Int,
|
|
13
|
+
val surface: Int,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
private val LIGHT_PALETTE = SheetPalette(
|
|
17
|
+
primaryText = 0xFF1D1B20.toInt(),
|
|
18
|
+
secondaryText = 0xFF49454F.toInt(),
|
|
19
|
+
error = 0xFFB3261E.toInt(),
|
|
20
|
+
surface = 0xFFECE6F0.toInt(),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
private val DARK_PALETTE = SheetPalette(
|
|
24
|
+
primaryText = 0xFFE6E0E9.toInt(),
|
|
25
|
+
secondaryText = 0xFFCAC4D0.toInt(),
|
|
26
|
+
error = 0xFFF2B8B5.toInt(),
|
|
27
|
+
surface = 0xFF2B2930.toInt(),
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
internal fun isDarkAppearance(activity: Activity, appearance: ForcedAppearance): Boolean =
|
|
31
|
+
when (appearance) {
|
|
32
|
+
ForcedAppearance.LIGHT -> false
|
|
33
|
+
ForcedAppearance.DARK -> true
|
|
34
|
+
ForcedAppearance.SYSTEM -> {
|
|
35
|
+
val nightMode = activity.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
|
|
36
|
+
nightMode == Configuration.UI_MODE_NIGHT_YES
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
internal fun dialogTheme(isDark: Boolean): Int = if (isDark) {
|
|
41
|
+
AppCompatR.style.Theme_AppCompat_Dialog
|
|
42
|
+
} else {
|
|
43
|
+
AppCompatR.style.Theme_AppCompat_Light_Dialog
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
internal fun paletteFor(isDark: Boolean): SheetPalette = if (isDark) DARK_PALETTE else LIGHT_PALETTE
|
|
47
|
+
|
|
48
|
+
internal fun selectableItemBackgroundRes(context: Context): Int {
|
|
49
|
+
val outValue = TypedValue()
|
|
50
|
+
context.theme.resolveAttribute(android.R.attr.selectableItemBackground, outValue, true)
|
|
51
|
+
|
|
52
|
+
return outValue.resourceId
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
internal fun dp(context: Context, value: Int): Int =
|
|
56
|
+
(value * context.resources.displayMetrics.density).toInt()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
package com.unifiedactionsheet
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.app.Dialog
|
|
5
|
+
import com.facebook.react.bridge.LifecycleEventListener
|
|
6
|
+
import com.facebook.react.bridge.Promise
|
|
7
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
8
|
+
import com.facebook.react.bridge.ReadableMap
|
|
9
|
+
import com.facebook.react.bridge.UiThreadUtil
|
|
10
|
+
import java.util.concurrent.atomic.AtomicBoolean
|
|
11
|
+
|
|
12
|
+
class UnifiedActionSheetModule(reactContext: ReactApplicationContext) :
|
|
13
|
+
NativeUnifiedActionSheetSpec(reactContext), LifecycleEventListener {
|
|
14
|
+
|
|
15
|
+
init {
|
|
16
|
+
reactContext.addLifecycleEventListener(this)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private val openDialogs = mutableListOf<Dialog>()
|
|
20
|
+
|
|
21
|
+
private val dismissedByApi = mutableSetOf<Dialog>()
|
|
22
|
+
|
|
23
|
+
override fun showActionSheetWithOptions(options: ReadableMap, promise: Promise) {
|
|
24
|
+
val parsed =
|
|
25
|
+
ActionSheetOptions.fromReadableMap(
|
|
26
|
+
options,
|
|
27
|
+
reactApplicationContext.resources.displayMetrics.density,
|
|
28
|
+
)
|
|
29
|
+
val activity = reactApplicationContext.currentActivity
|
|
30
|
+
?: return promise.reject(
|
|
31
|
+
"E_NO_ACTIVITY",
|
|
32
|
+
"No current activity to attach the action sheet to.",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
UiThreadUtil.runOnUiThread {
|
|
36
|
+
presentSheet(activity, parsed, promise)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
override fun dismissActionSheet() {
|
|
41
|
+
UiThreadUtil.runOnUiThread {
|
|
42
|
+
openDialogs.lastOrNull()?.let { dialog ->
|
|
43
|
+
dismissedByApi.add(dialog)
|
|
44
|
+
dialog.dismiss()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
override fun dismissAllActionSheets() {
|
|
50
|
+
UiThreadUtil.runOnUiThread {
|
|
51
|
+
openDialogs.toList().forEach { dialog ->
|
|
52
|
+
dismissedByApi.add(dialog)
|
|
53
|
+
dialog.dismiss()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
override fun onHostResume() = Unit
|
|
59
|
+
|
|
60
|
+
override fun onHostPause() = Unit
|
|
61
|
+
|
|
62
|
+
override fun onHostDestroy() {
|
|
63
|
+
UiThreadUtil.runOnUiThread { dismissAllOpenDialogs() }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun invalidate() {
|
|
67
|
+
reactApplicationContext.removeLifecycleEventListener(this)
|
|
68
|
+
UiThreadUtil.runOnUiThread { dismissAllOpenDialogs() }
|
|
69
|
+
super.invalidate()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private fun dismissAllOpenDialogs() {
|
|
73
|
+
openDialogs.toList().forEach { it.dismiss() }
|
|
74
|
+
openDialogs.clear()
|
|
75
|
+
dismissedByApi.clear()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private fun presentSheet(
|
|
79
|
+
activity: Activity,
|
|
80
|
+
options: ActionSheetOptions,
|
|
81
|
+
promise: Promise,
|
|
82
|
+
) {
|
|
83
|
+
val resolved = AtomicBoolean(false)
|
|
84
|
+
val resolveOnce: (Int?) -> Unit = { index ->
|
|
85
|
+
if (resolved.compareAndSet(false, true)) {
|
|
86
|
+
promise.resolve(index ?: -1)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
val presenter: SheetPresenter = when (options.presentationStyle) {
|
|
91
|
+
PresentationStyle.CENTERED -> CenteredDialogPresenter
|
|
92
|
+
PresentationStyle.ANCHORED -> {
|
|
93
|
+
val rect = options.anchorRect
|
|
94
|
+
if (rect != null && rect.width() > 0 && rect.height() > 0) {
|
|
95
|
+
AnchoredDialogPresenter(rect)
|
|
96
|
+
} else {
|
|
97
|
+
CenteredDialogPresenter
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
val dialog = presenter.build(activity, options) { presented, index ->
|
|
103
|
+
presented.dismiss()
|
|
104
|
+
resolveOnce(index)
|
|
105
|
+
}
|
|
106
|
+
openDialogs.add(dialog)
|
|
107
|
+
|
|
108
|
+
dialog.setOnCancelListener { resolveOnce(options.cancelButtonIndex) }
|
|
109
|
+
dialog.setOnDismissListener {
|
|
110
|
+
openDialogs.remove(dialog)
|
|
111
|
+
if (dismissedByApi.remove(dialog)) {
|
|
112
|
+
resolveOnce(DISMISSED_BY_API)
|
|
113
|
+
} else {
|
|
114
|
+
resolveOnce(options.cancelButtonIndex)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
dialog.show()
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
companion object {
|
|
122
|
+
const val NAME = NativeUnifiedActionSheetSpec.NAME
|
|
123
|
+
|
|
124
|
+
private const val DISMISSED_BY_API = -2
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
package com.unifiedactionsheet
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.BaseReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
|
|
9
|
+
class UnifiedActionSheetPackage : BaseReactPackage() {
|
|
10
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
11
|
+
return if (name == UnifiedActionSheetModule.NAME) {
|
|
12
|
+
UnifiedActionSheetModule(reactContext)
|
|
13
|
+
} else {
|
|
14
|
+
null
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
|
|
19
|
+
mapOf(
|
|
20
|
+
UnifiedActionSheetModule.NAME to ReactModuleInfo(
|
|
21
|
+
name = UnifiedActionSheetModule.NAME,
|
|
22
|
+
className = UnifiedActionSheetModule.NAME,
|
|
23
|
+
canOverrideExistingModule = false,
|
|
24
|
+
needsEagerInit = false,
|
|
25
|
+
isCxxModule = false,
|
|
26
|
+
isTurboModule = true
|
|
27
|
+
)
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#import "UnifiedActionSheet.h"
|
|
2
|
+
|
|
3
|
+
#import <RCTTypeSafety/RCTConvertHelpers.h>
|
|
4
|
+
#import <UnifiedActionSheetSpec/UnifiedActionSheetSpec.h>
|
|
5
|
+
|
|
6
|
+
#import "react_native_unified_action_sheet-Swift.h"
|
|
7
|
+
|
|
8
|
+
@interface UnifiedActionSheet () <NativeUnifiedActionSheetSpec>
|
|
9
|
+
@end
|
|
10
|
+
|
|
11
|
+
@implementation UnifiedActionSheet
|
|
12
|
+
|
|
13
|
+
RCT_EXPORT_MODULE()
|
|
14
|
+
|
|
15
|
+
+ (BOOL)requiresMainQueueSetup
|
|
16
|
+
{
|
|
17
|
+
return NO;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/// Only the keys the iOS presentation understands are forwarded; the
|
|
21
|
+
/// Android-only keys in the shared spec are ignored here.
|
|
22
|
+
RCT_EXPORT_METHOD(showActionSheetWithOptions
|
|
23
|
+
: (JS::NativeUnifiedActionSheet::SpecShowActionSheetWithOptionsOptions &)options resolve
|
|
24
|
+
: (RCTPromiseResolveBlock)resolve reject
|
|
25
|
+
: (RCTPromiseRejectBlock)reject)
|
|
26
|
+
{
|
|
27
|
+
NSMutableDictionary *payload = [NSMutableDictionary new];
|
|
28
|
+
|
|
29
|
+
payload[@"options"] = RCTConvertVecToArray(options.options(), ^id(NSString *element) {
|
|
30
|
+
return element;
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (options.cancelButtonIndex()) {
|
|
34
|
+
payload[@"cancelButtonIndex"] = @(*options.cancelButtonIndex());
|
|
35
|
+
}
|
|
36
|
+
if (options.destructiveButtonIndices()) {
|
|
37
|
+
payload[@"destructiveButtonIndices"] =
|
|
38
|
+
RCTConvertVecToArray(*options.destructiveButtonIndices(), ^id(double element) {
|
|
39
|
+
return @(element);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
if (options.disabledButtonIndices()) {
|
|
43
|
+
payload[@"disabledButtonIndices"] = RCTConvertVecToArray(*options.disabledButtonIndices(), ^id(double element) {
|
|
44
|
+
return @(element);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
payload[@"title"] = options.title();
|
|
49
|
+
payload[@"message"] = options.message();
|
|
50
|
+
payload[@"tintColor"] = options.tintColor();
|
|
51
|
+
payload[@"cancelButtonTintColor"] = options.cancelButtonTintColor();
|
|
52
|
+
payload[@"userInterfaceStyle"] = options.userInterfaceStyle();
|
|
53
|
+
payload[@"destructiveColor"] = options.destructiveColor();
|
|
54
|
+
payload[@"presentationStyle"] = options.presentationStyle();
|
|
55
|
+
|
|
56
|
+
// The anchor arrives already measured from the ref on the JS side, so this
|
|
57
|
+
// module never resolves a view and needs no React Native view API.
|
|
58
|
+
auto anchorRect = options.anchorRect();
|
|
59
|
+
if (anchorRect.has_value()) {
|
|
60
|
+
payload[@"anchorRect"] = @{
|
|
61
|
+
@"x" : @(anchorRect->x()),
|
|
62
|
+
@"y" : @(anchorRect->y()),
|
|
63
|
+
@"width" : @(anchorRect->width()),
|
|
64
|
+
@"height" : @(anchorRect->height()),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
69
|
+
[UnifiedActionSheetImpl.shared showWithOptions:payload
|
|
70
|
+
completion:^(NSInteger buttonIndex) {
|
|
71
|
+
resolve(@(buttonIndex));
|
|
72
|
+
}];
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
RCT_EXPORT_METHOD(dismissActionSheet)
|
|
77
|
+
{
|
|
78
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
79
|
+
[UnifiedActionSheetImpl.shared dismiss];
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
RCT_EXPORT_METHOD(dismissAllActionSheets)
|
|
84
|
+
{
|
|
85
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
86
|
+
[UnifiedActionSheetImpl.shared dismissAll];
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
|
|
91
|
+
(const facebook::react::ObjCTurboModule::InitParams &)params
|
|
92
|
+
{
|
|
93
|
+
return std::make_shared<facebook::react::NativeUnifiedActionSheetSpecJSI>(params);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
@end
|