react-native-unified-action-sheet 0.2.0 → 0.3.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 +33 -1
- package/README.md +25 -113
- package/android/build.gradle +28 -1
- package/android/src/main/java/com/unifiedactionsheet/ActionSheetOptions.kt +6 -2
- package/android/src/main/java/com/unifiedactionsheet/UnifiedActionSheetModule.kt +6 -0
- package/android/src/material/java/com/unifiedactionsheet/BottomSheetPresenterFactory.kt +10 -0
- package/android/src/material/java/com/unifiedactionsheet/MaterialBottomSheetPresenter.kt +90 -0
- package/android/src/material/res/values/unified_action_sheet_bottom_sheet.xml +22 -0
- package/android/src/noMaterial/java/com/unifiedactionsheet/BottomSheetPresenterFactory.kt +10 -0
- package/app.plugin.js +1 -0
- package/ios/BottomSheetViewController.swift +208 -0
- package/ios/UnifiedActionSheet.mm +26 -1
- package/ios/UnifiedActionSheetImpl.swift +147 -7
- package/lib/commonjs/NativeUnifiedActionSheet.js.map +1 -1
- package/lib/commonjs/index.js +18 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/module/NativeUnifiedActionSheet.js.map +1 -1
- package/lib/module/index.js +18 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/commonjs/src/NativeUnifiedActionSheet.d.ts +3 -0
- package/lib/typescript/commonjs/src/NativeUnifiedActionSheet.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/action-sheet-options.interface.d.ts +1 -1
- package/lib/typescript/commonjs/src/action-sheet-options.interface.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/NativeUnifiedActionSheet.d.ts +3 -0
- package/lib/typescript/module/src/NativeUnifiedActionSheet.d.ts.map +1 -1
- package/lib/typescript/module/src/action-sheet-options.interface.d.ts +1 -1
- package/lib/typescript/module/src/action-sheet-options.interface.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/package.json +12 -2
- package/plugin/withUnifiedActionSheet.js +58 -0
- package/src/NativeUnifiedActionSheet.ts +5 -0
- package/src/action-sheet-options.interface.ts +1 -1
- package/src/index.tsx +35 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,37 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.0]
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `presentationStyle: 'bottom'`: a native bottom sheet on both platforms, with
|
|
15
|
+
a drag handle and swipe to dismiss. A long list opens part-way and drags up
|
|
16
|
+
to expand. On iOS it is a `UISheetPresentationController` sheet with native
|
|
17
|
+
rows (on iPad, the action sheet's popover instead); on Android, a Material 3
|
|
18
|
+
bottom sheet. `anchor` is ignored for it.
|
|
19
|
+
- Material is opt-in, so apps that don't use the bottom sheet stay free of the
|
|
20
|
+
dependency. Set `unifiedActionSheet.material=true` in
|
|
21
|
+
`android/gradle.properties`. Only then does the library add
|
|
22
|
+
`com.google.android.material` (1.12.0 by default; override it with
|
|
23
|
+
`ext.materialVersion` or `unifiedActionSheet.materialVersion`).
|
|
24
|
+
- An Expo config plugin that writes those properties:
|
|
25
|
+
`plugins: [['react-native-unified-action-sheet', { material: true }]]`, with
|
|
26
|
+
an optional `materialVersion`. `@expo/config-plugins` is an optional peer
|
|
27
|
+
dependency.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- On iOS, a sheet, alert or prompt stayed on screen after a JS reload (pressing
|
|
32
|
+
`r` in Metro). It now closes on reload, as it already did on Android, and its
|
|
33
|
+
promise is left unresolved, since it belonged to the discarded runtime.
|
|
34
|
+
|
|
35
|
+
### Notes
|
|
36
|
+
|
|
37
|
+
- Without the Material opt-in, `'bottom'` on Android falls back to the centered
|
|
38
|
+
dialog, and warns once in development.
|
|
39
|
+
- Prompts do not support `'bottom'`; they stay centered on both platforms.
|
|
40
|
+
|
|
10
41
|
## [0.2.0]
|
|
11
42
|
|
|
12
43
|
### Added
|
|
@@ -75,7 +106,8 @@ Initial release.
|
|
|
75
106
|
- No `com.google.android.material` dependency, and no runtime dependencies —
|
|
76
107
|
`react` and `react-native` are peers.
|
|
77
108
|
|
|
78
|
-
[unreleased]: https://github.com/galikvalkin/react-native-unified-action-sheet/compare/v0.
|
|
109
|
+
[unreleased]: https://github.com/galikvalkin/react-native-unified-action-sheet/compare/v0.3.0...HEAD
|
|
110
|
+
[0.3.0]: https://github.com/galikvalkin/react-native-unified-action-sheet/compare/v0.2.0...v0.3.0
|
|
79
111
|
[0.2.0]: https://github.com/galikvalkin/react-native-unified-action-sheet/compare/v0.1.1...v0.2.0
|
|
80
112
|
[0.1.1]: https://github.com/galikvalkin/react-native-unified-action-sheet/compare/v0.1.0...v0.1.1
|
|
81
113
|
[0.1.0]: https://github.com/galikvalkin/react-native-unified-action-sheet/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
# react-native-unified-action-sheet
|
|
2
2
|
|
|
3
|
-
Unified action sheet
|
|
3
|
+
Unified native action sheet for React Native, one API on iOS and Android.
|
|
4
4
|
|
|
5
|
-
|
|
|
5
|
+
| | Light | Dark |
|
|
6
6
|
| :---: | :---: | :---: |
|
|
7
|
-
| <img src="https://raw.githubusercontent.com/galikvalkin/react-native-unified-action-sheet/HEAD/docs/android-demo.gif" width="280" alt="Android demo" /> | <img src="https://raw.githubusercontent.com/galikvalkin/react-native-unified-action-sheet/HEAD/docs/android-demo-dark-mode.gif" width="280" alt="Android
|
|
7
|
+
| **Android** | <img src="https://raw.githubusercontent.com/galikvalkin/react-native-unified-action-sheet/HEAD/docs/images/android-demo-light-mode.gif" width="280" alt="Android demo in light mode" /> | <img src="https://raw.githubusercontent.com/galikvalkin/react-native-unified-action-sheet/HEAD/docs/images/android-demo-dark-mode.gif" width="280" alt="Android demo in dark mode" /> |
|
|
8
|
+
| **iOS** | <img src="https://raw.githubusercontent.com/galikvalkin/react-native-unified-action-sheet/HEAD/docs/images/ios-demo-light-mode.gif" width="280" alt="iOS demo in light mode" /> | <img src="https://raw.githubusercontent.com/galikvalkin/react-native-unified-action-sheet/HEAD/docs/images/ios-demo-dark-mode.gif" width="280" alt="iOS demo in dark mode" /> |
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
</p>
|
|
14
|
-
|
|
15
|
-
- **Fully native, always on top.** The sheet gets its own platform window, so it renders above your whole view tree — including an already-open [`Modal`](https://reactnative.dev/docs/modal). JS-rendered sheets live *inside* the component tree, where `overflow`, `zIndex`, a transform or an open modal can clip or bury them.
|
|
16
|
-
- **One API, native on both platforms**, so the same options behave the same way.
|
|
17
|
-
- **Prompts included.** React Native's `Alert.prompt` is iOS-only and silently does nothing on Android; `showPromptWithOptions()` works on both.
|
|
18
|
-
- Works on React Native **0.81 (old and new architecture)** through the **latest** release.
|
|
19
|
-
- **No extra native dependencies** on either platform, and zero runtime dependencies (`react` / `react-native` peers only).
|
|
10
|
+
- **Fully native, always on top.** The sheet gets its own platform window, so it renders above your whole view tree, including an open [`Modal`](https://reactnative.dev/docs/modal). JS-rendered sheets live inside the component tree, where `overflow`, `zIndex` or a modal can clip or bury them.
|
|
11
|
+
- **One API on both platforms**: action sheets, anchored popovers, bottom sheets and text prompts.
|
|
12
|
+
- **Prompts on Android too.** React Native's `Alert.prompt` is iOS-only; `showPromptWithOptions()` works on both.
|
|
13
|
+
- **No dependencies.** `react` and `react-native` are peers; the Android bottom sheet's Material is opt-in.
|
|
20
14
|
|
|
21
15
|
## Installation
|
|
22
16
|
|
|
@@ -26,7 +20,9 @@ npm install react-native-unified-action-sheet
|
|
|
26
20
|
yarn add react-native-unified-action-sheet
|
|
27
21
|
```
|
|
28
22
|
|
|
29
|
-
Autolinking handles the rest
|
|
23
|
+
Autolinking handles the rest. In Expo, use a development build (`npx expo run:ios` / `npx expo run:android`); custom native modules do not work in Expo Go.
|
|
24
|
+
|
|
25
|
+
**Optional:** for the Android bottom sheet, set `unifiedActionSheet.material=true` in `android/gradle.properties`, or add the Expo plugin. See [Bottom sheets](docs/bottom-sheet.md).
|
|
30
26
|
|
|
31
27
|
## Usage
|
|
32
28
|
|
|
@@ -45,41 +41,19 @@ await showActionSheetWithOptions({
|
|
|
45
41
|
});
|
|
46
42
|
```
|
|
47
43
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
| | promise resolves with | `onPress` runs |
|
|
51
|
-
| --- | --- | --- |
|
|
52
|
-
| Tapping a button | its index | that button's |
|
|
53
|
-
| Backdrop tap, back button, swipe down | the `'cancel'` button's index, or `-1` if there is none | the cancel button's, if there is one |
|
|
54
|
-
| `dismissActionSheet()` | `undefined` | nothing |
|
|
55
|
-
|
|
56
|
-
The promise never rejects on its own — only if one of your own `onPress` handlers throws.
|
|
57
|
-
|
|
58
|
-
### Anchored sheets
|
|
44
|
+
The call resolves with the tapped button's index, and that button's `onPress` runs. A backdrop tap or back resolves the `'cancel'` button. The promise never rejects. See [Behavior](docs/behavior.md).
|
|
59
45
|
|
|
60
|
-
|
|
46
|
+
### Presentation styles
|
|
61
47
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
showActionSheetWithOptions({
|
|
69
|
-
options: [...],
|
|
70
|
-
presentationStyle: 'anchored',
|
|
71
|
-
anchor: anchorRef,
|
|
72
|
-
})
|
|
73
|
-
}
|
|
74
|
-
/>;
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
Anything with a `measureInWindow` method works, which is every React Native host component ref. On iOS the anchor only applies on iPad, where the sheet becomes a popover; iPhone always presents from the bottom.
|
|
48
|
+
| `presentationStyle` | iOS | Android |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| unset | Action sheet from the bottom | Centered dialog |
|
|
51
|
+
| `'centered'` | Centered alert | Centered dialog |
|
|
52
|
+
| `'anchored'` | Popover on iPad, pointing at `anchor` | Popup attached to `anchor` |
|
|
53
|
+
| `'bottom'` | Sheet with a grabber (popover on iPad) | Material bottom sheet ([opt-in](docs/bottom-sheet.md)) |
|
|
78
54
|
|
|
79
55
|
### Prompts
|
|
80
56
|
|
|
81
|
-
`showPromptWithOptions()` is the same dialog with a text field:
|
|
82
|
-
|
|
83
57
|
```ts
|
|
84
58
|
import { showPromptWithOptions } from 'react-native-unified-action-sheet';
|
|
85
59
|
|
|
@@ -95,78 +69,16 @@ const result = await showPromptWithOptions({
|
|
|
95
69
|
// { buttonIndex, text }, or undefined after dismissActionSheet().
|
|
96
70
|
```
|
|
97
71
|
|
|
98
|
-
Buttons behave as they do for the sheet, except `onPress` receives the field's
|
|
99
|
-
text. That text is whatever was in the field when the prompt closed, including
|
|
100
|
-
on a dismissal, so a draft is recoverable. Extra options: `placeholder`,
|
|
101
|
-
`defaultValue`, `secureTextEntry` and `keyboardType` (`'default' |
|
|
102
|
-
'email-address' | 'numeric' | 'phone-pad' | 'url'`).
|
|
103
|
-
|
|
104
|
-
A prompt is always centered — UIKit has no text field in an action sheet, so
|
|
105
|
-
`presentationStyle` and `anchor` do not apply.
|
|
106
|
-
|
|
107
72
|
### Dismissing from code
|
|
108
73
|
|
|
109
|
-
`dismissActionSheet()` closes the most recently opened sheet, `dismissAllActionSheets()` closes every open one.
|
|
110
|
-
|
|
111
|
-
```ts
|
|
112
|
-
import {
|
|
113
|
-
dismissActionSheet,
|
|
114
|
-
dismissAllActionSheets,
|
|
115
|
-
} from 'react-native-unified-action-sheet';
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
### Options
|
|
119
|
-
|
|
120
|
-
| Option | Type | iOS | Android | Description |
|
|
121
|
-
| --- | --- | :---: | :---: | --- |
|
|
122
|
-
| `options` | `ActionSheetButtonInterface[]` | ✅ | ✅ | The buttons, in order: `{ label, style?, disabled?, onPress? }`. |
|
|
123
|
-
| `style` (per button) | `'cancel' \| 'destructive'?` | ✅ | ✅ | `'cancel'` renders a separated row and resolves on backdrop tap / back; `'destructive'` uses the destructive color. Only the first `'cancel'` counts. |
|
|
124
|
-
| `disabled` (per button) | `boolean?` | ✅ | ✅ | Renders the row dimmed and unresponsive to taps. |
|
|
125
|
-
| `onPress` (per button) | `() => void?` | ✅ | ✅ | Runs when this button resolves the sheet. |
|
|
126
|
-
| `title` | `string?` | ✅ | ✅ | Sheet title. |
|
|
127
|
-
| `message` | `string?` | ✅ | ✅ | Secondary text under the title. |
|
|
128
|
-
| `presentationStyle` | `'centered' \| 'anchored'` | ✅ | ✅ | `'centered'` is a centered dialog; `'anchored'` attaches to `anchor` (an iPad popover on iOS). **Defaults differ**: Android `'centered'`, iOS the standard action sheet. |
|
|
129
|
-
| `anchor` | `ActionSheetAnchorInterface?` | ✅ | ✅ | A ref, or anything with `measureInWindow`. Without a measurable anchor an `'anchored'` sheet falls back to a centered dialog. |
|
|
130
|
-
| `anchorAlignment` | `'start' \| 'center'` | — | ✅ | Alignment of an `'anchored'` popup relative to its anchor. `'start'` (default) aligns leading edges, flipping in RTL. |
|
|
131
|
-
| `userInterfaceStyle` | `'light' \| 'dark'` | ✅ | ✅ | Forces the appearance; defaults to following the system setting. |
|
|
132
|
-
| `tintColor` | `string?` | ✅ | ✅ | Text color of non-destructive buttons. |
|
|
133
|
-
| `cancelButtonTintColor` | `string?` | ✅ | ✅ | Text color of the cancel button; overrides `tintColor` for that row. |
|
|
134
|
-
| `destructiveColor` | `string?` | ✅ | ✅ | Overrides the destructive row color (Android's palette error color, iOS system red). |
|
|
135
|
-
| `buttonTextAlignment` | `'start' \| 'center'` | — | ✅ | Alignment of button labels. Defaults to `'start'`, which follows layout direction. |
|
|
136
|
-
|
|
137
|
-
Every options and button type is exported for typing your own wrappers —
|
|
138
|
-
`ActionSheetOptionsInterface`, `PromptOptionsInterface`, `PromptResultInterface`
|
|
139
|
-
and the interfaces they are composed from.
|
|
140
|
-
|
|
141
|
-
### Platform notes
|
|
142
|
-
|
|
143
|
-
- **Which gestures dismiss differs.** On iOS a sheet can only be tapped away if it has a `'cancel'` button, and a `'centered'` one never can — UIKit treats it as strictly modal. Android's centered dialog always cancels on a backdrop tap. Give a sheet a cancel button if you want that gesture everywhere.
|
|
144
|
-
- **On iPad, a popover hides the cancel row**, since tapping outside already cancels. The index you receive is unaffected.
|
|
145
|
-
- **Sheets stack.** Opening one over another puts it on top, and each resolves its own promise. Opening a sheet over a `Modal` does not dismiss the modal.
|
|
146
|
-
- **Light or dark follows the system setting** unless `userInterfaceStyle` forces one, chosen when the sheet opens. It uses its own palette, so it looks the same in any host app.
|
|
147
|
-
|
|
148
|
-
## Testing
|
|
149
|
-
|
|
150
|
-
The package ships a Jest mock, so a screen that opens a sheet can be tested without the native module:
|
|
74
|
+
`dismissActionSheet()` closes the most recently opened sheet, and `dismissAllActionSheets()` closes every open one. Dismissed sheets resolve with `undefined`.
|
|
151
75
|
|
|
152
|
-
|
|
153
|
-
jest.mock('react-native-unified-action-sheet', () =>
|
|
154
|
-
require('react-native-unified-action-sheet/jest')
|
|
155
|
-
);
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
Every sheet then resolves with no selection. To simulate a tap, queue the index it should resolve with — the pressed button's `onPress` runs as it would for real:
|
|
159
|
-
|
|
160
|
-
```ts
|
|
161
|
-
import { setNextButtonIndex } from 'react-native-unified-action-sheet/jest';
|
|
162
|
-
|
|
163
|
-
setNextButtonIndex(0);
|
|
164
|
-
await openTheSheet();
|
|
165
|
-
```
|
|
76
|
+
## Documentation
|
|
166
77
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
78
|
+
- [API reference](docs/api.md): every option, per platform
|
|
79
|
+
- [Behavior](docs/behavior.md): what each gesture resolves with, and platform differences
|
|
80
|
+
- [Bottom sheets](docs/bottom-sheet.md): both platforms, and enabling Material on Android
|
|
81
|
+
- [Testing](docs/testing.md): the shipped Jest mock
|
|
170
82
|
|
|
171
83
|
## Compatibility
|
|
172
84
|
|
package/android/build.gradle
CHANGED
|
@@ -2,7 +2,8 @@ buildscript {
|
|
|
2
2
|
ext.UnifiedActionSheet = [
|
|
3
3
|
kotlinVersion: "2.0.21",
|
|
4
4
|
minSdkVersion: 24,
|
|
5
|
-
compileSdkVersion: 36
|
|
5
|
+
compileSdkVersion: 36,
|
|
6
|
+
materialVersion: "1.12.0"
|
|
6
7
|
]
|
|
7
8
|
|
|
8
9
|
ext.getExtOrDefault = { prop ->
|
|
@@ -31,6 +32,20 @@ apply plugin: "kotlin-android"
|
|
|
31
32
|
|
|
32
33
|
apply plugin: "com.facebook.react"
|
|
33
34
|
|
|
35
|
+
// Material is opt-in, so apps that don't use presentationStyle 'bottom' stay
|
|
36
|
+
// free of the dependency. The app sets unifiedActionSheet.material=true in its
|
|
37
|
+
// gradle.properties (the Expo config plugin writes it); only then is the
|
|
38
|
+
// Material presenter compiled and the dependency added. src/main never
|
|
39
|
+
// references a Material class: it goes through BottomSheetPresenterFactory,
|
|
40
|
+
// which both variant source sets define.
|
|
41
|
+
def materialEnabled = (findProperty("unifiedActionSheet.material") ?: "false").toString().toBoolean()
|
|
42
|
+
|
|
43
|
+
// ext.materialVersion on the root project wins, then the gradle property, then
|
|
44
|
+
// the version this library is tested against.
|
|
45
|
+
def materialVersion = rootProject.ext.has("materialVersion")
|
|
46
|
+
? rootProject.ext.get("materialVersion")
|
|
47
|
+
: (findProperty("unifiedActionSheet.materialVersion") ?: UnifiedActionSheet.materialVersion)
|
|
48
|
+
|
|
34
49
|
android {
|
|
35
50
|
namespace "com.unifiedactionsheet"
|
|
36
51
|
|
|
@@ -40,6 +55,15 @@ android {
|
|
|
40
55
|
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
41
56
|
}
|
|
42
57
|
|
|
58
|
+
sourceSets {
|
|
59
|
+
main {
|
|
60
|
+
java.srcDirs += materialEnabled ? "src/material/java" : "src/noMaterial/java"
|
|
61
|
+
if (materialEnabled) {
|
|
62
|
+
res.srcDirs += "src/material/res"
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
43
67
|
compileOptions {
|
|
44
68
|
sourceCompatibility JavaVersion.VERSION_17
|
|
45
69
|
targetCompatibility JavaVersion.VERSION_17
|
|
@@ -48,4 +72,7 @@ android {
|
|
|
48
72
|
|
|
49
73
|
dependencies {
|
|
50
74
|
implementation "com.facebook.react:react-android"
|
|
75
|
+
if (materialEnabled) {
|
|
76
|
+
implementation "com.google.android.material:material:${materialVersion}"
|
|
77
|
+
}
|
|
51
78
|
}
|
|
@@ -6,11 +6,15 @@ import com.facebook.react.bridge.ReadableMap
|
|
|
6
6
|
internal enum class PresentationStyle {
|
|
7
7
|
CENTERED,
|
|
8
8
|
ANCHORED,
|
|
9
|
+
BOTTOM,
|
|
9
10
|
;
|
|
10
11
|
|
|
11
12
|
companion object {
|
|
12
|
-
fun fromWire(value: String?): PresentationStyle =
|
|
13
|
-
|
|
13
|
+
fun fromWire(value: String?): PresentationStyle = when (value) {
|
|
14
|
+
"anchored" -> ANCHORED
|
|
15
|
+
"bottom" -> BOTTOM
|
|
16
|
+
else -> CENTERED
|
|
17
|
+
}
|
|
14
18
|
}
|
|
15
19
|
}
|
|
16
20
|
|
|
@@ -51,6 +51,9 @@ class UnifiedActionSheetModule(reactContext: ReactApplicationContext) :
|
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
override fun getTypedExportedConstants(): MutableMap<String, Any> =
|
|
55
|
+
mutableMapOf("isMaterialEnabled" to BottomSheetPresenterFactory.isAvailable)
|
|
56
|
+
|
|
54
57
|
override fun dismissActionSheet() {
|
|
55
58
|
UiThreadUtil.runOnUiThread {
|
|
56
59
|
openDialogs.lastOrNull()?.let { dialog ->
|
|
@@ -111,6 +114,9 @@ class UnifiedActionSheetModule(reactContext: ReactApplicationContext) :
|
|
|
111
114
|
CenteredDialogPresenter
|
|
112
115
|
}
|
|
113
116
|
}
|
|
117
|
+
// Without Material compiled in there is no bottom sheet; JS has already
|
|
118
|
+
// warned in development, from the isMaterialEnabled constant.
|
|
119
|
+
PresentationStyle.BOTTOM -> BottomSheetPresenterFactory.create() ?: CenteredDialogPresenter
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
val dialog = presenter.build(activity, options) { presented, index ->
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
package com.unifiedactionsheet
|
|
2
|
+
|
|
3
|
+
/// Compiled only when the app sets unifiedActionSheet.material=true, which also
|
|
4
|
+
/// adds the Material dependency. The noMaterial source set defines the same
|
|
5
|
+
/// object, so src/main compiles against either.
|
|
6
|
+
internal object BottomSheetPresenterFactory {
|
|
7
|
+
const val isAvailable = true
|
|
8
|
+
|
|
9
|
+
fun create(): SheetPresenter? = MaterialBottomSheetPresenter
|
|
10
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
package com.unifiedactionsheet
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.app.Dialog
|
|
5
|
+
import android.content.res.ColorStateList
|
|
6
|
+
import android.view.View
|
|
7
|
+
import android.widget.LinearLayout
|
|
8
|
+
import androidx.core.view.ViewCompat
|
|
9
|
+
import androidx.core.view.WindowCompat
|
|
10
|
+
import androidx.core.view.WindowInsetsCompat
|
|
11
|
+
import com.google.android.material.bottomsheet.BottomSheetDialog
|
|
12
|
+
import com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
|
13
|
+
import com.google.android.material.R as MaterialR
|
|
14
|
+
|
|
15
|
+
/// presentationStyle 'bottom': a Material 3 modal bottom sheet. Material's own
|
|
16
|
+
/// behavior does the rest: a long list opens at the peek height and drags up to
|
|
17
|
+
/// expand, a short one opens fully, and swiping it away cancels the dialog, so
|
|
18
|
+
/// it resolves through the same cancel path as a backdrop tap.
|
|
19
|
+
internal object MaterialBottomSheetPresenter : SheetPresenter {
|
|
20
|
+
override fun build(
|
|
21
|
+
activity: Activity,
|
|
22
|
+
options: ActionSheetOptions,
|
|
23
|
+
onSelect: (Dialog, Int) -> Unit,
|
|
24
|
+
): Dialog {
|
|
25
|
+
val isDark = isDarkAppearance(activity, options.userInterfaceStyle)
|
|
26
|
+
val palette = paletteFor(isDark)
|
|
27
|
+
// A full Material 3 theme rather than an overlay, so the sheet works inside
|
|
28
|
+
// an app whose own theme is plain AppCompat.
|
|
29
|
+
val dialog = BottomSheetDialog(activity, bottomSheetTheme(isDark))
|
|
30
|
+
|
|
31
|
+
val context = dialog.context
|
|
32
|
+
// The cancel row stays the last row, as on the centered dialog. With a long
|
|
33
|
+
// list it starts below the peek height, which Material users expect:
|
|
34
|
+
// swipe down, back and a backdrop tap all resolve the cancel index anyway.
|
|
35
|
+
val content = buildContent(context, options, palette) { index -> onSelect(dialog, index) }
|
|
36
|
+
|
|
37
|
+
val root = LinearLayout(context).apply {
|
|
38
|
+
orientation = LinearLayout.VERTICAL
|
|
39
|
+
addView(
|
|
40
|
+
BottomSheetDragHandleView(context),
|
|
41
|
+
LinearLayout.LayoutParams(
|
|
42
|
+
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
43
|
+
LinearLayout.LayoutParams.WRAP_CONTENT,
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
addView(
|
|
47
|
+
content,
|
|
48
|
+
LinearLayout.LayoutParams(
|
|
49
|
+
LinearLayout.LayoutParams.MATCH_PARENT,
|
|
50
|
+
LinearLayout.LayoutParams.WRAP_CONTENT,
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
dialog.setContentView(root)
|
|
55
|
+
dialog.setCanceledOnTouchOutside(true)
|
|
56
|
+
|
|
57
|
+
// Tint Material's own sheet background instead of replacing it, so its
|
|
58
|
+
// rounded top corners and expansion animation stay intact, while the color
|
|
59
|
+
// matches the palette the centered and anchored styles use.
|
|
60
|
+
dialog.findViewById<View>(MaterialR.id.design_bottom_sheet)?.let { sheet ->
|
|
61
|
+
ViewCompat.setBackgroundTintList(sheet, ColorStateList.valueOf(palette.surface))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// The sheet now sits behind the navigation bar, so its buttons or gesture
|
|
65
|
+
// handle need to contrast with the sheet, not with the app underneath.
|
|
66
|
+
dialog.window?.let { window ->
|
|
67
|
+
WindowCompat.getInsetsController(window, window.decorView)
|
|
68
|
+
.isAppearanceLightNavigationBars = !isDark
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Edge to edge is for the bottom only. It would also let a long list
|
|
72
|
+
// expand behind the status bar, so cap the sheet just below it (and any
|
|
73
|
+
// display cutout); the rows scroll inside it instead.
|
|
74
|
+
val decor = activity.window.decorView
|
|
75
|
+
val topInset = ViewCompat.getRootWindowInsets(decor)
|
|
76
|
+
?.getInsets(WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout())
|
|
77
|
+
?.top ?: 0
|
|
78
|
+
if (decor.height > topInset) {
|
|
79
|
+
dialog.behavior.maxHeight = decor.height - topInset
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return dialog
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private fun bottomSheetTheme(isDark: Boolean): Int = if (isDark) {
|
|
86
|
+
R.style.UnifiedActionSheet_BottomSheetDialog_Dark
|
|
87
|
+
} else {
|
|
88
|
+
R.style.UnifiedActionSheet_BottomSheetDialog_Light
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<resources>
|
|
3
|
+
<!-- Full Material 3 themes, not overlays: Material components refuse to
|
|
4
|
+
inflate under a non-Material theme, and the host app's may be plain
|
|
5
|
+
AppCompat. userInterfaceStyle picks light or dark explicitly, so neither
|
|
6
|
+
follows DayNight.
|
|
7
|
+
|
|
8
|
+
Edge to edge, so the sheet runs down behind the navigation bar instead
|
|
9
|
+
of stopping above it and leaving a strip of scrim there. Material only
|
|
10
|
+
does this when enableEdgeToEdge is set and the navigation bar is
|
|
11
|
+
translucent; the sheet then pads its content by the bar's inset, so the
|
|
12
|
+
last row stays clear of it. -->
|
|
13
|
+
<style name="UnifiedActionSheet.BottomSheetDialog.Light" parent="Theme.Material3.Light.BottomSheetDialog">
|
|
14
|
+
<item name="enableEdgeToEdge">true</item>
|
|
15
|
+
<item name="android:navigationBarColor">@android:color/transparent</item>
|
|
16
|
+
</style>
|
|
17
|
+
|
|
18
|
+
<style name="UnifiedActionSheet.BottomSheetDialog.Dark" parent="Theme.Material3.Dark.BottomSheetDialog">
|
|
19
|
+
<item name="enableEdgeToEdge">true</item>
|
|
20
|
+
<item name="android:navigationBarColor">@android:color/transparent</item>
|
|
21
|
+
</style>
|
|
22
|
+
</resources>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
package com.unifiedactionsheet
|
|
2
|
+
|
|
3
|
+
/// Compiled when the app has not set unifiedActionSheet.material=true. There is
|
|
4
|
+
/// no bottom sheet without Material, so the module falls back to the centered
|
|
5
|
+
/// dialog, and JS warns in development (see getTypedExportedConstants).
|
|
6
|
+
internal object BottomSheetPresenterFactory {
|
|
7
|
+
const val isAvailable = false
|
|
8
|
+
|
|
9
|
+
fun create(): SheetPresenter? = null
|
|
10
|
+
}
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./plugin/withUnifiedActionSheet');
|