@regantis-sdk/react-native-chat 0.1.1 → 0.1.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/README.md +34 -2
- package/android/build.gradle +1 -0
- package/android/src/main/AndroidManifest.xml +13 -1
- package/android/src/main/java/technology/regantis/chat/RegantisChatNativeModule.java +109 -56
- package/android/src/main/res/xml/regantis_chat_file_paths.xml +4 -0
- package/index.d.ts +5 -1
- package/ios/RegantisChatNative.h +1 -1
- package/ios/RegantisChatNative.m +81 -31
- package/package.json +1 -1
- package/src/RegantisChat.js +682 -205
- package/src/client.js +3 -3
- package/src/defaults.js +1 -1
- package/src/native.js +5 -5
package/README.md
CHANGED
|
@@ -25,6 +25,17 @@ pod install
|
|
|
25
25
|
|
|
26
26
|
Android is autolinked by React Native.
|
|
27
27
|
|
|
28
|
+
### iOS camera
|
|
29
|
+
|
|
30
|
+
To use the built-in camera action, add a camera usage description to the host app `Info.plist`:
|
|
31
|
+
|
|
32
|
+
```xml
|
|
33
|
+
<key>NSCameraUsageDescription</key>
|
|
34
|
+
<string>Take photos to attach to support chat messages.</string>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Android opens the system camera app and does not require an additional app permission for this SDK flow.
|
|
38
|
+
|
|
28
39
|
## Basic usage
|
|
29
40
|
|
|
30
41
|
```jsx
|
|
@@ -112,8 +123,8 @@ Server configuration remains the default. Per-app overrides are optional:
|
|
|
112
123
|
- WebSocket realtime updates with HTTP polling fallback
|
|
113
124
|
- typing state
|
|
114
125
|
- customer-side translation selector
|
|
115
|
-
-
|
|
116
|
-
-
|
|
126
|
+
- image gallery, file picker and direct camera capture
|
|
127
|
+
- in-chat image lightbox with pinch zoom and pan
|
|
117
128
|
- native incoming-message sound
|
|
118
129
|
- close/reopen conversation
|
|
119
130
|
- rating and rating comment
|
|
@@ -155,3 +166,24 @@ chat.stop();
|
|
|
155
166
|
npm login
|
|
156
167
|
npm publish --access public
|
|
157
168
|
```
|
|
169
|
+
|
|
170
|
+
## Mobile widget-style navigation
|
|
171
|
+
|
|
172
|
+
The React Native component now mirrors the mobile web widget flow: it opens on the welcome/home screen and the visitor enters the conversation using the chat card or Chat navigation item.
|
|
173
|
+
|
|
174
|
+
```jsx
|
|
175
|
+
<RegantisChat
|
|
176
|
+
apiKey="YOUR_REACT_NATIVE_CHAT_API_KEY"
|
|
177
|
+
initialView="home"
|
|
178
|
+
/>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Use `initialView="chat"` when an application should open directly in the conversation. `onClose` can be supplied when the host app wants the widget-style minimize action to navigate away from the chat page. `onBack` controls the top-left back button on the welcome screen.
|
|
182
|
+
|
|
183
|
+
```jsx
|
|
184
|
+
<RegantisChat
|
|
185
|
+
apiKey="YOUR_REACT_NATIVE_CHAT_API_KEY"
|
|
186
|
+
onBack={() => navigation.goBack()}
|
|
187
|
+
onClose={() => navigation.goBack()}
|
|
188
|
+
/>
|
|
189
|
+
```
|
package/android/build.gradle
CHANGED
|
@@ -1 +1,13 @@
|
|
|
1
|
-
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
1
|
+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
2
|
+
<application>
|
|
3
|
+
<provider
|
|
4
|
+
android:name="androidx.core.content.FileProvider"
|
|
5
|
+
android:authorities="${applicationId}.regantis.chat.fileprovider"
|
|
6
|
+
android:exported="false"
|
|
7
|
+
android:grantUriPermissions="true">
|
|
8
|
+
<meta-data
|
|
9
|
+
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
10
|
+
android:resource="@xml/regantis_chat_file_paths" />
|
|
11
|
+
</provider>
|
|
12
|
+
</application>
|
|
13
|
+
</manifest>
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
package technology.regantis.chat;
|
|
2
2
|
|
|
3
3
|
import android.app.Activity;
|
|
4
|
+
import android.content.ClipData;
|
|
4
5
|
import android.content.Context;
|
|
5
6
|
import android.content.Intent;
|
|
6
7
|
import android.content.SharedPreferences;
|
|
7
|
-
import android.
|
|
8
|
-
import android.graphics.Canvas;
|
|
8
|
+
import android.content.pm.ResolveInfo;
|
|
9
9
|
import android.database.Cursor;
|
|
10
10
|
import android.media.Ringtone;
|
|
11
11
|
import android.media.RingtoneManager;
|
|
12
12
|
import android.net.Uri;
|
|
13
|
+
import android.provider.MediaStore;
|
|
13
14
|
import android.provider.OpenableColumns;
|
|
14
|
-
import android.view.View;
|
|
15
15
|
import android.webkit.MimeTypeMap;
|
|
16
16
|
|
|
17
17
|
import androidx.annotation.NonNull;
|
|
18
18
|
import androidx.annotation.Nullable;
|
|
19
|
+
import androidx.core.content.FileProvider;
|
|
19
20
|
|
|
20
21
|
import com.facebook.react.bridge.ActivityEventListener;
|
|
21
22
|
import com.facebook.react.bridge.Arguments;
|
|
@@ -27,44 +28,87 @@ import com.facebook.react.bridge.ReactMethod;
|
|
|
27
28
|
import com.facebook.react.bridge.WritableMap;
|
|
28
29
|
|
|
29
30
|
import java.io.File;
|
|
30
|
-
import java.
|
|
31
|
+
import java.util.List;
|
|
31
32
|
|
|
32
33
|
public class RegantisChatNativeModule extends ReactContextBaseJavaModule {
|
|
33
34
|
private static final String MODULE_NAME = "RegantisChatNative";
|
|
34
35
|
private static final String STORAGE_NAME = "regantis_react_native_chat";
|
|
35
36
|
private static final int PICK_ATTACHMENT_REQUEST = 17841;
|
|
37
|
+
private static final int TAKE_PHOTO_REQUEST = 17842;
|
|
36
38
|
|
|
37
39
|
private final ReactApplicationContext reactContext;
|
|
38
40
|
private Promise pickerPromise;
|
|
41
|
+
private Promise cameraPromise;
|
|
42
|
+
private File cameraFile;
|
|
43
|
+
private Uri cameraOutputUri;
|
|
39
44
|
|
|
40
45
|
private final ActivityEventListener activityEventListener = new BaseActivityEventListener() {
|
|
41
46
|
@Override
|
|
42
47
|
public void onActivityResult(Activity activity, int requestCode, int resultCode, @Nullable Intent data) {
|
|
43
|
-
if (requestCode
|
|
48
|
+
if (requestCode == PICK_ATTACHMENT_REQUEST && pickerPromise != null) {
|
|
49
|
+
Promise promise = pickerPromise;
|
|
50
|
+
pickerPromise = null;
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
|
|
52
|
+
if (resultCode != Activity.RESULT_OK || data == null || data.getData() == null) {
|
|
53
|
+
promise.resolve(null);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
47
56
|
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
Uri uri = data.getData();
|
|
58
|
+
try {
|
|
59
|
+
int flags = data.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
|
60
|
+
if ((flags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
|
|
61
|
+
reactContext.getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
62
|
+
}
|
|
63
|
+
} catch (Exception ignored) {
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
WritableMap result = Arguments.createMap();
|
|
67
|
+
result.putString("uri", uri.toString());
|
|
68
|
+
result.putString("name", getDisplayName(uri));
|
|
69
|
+
result.putString("type", getMimeType(uri));
|
|
70
|
+
result.putDouble("size", getSize(uri));
|
|
71
|
+
promise.resolve(result);
|
|
50
72
|
return;
|
|
51
73
|
}
|
|
52
74
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
75
|
+
if (requestCode == TAKE_PHOTO_REQUEST && cameraPromise != null) {
|
|
76
|
+
Promise promise = cameraPromise;
|
|
77
|
+
File file = cameraFile;
|
|
78
|
+
Uri outputUri = cameraOutputUri;
|
|
79
|
+
cameraPromise = null;
|
|
80
|
+
cameraFile = null;
|
|
81
|
+
cameraOutputUri = null;
|
|
82
|
+
|
|
83
|
+
if (outputUri != null) {
|
|
84
|
+
try {
|
|
85
|
+
reactContext.revokeUriPermission(
|
|
86
|
+
outputUri,
|
|
87
|
+
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
88
|
+
);
|
|
89
|
+
} catch (Exception ignored) {
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (resultCode != Activity.RESULT_OK) {
|
|
94
|
+
if (file != null) file.delete();
|
|
95
|
+
promise.resolve(null);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (file == null || !file.exists() || file.length() < 1) {
|
|
100
|
+
if (file != null) file.delete();
|
|
101
|
+
promise.reject("CAMERA_FAILED", "Camera did not return an image");
|
|
102
|
+
return;
|
|
58
103
|
}
|
|
59
|
-
} catch (Exception ignored) {
|
|
60
|
-
}
|
|
61
104
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
105
|
+
WritableMap result = Arguments.createMap();
|
|
106
|
+
result.putString("uri", Uri.fromFile(file).toString());
|
|
107
|
+
result.putString("name", file.getName());
|
|
108
|
+
result.putString("type", "image/jpeg");
|
|
109
|
+
result.putDouble("size", file.length());
|
|
110
|
+
promise.resolve(result);
|
|
111
|
+
}
|
|
68
112
|
}
|
|
69
113
|
};
|
|
70
114
|
|
|
@@ -128,49 +172,58 @@ public class RegantisChatNativeModule extends ReactContextBaseJavaModule {
|
|
|
128
172
|
}
|
|
129
173
|
|
|
130
174
|
@ReactMethod
|
|
131
|
-
public void
|
|
175
|
+
public void takePhoto(Promise promise) {
|
|
132
176
|
Activity activity = getCurrentActivity();
|
|
133
177
|
if (activity == null) {
|
|
134
178
|
promise.reject("NO_ACTIVITY", "No active Android Activity");
|
|
135
179
|
return;
|
|
136
180
|
}
|
|
181
|
+
if (cameraPromise != null || pickerPromise != null) {
|
|
182
|
+
promise.reject("PICKER_BUSY", "An attachment picker is already open");
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
137
185
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
promise.reject("SCREENSHOT_FAILED", "Invalid app window size");
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
186
|
+
try {
|
|
187
|
+
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
|
|
188
|
+
if (intent.resolveActivity(reactContext.getPackageManager()) == null) {
|
|
189
|
+
promise.reject("CAMERA_UNAVAILABLE", "No camera application is available");
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
147
192
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
193
|
+
File directory = new File(reactContext.getCacheDir(), "regantis-chat/camera");
|
|
194
|
+
if (!directory.exists() && !directory.mkdirs()) {
|
|
195
|
+
promise.reject("CAMERA_FAILED", "Unable to create camera directory");
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
151
198
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
File file = new File(directory, "screenshot-" + System.currentTimeMillis() + ".png");
|
|
158
|
-
try (FileOutputStream stream = new FileOutputStream(file)) {
|
|
159
|
-
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
|
|
160
|
-
} finally {
|
|
161
|
-
bitmap.recycle();
|
|
162
|
-
}
|
|
199
|
+
File file = File.createTempFile("camera-", ".jpg", directory);
|
|
200
|
+
String authority = reactContext.getPackageName() + ".regantis.chat.fileprovider";
|
|
201
|
+
Uri outputUri = FileProvider.getUriForFile(reactContext, authority, file);
|
|
163
202
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
203
|
+
intent.putExtra(MediaStore.EXTRA_OUTPUT, outputUri);
|
|
204
|
+
intent.setClipData(ClipData.newRawUri("RegantisChatCamera", outputUri));
|
|
205
|
+
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
|
206
|
+
|
|
207
|
+
List<ResolveInfo> cameraActivities = reactContext.getPackageManager().queryIntentActivities(intent, 0);
|
|
208
|
+
for (ResolveInfo info : cameraActivities) {
|
|
209
|
+
reactContext.grantUriPermission(
|
|
210
|
+
info.activityInfo.packageName,
|
|
211
|
+
outputUri,
|
|
212
|
+
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
213
|
+
);
|
|
172
214
|
}
|
|
173
|
-
|
|
215
|
+
|
|
216
|
+
cameraPromise = promise;
|
|
217
|
+
cameraFile = file;
|
|
218
|
+
cameraOutputUri = outputUri;
|
|
219
|
+
activity.startActivityForResult(intent, TAKE_PHOTO_REQUEST);
|
|
220
|
+
} catch (Exception error) {
|
|
221
|
+
cameraPromise = null;
|
|
222
|
+
if (cameraFile != null) cameraFile.delete();
|
|
223
|
+
cameraFile = null;
|
|
224
|
+
cameraOutputUri = null;
|
|
225
|
+
promise.reject("CAMERA_FAILED", error);
|
|
226
|
+
}
|
|
174
227
|
}
|
|
175
228
|
|
|
176
229
|
@ReactMethod
|
|
@@ -180,7 +233,7 @@ public class RegantisChatNativeModule extends ReactContextBaseJavaModule {
|
|
|
180
233
|
promise.reject("NO_ACTIVITY", "No active Android Activity");
|
|
181
234
|
return;
|
|
182
235
|
}
|
|
183
|
-
if (pickerPromise != null) {
|
|
236
|
+
if (pickerPromise != null || cameraPromise != null) {
|
|
184
237
|
promise.reject("PICKER_BUSY", "An attachment picker is already open");
|
|
185
238
|
return;
|
|
186
239
|
}
|
package/index.d.ts
CHANGED
|
@@ -60,6 +60,10 @@ export type RegantisChatProps = {
|
|
|
60
60
|
texts?: Record<string, string>;
|
|
61
61
|
screen?: string;
|
|
62
62
|
style?: StyleProp<ViewStyle>;
|
|
63
|
+
initialView?: 'home' | 'chat';
|
|
64
|
+
onClose?: () => void;
|
|
65
|
+
onBack?: () => void;
|
|
66
|
+
onViewChange?: (view: 'home' | 'chat') => void;
|
|
63
67
|
keyboardVerticalOffset?: number;
|
|
64
68
|
onMessage?: (message: any) => void;
|
|
65
69
|
onUnreadChange?: (count: number) => void;
|
|
@@ -75,7 +79,7 @@ export class RegantisChatClient {
|
|
|
75
79
|
sendMessage(message: string): Promise<any>;
|
|
76
80
|
saveContact(name: string, email: string): Promise<any>;
|
|
77
81
|
uploadAttachment(file: { uri: string; name?: string; type?: string }, kind: 'image' | 'file'): Promise<any>;
|
|
78
|
-
pickAndUpload(kind: 'image' | 'file' | '
|
|
82
|
+
pickAndUpload(kind: 'image' | 'file' | 'camera'): Promise<any>;
|
|
79
83
|
setTranslationLanguage(languageCode: string): Promise<void>;
|
|
80
84
|
closeChat(): Promise<any>;
|
|
81
85
|
reopenChat(): Promise<any>;
|
package/ios/RegantisChatNative.h
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#import <React/RCTBridgeModule.h>
|
|
2
2
|
#import <UIKit/UIKit.h>
|
|
3
3
|
|
|
4
|
-
@interface RegantisChatNative : NSObject <RCTBridgeModule, UIDocumentPickerDelegate>
|
|
4
|
+
@interface RegantisChatNative : NSObject <RCTBridgeModule, UIDocumentPickerDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate>
|
|
5
5
|
@end
|
package/ios/RegantisChatNative.m
CHANGED
|
@@ -66,50 +66,35 @@ RCT_EXPORT_METHOD(playIncomingSound)
|
|
|
66
66
|
AudioServicesPlaySystemSound(1007);
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
RCT_REMAP_METHOD(
|
|
70
|
-
|
|
69
|
+
RCT_REMAP_METHOD(takePhoto,
|
|
70
|
+
takePhotoWithResolver:(RCTPromiseResolveBlock)resolve
|
|
71
71
|
rejecter:(RCTPromiseRejectBlock)reject)
|
|
72
72
|
{
|
|
73
73
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (window == nil) {
|
|
77
|
-
reject(@"SCREENSHOT_FAILED", @"No active iOS window", nil);
|
|
74
|
+
if (self.pickerResolve != nil) {
|
|
75
|
+
reject(@"PICKER_BUSY", @"An attachment picker is already open", nil);
|
|
78
76
|
return;
|
|
79
77
|
}
|
|
80
78
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
[window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES];
|
|
84
|
-
}];
|
|
85
|
-
NSData *data = UIImagePNGRepresentation(image);
|
|
86
|
-
if (data == nil) {
|
|
87
|
-
reject(@"SCREENSHOT_FAILED", @"Unable to encode screenshot", nil);
|
|
79
|
+
if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
|
|
80
|
+
reject(@"CAMERA_UNAVAILABLE", @"Camera is not available on this device", nil);
|
|
88
81
|
return;
|
|
89
82
|
}
|
|
90
83
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
[[NSFileManager defaultManager] createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:&directoryError];
|
|
95
|
-
if (directoryError != nil) {
|
|
96
|
-
reject(@"SCREENSHOT_FAILED", directoryError.localizedDescription, directoryError);
|
|
84
|
+
UIViewController *controller = RCTPresentedViewController();
|
|
85
|
+
if (controller == nil) {
|
|
86
|
+
reject(@"NO_VIEW_CONTROLLER", @"No active iOS view controller", nil);
|
|
97
87
|
return;
|
|
98
88
|
}
|
|
99
89
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
90
|
+
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
|
|
91
|
+
picker.sourceType = UIImagePickerControllerSourceTypeCamera;
|
|
92
|
+
picker.delegate = self;
|
|
93
|
+
picker.allowsEditing = NO;
|
|
106
94
|
|
|
107
|
-
resolve
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
@"type": @"image/png",
|
|
111
|
-
@"size": @(data.length)
|
|
112
|
-
});
|
|
95
|
+
self.pickerResolve = resolve;
|
|
96
|
+
self.pickerReject = reject;
|
|
97
|
+
[controller presentViewController:picker animated:YES completion:nil];
|
|
113
98
|
});
|
|
114
99
|
}
|
|
115
100
|
|
|
@@ -146,6 +131,71 @@ RCT_REMAP_METHOD(pickAttachment,
|
|
|
146
131
|
});
|
|
147
132
|
}
|
|
148
133
|
|
|
134
|
+
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
|
|
135
|
+
{
|
|
136
|
+
RCTPromiseResolveBlock resolve = self.pickerResolve;
|
|
137
|
+
self.pickerResolve = nil;
|
|
138
|
+
self.pickerReject = nil;
|
|
139
|
+
[picker dismissViewControllerAnimated:YES completion:^{
|
|
140
|
+
if (resolve) resolve(nil);
|
|
141
|
+
}];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info
|
|
145
|
+
{
|
|
146
|
+
RCTPromiseResolveBlock resolve = self.pickerResolve;
|
|
147
|
+
RCTPromiseRejectBlock reject = self.pickerReject;
|
|
148
|
+
self.pickerResolve = nil;
|
|
149
|
+
self.pickerReject = nil;
|
|
150
|
+
|
|
151
|
+
UIImage *image = info[UIImagePickerControllerOriginalImage];
|
|
152
|
+
if (image == nil) {
|
|
153
|
+
[picker dismissViewControllerAnimated:YES completion:^{
|
|
154
|
+
if (reject) reject(@"CAMERA_FAILED", @"Camera did not return an image", nil);
|
|
155
|
+
}];
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
NSData *data = UIImageJPEGRepresentation(image, 0.92);
|
|
160
|
+
if (data == nil) {
|
|
161
|
+
[picker dismissViewControllerAnimated:YES completion:^{
|
|
162
|
+
if (reject) reject(@"CAMERA_FAILED", @"Unable to encode camera image", nil);
|
|
163
|
+
}];
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
NSString *directory = [NSTemporaryDirectory() stringByAppendingPathComponent:@"regantis-chat/camera"];
|
|
168
|
+
NSError *directoryError = nil;
|
|
169
|
+
[[NSFileManager defaultManager] createDirectoryAtPath:directory withIntermediateDirectories:YES attributes:nil error:&directoryError];
|
|
170
|
+
if (directoryError != nil) {
|
|
171
|
+
[picker dismissViewControllerAnimated:YES completion:^{
|
|
172
|
+
if (reject) reject(@"CAMERA_FAILED", directoryError.localizedDescription, directoryError);
|
|
173
|
+
}];
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
NSString *filename = [NSString stringWithFormat:@"camera-%lld.jpg", (long long)(NSDate.date.timeIntervalSince1970 * 1000.0)];
|
|
178
|
+
NSString *path = [directory stringByAppendingPathComponent:filename];
|
|
179
|
+
NSError *writeError = nil;
|
|
180
|
+
if (![data writeToFile:path options:NSDataWritingAtomic error:&writeError]) {
|
|
181
|
+
[picker dismissViewControllerAnimated:YES completion:^{
|
|
182
|
+
if (reject) reject(@"CAMERA_FAILED", writeError.localizedDescription, writeError);
|
|
183
|
+
}];
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
NSDictionary *result = @{
|
|
188
|
+
@"uri": [NSURL fileURLWithPath:path].absoluteString ?: @"",
|
|
189
|
+
@"name": filename,
|
|
190
|
+
@"type": @"image/jpeg",
|
|
191
|
+
@"size": @(data.length)
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
[picker dismissViewControllerAnimated:YES completion:^{
|
|
195
|
+
if (resolve) resolve(result);
|
|
196
|
+
}];
|
|
197
|
+
}
|
|
198
|
+
|
|
149
199
|
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller
|
|
150
200
|
{
|
|
151
201
|
if (self.pickerResolve) self.pickerResolve(nil);
|