@uploadista/react-native-core 0.0.3
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/.turbo/turbo-check.log +396 -0
- package/LICENSE +21 -0
- package/README.md +426 -0
- package/package.json +42 -0
- package/src/client/create-uploadista-client.ts +65 -0
- package/src/client/index.ts +4 -0
- package/src/components/CameraUploadButton.tsx +130 -0
- package/src/components/FileUploadButton.tsx +130 -0
- package/src/components/GalleryUploadButton.tsx +199 -0
- package/src/components/UploadList.tsx +214 -0
- package/src/components/UploadProgress.tsx +196 -0
- package/src/components/index.ts +19 -0
- package/src/hooks/index.ts +29 -0
- package/src/hooks/uploadista-context.ts +17 -0
- package/src/hooks/use-camera-upload.ts +38 -0
- package/src/hooks/use-file-upload.ts +40 -0
- package/src/hooks/use-flow-upload.ts +242 -0
- package/src/hooks/use-gallery-upload.ts +65 -0
- package/src/hooks/use-multi-upload.ts +363 -0
- package/src/hooks/use-upload-metrics.ts +82 -0
- package/src/hooks/use-upload.ts +378 -0
- package/src/hooks/use-uploadista-client.ts +23 -0
- package/src/hooks/use-uploadista-context.ts +20 -0
- package/src/index.ts +111 -0
- package/src/types/index.ts +2 -0
- package/src/types/types.ts +359 -0
- package/src/types/upload-input.ts +1 -0
- package/src/utils/fileHelpers.ts +201 -0
- package/src/utils/index.ts +36 -0
- package/src/utils/permissions.ts +177 -0
- package/src/utils/uriHelpers.ts +148 -0
- package/test-compile.ts +5 -0
- package/tsconfig.json +21 -0
package/README.md
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# @uploadista/react-native
|
|
2
|
+
|
|
3
|
+
React Native client for Uploadista with mobile-optimized upload patterns and support for both Expo-managed and bare React Native environments.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`@uploadista/react-native` provides a first-class React Native integration for Uploadista, enabling seamless file uploads and flow management on iOS and Android. The client abstracts platform differences and provides idiomatic React Native patterns for:
|
|
8
|
+
|
|
9
|
+
- **Single file uploads** - Upload one file with progress tracking
|
|
10
|
+
- **Multi-file uploads** - Concurrent uploads with batch management
|
|
11
|
+
- **Flow uploads** - Orchestrated uploads through processing pipelines
|
|
12
|
+
- **Camera uploads** - Direct camera capture and upload
|
|
13
|
+
- **Gallery uploads** - Photo/video library selection and upload
|
|
14
|
+
- **File picker uploads** - Generic document selection
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- ✅ **Expo and Bare RN Support** - Works with both Expo-managed and bare React Native workflows
|
|
19
|
+
- ✅ **iOS & Android** - Full support for iOS 14+ and Android 7+
|
|
20
|
+
- ✅ **File System Abstraction** - Pluggable providers for different environments
|
|
21
|
+
- ✅ **Mobile Patterns** - Camera, gallery, and file picker integrations
|
|
22
|
+
- ✅ **Progress Tracking** - Real-time upload progress and metrics
|
|
23
|
+
- ✅ **WebSocket Support** - Flow uploads with real-time events
|
|
24
|
+
- ✅ **Type Safe** - Full TypeScript support with strict typing
|
|
25
|
+
- ✅ **Zero External Dependencies** - Only optional peer dependencies
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
### Prerequisites
|
|
30
|
+
|
|
31
|
+
- React Native 0.71+
|
|
32
|
+
- React 16.8+ (for hooks)
|
|
33
|
+
- Either Expo SDK 51+ or bare React Native environment
|
|
34
|
+
|
|
35
|
+
### NPM Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install @uploadista/react-native @uploadista/client @uploadista/core
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Yarn Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
yarn add @uploadista/react-native @uploadista/client @uploadista/core
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### pnpm Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pnpm add @uploadista/react-native @uploadista/client @uploadista/core
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
### Expo Setup
|
|
56
|
+
|
|
57
|
+
For Expo managed projects, install the required Expo modules:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
expo install expo-document-picker expo-image-picker expo-camera expo-file-system
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Then in your app:
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { UploadistaProvider } from '@uploadista/react-native';
|
|
67
|
+
import { UploadClient } from '@uploadista/client';
|
|
68
|
+
|
|
69
|
+
const client = new UploadClient({
|
|
70
|
+
apiUrl: 'https://api.example.com',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export default function App() {
|
|
74
|
+
return (
|
|
75
|
+
<UploadistaProvider client={client}>
|
|
76
|
+
<YourAppContent />
|
|
77
|
+
</UploadistaProvider>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Bare React Native Setup
|
|
83
|
+
|
|
84
|
+
For bare React Native, install native dependencies:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm install react-native-document-picker react-native-image-picker rn-fetch-blob
|
|
88
|
+
|
|
89
|
+
# For iOS
|
|
90
|
+
cd ios && pod install && cd ..
|
|
91
|
+
|
|
92
|
+
# For Android (usually automatic)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Then configure the provider:
|
|
96
|
+
|
|
97
|
+
```tsx
|
|
98
|
+
import { UploadistaProvider } from '@uploadista/react-native';
|
|
99
|
+
import { NativeFileSystemProvider } from '@uploadista/react-native/providers';
|
|
100
|
+
import { UploadClient } from '@uploadista/client';
|
|
101
|
+
|
|
102
|
+
const client = new UploadClient({
|
|
103
|
+
apiUrl: 'https://api.example.com',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const fileSystemProvider = new NativeFileSystemProvider();
|
|
107
|
+
|
|
108
|
+
export default function App() {
|
|
109
|
+
return (
|
|
110
|
+
<UploadistaProvider
|
|
111
|
+
client={client}
|
|
112
|
+
fileSystemProvider={fileSystemProvider}
|
|
113
|
+
>
|
|
114
|
+
<YourAppContent />
|
|
115
|
+
</UploadistaProvider>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Basic Usage
|
|
121
|
+
|
|
122
|
+
### Single File Upload
|
|
123
|
+
|
|
124
|
+
```tsx
|
|
125
|
+
import { useUploadistaClient } from '@uploadista/react-native';
|
|
126
|
+
|
|
127
|
+
export function SingleUploadScreen() {
|
|
128
|
+
const { fileSystemProvider } = useUploadistaClient();
|
|
129
|
+
|
|
130
|
+
const handlePickAndUpload = async () => {
|
|
131
|
+
try {
|
|
132
|
+
// Pick a file
|
|
133
|
+
const file = await fileSystemProvider.pickDocument();
|
|
134
|
+
|
|
135
|
+
// Read file content
|
|
136
|
+
const content = await fileSystemProvider.readFile(file.uri);
|
|
137
|
+
|
|
138
|
+
// Upload to server
|
|
139
|
+
const response = await fetch('https://api.example.com/upload', {
|
|
140
|
+
method: 'POST',
|
|
141
|
+
body: content,
|
|
142
|
+
headers: {
|
|
143
|
+
'Content-Type': file.mimeType || 'application/octet-stream',
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
console.log('Upload successful:', await response.json());
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error('Upload failed:', error);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<Button
|
|
155
|
+
title="Pick and Upload File"
|
|
156
|
+
onPress={handlePickAndUpload}
|
|
157
|
+
/>
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Camera Upload
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
import { useUploadistaClient } from '@uploadista/react-native';
|
|
166
|
+
|
|
167
|
+
export function CameraUploadScreen() {
|
|
168
|
+
const { fileSystemProvider } = useUploadistaClient();
|
|
169
|
+
|
|
170
|
+
const handleCaptureAndUpload = async () => {
|
|
171
|
+
try {
|
|
172
|
+
// Capture photo with camera
|
|
173
|
+
const photo = await fileSystemProvider.pickCamera();
|
|
174
|
+
|
|
175
|
+
// Upload to server
|
|
176
|
+
const formData = new FormData();
|
|
177
|
+
formData.append('file', {
|
|
178
|
+
uri: photo.uri,
|
|
179
|
+
name: photo.name,
|
|
180
|
+
type: photo.mimeType || 'image/jpeg',
|
|
181
|
+
} as any);
|
|
182
|
+
|
|
183
|
+
const response = await fetch('https://api.example.com/upload', {
|
|
184
|
+
method: 'POST',
|
|
185
|
+
body: formData,
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
console.log('Photo uploaded:', await response.json());
|
|
189
|
+
} catch (error) {
|
|
190
|
+
console.error('Upload failed:', error);
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<Button
|
|
196
|
+
title="Capture and Upload Photo"
|
|
197
|
+
onPress={handleCaptureAndUpload}
|
|
198
|
+
/>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## File System Provider
|
|
204
|
+
|
|
205
|
+
The file system abstraction layer allows you to work with files uniformly across Expo and bare React Native:
|
|
206
|
+
|
|
207
|
+
```tsx
|
|
208
|
+
interface FileSystemProvider {
|
|
209
|
+
// Select a document
|
|
210
|
+
pickDocument(options?: PickerOptions): Promise<FilePickResult>;
|
|
211
|
+
|
|
212
|
+
// Select an image from gallery
|
|
213
|
+
pickImage(options?: PickerOptions): Promise<FilePickResult>;
|
|
214
|
+
|
|
215
|
+
// Select a video from gallery
|
|
216
|
+
pickVideo(options?: PickerOptions): Promise<FilePickResult>;
|
|
217
|
+
|
|
218
|
+
// Capture a photo with camera
|
|
219
|
+
pickCamera(options?: CameraOptions): Promise<FilePickResult>;
|
|
220
|
+
|
|
221
|
+
// Read file as ArrayBuffer
|
|
222
|
+
readFile(uri: string): Promise<ArrayBuffer>;
|
|
223
|
+
|
|
224
|
+
// Get file information
|
|
225
|
+
getFileInfo(uri: string): Promise<FileInfo>;
|
|
226
|
+
|
|
227
|
+
// Convert file path to accessible URI
|
|
228
|
+
getDocumentUri(filePath: string): Promise<string>;
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Types
|
|
233
|
+
|
|
234
|
+
### FilePickResult
|
|
235
|
+
|
|
236
|
+
Result from file picker operations:
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
interface FilePickResult {
|
|
240
|
+
uri: string; // File URI (platform-specific)
|
|
241
|
+
name: string; // File name with extension
|
|
242
|
+
size: number; // File size in bytes
|
|
243
|
+
mimeType?: string; // MIME type (if available)
|
|
244
|
+
localPath?: string; // Local file path (if available)
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### FileInfo
|
|
249
|
+
|
|
250
|
+
Information about a file:
|
|
251
|
+
|
|
252
|
+
```tsx
|
|
253
|
+
interface FileInfo {
|
|
254
|
+
uri: string; // File URI
|
|
255
|
+
name: string; // File name
|
|
256
|
+
size: number; // File size in bytes
|
|
257
|
+
mimeType?: string; // MIME type (if available)
|
|
258
|
+
modificationTime?: number; // Last modified timestamp
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### PickerOptions
|
|
263
|
+
|
|
264
|
+
Options for file picker operations:
|
|
265
|
+
|
|
266
|
+
```tsx
|
|
267
|
+
interface PickerOptions {
|
|
268
|
+
allowedTypes?: string[]; // MIME types to filter
|
|
269
|
+
allowMultiple?: boolean; // Allow multiple selection
|
|
270
|
+
maxSize?: number; // Maximum file size in bytes
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Permissions
|
|
275
|
+
|
|
276
|
+
### iOS
|
|
277
|
+
|
|
278
|
+
Add to `Info.plist`:
|
|
279
|
+
|
|
280
|
+
```xml
|
|
281
|
+
<key>NSCameraUsageDescription</key>
|
|
282
|
+
<string>We need camera access to upload photos</string>
|
|
283
|
+
<key>NSPhotoLibraryUsageDescription</key>
|
|
284
|
+
<string>We need photo library access to upload images</string>
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Android
|
|
288
|
+
|
|
289
|
+
Add to `AndroidManifest.xml`:
|
|
290
|
+
|
|
291
|
+
```xml
|
|
292
|
+
<uses-permission android:name="android.permission.CAMERA" />
|
|
293
|
+
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
294
|
+
<uses-permission android:name="android.permission.INTERNET" />
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
The file system providers handle runtime permission requests automatically.
|
|
298
|
+
|
|
299
|
+
## Architecture
|
|
300
|
+
|
|
301
|
+
### Expo Provider
|
|
302
|
+
|
|
303
|
+
Uses Expo's native modules for file system access:
|
|
304
|
+
|
|
305
|
+
- `expo-document-picker` - Document selection
|
|
306
|
+
- `expo-image-picker` - Image and video selection from gallery
|
|
307
|
+
- `expo-camera` - Camera integration
|
|
308
|
+
- `expo-file-system` - File reading
|
|
309
|
+
|
|
310
|
+
### Native Provider
|
|
311
|
+
|
|
312
|
+
Uses third-party native modules for bare React Native:
|
|
313
|
+
|
|
314
|
+
- `react-native-document-picker` - Document selection
|
|
315
|
+
- `react-native-image-picker` - Image and video selection
|
|
316
|
+
- `rn-fetch-blob` - File system and network operations
|
|
317
|
+
|
|
318
|
+
Both providers implement the same `FileSystemProvider` interface, allowing you to write code once and run everywhere.
|
|
319
|
+
|
|
320
|
+
## Examples
|
|
321
|
+
|
|
322
|
+
See the `examples/` directory for complete working examples:
|
|
323
|
+
|
|
324
|
+
- `react-native-expo-client/` - Expo managed workflow example
|
|
325
|
+
- `react-native-cli-client/` - Bare React Native example
|
|
326
|
+
|
|
327
|
+
## Development
|
|
328
|
+
|
|
329
|
+
### Building
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
npm run build
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Type Checking
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
npm run type-check
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Linting
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
npm run lint
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Testing
|
|
348
|
+
|
|
349
|
+
```bash
|
|
350
|
+
npm test
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## API Reference
|
|
354
|
+
|
|
355
|
+
### createFileSystemProvider()
|
|
356
|
+
|
|
357
|
+
Creates a file system provider based on configuration:
|
|
358
|
+
|
|
359
|
+
```tsx
|
|
360
|
+
import { createFileSystemProvider } from '@uploadista/react-native/providers';
|
|
361
|
+
|
|
362
|
+
// Auto-detect environment
|
|
363
|
+
const provider = createFileSystemProvider();
|
|
364
|
+
|
|
365
|
+
// Specify Expo
|
|
366
|
+
const expoProvider = createFileSystemProvider({ type: 'expo' });
|
|
367
|
+
|
|
368
|
+
// Specify native
|
|
369
|
+
const nativeProvider = createFileSystemProvider({ type: 'native' });
|
|
370
|
+
|
|
371
|
+
// Use custom provider
|
|
372
|
+
const customProvider = createFileSystemProvider({
|
|
373
|
+
provider: myCustomProvider,
|
|
374
|
+
});
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### getDefaultFileSystemProvider()
|
|
378
|
+
|
|
379
|
+
Gets the automatically detected file system provider for the current environment:
|
|
380
|
+
|
|
381
|
+
```tsx
|
|
382
|
+
import { getDefaultFileSystemProvider } from '@uploadista/react-native/providers';
|
|
383
|
+
|
|
384
|
+
const provider = getDefaultFileSystemProvider();
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
## Troubleshooting
|
|
388
|
+
|
|
389
|
+
### Module not found errors
|
|
390
|
+
|
|
391
|
+
Ensure you've installed the required modules for your environment:
|
|
392
|
+
|
|
393
|
+
**Expo:**
|
|
394
|
+
```bash
|
|
395
|
+
expo install expo-document-picker expo-image-picker expo-camera expo-file-system
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
**Bare RN:**
|
|
399
|
+
```bash
|
|
400
|
+
npm install react-native-document-picker react-native-image-picker rn-fetch-blob
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Permission denied
|
|
404
|
+
|
|
405
|
+
Check that:
|
|
406
|
+
1. Permissions are declared in `Info.plist` (iOS) or `AndroidManifest.xml` (Android)
|
|
407
|
+
2. User has granted permissions at runtime (app will request automatically)
|
|
408
|
+
3. For Android 13+, ensure `READ_MEDIA_IMAGES` and `READ_MEDIA_VIDEO` are also declared
|
|
409
|
+
|
|
410
|
+
### File picker not opening
|
|
411
|
+
|
|
412
|
+
Verify that:
|
|
413
|
+
1. You're inside a valid React component
|
|
414
|
+
2. The provider is properly initialized
|
|
415
|
+
3. You're calling from a user interaction (not from render)
|
|
416
|
+
|
|
417
|
+
## Support
|
|
418
|
+
|
|
419
|
+
For issues and questions:
|
|
420
|
+
|
|
421
|
+
- GitHub Issues: https://github.com/uploadista/uploadista/issues
|
|
422
|
+
- Documentation: https://uploadista.dev/docs
|
|
423
|
+
|
|
424
|
+
## License
|
|
425
|
+
|
|
426
|
+
MIT - See LICENSE file for details
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uploadista/react-native-core",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Core React Native client for Uploadista",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Uploadista",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts",
|
|
10
|
+
"./hooks": "./src/hooks/index.ts",
|
|
11
|
+
"./components": "./src/components/index.ts",
|
|
12
|
+
"./utils": "./src/utils/index.ts",
|
|
13
|
+
"./services": "./src/services/index.ts"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"uuid": "^10.0.0",
|
|
17
|
+
"js-base64": "^3.7.7",
|
|
18
|
+
"@uploadista/core": "0.0.3",
|
|
19
|
+
"@uploadista/client-core": "0.0.3"
|
|
20
|
+
},
|
|
21
|
+
"peerDependencies": {
|
|
22
|
+
"react": ">=16.8.0",
|
|
23
|
+
"react-native": ">=0.71.0",
|
|
24
|
+
"@react-native-async-storage/async-storage": ">=1.17.0"
|
|
25
|
+
},
|
|
26
|
+
"peerDependenciesMeta": {
|
|
27
|
+
"@react-native-async-storage/async-storage": {
|
|
28
|
+
"optional": true
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/react": ">=18.0.0",
|
|
33
|
+
"@types/react-native": ">=0.71.0",
|
|
34
|
+
"@types/uuid": "^10.0.0",
|
|
35
|
+
"@uploadista/typescript-config": "0.0.3"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"format": "biome format --write ./src",
|
|
39
|
+
"lint": "biome lint --write ./src",
|
|
40
|
+
"check": "biome check --write ./src"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ConnectionPoolConfig,
|
|
3
|
+
createClientStorage,
|
|
4
|
+
createLogger,
|
|
5
|
+
createUploadistaClient as createUploadistaClientCore,
|
|
6
|
+
type ServiceContainer,
|
|
7
|
+
type UploadistaClientOptions as UploadistaClientOptionsCore,
|
|
8
|
+
} from "@uploadista/client-core";
|
|
9
|
+
import type { ReactNativeUploadInput } from "../types/upload-input";
|
|
10
|
+
|
|
11
|
+
export interface UploadistaClientOptions
|
|
12
|
+
extends Omit<
|
|
13
|
+
UploadistaClientOptionsCore<ReactNativeUploadInput>,
|
|
14
|
+
| "webSocketFactory"
|
|
15
|
+
| "abortControllerFactory"
|
|
16
|
+
| "generateId"
|
|
17
|
+
| "clientStorage"
|
|
18
|
+
| "logger"
|
|
19
|
+
| "httpClient"
|
|
20
|
+
| "fileReader"
|
|
21
|
+
| "base64"
|
|
22
|
+
> {
|
|
23
|
+
connectionPooling?: ConnectionPoolConfig;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether to use AsyncStorage for persistence
|
|
27
|
+
* If false, uses in-memory storage
|
|
28
|
+
* @default true
|
|
29
|
+
*/
|
|
30
|
+
useAsyncStorage?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Creates an upload client instance with React Native-specific service implementations
|
|
35
|
+
*
|
|
36
|
+
* @param options - Client configuration options
|
|
37
|
+
* @returns Configured UploadistaClient instance
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* import { createUploadistaClient } from '@uploadista/react-native'
|
|
42
|
+
*
|
|
43
|
+
* const client = createUploadistaClient({
|
|
44
|
+
* baseUrl: 'https://api.example.com',
|
|
45
|
+
* storageId: 'my-storage',
|
|
46
|
+
* chunkSize: 1024 * 1024, // 1MB
|
|
47
|
+
* useAsyncStorage: true,
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export function createUploadistaClient(
|
|
52
|
+
options: UploadistaClientOptions,
|
|
53
|
+
services: ServiceContainer<ReactNativeUploadInput>,
|
|
54
|
+
) {
|
|
55
|
+
return createUploadistaClientCore<ReactNativeUploadInput>({
|
|
56
|
+
...options,
|
|
57
|
+
webSocketFactory: services.websocket,
|
|
58
|
+
abortControllerFactory: services.abortController,
|
|
59
|
+
httpClient: services.httpClient,
|
|
60
|
+
fileReader: services.fileReader,
|
|
61
|
+
generateId: services.idGeneration,
|
|
62
|
+
logger: createLogger(false, () => {}),
|
|
63
|
+
clientStorage: createClientStorage(services.storage),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { type ReactNode, useEffect } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ActivityIndicator,
|
|
4
|
+
Pressable,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Text,
|
|
7
|
+
View,
|
|
8
|
+
} from "react-native";
|
|
9
|
+
import { useCameraUpload } from "../hooks";
|
|
10
|
+
import type { UseCameraUploadOptions } from "../types";
|
|
11
|
+
import { UploadProgress } from "./UploadProgress";
|
|
12
|
+
|
|
13
|
+
export interface CameraUploadButtonProps {
|
|
14
|
+
/** Options for camera upload */
|
|
15
|
+
options?: UseCameraUploadOptions;
|
|
16
|
+
/** Button label text */
|
|
17
|
+
label?: string;
|
|
18
|
+
/** Custom button content */
|
|
19
|
+
children?: ReactNode;
|
|
20
|
+
/** Callback when upload completes successfully */
|
|
21
|
+
onSuccess?: (result: unknown) => void;
|
|
22
|
+
/** Callback when upload fails */
|
|
23
|
+
onError?: (error: Error) => void;
|
|
24
|
+
/** Callback when upload is cancelled */
|
|
25
|
+
onCancel?: () => void;
|
|
26
|
+
/** Whether to show progress inline */
|
|
27
|
+
showProgress?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Button component for camera capture and upload
|
|
32
|
+
* Triggers camera on press and handles upload with progress display
|
|
33
|
+
*/
|
|
34
|
+
export function CameraUploadButton({
|
|
35
|
+
options,
|
|
36
|
+
label = "Take Photo",
|
|
37
|
+
children,
|
|
38
|
+
onSuccess,
|
|
39
|
+
onError,
|
|
40
|
+
onCancel,
|
|
41
|
+
showProgress = true,
|
|
42
|
+
}: CameraUploadButtonProps) {
|
|
43
|
+
const { state, captureAndUpload } = useCameraUpload(options);
|
|
44
|
+
|
|
45
|
+
const handlePress = async () => {
|
|
46
|
+
try {
|
|
47
|
+
await captureAndUpload();
|
|
48
|
+
} catch (error) {
|
|
49
|
+
if (error instanceof Error) {
|
|
50
|
+
if (
|
|
51
|
+
error.message.includes("cancelled") ||
|
|
52
|
+
error.message.includes("aborted")
|
|
53
|
+
) {
|
|
54
|
+
onCancel?.();
|
|
55
|
+
} else {
|
|
56
|
+
onError?.(error);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const isLoading = state.status === "uploading";
|
|
63
|
+
const isDisabled = isLoading || state.status === "aborted";
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (state.status === "success" && state.result) {
|
|
67
|
+
onSuccess?.(state.result);
|
|
68
|
+
}
|
|
69
|
+
}, [state.status, state.result, onSuccess]);
|
|
70
|
+
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
if (state.status === "error" && state.error) {
|
|
73
|
+
onError?.(state.error);
|
|
74
|
+
}
|
|
75
|
+
}, [state.status, state.error, onError]);
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<View style={styles.container}>
|
|
79
|
+
<Pressable
|
|
80
|
+
style={[styles.button, isDisabled && styles.buttonDisabled]}
|
|
81
|
+
onPress={handlePress}
|
|
82
|
+
disabled={isDisabled}
|
|
83
|
+
>
|
|
84
|
+
{isLoading && (
|
|
85
|
+
<ActivityIndicator
|
|
86
|
+
size="small"
|
|
87
|
+
color="#FFFFFF"
|
|
88
|
+
style={styles.spinner}
|
|
89
|
+
/>
|
|
90
|
+
)}
|
|
91
|
+
<Text style={styles.buttonText}>{children || label}</Text>
|
|
92
|
+
</Pressable>
|
|
93
|
+
{showProgress && state.status !== "idle" && (
|
|
94
|
+
<View style={styles.progressContainer}>
|
|
95
|
+
<UploadProgress state={state} label="Camera upload" />
|
|
96
|
+
</View>
|
|
97
|
+
)}
|
|
98
|
+
</View>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const styles = StyleSheet.create({
|
|
103
|
+
container: {
|
|
104
|
+
gap: 8,
|
|
105
|
+
},
|
|
106
|
+
button: {
|
|
107
|
+
flexDirection: "row",
|
|
108
|
+
alignItems: "center",
|
|
109
|
+
justifyContent: "center",
|
|
110
|
+
paddingVertical: 12,
|
|
111
|
+
paddingHorizontal: 16,
|
|
112
|
+
backgroundColor: "#007AFF",
|
|
113
|
+
borderRadius: 8,
|
|
114
|
+
gap: 8,
|
|
115
|
+
},
|
|
116
|
+
buttonDisabled: {
|
|
117
|
+
opacity: 0.6,
|
|
118
|
+
},
|
|
119
|
+
buttonText: {
|
|
120
|
+
fontSize: 16,
|
|
121
|
+
fontWeight: "600",
|
|
122
|
+
color: "#FFFFFF",
|
|
123
|
+
},
|
|
124
|
+
spinner: {
|
|
125
|
+
marginRight: 4,
|
|
126
|
+
},
|
|
127
|
+
progressContainer: {
|
|
128
|
+
marginTop: 4,
|
|
129
|
+
},
|
|
130
|
+
});
|