omikit-plugin 4.1.0 → 4.1.1
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 +385 -16
- package/android/build.gradle +1 -1
- package/android/src/main/java/com/omikitplugin/OmiLocalCameraView.kt +21 -39
- package/android/src/main/java/com/omikitplugin/OmiRemoteCameraView.kt +40 -22
- package/android/src/main/java/com/omikitplugin/OmikitPluginModule.kt +19 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The [omikit-plugin](https://www.npmjs.com/package/omikit-plugin) enables VoIP/SIP calling via the OMICALL platform with support for both Old and **New Architecture** (TurboModules + Fabric).
|
|
4
4
|
|
|
5
|
-
**Status:** Active maintenance | **Version:** 4.1.
|
|
5
|
+
**Status:** Active maintenance | **Version:** 4.1.1
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -46,7 +46,7 @@ The [omikit-plugin](https://www.npmjs.com/package/omikit-plugin) enables VoIP/SI
|
|
|
46
46
|
|
|
47
47
|
| Platform | SDK | Version |
|
|
48
48
|
|----------|-----|---------|
|
|
49
|
-
| Android | OMIKIT | 2.6.
|
|
49
|
+
| Android | OMIKIT | 2.6.6 |
|
|
50
50
|
| iOS | OmiKit | 1.11.4 |
|
|
51
51
|
|
|
52
52
|
### Platform Requirements
|
|
@@ -54,8 +54,8 @@ The [omikit-plugin](https://www.npmjs.com/package/omikit-plugin) enables VoIP/SI
|
|
|
54
54
|
| | Android | iOS |
|
|
55
55
|
|--|---------|-----|
|
|
56
56
|
| **Min SDK** | API 24 (Android 7.0) | iOS 13.0 |
|
|
57
|
-
| **Target SDK** | API
|
|
58
|
-
| **Compile SDK** | API
|
|
57
|
+
| **Target SDK** | API 36 (Android 16) | — |
|
|
58
|
+
| **Compile SDK** | API 36 | — |
|
|
59
59
|
|
|
60
60
|
### Device Requirements
|
|
61
61
|
|
|
@@ -131,7 +131,32 @@ Add to `android/app/src/main/AndroidManifest.xml`:
|
|
|
131
131
|
>
|
|
132
132
|
> Make sure to add the `tools` namespace to your manifest tag: `xmlns:tools="http://schemas.android.com/tools"`
|
|
133
133
|
|
|
134
|
-
### 2.
|
|
134
|
+
### 2. Incoming Call Activity (Required)
|
|
135
|
+
|
|
136
|
+
Your main Activity must handle incoming call intents from the SDK. Add the following `intent-filter` to your `MainActivity` in `AndroidManifest.xml`:
|
|
137
|
+
|
|
138
|
+
```xml
|
|
139
|
+
<activity
|
|
140
|
+
android:name=".MainActivity"
|
|
141
|
+
android:showWhenLocked="true"
|
|
142
|
+
android:turnScreenOn="true"
|
|
143
|
+
android:launchMode="singleTask"
|
|
144
|
+
...>
|
|
145
|
+
|
|
146
|
+
<!-- Incoming call intent-filter (required for lock screen) -->
|
|
147
|
+
<intent-filter>
|
|
148
|
+
<action android:name="${applicationId}.ACTION_INCOMING_CALL" />
|
|
149
|
+
<action android:name="android.intent.action.CALL" />
|
|
150
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
151
|
+
<data android:host="incoming_call" android:scheme="omisdk" />
|
|
152
|
+
</intent-filter>
|
|
153
|
+
</activity>
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
> **Important:** The `${applicationId}.ACTION_INCOMING_CALL` action ensures incoming calls show correctly on **lock screen** for Android 9-14. Without this, the default dialer may intercept the intent instead of your app.
|
|
157
|
+
|
|
158
|
+
### 3. Firebase Cloud Messaging (FCM)
|
|
159
|
+
|
|
135
160
|
|
|
136
161
|
Add your `google-services.json` to `android/app/`.
|
|
137
162
|
|
|
@@ -141,7 +166,7 @@ In `android/app/build.gradle`:
|
|
|
141
166
|
apply plugin: 'com.google.gms.google-services'
|
|
142
167
|
```
|
|
143
168
|
|
|
144
|
-
###
|
|
169
|
+
### 4. Maven Repository
|
|
145
170
|
|
|
146
171
|
**Option A — `settings.gradle.kts` (recommended for new projects)**
|
|
147
172
|
|
|
@@ -201,7 +226,7 @@ OMI_TOKEN=omi_github_access_token
|
|
|
201
226
|
|
|
202
227
|
> **Note:** Contact the OMICall development team to get `OMI_USER` and `OMI_TOKEN` credentials.
|
|
203
228
|
|
|
204
|
-
###
|
|
229
|
+
### 5. New Architecture (Optional)
|
|
205
230
|
|
|
206
231
|
To enable New Architecture on Android, in `android/gradle.properties`:
|
|
207
232
|
|
|
@@ -238,13 +263,352 @@ Enable **Push Notifications** capability in Xcode for VoIP push (PushKit).
|
|
|
238
263
|
|
|
239
264
|
### 4. AppDelegate Setup
|
|
240
265
|
|
|
241
|
-
|
|
266
|
+
The AppDelegate template differs depending on your React Native version. Choose the one that matches your project:
|
|
267
|
+
|
|
268
|
+
#### RN 0.74 – 0.78 (RCTAppDelegate pattern)
|
|
269
|
+
|
|
270
|
+
<details>
|
|
271
|
+
<summary>AppDelegate.h</summary>
|
|
242
272
|
|
|
243
273
|
```objc
|
|
244
|
-
#import <
|
|
245
|
-
#import <
|
|
274
|
+
#import <RCTAppDelegate.h>
|
|
275
|
+
#import <UIKit/UIKit.h>
|
|
276
|
+
#import <UserNotifications/UserNotifications.h>
|
|
277
|
+
#import <OmiKit/OmiKit.h>
|
|
278
|
+
|
|
279
|
+
@interface AppDelegate : RCTAppDelegate <UIApplicationDelegate, RCTBridgeDelegate, UNUserNotificationCenterDelegate>
|
|
280
|
+
|
|
281
|
+
@property (nonatomic, strong) UIWindow *window;
|
|
282
|
+
@property (nonatomic, strong) PushKitManager *pushkitManager;
|
|
283
|
+
@property (nonatomic, strong) CallKitProviderDelegate *provider;
|
|
284
|
+
@property (nonatomic, strong) PKPushRegistry *voipRegistry;
|
|
285
|
+
|
|
286
|
+
@end
|
|
246
287
|
```
|
|
247
288
|
|
|
289
|
+
</details>
|
|
290
|
+
|
|
291
|
+
<details>
|
|
292
|
+
<summary>AppDelegate.mm</summary>
|
|
293
|
+
|
|
294
|
+
```objc
|
|
295
|
+
#import "AppDelegate.h"
|
|
296
|
+
#import <Firebase.h>
|
|
297
|
+
#import <React/RCTBundleURLProvider.h>
|
|
298
|
+
#import <OmiKit/OmiKit.h>
|
|
299
|
+
|
|
300
|
+
#if __has_include("OmikitNotification.h")
|
|
301
|
+
#import "OmikitNotification.h"
|
|
302
|
+
#else
|
|
303
|
+
#import <omikit_plugin/OmikitNotification.h>
|
|
304
|
+
#endif
|
|
305
|
+
|
|
306
|
+
@implementation AppDelegate
|
|
307
|
+
|
|
308
|
+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
|
309
|
+
{
|
|
310
|
+
self.moduleName = @"YourAppName"; // Replace with your app name
|
|
311
|
+
|
|
312
|
+
// ----- OmiKit Config ------
|
|
313
|
+
[OmiClient setEnviroment:KEY_OMI_APP_ENVIROMENT_SANDBOX
|
|
314
|
+
userNameKey:@"full_name"
|
|
315
|
+
maxCall:2
|
|
316
|
+
callKitImage:@"call_image"
|
|
317
|
+
typePushVoip:TYPE_PUSH_CALLKIT_DEFAULT];
|
|
318
|
+
|
|
319
|
+
self.provider = [[CallKitProviderDelegate alloc]
|
|
320
|
+
initWithCallManager:[OMISIPLib sharedInstance].callManager];
|
|
321
|
+
self.voipRegistry = [[PKPushRegistry alloc]
|
|
322
|
+
initWithQueue:dispatch_get_main_queue()];
|
|
323
|
+
self.pushkitManager = [[PushKitManager alloc]
|
|
324
|
+
initWithVoipRegistry:self.voipRegistry];
|
|
325
|
+
|
|
326
|
+
if (@available(iOS 10.0, *)) {
|
|
327
|
+
[UNUserNotificationCenter currentNotificationCenter].delegate =
|
|
328
|
+
(id<UNUserNotificationCenterDelegate>)self;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if ([FIRApp defaultApp] == nil) {
|
|
332
|
+
[FIRApp configure];
|
|
333
|
+
}
|
|
334
|
+
// ----- End OmiKit Config ------
|
|
335
|
+
|
|
336
|
+
return [super application:application didFinishLaunchingWithOptions:launchOptions];
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Handle foreground notifications
|
|
340
|
+
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
|
341
|
+
willPresentNotification:(UNNotification *)notification
|
|
342
|
+
withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
|
|
343
|
+
{
|
|
344
|
+
completionHandler(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Handle missed call notification tap
|
|
348
|
+
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
|
349
|
+
didReceiveNotificationResponse:(UNNotificationResponse *)response
|
|
350
|
+
withCompletionHandler:(void (^)())completionHandler
|
|
351
|
+
{
|
|
352
|
+
NSDictionary *userInfo = response.notification.request.content.userInfo;
|
|
353
|
+
if (userInfo && [userInfo valueForKey:@"omisdkCallerNumber"]) {
|
|
354
|
+
[OmikitNotification didRecieve:userInfo];
|
|
355
|
+
}
|
|
356
|
+
completionHandler();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Register push notification token
|
|
360
|
+
- (void)application:(UIApplication *)app
|
|
361
|
+
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken
|
|
362
|
+
{
|
|
363
|
+
const unsigned char *data = (const unsigned char *)[devToken bytes];
|
|
364
|
+
NSMutableString *token = [NSMutableString string];
|
|
365
|
+
for (NSUInteger i = 0; i < [devToken length]; i++) {
|
|
366
|
+
[token appendFormat:@"%02.2hhX", data[i]];
|
|
367
|
+
}
|
|
368
|
+
[OmiClient setUserPushNotificationToken:[token copy]];
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Terminate all calls when app is killed
|
|
372
|
+
- (void)applicationWillTerminate:(UIApplication *)application {
|
|
373
|
+
@try {
|
|
374
|
+
[OmiClient OMICloseCall];
|
|
375
|
+
} @catch (NSException *exception) {}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
|
|
379
|
+
{
|
|
380
|
+
return [self bundleURL];
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
- (NSURL *)bundleURL
|
|
384
|
+
{
|
|
385
|
+
#if DEBUG
|
|
386
|
+
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
|
|
387
|
+
#else
|
|
388
|
+
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
|
389
|
+
#endif
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
@end
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
</details>
|
|
396
|
+
|
|
397
|
+
#### RN 0.79+ (RCTReactNativeFactory pattern)
|
|
398
|
+
|
|
399
|
+
RN 0.79+ uses `RCTReactNativeFactory` instead of `RCTAppDelegate`. Add OmiKit setup in your existing AppDelegate:
|
|
400
|
+
|
|
401
|
+
<details>
|
|
402
|
+
<summary>AppDelegate.swift (Swift template)</summary>
|
|
403
|
+
|
|
404
|
+
```swift
|
|
405
|
+
import UIKit
|
|
406
|
+
import React
|
|
407
|
+
import ReactAppDependencyProvider
|
|
408
|
+
import OmiKit
|
|
409
|
+
|
|
410
|
+
@main
|
|
411
|
+
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
|
|
412
|
+
var window: UIWindow?
|
|
413
|
+
var provider: CallKitProviderDelegate?
|
|
414
|
+
var pushkitManager: PushKitManager?
|
|
415
|
+
var voipRegistry: PKPushRegistry?
|
|
416
|
+
|
|
417
|
+
func application(_ application: UIApplication,
|
|
418
|
+
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
|
419
|
+
// React Native setup
|
|
420
|
+
let delegate = ReactNativeDelegate()
|
|
421
|
+
let factory = RCTReactNativeFactory(delegate: delegate)
|
|
422
|
+
delegate.dependencyProvider = RCTAppDependencyProvider()
|
|
423
|
+
|
|
424
|
+
window = UIWindow(frame: UIScreen.main.bounds)
|
|
425
|
+
factory.startReactNative(
|
|
426
|
+
withModuleName: "YourAppName",
|
|
427
|
+
in: window,
|
|
428
|
+
launchOptions: launchOptions
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
// ----- OmiKit Config ------
|
|
432
|
+
#ifdef DEBUG
|
|
433
|
+
OmiClient.setEnviroment(KEY_OMI_APP_ENVIROMENT_SANDBOX,
|
|
434
|
+
userNameKey: "full_name",
|
|
435
|
+
maxCall: 1,
|
|
436
|
+
callKitImage: "call_image",
|
|
437
|
+
typePushVoip: TYPE_PUSH_CALLKIT_DEFAULT)
|
|
438
|
+
#else
|
|
439
|
+
OmiClient.setEnviroment(KEY_OMI_APP_ENVIROMENT_PRODUCTION,
|
|
440
|
+
userNameKey: "full_name",
|
|
441
|
+
maxCall: 1,
|
|
442
|
+
callKitImage: "call_image",
|
|
443
|
+
typePushVoip: TYPE_PUSH_CALLKIT_DEFAULT)
|
|
444
|
+
#endif
|
|
445
|
+
|
|
446
|
+
provider = CallKitProviderDelegate(callManager: OMISIPLib.sharedInstance().callManager)
|
|
447
|
+
voipRegistry = PKPushRegistry(queue: .main)
|
|
448
|
+
pushkitManager = PushKitManager(voipRegistry: voipRegistry!)
|
|
449
|
+
|
|
450
|
+
UNUserNotificationCenter.current().delegate = self
|
|
451
|
+
FirebaseApp.configure()
|
|
452
|
+
// ----- End OmiKit Config ------
|
|
453
|
+
|
|
454
|
+
return true
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Handle missed call notification tap
|
|
458
|
+
func userNotificationCenter(_ center: UNUserNotificationCenter,
|
|
459
|
+
didReceive response: UNNotificationResponse,
|
|
460
|
+
withCompletionHandler completionHandler: @escaping () -> Void) {
|
|
461
|
+
let userInfo = response.notification.request.content.userInfo
|
|
462
|
+
if userInfo["omisdkCallerNumber"] != nil {
|
|
463
|
+
OmikitNotification.didRecieve(userInfo)
|
|
464
|
+
}
|
|
465
|
+
completionHandler()
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Register push notification token
|
|
469
|
+
func application(_ application: UIApplication,
|
|
470
|
+
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
|
471
|
+
let token = deviceToken.map { String(format: "%02.2hhX", $0) }.joined()
|
|
472
|
+
OmiClient.setUserPushNotificationToken(token)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Terminate all calls when app is killed
|
|
476
|
+
func applicationWillTerminate(_ application: UIApplication) {
|
|
477
|
+
try? OmiClient.omiCloseCall()
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate {
|
|
482
|
+
override func sourceURL(for bridge: RCTBridge) -> URL? {
|
|
483
|
+
return bundleURL()
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
override func bundleURL() -> URL? {
|
|
487
|
+
#if DEBUG
|
|
488
|
+
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
|
|
489
|
+
#else
|
|
490
|
+
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
|
|
491
|
+
#endif
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
</details>
|
|
497
|
+
|
|
498
|
+
<details>
|
|
499
|
+
<summary>AppDelegate.mm (Objective-C template)</summary>
|
|
500
|
+
|
|
501
|
+
```objc
|
|
502
|
+
#import "AppDelegate.h"
|
|
503
|
+
#import <Firebase.h>
|
|
504
|
+
#import <React/RCTBundleURLProvider.h>
|
|
505
|
+
#import <React/RCTReactNativeFactory.h>
|
|
506
|
+
#import <ReactAppDependencyProvider/RCTAppDependencyProvider.h>
|
|
507
|
+
#import <OmiKit/OmiKit.h>
|
|
508
|
+
|
|
509
|
+
#if __has_include("OmikitNotification.h")
|
|
510
|
+
#import "OmikitNotification.h"
|
|
511
|
+
#else
|
|
512
|
+
#import <omikit_plugin/OmikitNotification.h>
|
|
513
|
+
#endif
|
|
514
|
+
|
|
515
|
+
@interface ReactNativeDelegate : RCTDefaultReactNativeFactoryDelegate
|
|
516
|
+
@end
|
|
517
|
+
|
|
518
|
+
@implementation AppDelegate
|
|
519
|
+
|
|
520
|
+
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
|
521
|
+
{
|
|
522
|
+
ReactNativeDelegate *delegate = [ReactNativeDelegate new];
|
|
523
|
+
RCTReactNativeFactory *factory = [[RCTReactNativeFactory alloc] initWithDelegate:delegate];
|
|
524
|
+
delegate.dependencyProvider = [RCTAppDependencyProvider new];
|
|
525
|
+
|
|
526
|
+
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
|
527
|
+
[factory startReactNativeWithModuleName:@"YourAppName" in:self.window launchOptions:launchOptions];
|
|
528
|
+
|
|
529
|
+
// ----- OmiKit Config ------
|
|
530
|
+
#ifdef DEBUG
|
|
531
|
+
[OmiClient setEnviroment:KEY_OMI_APP_ENVIROMENT_SANDBOX userNameKey:@"full_name" maxCall:1 callKitImage:@"icYourApp" typePushVoip:@"background"];
|
|
532
|
+
#else
|
|
533
|
+
[OmiClient setEnviroment:KEY_OMI_APP_ENVIROMENT_PRODUCTION userNameKey:@"full_name" maxCall:1 callKitImage:@"icYourApp" typePushVoip:@"background"];
|
|
534
|
+
#endif
|
|
535
|
+
|
|
536
|
+
self.provider = [[CallKitProviderDelegate alloc]
|
|
537
|
+
initWithCallManager:[OMISIPLib sharedInstance].callManager];
|
|
538
|
+
self.voipRegistry = [[PKPushRegistry alloc]
|
|
539
|
+
initWithQueue:dispatch_get_main_queue()];
|
|
540
|
+
self.pushkitManager = [[PushKitManager alloc]
|
|
541
|
+
initWithVoipRegistry:self.voipRegistry];
|
|
542
|
+
|
|
543
|
+
if (@available(iOS 10.0, *)) {
|
|
544
|
+
[UNUserNotificationCenter currentNotificationCenter].delegate =
|
|
545
|
+
(id<UNUserNotificationCenterDelegate>)self;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if ([FIRApp defaultApp] == nil) {
|
|
549
|
+
[FIRApp configure];
|
|
550
|
+
}
|
|
551
|
+
// ----- End OmiKit Config ------
|
|
552
|
+
|
|
553
|
+
return YES;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Handle missed call notification tap
|
|
557
|
+
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
|
|
558
|
+
didReceiveNotificationResponse:(UNNotificationResponse *)response
|
|
559
|
+
withCompletionHandler:(void (^)())completionHandler
|
|
560
|
+
{
|
|
561
|
+
NSDictionary *userInfo = response.notification.request.content.userInfo;
|
|
562
|
+
if (userInfo && [userInfo valueForKey:@"omisdkCallerNumber"]) {
|
|
563
|
+
[OmikitNotification didRecieve:userInfo];
|
|
564
|
+
}
|
|
565
|
+
completionHandler();
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Register push notification token
|
|
569
|
+
- (void)application:(UIApplication *)app
|
|
570
|
+
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken
|
|
571
|
+
{
|
|
572
|
+
const unsigned char *data = (const unsigned char *)[devToken bytes];
|
|
573
|
+
NSMutableString *token = [NSMutableString string];
|
|
574
|
+
for (NSUInteger i = 0; i < [devToken length]; i++) {
|
|
575
|
+
[token appendFormat:@"%02.2hhX", data[i]];
|
|
576
|
+
}
|
|
577
|
+
[OmiClient setUserPushNotificationToken:[token copy]];
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Terminate all calls when app is killed
|
|
581
|
+
- (void)applicationWillTerminate:(UIApplication *)application {
|
|
582
|
+
@try {
|
|
583
|
+
[OmiClient OMICloseCall];
|
|
584
|
+
} @catch (NSException *exception) {}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
@end
|
|
588
|
+
|
|
589
|
+
@implementation ReactNativeDelegate
|
|
590
|
+
|
|
591
|
+
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
|
|
592
|
+
{
|
|
593
|
+
return [self bundleURL];
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
- (NSURL *)bundleURL
|
|
597
|
+
{
|
|
598
|
+
#if DEBUG
|
|
599
|
+
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
|
|
600
|
+
#else
|
|
601
|
+
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
|
|
602
|
+
#endif
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
@end
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
</details>
|
|
609
|
+
|
|
610
|
+
> **Note:** Replace `YourAppName` with your app's module name. For production, change `KEY_OMI_APP_ENVIROMENT_SANDBOX` to `KEY_OMI_APP_ENVIROMENT_PRODUCTION`.
|
|
611
|
+
|
|
248
612
|
### 5. New Architecture (Optional)
|
|
249
613
|
|
|
250
614
|
In your `Podfile`:
|
|
@@ -253,15 +617,20 @@ In your `Podfile`:
|
|
|
253
617
|
ENV['RN_NEW_ARCH_ENABLED'] = '1'
|
|
254
618
|
```
|
|
255
619
|
|
|
256
|
-
For **
|
|
620
|
+
For **New Architecture with video call support**, add Fabric interop registration in `AppDelegate.mm` inside `didFinishLaunchingWithOptions`, **before** `return [super ...]`:
|
|
257
621
|
|
|
258
622
|
```objc
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
623
|
+
// Required imports at the top of AppDelegate.mm
|
|
624
|
+
#import <React-RCTFabric/React/RCTComponentViewFactory.h>
|
|
625
|
+
#import <React-RCTFabric/React/RCTLegacyViewManagerInteropComponentView.h>
|
|
626
|
+
|
|
627
|
+
// Inside didFinishLaunchingWithOptions, before return:
|
|
628
|
+
[RCTLegacyViewManagerInteropComponentView supportLegacyViewManagerWithName:@"OmiLocalCameraView"];
|
|
629
|
+
[RCTLegacyViewManagerInteropComponentView supportLegacyViewManagerWithName:@"OmiRemoteCameraView"];
|
|
263
630
|
```
|
|
264
631
|
|
|
632
|
+
> **Important:** Bridgeless mode is **not yet supported** for video call views. If you use New Architecture, keep bridge mode enabled (do **not** add `bridgelessEnabled` returning `YES`).
|
|
633
|
+
|
|
265
634
|
Then run `cd ios && pod install`.
|
|
266
635
|
|
|
267
636
|
---
|
|
@@ -293,7 +662,7 @@ Then run `cd ios && pod install`.
|
|
|
293
662
|
│ │ │ │ │ │
|
|
294
663
|
│ ▼ │ │ ▼ │
|
|
295
664
|
│ OMIKIT SDK │ │ OmiKit SDK │
|
|
296
|
-
│ (v2.6.
|
|
665
|
+
│ (v2.6.5) │ │ (v1.11.4) │
|
|
297
666
|
│ │ │ │ │ │
|
|
298
667
|
│ ▼ │ │ ▼ │
|
|
299
668
|
│ SIP Stack │ │ SIP Stack │
|
package/android/build.gradle
CHANGED
|
@@ -65,7 +65,7 @@ dependencies {
|
|
|
65
65
|
// OMISDK
|
|
66
66
|
implementation("androidx.work:work-runtime:2.8.1")
|
|
67
67
|
implementation "androidx.security:security-crypto:1.1.0-alpha06"
|
|
68
|
-
api "io.omicrm.vihat:omi-sdk:2.6.
|
|
68
|
+
api "io.omicrm.vihat:omi-sdk:2.6.6"
|
|
69
69
|
|
|
70
70
|
// React Native — resolved from consumer's node_modules
|
|
71
71
|
implementation "com.facebook.react:react-native:+"
|
|
@@ -5,7 +5,7 @@ import android.util.Log
|
|
|
5
5
|
import android.view.Surface
|
|
6
6
|
import android.view.TextureView
|
|
7
7
|
import android.view.ViewGroup
|
|
8
|
-
import android.widget.
|
|
8
|
+
import android.widget.FrameLayout
|
|
9
9
|
import com.facebook.react.bridge.Promise
|
|
10
10
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
11
11
|
import com.facebook.react.bridge.ReactMethod
|
|
@@ -17,24 +17,25 @@ import vn.vihat.omicall.omisdk.videoutils.ScaleManager
|
|
|
17
17
|
import vn.vihat.omicall.omisdk.videoutils.Size
|
|
18
18
|
|
|
19
19
|
class OmiLocalCameraView(private val context: ReactApplicationContext) :
|
|
20
|
-
SimpleViewManager<
|
|
20
|
+
SimpleViewManager<FrameLayout>() {
|
|
21
21
|
|
|
22
|
-
val localView:
|
|
22
|
+
val localView: FrameLayout = FrameLayout(context)
|
|
23
23
|
private val cameraView: TextureView = TextureView(context)
|
|
24
24
|
|
|
25
|
-
// Track whether the surface is ready for rendering
|
|
26
25
|
@Volatile
|
|
27
26
|
private var isSurfaceReady = false
|
|
28
|
-
|
|
29
|
-
// Queued refresh — executed when surface becomes available
|
|
30
27
|
private var pendingRefreshPromise: Promise? = null
|
|
31
28
|
|
|
32
29
|
init {
|
|
33
|
-
|
|
30
|
+
// TextureView fills container — RN styles (width/height) control the FrameLayout
|
|
31
|
+
localView.addView(cameraView, FrameLayout.LayoutParams(
|
|
32
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
33
|
+
FrameLayout.LayoutParams.MATCH_PARENT
|
|
34
|
+
))
|
|
35
|
+
|
|
34
36
|
cameraView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
|
|
35
37
|
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
|
|
36
38
|
isSurfaceReady = true
|
|
37
|
-
// Execute queued refresh if any
|
|
38
39
|
pendingRefreshPromise?.let { promise ->
|
|
39
40
|
pendingRefreshPromise = null
|
|
40
41
|
doRefresh(promise)
|
|
@@ -46,36 +47,28 @@ class OmiLocalCameraView(private val context: ReactApplicationContext) :
|
|
|
46
47
|
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
|
|
47
48
|
isSurfaceReady = false
|
|
48
49
|
pendingRefreshPromise = null
|
|
49
|
-
return
|
|
50
|
+
return false
|
|
50
51
|
}
|
|
51
52
|
|
|
52
53
|
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
56
|
-
override fun getName(): String
|
|
57
|
-
return "OmiLocalCameraView"
|
|
58
|
-
}
|
|
57
|
+
override fun getName(): String = "OmiLocalCameraView"
|
|
59
58
|
|
|
60
|
-
override fun createViewInstance(p0: ThemedReactContext):
|
|
61
|
-
// Detach from previous parent if remounted
|
|
62
|
-
// (avoids "The specified child already has a parent" crash)
|
|
59
|
+
override fun createViewInstance(p0: ThemedReactContext): FrameLayout {
|
|
63
60
|
(localView.parent as? ViewGroup)?.removeView(localView)
|
|
64
61
|
return localView
|
|
65
62
|
}
|
|
66
63
|
|
|
67
|
-
fun localViewInstance():
|
|
68
|
-
return localView
|
|
69
|
-
}
|
|
64
|
+
fun localViewInstance(): FrameLayout = localView
|
|
70
65
|
|
|
71
|
-
// Exposed to JS via NativeModules.OmiLocalCameraView.refresh()
|
|
72
66
|
@ReactMethod
|
|
73
67
|
fun refresh(promise: Promise) {
|
|
74
68
|
UiThreadUtil.runOnUiThread {
|
|
75
69
|
if (isSurfaceReady && cameraView.surfaceTexture != null) {
|
|
76
70
|
doRefresh(promise)
|
|
77
71
|
} else {
|
|
78
|
-
// Surface not ready yet — queue and execute when available
|
|
79
72
|
pendingRefreshPromise = promise
|
|
80
73
|
}
|
|
81
74
|
}
|
|
@@ -84,25 +77,14 @@ class OmiLocalCameraView(private val context: ReactApplicationContext) :
|
|
|
84
77
|
private fun doRefresh(promise: Promise) {
|
|
85
78
|
try {
|
|
86
79
|
val surface = Surface(cameraView.surfaceTexture)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
ScaleManager.adjustAspectRatio(
|
|
97
|
-
cameraView,
|
|
98
|
-
Size(cameraView.width, cameraView.height),
|
|
99
|
-
Size(9, 16)
|
|
100
|
-
)
|
|
101
|
-
} catch (e: Exception) {
|
|
102
|
-
Log.e("OmiLocalCameraView", "Error setting up local feed: ${e.message}")
|
|
103
|
-
}
|
|
104
|
-
}, 300)
|
|
105
|
-
|
|
80
|
+
OmiClient.getInstance(context.applicationContext).setupLocalVideoFeed(surface)
|
|
81
|
+
Log.d("OmiLocalCameraView", "Connected local video feed to surface")
|
|
82
|
+
|
|
83
|
+
ScaleManager.adjustAspectRatioCrop(
|
|
84
|
+
cameraView,
|
|
85
|
+
Size(cameraView.width, cameraView.height),
|
|
86
|
+
Size(3, 4)
|
|
87
|
+
)
|
|
106
88
|
promise.resolve(true)
|
|
107
89
|
} catch (e: Exception) {
|
|
108
90
|
Log.e("OmiLocalCameraView", "Error refreshing: ${e.message}")
|
|
@@ -5,6 +5,7 @@ import android.util.Log
|
|
|
5
5
|
import android.view.Surface
|
|
6
6
|
import android.view.TextureView
|
|
7
7
|
import android.view.ViewGroup
|
|
8
|
+
import android.widget.FrameLayout
|
|
8
9
|
import com.facebook.react.bridge.Promise
|
|
9
10
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
10
11
|
import com.facebook.react.bridge.ReactMethod
|
|
@@ -16,22 +17,30 @@ import vn.vihat.omicall.omisdk.videoutils.ScaleManager
|
|
|
16
17
|
import vn.vihat.omicall.omisdk.videoutils.Size
|
|
17
18
|
|
|
18
19
|
class OmiRemoteCameraView(private val context: ReactApplicationContext) :
|
|
19
|
-
SimpleViewManager<
|
|
20
|
+
SimpleViewManager<FrameLayout>() {
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
companion object {
|
|
23
|
+
@Volatile
|
|
24
|
+
var instance: OmiRemoteCameraView? = null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
val remoteContainer: FrameLayout = FrameLayout(context)
|
|
28
|
+
private val remoteView: TextureView = TextureView(context)
|
|
22
29
|
|
|
23
|
-
// Track whether the surface is ready for rendering
|
|
24
30
|
@Volatile
|
|
25
31
|
private var isSurfaceReady = false
|
|
26
|
-
|
|
27
|
-
// Queued refresh — executed when surface becomes available
|
|
28
32
|
private var pendingRefreshPromise: Promise? = null
|
|
29
33
|
|
|
30
34
|
init {
|
|
35
|
+
instance = this
|
|
36
|
+
remoteContainer.addView(remoteView, FrameLayout.LayoutParams(
|
|
37
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
38
|
+
FrameLayout.LayoutParams.MATCH_PARENT
|
|
39
|
+
))
|
|
40
|
+
|
|
31
41
|
remoteView.surfaceTextureListener = object : TextureView.SurfaceTextureListener {
|
|
32
42
|
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
|
|
33
43
|
isSurfaceReady = true
|
|
34
|
-
// Execute queued refresh if any
|
|
35
44
|
pendingRefreshPromise?.let { promise ->
|
|
36
45
|
pendingRefreshPromise = null
|
|
37
46
|
doRefresh(promise)
|
|
@@ -43,36 +52,28 @@ class OmiRemoteCameraView(private val context: ReactApplicationContext) :
|
|
|
43
52
|
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
|
|
44
53
|
isSurfaceReady = false
|
|
45
54
|
pendingRefreshPromise = null
|
|
46
|
-
return
|
|
55
|
+
return false
|
|
47
56
|
}
|
|
48
57
|
|
|
49
58
|
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
|
|
50
59
|
}
|
|
51
60
|
}
|
|
52
61
|
|
|
53
|
-
override fun getName(): String
|
|
54
|
-
return "OmiRemoteCameraView"
|
|
55
|
-
}
|
|
62
|
+
override fun getName(): String = "OmiRemoteCameraView"
|
|
56
63
|
|
|
57
|
-
override fun createViewInstance(p0: ThemedReactContext):
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
(remoteView.parent as? ViewGroup)?.removeView(remoteView)
|
|
61
|
-
return remoteView
|
|
64
|
+
override fun createViewInstance(p0: ThemedReactContext): FrameLayout {
|
|
65
|
+
(remoteContainer.parent as? ViewGroup)?.removeView(remoteContainer)
|
|
66
|
+
return remoteContainer
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
fun remoteViewInstance():
|
|
65
|
-
return remoteView
|
|
66
|
-
}
|
|
69
|
+
fun remoteViewInstance(): FrameLayout = remoteContainer
|
|
67
70
|
|
|
68
|
-
// Exposed to JS via NativeModules.OmiRemoteCameraView.refresh()
|
|
69
71
|
@ReactMethod
|
|
70
72
|
fun refresh(promise: Promise) {
|
|
71
73
|
UiThreadUtil.runOnUiThread {
|
|
72
74
|
if (isSurfaceReady && remoteView.surfaceTexture != null) {
|
|
73
75
|
doRefresh(promise)
|
|
74
76
|
} else {
|
|
75
|
-
// Surface not ready yet — queue and execute when available
|
|
76
77
|
pendingRefreshPromise = promise
|
|
77
78
|
}
|
|
78
79
|
}
|
|
@@ -80,15 +81,15 @@ class OmiRemoteCameraView(private val context: ReactApplicationContext) :
|
|
|
80
81
|
|
|
81
82
|
private fun doRefresh(promise: Promise) {
|
|
82
83
|
try {
|
|
83
|
-
// Connect TextureView surface to SDK incoming video feed
|
|
84
84
|
val surface = Surface(remoteView.surfaceTexture)
|
|
85
85
|
OmiClient.getInstance(context.applicationContext).setupIncomingVideoFeed(surface)
|
|
86
86
|
Log.d("OmiRemoteCameraView", "Connected remote video feed to surface")
|
|
87
87
|
|
|
88
|
+
// Default landscape; updated by onVideoSize when PJSIP reports actual dimensions
|
|
88
89
|
ScaleManager.adjustAspectRatio(
|
|
89
90
|
remoteView,
|
|
90
91
|
Size(remoteView.width, remoteView.height),
|
|
91
|
-
Size(
|
|
92
|
+
Size(640, 480)
|
|
92
93
|
)
|
|
93
94
|
promise.resolve(true)
|
|
94
95
|
} catch (e: Exception) {
|
|
@@ -96,4 +97,21 @@ class OmiRemoteCameraView(private val context: ReactApplicationContext) :
|
|
|
96
97
|
promise.resolve(false)
|
|
97
98
|
}
|
|
98
99
|
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Called from OmikitPluginModule.onVideoSize() when PJSIP reports
|
|
103
|
+
* actual remote video dimensions. Re-applies correct aspect ratio.
|
|
104
|
+
*/
|
|
105
|
+
fun updateAspectRatio(videoWidth: Int, videoHeight: Int) {
|
|
106
|
+
UiThreadUtil.runOnUiThread {
|
|
107
|
+
if (remoteView.width > 0 && remoteView.height > 0 && videoWidth > 0 && videoHeight > 0) {
|
|
108
|
+
Log.d("OmiRemoteCameraView", "updateAspectRatio: video=${videoWidth}x${videoHeight}")
|
|
109
|
+
ScaleManager.adjustAspectRatio(
|
|
110
|
+
remoteView,
|
|
111
|
+
Size(remoteView.width, remoteView.height),
|
|
112
|
+
Size(videoWidth, videoHeight)
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
99
117
|
}
|
|
@@ -121,6 +121,11 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
121
121
|
private var isIncoming: Boolean = false
|
|
122
122
|
private var isAnswerCall: Boolean = false
|
|
123
123
|
@Volatile private var permissionPromise: Promise? = null
|
|
124
|
+
|
|
125
|
+
// Helper for bridgeless mode (Expo/RN 0.81+) where currentActivity
|
|
126
|
+
// is not directly available as inherited property
|
|
127
|
+
private val safeActivity: Activity?
|
|
128
|
+
get() = reactApplicationContext?.currentActivity
|
|
124
129
|
|
|
125
130
|
// Call state management to prevent concurrent calls
|
|
126
131
|
private var isCallInProgress: Boolean = false
|
|
@@ -409,7 +414,8 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
409
414
|
}
|
|
410
415
|
|
|
411
416
|
override fun onVideoSize(width: Int, height: Int) {
|
|
412
|
-
|
|
417
|
+
// PJSIP reports actual remote video dimensions — update aspect ratio dynamically
|
|
418
|
+
OmiRemoteCameraView.instance?.updateAspectRatio(width, height)
|
|
413
419
|
}
|
|
414
420
|
|
|
415
421
|
private val accountListener = object : OmiAccountListener {
|
|
@@ -633,7 +639,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
633
639
|
return
|
|
634
640
|
}
|
|
635
641
|
|
|
636
|
-
|
|
642
|
+
safeActivity?.runOnUiThread {
|
|
637
643
|
try {
|
|
638
644
|
// Extract parameters from data with proper defaults
|
|
639
645
|
val notificationIcon = data.getString("notificationIcon") ?: "ic_notification"
|
|
@@ -898,7 +904,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
898
904
|
)
|
|
899
905
|
val map: WritableMap = WritableNativeMap()
|
|
900
906
|
if (audio == PackageManager.PERMISSION_GRANTED) {
|
|
901
|
-
val activity =
|
|
907
|
+
val activity = safeActivity
|
|
902
908
|
if (activity == null) {
|
|
903
909
|
promise.reject("E_NO_ACTIVITY", "Current activity is null")
|
|
904
910
|
return
|
|
@@ -966,7 +972,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
966
972
|
@ReactMethod
|
|
967
973
|
fun joinCall(promise: Promise) {
|
|
968
974
|
val appContext = reactApplicationContext.applicationContext
|
|
969
|
-
val activity =
|
|
975
|
+
val activity = safeActivity
|
|
970
976
|
|
|
971
977
|
if (appContext == null) {
|
|
972
978
|
promise.reject("E_NULL_CONTEXT", "Application context is null")
|
|
@@ -1075,7 +1081,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1075
1081
|
|
|
1076
1082
|
@ReactMethod
|
|
1077
1083
|
fun toggleSpeaker(promise: Promise) {
|
|
1078
|
-
val activity =
|
|
1084
|
+
val activity = safeActivity
|
|
1079
1085
|
if (activity == null) { promise.resolve(null); return }
|
|
1080
1086
|
activity.runOnUiThread {
|
|
1081
1087
|
val newStatus = OmiClient.getInstance(reactApplicationContext!!).toggleSpeaker()
|
|
@@ -1086,7 +1092,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1086
1092
|
|
|
1087
1093
|
@ReactMethod
|
|
1088
1094
|
fun sendDTMF(data: ReadableMap, promise: Promise) {
|
|
1089
|
-
val activity =
|
|
1095
|
+
val activity = safeActivity
|
|
1090
1096
|
if (activity == null) { promise.resolve(false); return }
|
|
1091
1097
|
activity.runOnUiThread {
|
|
1092
1098
|
val character = data.getString("character")
|
|
@@ -1106,7 +1112,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1106
1112
|
|
|
1107
1113
|
@ReactMethod
|
|
1108
1114
|
fun switchOmiCamera(promise: Promise) {
|
|
1109
|
-
val activity =
|
|
1115
|
+
val activity = safeActivity
|
|
1110
1116
|
if (activity == null) { promise.resolve(false); return }
|
|
1111
1117
|
activity.runOnUiThread {
|
|
1112
1118
|
OmiClient.getInstance(reactApplicationContext!!).switchCamera()
|
|
@@ -1116,7 +1122,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1116
1122
|
|
|
1117
1123
|
@ReactMethod
|
|
1118
1124
|
fun toggleOmiVideo(promise: Promise) {
|
|
1119
|
-
val activity =
|
|
1125
|
+
val activity = safeActivity
|
|
1120
1126
|
if (activity == null) { promise.resolve(false); return }
|
|
1121
1127
|
activity.runOnUiThread {
|
|
1122
1128
|
OmiClient.getInstance(reactApplicationContext!!).toggleCamera()
|
|
@@ -1354,7 +1360,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1354
1360
|
|
|
1355
1361
|
@ReactMethod
|
|
1356
1362
|
fun transferCall(data: ReadableMap, promise: Promise) {
|
|
1357
|
-
val activity =
|
|
1363
|
+
val activity = safeActivity
|
|
1358
1364
|
if (activity == null) { promise.resolve(false); return }
|
|
1359
1365
|
activity.runOnUiThread {
|
|
1360
1366
|
val phone = data.getString("phoneNumber")
|
|
@@ -1559,7 +1565,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1559
1565
|
// Store promise for callback
|
|
1560
1566
|
permissionPromise = promise
|
|
1561
1567
|
|
|
1562
|
-
val activity =
|
|
1568
|
+
val activity = safeActivity ?: run {
|
|
1563
1569
|
promise.resolve(false)
|
|
1564
1570
|
return
|
|
1565
1571
|
}
|
|
@@ -1681,7 +1687,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1681
1687
|
Uri.parse("package:${reactApplicationContext.packageName}")
|
|
1682
1688
|
)
|
|
1683
1689
|
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
1684
|
-
|
|
1690
|
+
safeActivity?.startActivityForResult(intent, REQUEST_OVERLAY_PERMISSION_CODE)
|
|
1685
1691
|
} else {
|
|
1686
1692
|
promise.resolve(true)
|
|
1687
1693
|
}
|
|
@@ -1737,7 +1743,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1737
1743
|
// Store promise for callback
|
|
1738
1744
|
permissionPromise = promise
|
|
1739
1745
|
|
|
1740
|
-
val activity =
|
|
1746
|
+
val activity = safeActivity ?: run {
|
|
1741
1747
|
promise.reject("E_NULL_ACTIVITY", "Current activity is null")
|
|
1742
1748
|
return
|
|
1743
1749
|
}
|
|
@@ -1759,7 +1765,7 @@ class OmikitPluginModule(reactContext: ReactApplicationContext?) :
|
|
|
1759
1765
|
return
|
|
1760
1766
|
}
|
|
1761
1767
|
|
|
1762
|
-
val activity =
|
|
1768
|
+
val activity = safeActivity ?: return
|
|
1763
1769
|
ActivityCompat.requestPermissions(
|
|
1764
1770
|
activity,
|
|
1765
1771
|
missingPermissions.toTypedArray(),
|