Skip to content

Commit 30d1234

Browse files
committed
feat: implement support for mime type on Android, fix: types (#46)
1 parent 5c659c9 commit 30d1234

File tree

11 files changed

+158
-64
lines changed

11 files changed

+158
-64
lines changed

example/tests/filesystem.tsx

+17-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import React, { useState } from 'react'
22
import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'
3-
import { CompressionStream, fetch, showOpenFilePicker } from 'react-native-fast-io'
3+
import {
4+
CompressionStream,
5+
fetch,
6+
OpenFilePickerOptions,
7+
showOpenFilePicker,
8+
} from 'react-native-fast-io'
49

510
export function FileSystemUI() {
611
const [file, setFile] = useState<File | null>(null)
712

8-
const pickFile = async () => {
9-
const [fileHandle] = await showOpenFilePicker()
13+
const pickFile = async (options?: OpenFilePickerOptions) => {
14+
const [fileHandle] = await showOpenFilePicker(options)
1015
const file = await fileHandle.getFile()
1116
// @ts-ignore
1217
setFile(file)
@@ -35,8 +40,15 @@ export function FileSystemUI() {
3540
<View>
3641
<Text style={styles.header}>File System Test</Text>
3742

38-
<TouchableOpacity style={styles.button} onPress={pickFile}>
39-
<Text style={styles.buttonText}>Pick File</Text>
43+
<TouchableOpacity style={styles.button} onPress={() => pickFile()}>
44+
<Text style={styles.buttonText}>Pick any file</Text>
45+
</TouchableOpacity>
46+
47+
<TouchableOpacity
48+
style={styles.button}
49+
onPress={() => pickFile({ types: [{ accept: { 'image/*': ['.png', '.jpg', '.jpeg'] } }] })}
50+
>
51+
<Text style={styles.buttonText}>Pick image</Text>
4052
</TouchableOpacity>
4153

4254
{file && (

packages/react-native-fast-io/android/src/main/java/com/margelo/nitro/fastio/HybridFileSystem.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,9 @@ class HybridFileSystem : HybridFileSystemSpec() {
6969
type = "*/*"
7070
addCategory(Intent.CATEGORY_OPENABLE)
7171

72-
// tbd: handle file extensions - must pass mime type
73-
// on Android, will require updating code on iOS
72+
if (options?.mimeTypes != null) {
73+
putExtra(Intent.EXTRA_MIME_TYPES, options.mimeTypes)
74+
}
7475

7576
if (options?.multiple == true) {
7677
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true)

packages/react-native-fast-io/nitrogen/generated/android/c++/JNativeFilePickerOptions.hpp

+21
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ namespace margelo::nitro::fastio {
3838
jni::local_ref<jni::JString> startIn = this->getFieldValue(fieldStartIn);
3939
static const auto fieldExtensions = clazz->getField<jni::JArrayClass<jni::JString>>("extensions");
4040
jni::local_ref<jni::JArrayClass<jni::JString>> extensions = this->getFieldValue(fieldExtensions);
41+
static const auto fieldMimeTypes = clazz->getField<jni::JArrayClass<jni::JString>>("mimeTypes");
42+
jni::local_ref<jni::JArrayClass<jni::JString>> mimeTypes = this->getFieldValue(fieldMimeTypes);
4143
return NativeFilePickerOptions(
4244
multiple != nullptr ? std::make_optional(static_cast<bool>(multiple->value())) : std::nullopt,
4345
startIn != nullptr ? std::make_optional(startIn->toStdString()) : std::nullopt,
@@ -50,6 +52,16 @@ namespace margelo::nitro::fastio {
5052
__vector.push_back(__element->toStdString());
5153
}
5254
return __vector;
55+
}()) : std::nullopt,
56+
mimeTypes != nullptr ? std::make_optional([&]() {
57+
size_t __size = mimeTypes->size();
58+
std::vector<std::string> __vector;
59+
__vector.reserve(__size);
60+
for (size_t __i = 0; __i < __size; __i++) {
61+
auto __element = mimeTypes->getElement(__i);
62+
__vector.push_back(__element->toStdString());
63+
}
64+
return __vector;
5365
}()) : std::nullopt
5466
);
5567
}
@@ -71,6 +83,15 @@ namespace margelo::nitro::fastio {
7183
__array->setElement(__i, *jni::make_jstring(__element));
7284
}
7385
return __array;
86+
}() : nullptr,
87+
value.mimeTypes.has_value() ? [&]() {
88+
size_t __size = value.mimeTypes.value().size();
89+
jni::local_ref<jni::JArrayClass<jni::JString>> __array = jni::JArrayClass<jni::JString>::newArray(__size);
90+
for (size_t __i = 0; __i < __size; __i++) {
91+
const auto& __element = value.mimeTypes.value()[__i];
92+
__array->setElement(__i, *jni::make_jstring(__element));
93+
}
94+
return __array;
7495
}() : nullptr
7596
);
7697
}

packages/react-native-fast-io/nitrogen/generated/android/kotlin/com/margelo/nitro/fastio/NativeFilePickerOptions.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ import com.margelo.nitro.core.*
1919
data class NativeFilePickerOptions(
2020
val multiple: Boolean?,
2121
val startIn: String?,
22-
val extensions: Array<String>?
22+
val extensions: Array<String>?,
23+
val mimeTypes: Array<String>?
2324
)

packages/react-native-fast-io/nitrogen/generated/ios/swift/NativeFilePickerOptions.swift

+42-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public extension NativeFilePickerOptions {
1818
/**
1919
* Create a new instance of `NativeFilePickerOptions`.
2020
*/
21-
init(multiple: Bool?, startIn: String?, extensions: [String]?) {
21+
init(multiple: Bool?, startIn: String?, extensions: [String]?, mimeTypes: [String]?) {
2222
self.init({ () -> bridge.std__optional_bool_ in
2323
if let __unwrappedValue = multiple {
2424
return bridge.create_std__optional_bool_(__unwrappedValue)
@@ -43,6 +43,18 @@ public extension NativeFilePickerOptions {
4343
} else {
4444
return .init()
4545
}
46+
}(), { () -> bridge.std__optional_std__vector_std__string__ in
47+
if let __unwrappedValue = mimeTypes {
48+
return bridge.create_std__optional_std__vector_std__string__({ () -> bridge.std__vector_std__string_ in
49+
var __vector = bridge.create_std__vector_std__string_(__unwrappedValue.count)
50+
for __item in __unwrappedValue {
51+
__vector.push_back(std.string(__item))
52+
}
53+
return __vector
54+
}())
55+
} else {
56+
return .init()
57+
}
4658
}())
4759
}
4860

@@ -114,4 +126,33 @@ public extension NativeFilePickerOptions {
114126
}()
115127
}
116128
}
129+
130+
var mimeTypes: [String]? {
131+
@inline(__always)
132+
get {
133+
return { () -> [String]? in
134+
if let __unwrapped = self.__mimeTypes.value {
135+
return __unwrapped.map({ __item in String(__item) })
136+
} else {
137+
return nil
138+
}
139+
}()
140+
}
141+
@inline(__always)
142+
set {
143+
self.__mimeTypes = { () -> bridge.std__optional_std__vector_std__string__ in
144+
if let __unwrappedValue = newValue {
145+
return bridge.create_std__optional_std__vector_std__string__({ () -> bridge.std__vector_std__string_ in
146+
var __vector = bridge.create_std__vector_std__string_(__unwrappedValue.count)
147+
for __item in __unwrappedValue {
148+
__vector.push_back(std.string(__item))
149+
}
150+
return __vector
151+
}())
152+
} else {
153+
return .init()
154+
}
155+
}()
156+
}
157+
}
117158
}

packages/react-native-fast-io/nitrogen/generated/shared/c++/NativeFilePickerOptions.hpp

+6-2
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ namespace margelo::nitro::fastio {
3434
std::optional<bool> multiple SWIFT_PRIVATE;
3535
std::optional<std::string> startIn SWIFT_PRIVATE;
3636
std::optional<std::vector<std::string>> extensions SWIFT_PRIVATE;
37+
std::optional<std::vector<std::string>> mimeTypes SWIFT_PRIVATE;
3738

3839
public:
39-
explicit NativeFilePickerOptions(std::optional<bool> multiple, std::optional<std::string> startIn, std::optional<std::vector<std::string>> extensions): multiple(multiple), startIn(startIn), extensions(extensions) {}
40+
explicit NativeFilePickerOptions(std::optional<bool> multiple, std::optional<std::string> startIn, std::optional<std::vector<std::string>> extensions, std::optional<std::vector<std::string>> mimeTypes): multiple(multiple), startIn(startIn), extensions(extensions), mimeTypes(mimeTypes) {}
4041
};
4142

4243
} // namespace margelo::nitro::fastio
@@ -53,14 +54,16 @@ namespace margelo::nitro {
5354
return NativeFilePickerOptions(
5455
JSIConverter<std::optional<bool>>::fromJSI(runtime, obj.getProperty(runtime, "multiple")),
5556
JSIConverter<std::optional<std::string>>::fromJSI(runtime, obj.getProperty(runtime, "startIn")),
56-
JSIConverter<std::optional<std::vector<std::string>>>::fromJSI(runtime, obj.getProperty(runtime, "extensions"))
57+
JSIConverter<std::optional<std::vector<std::string>>>::fromJSI(runtime, obj.getProperty(runtime, "extensions")),
58+
JSIConverter<std::optional<std::vector<std::string>>>::fromJSI(runtime, obj.getProperty(runtime, "mimeTypes"))
5759
);
5860
}
5961
static inline jsi::Value toJSI(jsi::Runtime& runtime, const NativeFilePickerOptions& arg) {
6062
jsi::Object obj(runtime);
6163
obj.setProperty(runtime, "multiple", JSIConverter<std::optional<bool>>::toJSI(runtime, arg.multiple));
6264
obj.setProperty(runtime, "startIn", JSIConverter<std::optional<std::string>>::toJSI(runtime, arg.startIn));
6365
obj.setProperty(runtime, "extensions", JSIConverter<std::optional<std::vector<std::string>>>::toJSI(runtime, arg.extensions));
66+
obj.setProperty(runtime, "mimeTypes", JSIConverter<std::optional<std::vector<std::string>>>::toJSI(runtime, arg.mimeTypes));
6467
return obj;
6568
}
6669
static inline bool canConvert(jsi::Runtime& runtime, const jsi::Value& value) {
@@ -71,6 +74,7 @@ namespace margelo::nitro {
7174
if (!JSIConverter<std::optional<bool>>::canConvert(runtime, obj.getProperty(runtime, "multiple"))) return false;
7275
if (!JSIConverter<std::optional<std::string>>::canConvert(runtime, obj.getProperty(runtime, "startIn"))) return false;
7376
if (!JSIConverter<std::optional<std::vector<std::string>>>::canConvert(runtime, obj.getProperty(runtime, "extensions"))) return false;
77+
if (!JSIConverter<std::optional<std::vector<std::string>>>::canConvert(runtime, obj.getProperty(runtime, "mimeTypes"))) return false;
7478
return true;
7579
}
7680
};

packages/react-native-fast-io/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './types/fs'
12
export * from './w3c/blob'
23
export * from './w3c/fs'
34
export * from './w3c/network'

packages/react-native-fast-io/src/native/fs.nitro.ts

+2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ type WellKnownDirectory = 'desktop' | 'documents' | 'downloads' | 'music' | 'pic
1414
export type NativeFilePickerOptions = {
1515
multiple?: boolean
1616
startIn?: string
17+
1718
extensions?: string[]
19+
mimeTypes?: string[]
1820
}
1921

2022
interface FileSystem extends HybridObject<{ ios: 'swift'; android: 'kotlin' }> {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/**
2+
* Additional types for File System Access API that are currently not available in TypeScript.
3+
* This is because they are not supported by more than one browser engine.
4+
*/
5+
6+
export {}
7+
8+
export type WellKnownDirectory =
9+
| 'desktop'
10+
| 'documents'
11+
| 'downloads'
12+
| 'music'
13+
| 'pictures'
14+
| 'videos'
15+
export type FileExtension = `.${string}`
16+
export type MIMEType = `${string}/${string}`
17+
18+
export interface FilePickerAcceptType {
19+
/**
20+
* @default ""
21+
*/
22+
description?: string | undefined
23+
accept: Record<MIMEType, FileExtension | FileExtension[]> | undefined
24+
}
25+
26+
export interface FilePickerOptions {
27+
types?: FilePickerAcceptType[] | undefined
28+
/**
29+
* @default false
30+
*/
31+
excludeAcceptAllOption?: boolean | undefined
32+
startIn?: WellKnownDirectory | /* | FileSystemHandle */ undefined
33+
id?: string | undefined
34+
}
35+
36+
export interface OpenFilePickerOptions extends FilePickerOptions {
37+
/**
38+
* @default false
39+
*/
40+
multiple?: boolean | undefined
41+
}
42+
43+
export interface SaveFilePickerOptions extends FilePickerOptions {
44+
suggestedName?: string | undefined
45+
}
46+
47+
export type FileSystemPermissionMode = 'read' | 'readwrite'
48+
49+
export interface DirectoryPickerOptions {
50+
id?: string | undefined
51+
startIn?: WellKnownDirectory /* | FileSystemHandle */ | undefined
52+
/**
53+
* @default "read"
54+
*/
55+
mode?: FileSystemPermissionMode | undefined
56+
}

packages/react-native-fast-io/src/w3c/fs.d.ts

-52
This file was deleted.

packages/react-native-fast-io/src/w3c/fs.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { FileSystem, Metadata, NativeFilePickerOptions } from '../native/fs.nitro'
22
import { StreamFactory } from '../native/streams.nitro'
3+
import {
4+
DirectoryPickerOptions,
5+
FileExtension,
6+
OpenFilePickerOptions,
7+
SaveFilePickerOptions,
8+
} from '../types/fs'
39
import { Blob } from './blob'
410
import { toReadableStream } from './streams'
511

@@ -93,12 +99,13 @@ export async function showOpenFilePicker(
9399
}
94100

95101
if (options.types) {
96-
nativePickerOptions.extensions = options.types?.reduce<FileExtension[]>((acc, type) => {
102+
nativePickerOptions.extensions = options.types.reduce<FileExtension[]>((acc, type) => {
97103
if (!type.accept) {
98104
return acc
99105
}
100106
return acc.concat(...Object.values(type.accept))
101107
}, [])
108+
nativePickerOptions.mimeTypes = options.types.flatMap((type) => Object.keys(type.accept || {}))
102109
}
103110

104111
const paths = await FileSystem.showOpenFilePicker(nativePickerOptions)

0 commit comments

Comments
 (0)