rns-nativecall 0.1.8 → 0.1.9
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/android/src/main/java/com/rnsnativecall/AcceptCallActivity.kt +1 -1
- package/android/src/main/java/com/rnsnativecall/CallActionReceiver.kt +3 -0
- package/android/src/main/java/com/rnsnativecall/CallModule.kt +1 -0
- package/android/src/main/java/com/rnsnativecall/NativeCallManager.kt +17 -0
- package/index.d.ts +12 -16
- package/index.js +32 -23
- package/ios/CallModule.h +4 -0
- package/ios/CallModule.m +33 -44
- package/package.json +2 -1
- package/withNativeCallVoip.js +6 -7
- package/android/src/main/java/com/rnsnativecall/IncomingCallActivity.kt +0 -74
- package/android/src/main/res/drawable/circle_green.xml +0 -4
- package/android/src/main/res/drawable/circle_red.xml +0 -4
- package/android/src/main/res/drawable/pill_background.xml +0 -5
- package/android/src/main/res/layout/activity_incoming_call.xml +0 -67
- package/android/src/main/res/values/styles.xml +0 -12
|
@@ -9,7 +9,7 @@ import android.os.Bundle
|
|
|
9
9
|
class AcceptCallActivity : Activity() {
|
|
10
10
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
11
11
|
super.onCreate(savedInstanceState)
|
|
12
|
-
|
|
12
|
+
NativeCallManager.stopRingtone()
|
|
13
13
|
// 1. CLEAR THE NOTIFICATION
|
|
14
14
|
// Use the same ID (101) used in NativeCallManager
|
|
15
15
|
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
@@ -8,6 +8,7 @@ import android.os.Bundle
|
|
|
8
8
|
|
|
9
9
|
class CallActionReceiver : BroadcastReceiver() {
|
|
10
10
|
override fun onReceive(context: Context, intent: Intent) {
|
|
11
|
+
NativeCallManager.stopRingtone()
|
|
11
12
|
val uuid = intent.getStringExtra("EXTRA_CALL_UUID")
|
|
12
13
|
|
|
13
14
|
// 1. Clear the notification
|
|
@@ -15,6 +16,7 @@ class CallActionReceiver : BroadcastReceiver() {
|
|
|
15
16
|
notificationManager.cancel(101)
|
|
16
17
|
|
|
17
18
|
if (intent.action == "ACTION_ACCEPT") {
|
|
19
|
+
NativeCallManager.stopRingtone()
|
|
18
20
|
// 2. Prepare the data for React Native
|
|
19
21
|
val dataMap = mutableMapOf<String, String>()
|
|
20
22
|
intent.extras?.keySet()?.forEach { key ->
|
|
@@ -34,6 +36,7 @@ class CallActionReceiver : BroadcastReceiver() {
|
|
|
34
36
|
}
|
|
35
37
|
context.startActivity(launchIntent)
|
|
36
38
|
} else {
|
|
39
|
+
NativeCallManager.stopRingtone()
|
|
37
40
|
// Logic for Reject
|
|
38
41
|
CallModule.sendEventToJS("onCallRejected", mapOf("callUUID" to uuid))
|
|
39
42
|
}
|
|
@@ -64,6 +64,7 @@ class CallModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaMo
|
|
|
64
64
|
|
|
65
65
|
@ReactMethod
|
|
66
66
|
fun endNativeCall(uuid: String) {
|
|
67
|
+
NativeCallManager.stopRingtone()
|
|
67
68
|
val notificationManager = reactApplicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
68
69
|
notificationManager.cancel(101)
|
|
69
70
|
|
|
@@ -7,11 +7,16 @@ import android.content.Context
|
|
|
7
7
|
import android.content.Intent
|
|
8
8
|
import android.os.Build
|
|
9
9
|
import androidx.core.app.NotificationCompat
|
|
10
|
+
import android.media.Ringtone
|
|
11
|
+
import android.media.RingtoneManager
|
|
10
12
|
|
|
11
13
|
object NativeCallManager {
|
|
12
14
|
|
|
15
|
+
private var ringtone: Ringtone? = null
|
|
16
|
+
|
|
13
17
|
fun handleIncomingPush(context: Context, data: Map<String, String>) {
|
|
14
18
|
val uuid = data["callUuid"] ?: return
|
|
19
|
+
stopRingtone()
|
|
15
20
|
val name = data["name"] ?: "Incoming Call"
|
|
16
21
|
val callType = data["callType"] ?: "audio"
|
|
17
22
|
|
|
@@ -91,5 +96,17 @@ object NativeCallManager {
|
|
|
91
96
|
.addAction(0, "Decline", rejectPendingIntent)
|
|
92
97
|
|
|
93
98
|
notificationManager.notify(101, builder.build())
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
|
|
102
|
+
ringtone = RingtoneManager.getRingtone(context, ringtoneUri)
|
|
103
|
+
ringtone?.play()
|
|
104
|
+
} catch (e: Exception) {
|
|
105
|
+
e.printStackTrace()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
fun stopRingtone() {
|
|
109
|
+
ringtone?.stop()
|
|
110
|
+
ringtone = null
|
|
94
111
|
}
|
|
95
112
|
}
|
package/index.d.ts
CHANGED
|
@@ -1,45 +1,41 @@
|
|
|
1
|
-
///Users/bush/Desktop/Apps/Raiidr/package/index.d.ts
|
|
2
|
-
|
|
3
1
|
export interface CallData {
|
|
4
|
-
|
|
2
|
+
callUuid: string;
|
|
3
|
+
name?: string;
|
|
4
|
+
callType?: 'audio' | 'video';
|
|
5
|
+
[key: string]: any; // To allow for custom FCM payload data
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
export type CallAcceptedCallback = (data: CallData) => void;
|
|
8
9
|
export type CallRejectedCallback = (data: CallData) => void;
|
|
9
|
-
export type CallFailedCallback = (data:
|
|
10
|
+
export type CallFailedCallback = (data: any) => void;
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Manually request/check Android permissions for Telecom.
|
|
13
|
-
* @returns Promise resolving to true if granted
|
|
14
14
|
*/
|
|
15
15
|
export function ensureAndroidPermissions(): Promise<boolean>;
|
|
16
16
|
|
|
17
17
|
export interface CallHandlerType {
|
|
18
18
|
/**
|
|
19
|
-
* Display
|
|
19
|
+
* Display the Sticky Pill notification UI.
|
|
20
20
|
* @param uuid Unique call identifier
|
|
21
21
|
* @param name Caller display name
|
|
22
|
-
* @param callType
|
|
23
|
-
* @returns Promise resolving to true if successfully displayed
|
|
22
|
+
* @param callType 'audio' or 'video'
|
|
24
23
|
*/
|
|
25
24
|
displayCall(
|
|
26
25
|
uuid: string,
|
|
27
26
|
name: string,
|
|
28
|
-
callType:
|
|
27
|
+
callType: 'audio' | 'video',
|
|
29
28
|
): Promise<boolean>;
|
|
30
29
|
|
|
31
30
|
/**
|
|
32
|
-
* Dismiss the native call UI (
|
|
33
|
-
* @param uuid Call identifier
|
|
31
|
+
* Dismiss the native call UI (Sticky Pill).
|
|
34
32
|
*/
|
|
35
33
|
destroyNativeCallUI(uuid: string): void;
|
|
36
34
|
|
|
37
35
|
/**
|
|
38
|
-
* Subscribe to call events
|
|
39
|
-
*
|
|
40
|
-
* @
|
|
41
|
-
* @param onFailed Optional callback for system-level failures
|
|
42
|
-
* @returns Function to unsubscribe all listeners
|
|
36
|
+
* Subscribe to call events. Automatically checks for cold-start data
|
|
37
|
+
* if the app was opened via the Answer button.
|
|
38
|
+
* @returns Function to unsubscribe
|
|
43
39
|
*/
|
|
44
40
|
subscribe(
|
|
45
41
|
onAccept: CallAcceptedCallback,
|
package/index.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
///Users/bush/Desktop/Apps/Raiidr/package/index.js
|
|
2
1
|
import {
|
|
3
2
|
NativeModules,
|
|
4
3
|
NativeEventEmitter,
|
|
@@ -8,11 +7,10 @@ import {
|
|
|
8
7
|
|
|
9
8
|
const { CallModule } = NativeModules;
|
|
10
9
|
|
|
11
|
-
// Safety check for the Native Module
|
|
12
10
|
if (!CallModule && __DEV__) {
|
|
13
11
|
console.warn(
|
|
14
12
|
"rns-nativecall: NativeModule.CallModule is undefined. " +
|
|
15
|
-
"Make sure you have rebuilt your native project
|
|
13
|
+
"Make sure you have rebuilt your native project."
|
|
16
14
|
);
|
|
17
15
|
}
|
|
18
16
|
|
|
@@ -25,57 +23,68 @@ const REQUIRED_PERMISSIONS = Platform.OS === 'android' ? [
|
|
|
25
23
|
|
|
26
24
|
export async function ensureAndroidPermissions() {
|
|
27
25
|
if (Platform.OS !== 'android') return true;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
try {
|
|
27
|
+
const result = await PermissionsAndroid.requestMultiple(REQUIRED_PERMISSIONS);
|
|
28
|
+
const isAccountEnabled = await CallModule.checkTelecomPermissions();
|
|
29
|
+
if (!isAccountEnabled) return false;
|
|
30
|
+
return Object.values(result).every(status => status === PermissionsAndroid.RESULTS.GRANTED);
|
|
31
|
+
} catch (e) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
export const CallHandler = {
|
|
38
|
-
displayCall: async (uuid, name, callType) => {
|
|
37
|
+
displayCall: async (uuid, name, callType = "audio") => {
|
|
39
38
|
if (!CallModule) return false;
|
|
40
|
-
|
|
41
|
-
// if (Platform.OS === 'android') {
|
|
42
|
-
// const hasPerms = await ensureAndroidPermissions();
|
|
43
|
-
// if (!hasPerms) return false;
|
|
44
|
-
// }
|
|
45
|
-
|
|
46
39
|
try {
|
|
47
40
|
return await CallModule.displayIncomingCall(
|
|
48
41
|
uuid.toLowerCase().trim(),
|
|
49
42
|
name,
|
|
50
|
-
callType
|
|
43
|
+
callType
|
|
51
44
|
);
|
|
52
45
|
} catch (e) {
|
|
53
|
-
// console.log("Native Call Error:", e);
|
|
54
46
|
return false;
|
|
55
47
|
}
|
|
56
48
|
},
|
|
57
49
|
|
|
58
50
|
destroyNativeCallUI: (uuid) => {
|
|
59
51
|
if (CallModule?.endNativeCall) {
|
|
60
|
-
CallModule.endNativeCall(uuid.toLowerCase());
|
|
52
|
+
CallModule.endNativeCall(uuid.toLowerCase().trim());
|
|
61
53
|
}
|
|
62
54
|
},
|
|
63
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Subscribes to call events and checks for initial cold-start data.
|
|
58
|
+
*/
|
|
64
59
|
subscribe: (onAccept, onReject, onFailed) => {
|
|
65
60
|
if (!callEventEmitter) return () => { };
|
|
66
61
|
|
|
67
62
|
const subs = [
|
|
68
|
-
callEventEmitter.addListener('onCallAccepted',
|
|
69
|
-
|
|
63
|
+
callEventEmitter.addListener('onCallAccepted', (data) => {
|
|
64
|
+
onAccept(data);
|
|
65
|
+
}),
|
|
66
|
+
callEventEmitter.addListener('onCallRejected', (data) => {
|
|
67
|
+
onReject(data);
|
|
68
|
+
}),
|
|
70
69
|
];
|
|
71
70
|
|
|
72
71
|
if (onFailed) {
|
|
73
72
|
subs.push(callEventEmitter.addListener('onCallFailed', onFailed));
|
|
74
73
|
}
|
|
75
74
|
|
|
75
|
+
// --- COLD START LOGIC ---
|
|
76
|
+
// If the app was killed and opened via the Answer button,
|
|
77
|
+
// the event might have fired before this listener was ready.
|
|
78
|
+
if (Platform.OS === 'android' && CallModule.getInitialCallData) {
|
|
79
|
+
CallModule.getInitialCallData().then((data) => {
|
|
80
|
+
if (data) {
|
|
81
|
+
onAccept(data);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
76
86
|
return () => subs.forEach(s => s.remove());
|
|
77
87
|
}
|
|
78
88
|
};
|
|
79
89
|
|
|
80
|
-
// IMPORTANT: Exporting as default ensures "import CallHandler from '...'" works
|
|
81
90
|
export default CallHandler;
|
package/ios/CallModule.h
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
#import <React/RCTBridgeModule.h>
|
|
2
2
|
#import <React/RCTEventEmitter.h>
|
|
3
3
|
#import <CallKit/CallKit.h>
|
|
4
|
+
#import <AVFoundation/AVFoundation.h>
|
|
5
|
+
|
|
4
6
|
@interface CallModule : RCTEventEmitter <RCTBridgeModule, CXProviderDelegate>
|
|
5
7
|
@property(nonatomic, strong) CXProvider *provider;
|
|
8
|
+
// Add this property
|
|
9
|
+
@property(nonatomic, strong) NSUUID *currentCallUUID;
|
|
6
10
|
@end
|
package/ios/CallModule.m
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
RCT_EXPORT_MODULE();
|
|
6
6
|
|
|
7
|
+
// Crucial: This keeps the module alive and on the main thread
|
|
8
|
+
+ (BOOL)requiresMainQueueSetup {
|
|
9
|
+
return YES;
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
- (NSArray<NSString *> *)supportedEvents {
|
|
8
13
|
return @[ @"onCallAccepted", @"onCallRejected", @"onCallFailed" ];
|
|
9
14
|
}
|
|
@@ -11,22 +16,15 @@ RCT_EXPORT_MODULE();
|
|
|
11
16
|
- (instancetype)init {
|
|
12
17
|
self = [super init];
|
|
13
18
|
if (self) {
|
|
14
|
-
|
|
15
|
-
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"];
|
|
16
|
-
if (!appName) {
|
|
17
|
-
appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
|
|
18
|
-
}
|
|
19
|
+
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"] ?: [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
|
|
19
20
|
|
|
20
|
-
#pragma clang diagnostic push
|
|
21
|
-
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
22
21
|
CXProviderConfiguration *config = [[CXProviderConfiguration alloc] initWithLocalizedName:appName];
|
|
23
|
-
#pragma clang diagnostic pop
|
|
24
|
-
|
|
25
22
|
config.supportsVideo = YES;
|
|
26
23
|
config.maximumCallGroups = 1;
|
|
27
24
|
config.maximumCallsPerCallGroup = 1;
|
|
28
25
|
config.supportedHandleTypes = [NSSet setWithObject:@(CXHandleTypeGeneric)];
|
|
29
|
-
|
|
26
|
+
// Add this to prevent system confusion
|
|
27
|
+
config.ringtoneSound = @"Ringtone.caf";
|
|
30
28
|
|
|
31
29
|
self.provider = [[CXProvider alloc] initWithConfiguration:config];
|
|
32
30
|
[self.provider setDelegate:self queue:nil];
|
|
@@ -34,32 +32,33 @@ RCT_EXPORT_MODULE();
|
|
|
34
32
|
return self;
|
|
35
33
|
}
|
|
36
34
|
|
|
37
|
-
// Matches the 5 arguments in your JS/Android implementation
|
|
38
35
|
RCT_EXPORT_METHOD(displayIncomingCall:(NSString *)uuidString
|
|
39
|
-
number:(NSString *)number
|
|
40
36
|
name:(NSString *)name
|
|
41
|
-
|
|
42
|
-
showRing:(BOOL)showRing
|
|
37
|
+
callType:(NSString *)callType
|
|
43
38
|
resolve:(RCTPromiseResolveBlock)resolve
|
|
44
39
|
reject:(RCTPromiseRejectBlock)reject)
|
|
45
40
|
{
|
|
46
41
|
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
|
47
42
|
if (!uuid) {
|
|
48
|
-
reject(@"INVALID_UUID", @"
|
|
43
|
+
reject(@"INVALID_UUID", @"Invalid UUID", nil);
|
|
49
44
|
return;
|
|
50
45
|
}
|
|
46
|
+
|
|
47
|
+
self.currentCallUUID = uuid;
|
|
48
|
+
|
|
49
|
+
// 1. CONFIGURE AUDIO SESSION FIRST (Prevents Auto-Drop)
|
|
50
|
+
AVAudioSession *session = [AVAudioSession sharedInstance];
|
|
51
|
+
NSError *error = nil;
|
|
52
|
+
[session setCategory:AVAudioSessionCategoryPlayAndRecord
|
|
53
|
+
mode:AVAudioSessionModeVoiceChat
|
|
54
|
+
options:AVAudioSessionCategoryOptionAllowBluetooth
|
|
55
|
+
error:&error];
|
|
51
56
|
|
|
52
57
|
CXCallUpdate *update = [[CXCallUpdate alloc] init];
|
|
53
|
-
// We use 'name' here so the system UI shows the caller's name instead of just their number
|
|
54
58
|
update.remoteHandle = [[CXHandle alloc] initWithType:CXHandleTypeGeneric value:name];
|
|
55
|
-
update.hasVideo =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
update.supportsHolding = NO;
|
|
59
|
-
|
|
60
|
-
[self.provider reportNewIncomingCallWithUUID:uuid
|
|
61
|
-
update:update
|
|
62
|
-
completion:^(NSError *_Nullable error) {
|
|
59
|
+
update.hasVideo = [callType isEqualToString:@"video"];
|
|
60
|
+
|
|
61
|
+
[self.provider reportNewIncomingCallWithUUID:uuid update:update completion:^(NSError * _Nullable error) {
|
|
63
62
|
if (error) {
|
|
64
63
|
reject(@"CALL_ERROR", error.localizedDescription, error);
|
|
65
64
|
} else {
|
|
@@ -68,35 +67,25 @@ RCT_EXPORT_METHOD(displayIncomingCall:(NSString *)uuidString
|
|
|
68
67
|
}];
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
RCT_EXPORT_METHOD(endNativeCall:(NSString *)uuidString) {
|
|
72
|
-
NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:uuidString];
|
|
73
|
-
if (uuid) {
|
|
74
|
-
[self.provider reportCallWithUUID:uuid
|
|
75
|
-
endedAtDate:[NSDate date]
|
|
76
|
-
reason:CXCallEndedReasonRemoteEnded];
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Placeholder for Android parity
|
|
81
|
-
RCT_EXPORT_METHOD(checkTelecomPermissions:(RCTPromiseResolveBlock)resolve
|
|
82
|
-
reject:(RCTPromiseRejectBlock)reject) {
|
|
83
|
-
resolve(@YES);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
70
|
// MARK: - CXProviderDelegate
|
|
87
71
|
|
|
88
72
|
- (void)provider:(CXProvider *)provider performAnswerCallAction:(CXAnswerCallAction *)action {
|
|
73
|
+
// 2. ACTIVATE AUDIO SESSION (Crucial for CallKit)
|
|
74
|
+
[[AVAudioSession sharedInstance] setActive:YES error:nil];
|
|
75
|
+
|
|
89
76
|
[action fulfill];
|
|
90
|
-
[self sendEventWithName:@"onCallAccepted"
|
|
91
|
-
body:@{@"callUUID" : [action.callUUID.UUIDString lowercaseString]}];
|
|
77
|
+
[self sendEventWithName:@"onCallAccepted" body:@{@"callUuid": [action.callUUID.UUIDString lowercaseString]}];
|
|
92
78
|
}
|
|
93
79
|
|
|
94
80
|
- (void)provider:(CXProvider *)provider performEndCallAction:(CXEndCallAction *)action {
|
|
95
81
|
[action fulfill];
|
|
96
|
-
|
|
97
|
-
|
|
82
|
+
self.currentCallUUID = nil;
|
|
83
|
+
[self sendEventWithName:@"onCallRejected" body:@{@"callUuid": [action.callUUID.UUIDString lowercaseString]}];
|
|
98
84
|
}
|
|
99
85
|
|
|
100
|
-
- (void)providerDidReset:(CXProvider *)provider {
|
|
86
|
+
- (void)providerDidReset:(CXProvider *)provider {
|
|
87
|
+
// Stop all audio if provider resets
|
|
88
|
+
[[AVAudioSession sharedInstance] setActive:NO error:nil];
|
|
89
|
+
}
|
|
101
90
|
|
|
102
91
|
@end
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rns-nativecall",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "RNS nativecall component with native Android/iOS for handling native call ui, when app is not open or open.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"p": "npm publish --access public"
|
|
20
20
|
},
|
|
21
21
|
"expo": {
|
|
22
|
+
"autolink": true,
|
|
22
23
|
"plugins": [
|
|
23
24
|
"./app.plugin.js"
|
|
24
25
|
]
|
package/withNativeCallVoip.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
const { withAndroidManifest, withInfoPlist, withPlugins, withMainActivity } = require('@expo/config-plugins');
|
|
2
2
|
|
|
3
|
-
/** 1. ANDROID MAIN ACTIVITY MOD
|
|
3
|
+
/** 1. ANDROID MAIN ACTIVITY MOD **/
|
|
4
4
|
function withMainActivityDataFix(config) {
|
|
5
5
|
return withMainActivity(config, (config) => {
|
|
6
6
|
let contents = config.modResults.contents;
|
|
7
7
|
|
|
8
|
-
//
|
|
8
|
+
// Ensure Intent import exists
|
|
9
9
|
if (!contents.includes('import android.content.Intent')) {
|
|
10
10
|
contents = contents.replace(/package .*/, (match) => `${match}\n\nimport android.content.Intent`);
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
// Add onNewIntent
|
|
13
|
+
// Add onNewIntent to catch data when app is open
|
|
14
14
|
const onNewIntentCode = `
|
|
15
15
|
override fun onNewIntent(intent: Intent) {
|
|
16
16
|
super.onNewIntent(intent)
|
|
@@ -19,7 +19,6 @@ function withMainActivityDataFix(config) {
|
|
|
19
19
|
`;
|
|
20
20
|
|
|
21
21
|
if (!contents.includes('override fun onNewIntent')) {
|
|
22
|
-
// Find the last closing brace and insert before it
|
|
23
22
|
const lastBraceIndex = contents.lastIndexOf('}');
|
|
24
23
|
contents = contents.slice(0, lastBraceIndex) + onNewIntentCode + contents.slice(lastBraceIndex);
|
|
25
24
|
}
|
|
@@ -56,13 +55,13 @@ function withAndroidConfig(config) {
|
|
|
56
55
|
// Activities
|
|
57
56
|
application.activity = application.activity || [];
|
|
58
57
|
|
|
59
|
-
//
|
|
58
|
+
// Ensure MainActivity is singleTask
|
|
60
59
|
const mainActivity = application.activity.find(a => a.$['android:name'] === '.MainActivity');
|
|
61
60
|
if (mainActivity) {
|
|
62
61
|
mainActivity.$['android:launchMode'] = 'singleTask';
|
|
63
62
|
}
|
|
64
63
|
|
|
65
|
-
// AcceptCallActivity (Trampoline)
|
|
64
|
+
// AcceptCallActivity (The Trampoline - Must remain in Manifest)
|
|
66
65
|
if (!application.activity.some(a => a.$['android:name'] === 'com.rnsnativecall.AcceptCallActivity')) {
|
|
67
66
|
application.activity.push({
|
|
68
67
|
$: {
|
|
@@ -86,7 +85,7 @@ function withAndroidConfig(config) {
|
|
|
86
85
|
services.forEach(svc => {
|
|
87
86
|
if (!application.service.some(s => s.$['android:name'] === svc.name)) {
|
|
88
87
|
application.service.push({
|
|
89
|
-
$: { 'android:name': svc.name, 'android:exported':
|
|
88
|
+
$: { 'android:name': svc.name, 'android:exported': 'true', 'android:permission': svc.permission },
|
|
90
89
|
'intent-filter': [{ action: [{ $: { 'android:name': svc.action } }] }]
|
|
91
90
|
});
|
|
92
91
|
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
package com.rnsnativecall
|
|
2
|
-
|
|
3
|
-
import android.app.NotificationManager
|
|
4
|
-
import android.content.Context
|
|
5
|
-
import android.os.Bundle
|
|
6
|
-
import android.view.View
|
|
7
|
-
import android.view.WindowManager
|
|
8
|
-
import android.widget.ImageView
|
|
9
|
-
import android.widget.TextView
|
|
10
|
-
import androidx.appcompat.app.AppCompatActivity
|
|
11
|
-
import com.bumptech.glide.Glide
|
|
12
|
-
|
|
13
|
-
class IncomingCallActivity : AppCompatActivity() {
|
|
14
|
-
|
|
15
|
-
override fun onCreate(savedInstanceState: Bundle?) {
|
|
16
|
-
super.onCreate(savedInstanceState)
|
|
17
|
-
|
|
18
|
-
// 1. Show over lockscreen
|
|
19
|
-
window.addFlags(
|
|
20
|
-
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
|
|
21
|
-
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
|
|
22
|
-
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or
|
|
23
|
-
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
// 2. Set the layout FIRST
|
|
27
|
-
setContentView(R.layout.activity_incoming_call)
|
|
28
|
-
|
|
29
|
-
// 3. Extract data from Intent
|
|
30
|
-
val uuid = intent.getStringExtra("EXTRA_CALL_UUID")
|
|
31
|
-
val name = intent.getStringExtra("EXTRA_CALLER_NAME") ?: "Unknown"
|
|
32
|
-
val profileUrl = intent.getStringExtra("profile")
|
|
33
|
-
val callType = intent.getStringExtra("callType") ?: "audio"
|
|
34
|
-
|
|
35
|
-
// 4. Initialize Views
|
|
36
|
-
val imageView = findViewById<ImageView>(R.id.caller_image)
|
|
37
|
-
val nameText = findViewById<TextView>(R.id.caller_name)
|
|
38
|
-
val typeText = findViewById<TextView>(R.id.call_type)
|
|
39
|
-
|
|
40
|
-
nameText.text = name
|
|
41
|
-
typeText.text = if (callType == "video") "Incoming Video..." else "Incoming Audio..."
|
|
42
|
-
|
|
43
|
-
// 5. Load Image with Glide
|
|
44
|
-
if (!profileUrl.isNullOrEmpty()) {
|
|
45
|
-
Glide.with(this)
|
|
46
|
-
.load(profileUrl)
|
|
47
|
-
.circleCrop()
|
|
48
|
-
.into(imageView)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// 6. Handle buttons
|
|
52
|
-
findViewById<View>(R.id.btn_accept).setOnClickListener {
|
|
53
|
-
dismissCall()
|
|
54
|
-
CallModule.sendEventToJS("onCallAccepted", mapOf("callUUID" to uuid))
|
|
55
|
-
finish()
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
findViewById<View>(R.id.btn_reject).setOnClickListener {
|
|
59
|
-
dismissCall()
|
|
60
|
-
CallModule.sendEventToJS("onCallRejected", mapOf("callUUID" to uuid))
|
|
61
|
-
finish()
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
private fun dismissCall() {
|
|
66
|
-
// This removes the "Pill" notification from the status bar
|
|
67
|
-
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
68
|
-
notificationManager.cancel(101) // Use the same ID you used in NativeCallManager
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
override fun onBackPressed() {
|
|
72
|
-
// Disable back button so they have to click Accept or Reject
|
|
73
|
-
}
|
|
74
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
-
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
3
|
-
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
4
|
-
android:layout_width="match_parent"
|
|
5
|
-
android:layout_height="match_parent"
|
|
6
|
-
android:background="#00000000">
|
|
7
|
-
<LinearLayout
|
|
8
|
-
android:layout_width="match_parent"
|
|
9
|
-
android:layout_height="wrap_content"
|
|
10
|
-
android:layout_marginHorizontal="8dp"
|
|
11
|
-
android:layout_marginTop="30dp"
|
|
12
|
-
android:background="@drawable/pill_background"
|
|
13
|
-
android:elevation="15dp"
|
|
14
|
-
android:orientation="horizontal"
|
|
15
|
-
android:padding="12dp"
|
|
16
|
-
android:gravity="center_vertical">
|
|
17
|
-
|
|
18
|
-
<com.google.android.material.imageview.ShapeableImageView
|
|
19
|
-
android:id="@+id/caller_image"
|
|
20
|
-
android:layout_width="50dp"
|
|
21
|
-
android:layout_height="50dp"
|
|
22
|
-
android:background="#ccc"
|
|
23
|
-
app:shapeAppearanceOverlay="@style/CircleImage" />
|
|
24
|
-
|
|
25
|
-
<LinearLayout
|
|
26
|
-
android:layout_width="0dp"
|
|
27
|
-
android:layout_height="wrap_content"
|
|
28
|
-
android:layout_weight="1"
|
|
29
|
-
android:layout_marginStart="15dp"
|
|
30
|
-
android:orientation="vertical">
|
|
31
|
-
|
|
32
|
-
<TextView
|
|
33
|
-
android:id="@+id/caller_name"
|
|
34
|
-
android:layout_width="wrap_content"
|
|
35
|
-
android:layout_height="wrap_content"
|
|
36
|
-
android:text="Caller Name"
|
|
37
|
-
android:textColor="#FFFFFF"
|
|
38
|
-
android:textSize="16sp"
|
|
39
|
-
android:textStyle="bold" />
|
|
40
|
-
|
|
41
|
-
<TextView
|
|
42
|
-
android:id="@+id/call_type"
|
|
43
|
-
android:layout_width="wrap_content"
|
|
44
|
-
android:layout_height="wrap_content"
|
|
45
|
-
android:text="Incoming Audio..."
|
|
46
|
-
android:textColor="#FFFFFF"
|
|
47
|
-
android:textSize="13sp" />
|
|
48
|
-
</LinearLayout>
|
|
49
|
-
|
|
50
|
-
<ImageButton
|
|
51
|
-
android:id="@+id/btn_accept"
|
|
52
|
-
android:layout_width="42dp"
|
|
53
|
-
android:layout_height="42dp"
|
|
54
|
-
android:background="@drawable/circle_green"
|
|
55
|
-
android:src="@android:drawable/ic_menu_call"
|
|
56
|
-
android:layout_marginEnd="12dp"
|
|
57
|
-
android:tint="#FFFFFF" />
|
|
58
|
-
|
|
59
|
-
<ImageButton
|
|
60
|
-
android:id="@+id/btn_reject"
|
|
61
|
-
android:layout_width="42dp"
|
|
62
|
-
android:layout_height="42dp"
|
|
63
|
-
android:background="@drawable/circle_red"
|
|
64
|
-
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
|
65
|
-
android:tint="#FFFFFF" />
|
|
66
|
-
</LinearLayout>
|
|
67
|
-
</FrameLayout>
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
-
<resources>
|
|
3
|
-
<style name="CircleImage" parent="">
|
|
4
|
-
<item name="cornerSize">50%</item>
|
|
5
|
-
</style>
|
|
6
|
-
|
|
7
|
-
<style name="Theme.IncomingCall" parent="Theme.AppCompat.Light.NoActionBar">
|
|
8
|
-
<item name="android:windowBackground">@android:color/transparent</item>
|
|
9
|
-
<item name="android:windowIsTranslucent">true</item>
|
|
10
|
-
<item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
|
|
11
|
-
</style>
|
|
12
|
-
</resources>
|