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.
@@ -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
- callUUID: string;
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: CallData) => void;
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 an incoming call UI using ConnectionService (Android) or CallKit (iOS).
19
+ * Display the Sticky Pill notification UI.
20
20
  * @param uuid Unique call identifier
21
21
  * @param name Caller display name
22
- * @param callType True if video call (default: false)
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: string,
27
+ callType: 'audio' | 'video',
29
28
  ): Promise<boolean>;
30
29
 
31
30
  /**
32
- * Dismiss the native call UI (e.g., if the caller hangs up before the user answers).
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 from the native side.
39
- * @param onAccept Callback when user presses Answer
40
- * @param onReject Callback when user presses Decline
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 (npx expo run:android / ios)."
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
- const result = await PermissionsAndroid.requestMultiple(REQUIRED_PERMISSIONS);
29
-
30
- // This triggers the "Calling Accounts" system settings if not enabled
31
- const isAccountEnabled = await CallModule.checkTelecomPermissions();
32
- if (!isAccountEnabled) return false;
33
-
34
- return Object.values(result).every(status => status === PermissionsAndroid.RESULTS.GRANTED);
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', onAccept),
69
- callEventEmitter.addListener('onCallRejected', onReject),
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
- // Dynamically get the App Name from the bundle so it's not hardcoded
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
- config.includesCallsInRecents = NO;
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
- hasVideo:(BOOL)hasVideo
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", @"The provided UUID string is invalid", nil);
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 = hasVideo;
56
- update.supportsGrouping = NO;
57
- update.supportsUngrouping = NO;
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
- [self sendEventWithName:@"onCallRejected"
97
- body:@{@"callUUID" : [action.callUUID.UUIDString lowercaseString]}];
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.8",
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
  ]
@@ -1,16 +1,16 @@
1
1
  const { withAndroidManifest, withInfoPlist, withPlugins, withMainActivity } = require('@expo/config-plugins');
2
2
 
3
- /** 1. ANDROID MAIN ACTIVITY MOD (Fixes data passing) **/
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
- // Add necessary import
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 override to ensure data is updated when app is already open
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
- // Update MainActivity to use singleTask for better call handling
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': svc.permission ? 'true' : 'false', 'android:permission': svc.permission },
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,4 +0,0 @@
1
- <shape xmlns:android="http://schemas.android.com/apk/res/android">
2
- <solid android:color="#34C759" />
3
- <corners android:radius="21dp" />
4
- </shape>
@@ -1,4 +0,0 @@
1
- <shape xmlns:android="http://schemas.android.com/apk/res/android">
2
- <solid android:color="#FF3B30" />
3
- <corners android:radius="21dp" />
4
- </shape>
@@ -1,5 +0,0 @@
1
- <shape xmlns:android="http://schemas.android.com/apk/res/android">
2
- <solid android:color="#212121" />
3
- <corners android:radius="100dp" />
4
- <stroke android:width="1dp" android:color="#33FFFFFF" />
5
- </shape>
@@ -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>