expo-simple-gallery 0.2.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.
Files changed (91) hide show
  1. package/.eslintrc.js +2 -0
  2. package/README.md +295 -0
  3. package/android/build.gradle +43 -0
  4. package/android/src/main/AndroidManifest.xml +2 -0
  5. package/android/src/main/java/expo/modules/simplegallery/ExpoSimpleGalleryModule.kt +50 -0
  6. package/android/src/main/java/expo/modules/simplegallery/ExpoSimpleGalleryView.kt +30 -0
  7. package/biome.json +200 -0
  8. package/build/ExpoSimpleGallery.types.d.ts +89 -0
  9. package/build/ExpoSimpleGallery.types.d.ts.map +1 -0
  10. package/build/ExpoSimpleGallery.types.js +7 -0
  11. package/build/ExpoSimpleGallery.types.js.map +1 -0
  12. package/build/ExpoSimpleGalleryModal.d.ts +3 -0
  13. package/build/ExpoSimpleGalleryModal.d.ts.map +1 -0
  14. package/build/ExpoSimpleGalleryModal.js +46 -0
  15. package/build/ExpoSimpleGalleryModal.js.map +1 -0
  16. package/build/ExpoSimpleGalleryModal.types.d.ts +23 -0
  17. package/build/ExpoSimpleGalleryModal.types.d.ts.map +1 -0
  18. package/build/ExpoSimpleGalleryModal.types.js +2 -0
  19. package/build/ExpoSimpleGalleryModal.types.js.map +1 -0
  20. package/build/ExpoSimpleGalleryModule.d.ts +7 -0
  21. package/build/ExpoSimpleGalleryModule.d.ts.map +1 -0
  22. package/build/ExpoSimpleGalleryModule.js +4 -0
  23. package/build/ExpoSimpleGalleryModule.js.map +1 -0
  24. package/build/ExpoSimpleGalleryView.d.ts +23 -0
  25. package/build/ExpoSimpleGalleryView.d.ts.map +1 -0
  26. package/build/ExpoSimpleGalleryView.js +172 -0
  27. package/build/ExpoSimpleGalleryView.js.map +1 -0
  28. package/build/SfSymbol.types.d.ts +213 -0
  29. package/build/SfSymbol.types.d.ts.map +1 -0
  30. package/build/SfSymbol.types.js +2 -0
  31. package/build/SfSymbol.types.js.map +1 -0
  32. package/build/components/MemoizedSectionHeader.d.ts +10 -0
  33. package/build/components/MemoizedSectionHeader.d.ts.map +1 -0
  34. package/build/components/MemoizedSectionHeader.js +12 -0
  35. package/build/components/MemoizedSectionHeader.js.map +1 -0
  36. package/build/components/MemoizedThumbnailOverlayComponent.d.ts +12 -0
  37. package/build/components/MemoizedThumbnailOverlayComponent.d.ts.map +1 -0
  38. package/build/components/MemoizedThumbnailOverlayComponent.js +12 -0
  39. package/build/components/MemoizedThumbnailOverlayComponent.js.map +1 -0
  40. package/build/index.d.ts +4 -0
  41. package/build/index.d.ts.map +1 -0
  42. package/build/index.js +4 -0
  43. package/build/index.js.map +1 -0
  44. package/expo-module.config.json +6 -0
  45. package/ios/ExpoSimpleGallery.podspec +24 -0
  46. package/ios/ExpoSimpleGalleryModule.swift +89 -0
  47. package/ios/ExpoSimpleGalleryView.swift +185 -0
  48. package/ios/Gallery/GalleryCell.swift +191 -0
  49. package/ios/Gallery/GalleryGridView+Configuration.swift +253 -0
  50. package/ios/Gallery/GalleryGridView+Data.swift +122 -0
  51. package/ios/Gallery/GalleryGridView+Gestures.swift +361 -0
  52. package/ios/Gallery/GalleryGridView.swift +85 -0
  53. package/ios/Gallery/GallerySectionHeaderView.swift +48 -0
  54. package/ios/Gallery/PreviewViewController.swift +90 -0
  55. package/ios/GalleryImageViewerModule.swift +24 -0
  56. package/ios/GalleryImageViewerView.swift +637 -0
  57. package/ios/Protocols/Cancellable.swift +3 -0
  58. package/ios/Protocols/ContextMenuActionsDelegate.swift +5 -0
  59. package/ios/Protocols/GestureEventDelegate.swift +17 -0
  60. package/ios/Protocols/ImageLoaderProtocol.swift +4 -0
  61. package/ios/Protocols/OverlayMountingDelegate.swift +26 -0
  62. package/ios/Protocols/OverlayPreloadingDelegate.swift +20 -0
  63. package/ios/Protocols/ScrollableToIndexDelegate.swift +3 -0
  64. package/ios/Services/ImageLoadTask.swift +7 -0
  65. package/ios/Services/ImageLoader.swift +213 -0
  66. package/ios/Services/ImageResizer.swift +17 -0
  67. package/ios/Types/GalleryTypes.swift +26 -0
  68. package/ios/Utils/LayoutCalculator.swift +15 -0
  69. package/ios/Viewer/ImageLoaderService.swift +439 -0
  70. package/ios/Viewer/ImagePageViewController+UIGestureRecognizerDelegate.swift +50 -0
  71. package/ios/Viewer/ImagePageViewController+UIScrollViewDelegate.swift +32 -0
  72. package/ios/Viewer/ImagePageViewController.swift +346 -0
  73. package/ios/Viewer/ImagePageViewControllerDelegate.swift +3 -0
  74. package/ios/Viewer/LivePhotoViewController.swift +192 -0
  75. package/ios/Viewer/MediaViewControllerDelegate.swift +6 -0
  76. package/ios/Viewer/MediaViewControllerFactory.swift +75 -0
  77. package/ios/Viewer/UIImage+Placeholder.swift +54 -0
  78. package/ios/Viewer/VideoViewController.swift +305 -0
  79. package/ios/exposimplegallery.xcodeproj/project.xcworkspace/contents.xcworkspacedata +7 -0
  80. package/ios/exposimplegallery.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +8 -0
  81. package/package.json +43 -0
  82. package/src/ExpoSimpleGallery.types.ts +137 -0
  83. package/src/ExpoSimpleGalleryModal.tsx +103 -0
  84. package/src/ExpoSimpleGalleryModal.types.ts +27 -0
  85. package/src/ExpoSimpleGalleryModule.ts +9 -0
  86. package/src/ExpoSimpleGalleryView.tsx +359 -0
  87. package/src/SfSymbol.types.ts +8426 -0
  88. package/src/components/MemoizedSectionHeader.tsx +33 -0
  89. package/src/components/MemoizedThumbnailOverlayComponent.tsx +38 -0
  90. package/src/index.ts +4 -0
  91. package/tsconfig.json +9 -0
package/.eslintrc.js ADDED
@@ -0,0 +1,2 @@
1
+ // @generated by expo-module-scripts
2
+ module.exports = require('expo-module-scripts/eslintrc.base.js');
package/README.md ADDED
@@ -0,0 +1,295 @@
1
+ ## Overview
2
+
3
+ ExpoSimpleGallery is a high-performance image gallery module for iOS Expo applications. It provides a native implementation of image grid views with various customization options and interactive features.
4
+
5
+ ## Motivation
6
+
7
+ ExpoSimpleGallery addresses common challenges in displaying image collections in React Native iOS applications. Standard React Native image components can struggle with performance when rendering multiple high-resolution images, especially in grid layouts with scrolling and selection capabilities. This module bridges this gap by providing a native iOS implementation that leverages UICollectionView and the Photos framework, offering better memory management and more responsive user interactions. By handling image loading, caching, and rendering on the native side rather than through the JavaScript bridge, ExpoSimpleGallery helps reduce common performance bottlenecks in image-heavy applications. The module supports practical features like image selection, context menus, and customizable layouts while maintaining excellent scroll performance.
8
+
9
+ ## Implementation
10
+ React components are dynamically mounted to native cell containers only when they become visible. The library passes information about visible ranges to JavaScript, allowing React to prepare components only for items in or near the viewport. For grouped layouts, additional section header mounting logic ensures that headers appear and disappear appropriately during scrolling. This implementation minimizes the number of simultaneously mounted React components while maintaining a responsive user experience, even with large image collections.
11
+
12
+ ## Requirements
13
+
14
+ - ⚠️ iOS 15.1+ only
15
+ - ⚠️ New architecture (Fabric) only
16
+ - ⚠️ Expo SDK 52+
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install expo-simple-gallery
22
+ npx pod-install
23
+ ```
24
+
25
+ ## URI Formats
26
+
27
+ The gallery supports multiple URI formats:
28
+
29
+ - **Local Files**: `file:///path/to/image.jpg`
30
+ - **Photo Library Assets**: `ph://asset-id` (iOS Photos framework)
31
+
32
+ ## Image Loading and Caching
33
+
34
+ ExpoSimpleGallery implements efficient image loading and caching mechanisms:
35
+
36
+ 1. **Automatic Thumbnail Generation**: For photos library assets, the module automatically generates thumbnails for faster loading.
37
+ 2. **Progressive Loading**: Images first load as lower-quality thumbnails then upgrade to high-quality versions when available.
38
+ 3. **Memory Management**: Images are loaded and unloaded as the user scrolls to maintain smooth performance.
39
+ 4. **Error Handling**: Built-in error handling for missing or unloadable images.
40
+
41
+ ## Fullscreen Image Viewer
42
+
43
+ The gallery includes a built-in fullscreen image viewer with these features:
44
+
45
+ - **Pinch to zoom**: Double tap or pinch to zoom in/out on images
46
+ - **Swipe to navigate**: Horizontal swipe to navigate between images
47
+ - **Pull to dismiss**: Pull down to dismiss the viewer
48
+ - **High-quality loading**: Seamless transition to high-quality images
49
+
50
+ The viewer can be programmatically controlled using `ref.openImageViewer(index)` and `ref.closeImageViewer()`.
51
+
52
+ ## Basic Usage
53
+
54
+ ```jsx
55
+ import { ExpoSimpleGalleryView } from 'expo-simple-gallery';
56
+
57
+ export default function App() {
58
+ return (
59
+ <ExpoSimpleGalleryView
60
+ assets={['file:///path/to/image1.jpg', 'ph://asset-id-2']}
61
+ columnsCount={3}
62
+ />
63
+ );
64
+ }
65
+ ```
66
+
67
+ ## Component
68
+
69
+ ### ExpoSimpleGalleryView
70
+
71
+ The main component for rendering an image gallery grid.
72
+
73
+ #### Props
74
+
75
+ | Prop | Type | Description |
76
+ |------|------|-------------|
77
+ | `assets` | `string[]` or `string[][]` | Array of image URIs to display. Can be nested arrays for grouped sections. |
78
+ | `columnsCount` | `number` | Number of columns to display in the grid. |
79
+ | `thumbnailPressAction` | `'select'` \| `'open'` \| `'none'` | Action to perform when a thumbnail is pressed. |
80
+ | `thumbnailLongPressAction` | `'select'` \| `'open'` \| `'preview'` \| `'none'` | Action to perform when a thumbnail is long pressed. |
81
+ | `thumbnailPanAction` | `'select'` \| `'none'` | Action to perform when panning across thumbnails. |
82
+ | `contextMenuOptions` | `array` (`UIAction[]`) | Options for the context menu that appears on long press. |
83
+ | `initiallySelected` | `string[]` | URIs of items to be marked as selected on initial render. |
84
+ | `style` | `object` (`ViewStyle`) | Style object for the gallery container. |
85
+ | `thumbnailStyle` | `object` | Style object for thumbnail items. Available properties: `aspectRatio`, `borderRadius`, `borderWidth`, `borderColor' |
86
+ | `contentContainerStyle` | `object` | Style object for the content container. Available properties: `padding`, `paddingHorizontal`, `paddingVertical`, `paddingTop`, `paddingBottom`, `paddingLeft`, `paddingRight`, `gap` |
87
+ | `sectionHeaderStyle` | `object` | Style object for section headers when using grouped assets. Available properties: `height` |
88
+ | `fullscreenViewOverlayStyle` | `object` (`ViewStyle`) | Style object for the fullscreen view overlay. |
89
+ | `thumbnailOverlayComponent` | `({uri: string, index: number, selected: boolean }) => Component` | React component to render as overlay on thumbnails. |
90
+ | `fullscreenViewOverlayComponent` | `({uri: string, index: number, selected: boolean, toggleSelection: (selected?: boolean) => void }) => Component` | React component to render as overlay in fullscreen view. `toggleSelection` toggles the selection state of the item if param is undefined, otherwise sets the selection state to the provided value. |
91
+ | `sectionHeaderComponent` | `({index: number}) => Component` | React component to render as section header. |
92
+ | `showMediaTypeIcon` | `boolean` | Whether to show the media type icon on thumbnails (for videos and live photos). Default: `true` |
93
+
94
+ #### Gesture Actions Reference
95
+
96
+ - **select**: Adds/removes the item to/from the selection and fires the `onSelectionChange` event.
97
+ - **open**: Opens the fullscreen viewer with the selected image.
98
+ - **preview**: Opens a preview with context menu with additional options (if provided).
99
+ - **none**: No action is performed, still fires the corresponding event (`onThumbnailPress`, `onThumbnailLongPress`).
100
+
101
+ #### Events
102
+
103
+ ```jsx
104
+ <ExpoSimpleGalleryView
105
+ assets={['file:///path/to/image1.jpg', 'ph://asset-id-2']}
106
+ onThumbnailPress={({nativeEvent}) => console.log(nativeEvent.uri)}
107
+ />
108
+ ```
109
+
110
+ | Event | Type | Description |
111
+ |-------|------|-------------|
112
+ | `onThumbnailPress` | `(event: NativeSyntheticEvent<{ uri: string, index: number }>) => void` | Fired when a thumbnail is pressed. Returns the URI and index of the pressed thumbnail. |
113
+ | `onThumbnailLongPress` | `(event: NativeSyntheticEvent<{ uri: string, index: number }>) => void` | Fired when a thumbnail is long pressed. Returns the URI and index of the long pressed thumbnail. |
114
+ | `onSelectionChange` | `(event: NativeSyntheticEvent<{ selected: string[] }>) => void` | Fired when selected items change. Returns the array of selected URIs. |
115
+ | `onOverlayPreloadRequested` | `(event: NativeSyntheticEvent<{ range: [number, number] }>) => void` | Fired when overlays need to be preloaded for optimization. Returns the range of indices to preload. |
116
+ | `onSectionHeadersVisible` | `(event: NativeSyntheticEvent<{ sections: number[] }>) => void` | Fired when section headers become visible. Returns the array of visible section indices. |
117
+ | `onPreviewMenuOptionSelected` | `(event: NativeSyntheticEvent<{ uri: string, index: number, optionIndex: number }>) => void` | Fired when an option is selected from the context menu. Returns the URI, index of the item, and index of the selected option. |
118
+
119
+ #### Methods (available via `ref`)
120
+
121
+ ```typescript
122
+ import { type ExpoSimpleGalleryMethods } from 'expo-simple-gallery';
123
+ ...
124
+ const galleryRef = useRef<ExpoSimpleGalleryMethods>(null)
125
+ useEffect(() => {
126
+ galleryRef.current?.centerOnIndex(0);
127
+ galleryRef.current?.setSelected(['file:///path/to/image1.jpg', 'ph://asset-id-2']);
128
+ galleryRef.current?.setThumbnailPressAction('open');
129
+ }, []);
130
+ ```
131
+
132
+ | Method | Description |
133
+ |--------|-------------|
134
+ | `centerOnIndex(index: number)` | Scrolls to center the item at the specified index. |
135
+ | `setSelected(uris: string[])` | Sets the selected items by their URIs. Useful is you want to manage selection state externally. Triggers `onSelectionChanged` event. |
136
+ | `setThumbnailPressAction(action: string)` | Sets the action performed when a thumbnail is pressed. Preferred over prop if dynamic changes are needed and you don't want to re-render the component. |
137
+ | `setThumbnailLongPressAction(action: string)` | Sets the action performed when a thumbnail is long pressed. Preferred over prop if dynamic changes are needed and you don't want to re-render the component. |
138
+ | `setThumbnailPanAction(action: string)` | Sets the action performed when panning across thumbnails. Preferred over prop if dynamic changes are needed and you don't want to re-render the component. |
139
+ | `setContextMenuOptions(options: array)` | Sets the context menu options. Preferred over prop if dynamic changes are needed and you don't want to re-render the component. |
140
+ | `openImageViewer(index: number)` | Opens the fullscreen image viewer with inmage at the specified index. |
141
+ | `closeImageViewer()` | Closes the fullscreen image viewer. |
142
+
143
+ ## Advanced Usage
144
+
145
+ ### Grouped Gallery with Sections
146
+
147
+ If you have multiple groups of images, you can pass nested arrays as `assets` to create sections. Each nested array will be displayed as a separate section with a header. Headers can be set using the `sectionHeaderComponent` prop.
148
+
149
+ ```jsx
150
+ import { ExpoSimpleGalleryView } from 'expo-simple-gallery';
151
+
152
+ export default function GroupedGallery() {
153
+ // Images grouped by date or category
154
+ const groups = [
155
+ ['file:///path/to/group1/image1.jpg', 'file:///path/to/group1/image2.jpg'],
156
+ ['file:///path/to/group2/image1.jpg', 'file:///path/to/group2/image2.jpg'],
157
+ ];
158
+
159
+ return (
160
+ <ExpoSimpleGalleryView
161
+ assets={groups}
162
+ columnsCount={3}
163
+ sectionHeaderStyle={{ height: 40 }}
164
+ sectionHeaderComponent={({ index }) => (
165
+ <View style={{ height: 40, justifyContent: 'center', paddingLeft: 10 }}>
166
+ <Text style={{ fontWeight: 'bold' }}>Section {index + 1}</Text>
167
+ </View>
168
+ )}
169
+ />
170
+ );
171
+ }
172
+ ```
173
+
174
+ ### Customizing Selection Behavior
175
+
176
+ ```jsx
177
+ import { useRef } from 'react';
178
+ import { ExpoSimpleGalleryView, ExpoSimpleGalleryMethods } from 'expo-simple-gallery';
179
+
180
+ export default function SelectionExample() {
181
+ const galleryRef = useRef<ExpoSimpleGalleryMethods>(null);
182
+ const assets = ['file:///path/to/image1.jpg', 'ph://asset-id-2'];
183
+ const [selectedUris, setSelectedUris] = useState<string[]>(['file:///path/to/image1.jpg']);
184
+
185
+ const selectAll = useCallback(() => {
186
+ galleryRef.current?.setSelected(assets)
187
+ }, [assets]);
188
+
189
+ const clearSelection = useCallback(() => {
190
+ galleryRef.current?.setSelected([]);
191
+ }, []);
192
+
193
+ return (
194
+ <>
195
+ <ExpoSimpleGalleryView
196
+ ref={galleryRef}
197
+ assets={assets}
198
+ thumbnailPressAction="select"
199
+ thumbnailLongPressAction="preview"
200
+ thumbnailPanAction="select"
201
+ initiallySelected={selectedUris}
202
+ onSelectionChange={(e) => {
203
+ setSelectedUris(e.nativeEvent.selected);
204
+ }}
205
+ />
206
+
207
+
208
+ <Button
209
+ title="Select All"
210
+ onPress={selectAll}
211
+ />
212
+ <Button
213
+ title="Clear Selection"
214
+ onPress={clearSelection}
215
+ />
216
+ </>
217
+ );
218
+ }
219
+ ```
220
+
221
+ ### Implementing Custom Context Menu
222
+
223
+ Context menus appear when using `thumbnailLongPressAction="preview"` and provide additional options for interacting with the image.
224
+
225
+ #### Type definitions
226
+
227
+ ```typescript
228
+ type UIMenuElementAttribute = 'disabled' | 'destructive' | 'hidden';
229
+ type UIMenuElementState = 'on' | 'off' | 'mixed'; // For toggle actions: checked | unchecked (default) | partially checked
230
+
231
+ export type UIAction = {
232
+ title: string;
233
+ sfSymbol?: string; /** @see https://developer.apple.com/sf-symbols/ */
234
+ action?: ({uri: string, index: number}) => void;
235
+ attributes?: UIMenuElementAttribute[];
236
+ state?: UIMenuElementState;
237
+ };
238
+ ```
239
+
240
+ ```jsx
241
+ <ExpoSimpleGalleryView
242
+ assets={assets}
243
+ contextMenuOptions={[
244
+ {
245
+ title: 'Share',
246
+ sfSymbol: 'square.and.arrow.up', // iOS SF Symbol name
247
+ action: (item) => {
248
+ // Handle share action
249
+ console.log('Share item:', item.uri);
250
+ }
251
+ },
252
+ {
253
+ title: 'Delete',
254
+ sfSymbol: 'trash',
255
+ attributes: ['destructive'], // Special styling
256
+ action: (item) => {
257
+ // Handle delete action
258
+ console.log('Delete item:', item.uri);
259
+ }
260
+ }
261
+ ]}
262
+ />
263
+ ```
264
+
265
+ ## Custom Overlays
266
+
267
+ You can provide custom overlay components for thumbnails, section headers and the fullscreen viewer:
268
+
269
+ ```jsx
270
+ <ExpoSimpleGalleryView
271
+ assets={assets}
272
+ thumbnailOverlayComponent={({ uri, index, selected }) => (
273
+ <View style={{ position: 'absolute', top: 5, right: 5, opacity: selected ? 1 : 0.5 }}>
274
+ <CheckboxIcon checked={selected} />
275
+ </View>
276
+ )}
277
+ fullscreenViewOverlayComponent={({ uri, index }) => (
278
+ /* Pay attention to not blocking touch events on the image view */
279
+ <View style={{ position: 'absolute', top: 20, left: 0, right: 0 }}>
280
+ <Text style={{ color: 'white', textAlign: 'center' }}>
281
+ URI: {uri} | Index: {index}
282
+ </Text>
283
+ </View>
284
+ )}
285
+ sectionHeaderComponent={({ index }) => (
286
+ <View style={{ height: 40, justifyContent: 'center', paddingLeft: 10 }}>
287
+ <Text style={{ fontWeight: 'bold' }}>Section {index + 1}</Text>
288
+ </View>
289
+ )}
290
+ />
291
+ ```
292
+
293
+ ## Contributing
294
+
295
+ Contributions are welcome! Please feel free to submit a Pull Request.
@@ -0,0 +1,43 @@
1
+ apply plugin: 'com.android.library'
2
+
3
+ group = 'expo.modules.simplegallery'
4
+ version = '0.1.0'
5
+
6
+ def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
7
+ apply from: expoModulesCorePlugin
8
+ applyKotlinExpoModulesCorePlugin()
9
+ useCoreDependencies()
10
+ useExpoPublishing()
11
+
12
+ // If you want to use the managed Android SDK versions from expo-modules-core, set this to true.
13
+ // The Android SDK versions will be bumped from time to time in SDK releases and may introduce breaking changes in your module code.
14
+ // Most of the time, you may like to manage the Android SDK versions yourself.
15
+ def useManagedAndroidSdkVersions = false
16
+ if (useManagedAndroidSdkVersions) {
17
+ useDefaultAndroidSdkVersions()
18
+ } else {
19
+ buildscript {
20
+ // Simple helper that allows the root project to override versions declared by this library.
21
+ ext.safeExtGet = { prop, fallback ->
22
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
23
+ }
24
+ }
25
+ project.android {
26
+ compileSdkVersion safeExtGet("compileSdkVersion", 34)
27
+ defaultConfig {
28
+ minSdkVersion safeExtGet("minSdkVersion", 21)
29
+ targetSdkVersion safeExtGet("targetSdkVersion", 34)
30
+ }
31
+ }
32
+ }
33
+
34
+ android {
35
+ namespace "expo.modules.simplegallery"
36
+ defaultConfig {
37
+ versionCode 1
38
+ versionName "0.1.0"
39
+ }
40
+ lintOptions {
41
+ abortOnError false
42
+ }
43
+ }
@@ -0,0 +1,2 @@
1
+ <manifest>
2
+ </manifest>
@@ -0,0 +1,50 @@
1
+ package expo.modules.simplegallery
2
+
3
+ import expo.modules.kotlin.modules.Module
4
+ import expo.modules.kotlin.modules.ModuleDefinition
5
+ import java.net.URL
6
+
7
+ class ExpoSimpleGalleryModule : Module() {
8
+ // Each module class must implement the definition function. The definition consists of components
9
+ // that describes the module's functionality and behavior.
10
+ // See https://docs.expo.dev/modules/module-api for more details about available components.
11
+ override fun definition() = ModuleDefinition {
12
+ // Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
13
+ // Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
14
+ // The module will be accessible from `requireNativeModule('ExpoSimpleGallery')` in JavaScript.
15
+ Name("ExpoSimpleGallery")
16
+
17
+ // Sets constant properties on the module. Can take a dictionary or a closure that returns a dictionary.
18
+ Constants(
19
+ "PI" to Math.PI
20
+ )
21
+
22
+ // Defines event names that the module can send to JavaScript.
23
+ Events("onChange")
24
+
25
+ // Defines a JavaScript synchronous function that runs the native code on the JavaScript thread.
26
+ Function("hello") {
27
+ "Hello world! 👋"
28
+ }
29
+
30
+ // Defines a JavaScript function that always returns a Promise and whose native code
31
+ // is by default dispatched on the different thread than the JavaScript runtime runs on.
32
+ AsyncFunction("setValueAsync") { value: String ->
33
+ // Send an event to JavaScript.
34
+ sendEvent("onChange", mapOf(
35
+ "value" to value
36
+ ))
37
+ }
38
+
39
+ // Enables the module to be used as a native view. Definition components that are accepted as part of
40
+ // the view definition: Prop, Events.
41
+ View(ExpoSimpleGalleryView::class) {
42
+ // Defines a setter for the `url` prop.
43
+ Prop("url") { view: ExpoSimpleGalleryView, url: URL ->
44
+ view.webView.loadUrl(url.toString())
45
+ }
46
+ // Defines an event that the view can send to JavaScript.
47
+ Events("onLoad")
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,30 @@
1
+ package expo.modules.simplegallery
2
+
3
+ import android.content.Context
4
+ import android.webkit.WebView
5
+ import android.webkit.WebViewClient
6
+ import expo.modules.kotlin.AppContext
7
+ import expo.modules.kotlin.viewevent.EventDispatcher
8
+ import expo.modules.kotlin.views.ExpoView
9
+
10
+ class ExpoSimpleGalleryView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
11
+ // Creates and initializes an event dispatcher for the `onLoad` event.
12
+ // The name of the event is inferred from the value and needs to match the event name defined in the module.
13
+ private val onLoad by EventDispatcher()
14
+
15
+ // Defines a WebView that will be used as the root subview.
16
+ internal val webView = WebView(context).apply {
17
+ layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
18
+ webViewClient = object : WebViewClient() {
19
+ override fun onPageFinished(view: WebView, url: String) {
20
+ // Sends an event to JavaScript. Triggers a callback defined on the view component in JavaScript.
21
+ onLoad(mapOf("url" to url))
22
+ }
23
+ }
24
+ }
25
+
26
+ init {
27
+ // Adds the WebView to the view hierarchy.
28
+ addView(webView)
29
+ }
30
+ }
package/biome.json ADDED
@@ -0,0 +1,200 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
3
+ "vcs": {
4
+ "enabled": false,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": false
7
+ },
8
+ "files": {
9
+ "ignoreUnknown": false,
10
+ "ignore": []
11
+ },
12
+ "formatter": {
13
+ "enabled": true,
14
+ "useEditorconfig": true,
15
+ "formatWithErrors": true,
16
+ "indentStyle": "space",
17
+ "indentWidth": 2,
18
+ "lineEnding": "lf",
19
+ "lineWidth": 80,
20
+ "attributePosition": "auto",
21
+ "bracketSpacing": true
22
+ },
23
+ "organizeImports": {
24
+ "enabled": true
25
+ },
26
+ "linter": {
27
+ "enabled": true,
28
+ "rules": {
29
+ "recommended": true,
30
+ "complexity": {
31
+ "noWith": "warn"
32
+ },
33
+ "correctness": {
34
+ "noEmptyCharacterClassInRegex": "warn",
35
+ "noEmptyPattern": "warn",
36
+ "noUndeclaredVariables": "error",
37
+ "noUnreachable": "warn",
38
+ "noUnusedImports": "error",
39
+ "noUnusedLabels": "warn",
40
+ "noUnusedVariables": "warn",
41
+ "useExhaustiveDependencies": "warn",
42
+ "useHookAtTopLevel": "error",
43
+ "useIsNan": "error"
44
+ },
45
+ "security": {
46
+ "noDangerouslySetInnerHtmlWithChildren": "warn"
47
+ },
48
+ "style": {
49
+ "useBlockStatements": "off",
50
+ "useConst": "error",
51
+ "useFilenamingConvention": {
52
+ "level": "error",
53
+ "options": {
54
+ "requireAscii": true,
55
+ "filenameCases": [
56
+ "kebab-case"
57
+ ]
58
+ }
59
+ }
60
+ },
61
+ "suspicious": {
62
+ "noDoubleEquals": "warn",
63
+ "noDuplicateCase": "error",
64
+ "noDuplicateClassMembers": "error",
65
+ "noDuplicateJsxProps": "error",
66
+ "noDuplicateObjectKeys": "error",
67
+ "noDuplicateParameters": "error",
68
+ "noRedeclare": "warn",
69
+ "noUnsafeNegation": "warn",
70
+ "useValidTypeof": "error"
71
+ }
72
+ },
73
+ "ignore": [
74
+ "android/app/build",
75
+ "**/credential-provider-plugin/"
76
+ ]
77
+ },
78
+ "javascript": {
79
+ "formatter": {
80
+ "jsxQuoteStyle": "double",
81
+ "quoteProperties": "asNeeded",
82
+ "trailingCommas": "es5",
83
+ "semicolons": "always",
84
+ "arrowParentheses": "always",
85
+ "bracketSameLine": false,
86
+ "quoteStyle": "single",
87
+ "attributePosition": "auto",
88
+ "bracketSpacing": true
89
+ },
90
+ "globals": [
91
+ "console",
92
+ "__DEV__",
93
+ "shared-node-browser"
94
+ ]
95
+ },
96
+ "overrides": [
97
+ {
98
+ "include": [
99
+ "*.web.*"
100
+ ]
101
+ },
102
+ {
103
+ "include": [
104
+ "*.d.ts"
105
+ ],
106
+ "linter": {
107
+ "rules": {}
108
+ }
109
+ },
110
+ {
111
+ "include": [
112
+ "*.js",
113
+ "*.jsx"
114
+ ]
115
+ },
116
+ {
117
+ "include": [
118
+ "*.ts",
119
+ "*.tsx",
120
+ "*.d.ts"
121
+ ],
122
+ "linter": {
123
+ "rules": {
124
+ "complexity": {
125
+ "noBannedTypes": "error",
126
+ "noUselessConstructor": "warn"
127
+ },
128
+ "correctness": {
129
+ "noUndeclaredVariables": "off",
130
+ "noUnusedVariables": "warn"
131
+ },
132
+ "style": {
133
+ "useConsistentArrayType": {
134
+ "level": "warn",
135
+ "options": {
136
+ "syntax": "shorthand"
137
+ }
138
+ }
139
+ },
140
+ "suspicious": {
141
+ "noDuplicateClassMembers": "error",
142
+ "noExtraNonNullAssertion": "warn",
143
+ "noRedeclare": "warn"
144
+ }
145
+ }
146
+ }
147
+ },
148
+ {
149
+ "include": [
150
+ "*.web.*"
151
+ ]
152
+ },
153
+ {
154
+ "include": [
155
+ "*.d.ts"
156
+ ],
157
+ "linter": {
158
+ "rules": {}
159
+ }
160
+ },
161
+ {
162
+ "include": [
163
+ "*.js",
164
+ "*.jsx"
165
+ ]
166
+ },
167
+ {
168
+ "include": [
169
+ "*.ts",
170
+ "*.tsx",
171
+ "*.d.ts"
172
+ ],
173
+ "linter": {
174
+ "rules": {
175
+ "complexity": {
176
+ "noBannedTypes": "error",
177
+ "noUselessConstructor": "warn"
178
+ },
179
+ "correctness": {
180
+ "noUndeclaredVariables": "off",
181
+ "noUnusedVariables": "warn"
182
+ },
183
+ "style": {
184
+ "useConsistentArrayType": {
185
+ "level": "warn",
186
+ "options": {
187
+ "syntax": "shorthand"
188
+ }
189
+ }
190
+ },
191
+ "suspicious": {
192
+ "noDuplicateClassMembers": "error",
193
+ "noExtraNonNullAssertion": "warn",
194
+ "noRedeclare": "warn"
195
+ }
196
+ }
197
+ }
198
+ }
199
+ ]
200
+ }