rns-nativecall 1.2.3 → 1.2.5

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 CHANGED
@@ -130,20 +130,32 @@ export default function App() {
130
130
  ### 📖 API Reference
131
131
  # rns-nativecall API Reference
132
132
 
133
+ ## API Reference
134
+
135
+ ### Core Methods
133
136
  | Method | Platform | Description |
134
137
  | :--- | :--- | :--- |
135
138
  | **registerHeadlessTask(callback)** | All | Registers the background task. `eventType` is 'INCOMING_CALL', 'BUSY', or 'ABORTED_CALL'. |
136
- | **displayCall(uuid, name, type)** | All | Launches full-screen Activity. iOS: Reports incoming call to CallKit. |
137
- | **checkCallValidity(uuid)** | All | Returns {isValid, isCanceled} to prevent ghost/canceled calls. |
138
- | **checkCallStatus(uuid)** | All | Returns {isCanceled, isActive, shouldDisplay} for UI syncing. |
139
- | **showMissedCall(uuid, name, type)** | All | Shows miss call on the deivce notification tray|
140
- | **destroyNativeCallUI(uuid)** | All | Stops ringtone/Activity. iOS: Ends CallKit (Handoff vs Hangup logic). |
141
- | **getInitialCallData()** | All | Retrieves the call payload if the app was cold-started via a notification Answer. |
142
- | **subscribe(onAccept, onReject, onFailed)** | All | Listens for Answer/Decline button presses and system-level bridge errors. |
143
- | **checkOverlayPermission()** | Android | Android only: Returns true if app can draw over other apps while unlocked. |
144
- | **checkFullScreenPermission()** | Android | Android 14+: Checks if app can trigger full-screen intents on lockscreen. |
145
- | **requestOverlayPermission()** | Android | Android only: Navigates user to "Draw over other apps" system settings. |
146
- | **requestFullScreenSettings()** | Android | Android 14+: Navigates user to "Full Screen Intent" system settings. |
139
+ | **displayCall(uuid, name, type)** | All | Launches full-screen Activity (Android) or reports to CallKit (iOS). |
140
+ | **destroyNativeCallUI(uuid)** | All | Stops ringtone/Activity (Android) or ends CallKit session (iOS). |
141
+ | **showMissedCall(uuid, name, type)** | Android | Posts a persistent notification in the device tray for missed calls. |
142
+ | **subscribe(onAccept, onReject, onFailed)** | All | Listens for Answer/Decline actions and system-level bridge errors. |
143
+
144
+ ### Data & State Management
145
+ | Method | Platform | Description |
146
+ | :--- | :--- | :--- |
147
+ | **getInitialCallData()** | All | Retrieves the call payload if the app was cold-started via a notification. |
148
+ | **checkCallValidity(uuid)** | All | Returns `{isValid, isCanceled}` to prevent ghost or aborted calls. |
149
+ | **checkCallStatus(uuid)** | All | Returns `{isCanceled, isActive, shouldDisplay}` for UI syncing. |
150
+
151
+ ### Android Permissions
152
+ | Method | Platform | Description |
153
+ | :--- | :--- | :--- |
154
+ | **checkOverlayPermission()** | Android | Returns `true` if app can draw over other apps (Heads-up UI). |
155
+ | **checkFullScreenPermission()** | Android | (Android 14+) Checks if app can trigger full-screen intents. |
156
+ | **requestOverlayPermission()** | Android | Navigates user to "Draw over other apps" system settings. |
157
+ | **requestFullScreenSettings()** | Android | (Android 14+) Navigates user to "Full Screen Intent" settings. |
158
+
147
159
  ---
148
160
 
149
161
  # Implementation Notes
@@ -32,7 +32,7 @@ class CallMessagingService : FirebaseMessagingService() {
32
32
  }
33
33
 
34
34
  NativeCallManager.dismissIncomingCall(context, uuid)
35
- showMissedCallNotification(context, data, uuid)
35
+ NativeCallManager.showMissedCallNotification(context, data)
36
36
  return
37
37
  }
38
38
 
@@ -73,63 +73,6 @@ class CallMessagingService : FirebaseMessagingService() {
73
73
  }
74
74
  }
75
75
 
76
- private fun showMissedCallNotification(
77
- context: Context,
78
- data: Map<String, String>,
79
- uuid: String,
80
- ) {
81
- val name = data["name"] ?: "Unknown"
82
- val callType = data["callType"] ?: "video"
83
- val channelId = "missed_calls"
84
-
85
- val notificationManager =
86
- context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
87
-
88
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
89
- val channel =
90
- NotificationChannel(
91
- channelId,
92
- "Missed Calls",
93
- NotificationManager.IMPORTANCE_DEFAULT,
94
- ).apply { description = "Missed call notifications" }
95
- notificationManager.createNotificationChannel(channel)
96
- }
97
-
98
- val iconResId =
99
- context.resources
100
- .getIdentifier("ic_missed_call", "drawable", context.packageName)
101
- .takeIf { it != 0 } ?: android.R.drawable.sym_call_missed
102
- val appName = context.applicationInfo.loadLabel(context.packageManager).toString()
103
-
104
- val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
105
- launchIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
106
- val pendingIntent =
107
- PendingIntent.getActivity(
108
- context,
109
- uuid.hashCode(),
110
- launchIntent,
111
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
112
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
113
- } else {
114
- PendingIntent.FLAG_UPDATE_CURRENT
115
- },
116
- )
117
-
118
- val builder =
119
- NotificationCompat
120
- .Builder(context, channelId)
121
- .setSmallIcon(iconResId)
122
- .setContentTitle("$appName • Missed $callType call")
123
- .setContentText("You missed a call from $name")
124
- .setPriority(NotificationCompat.PRIORITY_HIGH)
125
- .setAutoCancel(true)
126
- .setCategory(NotificationCompat.CATEGORY_MISSED_CALL)
127
- .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
128
- .setContentIntent(pendingIntent)
129
-
130
- notificationManager.notify(uuid.hashCode(), builder.build())
131
- }
132
-
133
76
  private fun isAppInForeground(context: Context): Boolean {
134
77
  val activityManager =
135
78
  context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
@@ -14,7 +14,9 @@ import androidx.core.app.Person
14
14
  import androidx.core.content.ContextCompat
15
15
 
16
16
  object NativeCallManager {
17
- const val channelId = "CALL_CHANNEL_V0_URGENT"
17
+ const val channelId = "CALL_CHANNEL_URGENT_V2"
18
+ private const val MISSED_CHANNEL_ID = "missed_calls"
19
+
18
20
  private var currentCallData: Map<String, String>? = null
19
21
 
20
22
  @JvmStatic internal var pendingCallNotification: Notification? = null
@@ -23,6 +25,37 @@ object NativeCallManager {
23
25
 
24
26
  fun getCurrentCallData(): Map<String, String>? = currentCallData
25
27
 
28
+ // --- REUSABLE HELPERS ---
29
+
30
+ private fun getPendingIntentFlags(): Int =
31
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
32
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
33
+ } else {
34
+ PendingIntent.FLAG_UPDATE_CURRENT
35
+ }
36
+
37
+ private fun getResourceColor(context: Context): Int {
38
+ val colorId = context.resources.getIdentifier("notification_icon_color", "color", context.packageName)
39
+ return if (colorId != 0) ContextCompat.getColor(context, colorId) else Color.parseColor("#ffffff")
40
+ }
41
+
42
+ private fun createCallIntent(
43
+ context: Context,
44
+ targetClass: Class<*>,
45
+ actionName: String?,
46
+ uuid: String,
47
+ data: Map<String, String>,
48
+ ): Intent =
49
+ Intent(context, targetClass).apply {
50
+ action = actionName
51
+ putExtra("EXTRA_CALL_UUID", uuid)
52
+ data.forEach { (k, v) -> putExtra(k, v) }
53
+ }
54
+
55
+ private fun getNotificationManager(context: Context) = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
56
+
57
+ // --- MAIN LOGIC ---
58
+
26
59
  fun handleIncomingPush(
27
60
  context: Context,
28
61
  data: Map<String, String>,
@@ -32,45 +65,33 @@ object NativeCallManager {
32
65
  val uuid = data["callUuid"] ?: return@post
33
66
  val name = data["name"] ?: "Someone"
34
67
  val callType = data["callType"] ?: "audio"
35
- val isVideo = callType.equals("video", ignoreCase = true)
36
68
  val notificationId = uuid.hashCode()
69
+ val flags = getPendingIntentFlags()
37
70
 
38
- val ringtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE)
39
-
40
- val pendingFlags =
41
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
42
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
43
- } else {
44
- PendingIntent.FLAG_UPDATE_CURRENT
45
- }
46
-
47
- // Intents
71
+ // 1. Setup Intents using helper
48
72
  val overlayIntent =
49
- Intent(context, NotificationOverlayActivity::class.java).apply {
50
- putExtra("EXTRA_CALL_UUID", uuid)
51
- data.forEach { (k, v) -> putExtra(k, v) }
73
+ createCallIntent(context, NotificationOverlayActivity::class.java, null, uuid, data).apply {
52
74
  addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_USER_ACTION)
53
75
  }
54
- val fullScreenPendingIntent = PendingIntent.getActivity(context, notificationId, overlayIntent, pendingFlags)
55
-
56
- val answerIntent =
57
- Intent(context, CallActionReceiver::class.java).apply {
58
- action = "ACTION_ANSWER"
59
- putExtra("EXTRA_CALL_UUID", uuid)
60
- data.forEach { (k, v) -> putExtra(k, v) }
61
- }
62
- val answerPendingIntent = PendingIntent.getBroadcast(context, notificationId + 1, answerIntent, pendingFlags)
76
+ val fullScreenPI = PendingIntent.getActivity(context, notificationId, overlayIntent, flags)
77
+
78
+ val answerPI =
79
+ PendingIntent.getBroadcast(
80
+ context,
81
+ notificationId + 1,
82
+ createCallIntent(context, CallActionReceiver::class.java, "ACTION_ANSWER", uuid, data),
83
+ flags,
84
+ )
63
85
 
64
- val rejectIntent =
65
- Intent(context, CallActionReceiver::class.java).apply {
66
- action = "ACTION_REJECT"
67
- putExtra("EXTRA_CALL_UUID", uuid)
68
- data.forEach { (k, v) -> putExtra(k, v) }
69
- }
70
- val rejectPendingIntent = PendingIntent.getBroadcast(context, notificationId + 2, rejectIntent, pendingFlags)
86
+ val rejectPI =
87
+ PendingIntent.getBroadcast(
88
+ context,
89
+ notificationId + 2,
90
+ createCallIntent(context, CallActionReceiver::class.java, "ACTION_REJECT", uuid, data),
91
+ flags,
92
+ )
71
93
 
72
- // Channel
73
- val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
94
+ // 2. Setup Urgent Channel
74
95
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
75
96
  val channel =
76
97
  NotificationChannel(channelId, "Incoming Calls", NotificationManager.IMPORTANCE_HIGH).apply {
@@ -78,7 +99,7 @@ object NativeCallManager {
78
99
  enableVibration(true)
79
100
  setBypassDnd(true)
80
101
  setSound(
81
- ringtoneUri,
102
+ RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE),
82
103
  AudioAttributes
83
104
  .Builder()
84
105
  .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
@@ -86,31 +107,14 @@ object NativeCallManager {
86
107
  .build(),
87
108
  )
88
109
  }
89
- notificationManager.createNotificationChannel(channel)
110
+ getNotificationManager(context).createNotificationChannel(channel)
90
111
  }
91
112
 
113
+ // 3. Resources & Style
92
114
  val iconId =
93
115
  context.resources
94
116
  .getIdentifier("notification_icon", "drawable", context.packageName)
95
- .let { id ->
96
- if (id != 0) {
97
- id // Found the one you saw in the folder!
98
- } else {
99
- // Fallback chain if notification_icon is missing
100
- val shellId = context.resources.getIdentifier("shell_notification_icon", "drawable", context.packageName)
101
- if (shellId != 0) shellId else context.applicationInfo.icon
102
- }
103
- }
104
-
105
- val colorId = context.resources.getIdentifier("notification_icon_color", "color", context.packageName)
106
-
107
- val iconColor =
108
- if (colorId != 0) {
109
- ContextCompat.getColor(context, colorId)
110
- } else {
111
- // Fallback to a default blue or grey if they didn't provide a color
112
- Color.parseColor("#ffffff")
113
- }
117
+ .let { if (it != 0) it else context.applicationInfo.icon }
114
118
 
115
119
  val caller =
116
120
  Person
@@ -118,62 +122,42 @@ object NativeCallManager {
118
122
  .setName(name)
119
123
  .setImportant(true)
120
124
  .build()
125
+ val incomingCallStyle = NotificationCompat.CallStyle.forIncomingCall(caller, rejectPI, answerPI)
121
126
 
122
- val incomingCallTemplate =
123
- NotificationCompat.CallStyle.forIncomingCall(
124
- caller,
125
- rejectPendingIntent,
126
- answerPendingIntent,
127
- )
128
-
129
- if (isVideo) {
127
+ if (callType.equals("video", ignoreCase = true)) {
130
128
  try {
131
- incomingCallTemplate.setIsVideo(true)
129
+ incomingCallStyle.setIsVideo(true)
132
130
  } catch (e: Exception) {
133
- // Some versions of Android may throw here; ignore
131
+ // No-op
134
132
  }
135
133
  }
136
-
137
- val builder =
134
+ // 4. Build Notification
135
+ val notification =
138
136
  NotificationCompat
139
137
  .Builder(context, channelId)
140
138
  .setSmallIcon(iconId)
141
- // 1. FOR ANDROID 8/9: They prioritize ContentTitle over the Style's internal logic.
142
- // We include the name here so it's impossible to miss.
143
139
  .setContentTitle("Incoming $callType Call from $name")
144
- // 2. FOR ALL VERSIONS: This helps fill the "Status" line below the name.
145
140
  .setContentText("Incoming $callType Call")
146
- // 3. FOR ANDROID 12+: This places the text in the header next to the App Name.
147
141
  .setSubText("Incoming $callType Call")
148
- .setColor(iconColor)
142
+ .setColor(getResourceColor(context))
149
143
  .setPriority(NotificationCompat.PRIORITY_MAX)
150
144
  .setCategory(NotificationCompat.CATEGORY_CALL)
151
145
  .setOngoing(true)
152
- .setFullScreenIntent(fullScreenPendingIntent, true)
153
- .setStyle(incomingCallTemplate)
146
+ .setFullScreenIntent(fullScreenPI, true)
147
+ .setStyle(incomingCallStyle)
154
148
  .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
149
+ .build()
155
150
 
156
- val notification = builder.build()
157
151
  pendingCallNotification = notification
158
152
  pendingNotificationId = notificationId
159
153
 
160
- // 1. Start the LOUD UI Service (Google allows this for PHONE_CALL)
161
- val uiIntent = Intent(context, CallUiForegroundService::class.java)
162
- ContextCompat.startForegroundService(context, uiIntent)
163
-
164
- // 2. Stop the SILENT Wake service (which no longer needs DATA_SYNC)
154
+ // 5. Execution
155
+ getNotificationManager(context).notify(notificationId, notification)
156
+ ContextCompat.startForegroundService(context, Intent(context, CallUiForegroundService::class.java))
165
157
  CallForegroundService.stop(context)
166
158
  }
167
159
  }
168
160
 
169
- fun refreshNotificationOnly(
170
- context: Context,
171
- uuid: String,
172
- ) {
173
- val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
174
- manager.cancel(uuid.hashCode())
175
- }
176
-
177
161
  fun showMissedCallNotification(
178
162
  context: Context,
179
163
  data: Map<String, String>,
@@ -181,57 +165,46 @@ object NativeCallManager {
181
165
  val uuid = data["callUuid"] ?: return
182
166
  val name = data["name"] ?: "Unknown"
183
167
  val callType = data["callType"] ?: "video"
184
- val channelId = "missed_calls"
185
-
186
- val notificationManager =
187
- context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
188
168
 
169
+ // Setup Missed Channel
189
170
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
190
- val channel =
191
- NotificationChannel(
192
- channelId,
193
- "Missed Calls",
194
- NotificationManager.IMPORTANCE_DEFAULT,
195
- ).apply { description = "Missed call notifications" }
196
- notificationManager.createNotificationChannel(channel)
171
+ val channel = NotificationChannel(MISSED_CHANNEL_ID, "Missed Calls", NotificationManager.IMPORTANCE_DEFAULT)
172
+ getNotificationManager(context).createNotificationChannel(channel)
197
173
  }
198
174
 
199
- // Use your custom notification icon if available, fallback to system missed call icon
200
175
  val iconResId =
201
176
  context.resources
202
- .getIdentifier("notification_icon", "drawable", context.packageName)
203
- .takeIf { it != 0 } ?: android.R.drawable.sym_call_missed
204
-
205
- val appName = context.applicationInfo.loadLabel(context.packageManager).toString()
206
-
207
- val launchIntent = context.packageManager.getLaunchIntentForPackage(context.packageName)
208
- launchIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
209
-
210
- val pendingIntent =
211
- PendingIntent.getActivity(
212
- context,
213
- uuid.hashCode(),
214
- launchIntent,
215
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
216
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
217
- } else {
218
- PendingIntent.FLAG_UPDATE_CURRENT
219
- },
220
- )
221
-
222
- val builder =
177
+ .getIdentifier("ic_missed_call", "drawable", context.packageName)
178
+ .let { if (it != 0) it else android.R.drawable.sym_call_missed }
179
+
180
+ val launchIntent =
181
+ context.packageManager.getLaunchIntentForPackage(context.packageName)?.apply {
182
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
183
+ }
184
+ val pendingIntent = PendingIntent.getActivity(context, uuid.hashCode(), launchIntent, getPendingIntentFlags())
185
+
186
+ val notification =
223
187
  NotificationCompat
224
- .Builder(context, channelId)
188
+ .Builder(context, MISSED_CHANNEL_ID)
225
189
  .setSmallIcon(iconResId)
226
- .setContentTitle("Missed $callType call")
227
- .setContentText("You missed a call from $name")
190
+ .setContentTitle(context.applicationInfo.loadLabel(context.packageManager).toString())
191
+ .setContentText("You missed a $callType call from $name")
228
192
  .setPriority(NotificationCompat.PRIORITY_HIGH)
193
+ .setColor(getResourceColor(context))
229
194
  .setAutoCancel(true)
230
195
  .setCategory(NotificationCompat.CATEGORY_MISSED_CALL)
231
196
  .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
232
197
  .setContentIntent(pendingIntent)
198
+ .build()
233
199
 
234
- notificationManager.notify(uuid.hashCode(), builder.build())
200
+ getNotificationManager(context).notify(uuid.hashCode() + 10000, notification)
201
+ }
202
+
203
+ fun refreshNotificationOnly(
204
+ context: Context,
205
+ uuid: String,
206
+ ) {
207
+ getNotificationManager(context).cancel(uuid.hashCode())
235
208
  }
236
209
 
237
210
  fun dismissIncomingCall(
@@ -241,8 +214,7 @@ object NativeCallManager {
241
214
  pendingCallNotification = null
242
215
  pendingNotificationId = null
243
216
  currentCallData = null
244
- val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
245
- uuid?.let { manager.cancel(it.hashCode()) }
217
+ uuid?.let { getNotificationManager(context).cancel(it.hashCode()) }
246
218
  context.stopService(Intent(context, CallUiForegroundService::class.java))
247
219
  }
248
220
  }
package/index.d.ts CHANGED
@@ -63,7 +63,7 @@ export interface CallHandlerType {
63
63
  ): Promise<boolean>;
64
64
 
65
65
  /**
66
- * Displays the full-screen Native Incoming Call UI.
66
+ * Show showMissedCall on the device notification tray.
67
67
  */
68
68
  showMissedCall(
69
69
  uuid: string,
package/index.js CHANGED
@@ -78,11 +78,6 @@ export const CallHandler = {
78
78
  return await CallModule.checkCallStatus(uuid.toLowerCase().trim());
79
79
  },
80
80
 
81
- showMissedCall: async (uuid, name, callType) => {
82
- if (!CallModule?.showMissedCall) return false;
83
- return await CallModule.showMissedCall(uuid.toLowerCase().trim(), name, callType);
84
- },
85
-
86
81
  //--------------------------------------------------------------------------------------
87
82
 
88
83
  requestOverlayPermission: async () => {
@@ -97,6 +92,12 @@ export const CallHandler = {
97
92
  return await CallModule.requestFullScreenSettings();
98
93
  },
99
94
 
95
+ showMissedCall: async (uuid, name, callType) => {
96
+ if (Platform.OS === 'ios') return true;
97
+ if (!CallModule?.showMissedCall) return false;
98
+ return await CallModule.showMissedCall(uuid.toLowerCase().trim(), name, callType);
99
+ },
100
+
100
101
  checkOverlayPermission: async () => {
101
102
  if (Platform.OS === 'ios') return true;
102
103
  if (!CallModule?.checkOverlayPermission) return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rns-nativecall",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "High-performance React Native module for handling native VoIP call UI on Android and iOS.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -1,4 +0,0 @@
1
- <shape xmlns:android="http://schemas.android.com/apk/res/android"
2
- android:shape="oval">
3
- <solid android:color="#333333" />
4
- </shape>
@@ -1,9 +0,0 @@
1
- <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
- android:width="24dp"
3
- android:height="24dp"
4
- android:viewportWidth="24"
5
- android:viewportHeight="24">
6
- <path
7
- android:fillColor="#FFFFFF"
8
- android:pathData="M20,15.5c-1.25,0 -2.45,-0.2 -3.57,-0.57a1.02,1.02 0,0 0,-1.02 0.24l-2.2,2.2a15.05,15.05 0,0 1,-6.59 -6.59l2.2,-2.2a1.02,1.02 0,0 0,0.24 -1.02A11.36,11.36 0,0 1,8.5 4c0,-0.55 -0.45,-1 -1,-1H4c-0.55,0 -1,0.45 -1,1c0,9.39 7.61,17 17,17c0.55,0 1,-0.45 1,-1v-3.5c0,-0.55 -0.45,-1 -1,-1z" />
9
- </vector>
@@ -1,9 +0,0 @@
1
- <vector xmlns:android="http://schemas.android.com/apk/res/android"
2
- android:width="24dp"
3
- android:height="24dp"
4
- android:viewportWidth="24"
5
- android:viewportHeight="24">
6
- <path
7
- android:fillColor="#FFFFFF"
8
- android:pathData="M12,9c-1.6,0 -3.15,0.25 -4.6,0.72v3.1c0,0.39 -0.23,0.74 -0.56,0.9 -0.98,0.49 -1.65,1.45 -1.65,2.58c0,1.65 1.34,3 3,3h2v-3H8.1c0.01,-0.99 0.87,-1.8 1.85,-1.8c0.51,0 1,-0.21 1.35,-0.56l1.8,-1.8C13.04,9.3 12.55,9 12,9z" />
9
- </vector>
@@ -1,139 +0,0 @@
1
- <?xml version="1.0" encoding="utf-8"?>
2
- <androidx.constraintlayout.widget.ConstraintLayout
3
- xmlns:android="http://schemas.android.com/apk/res/android"
4
- xmlns:app="http://schemas.android.com/apk/res-auto"
5
- xmlns:tools="http://schemas.android.com/tools"
6
- android:layout_width="match_parent"
7
- android:layout_height="match_parent"
8
- android:background="#212121">
9
-
10
- <!-- Profile Section -->
11
- <ImageView
12
- android:id="@+id/profileImage"
13
- android:layout_width="200dp"
14
- android:layout_height="200dp"
15
- android:scaleType="centerCrop"
16
- android:background="@drawable/circle_background"
17
- app:layout_constraintTop_toTopOf="parent"
18
- app:layout_constraintStart_toStartOf="parent"
19
- app:layout_constraintEnd_toEndOf="parent"
20
- app:layout_constraintBottom_toTopOf="@id/usernameText"
21
- app:layout_constraintVertical_chainStyle="packed"
22
- app:layout_constraintVertical_bias="0.35"
23
- tools:src="@drawable/ic_profile_placeholder" />
24
-
25
- <!-- Optional: Blur effect for discreet mode (apply programmatically) -->
26
- <!-- You can use RenderScript or BlurView library for real blur -->
27
-
28
- <TextView
29
- android:id="@+id/usernameText"
30
- android:layout_width="wrap_content"
31
- android:layout_height="wrap_content"
32
- android:text="John Doe"
33
- android:textColor="#FFFFFF"
34
- android:textSize="32sp"
35
- android:fontFamily="sans-serif-medium"
36
- android:layout_marginTop="20dp"
37
- app:layout_constraintTop_toBottomOf="@id/profileImage"
38
- app:layout_constraintStart_toStartOf="parent"
39
- app:layout_constraintEnd_toEndOf="parent"
40
- tools:text="John Doe" />
41
-
42
- <TextView
43
- android:id="@+id/callStatusText"
44
- android:layout_width="wrap_content"
45
- android:layout_height="wrap_content"
46
- android:text="Incoming Video Call..."
47
- android:textColor="#FFFFFF"
48
- android:textSize="18sp"
49
- android:alpha="0.8"
50
- android:layout_marginTop="10dp"
51
- app:layout_constraintTop_toBottomOf="@id/usernameText"
52
- app:layout_constraintStart_toStartOf="parent"
53
- app:layout_constraintEnd_toEndOf="parent" />
54
-
55
- <!-- Action Buttons Container -->
56
- <LinearLayout
57
- android:id="@+id/buttonContainer"
58
- android:layout_width="match_parent"
59
- android:layout_height="wrap_content"
60
- android:orientation="horizontal"
61
- android:gravity="center"
62
- android:paddingBottom="100dp"
63
- app:layout_constraintBottom_toBottomOf="parent"
64
- app:layout_constraintStart_toStartOf="parent"
65
- app:layout_constraintEnd_toEndOf="parent">
66
-
67
- <!-- Decline Button -->
68
- <LinearLayout
69
- android:layout_width="wrap_content"
70
- android:layout_height="wrap_content"
71
- android:orientation="vertical"
72
- android:gravity="center"
73
- android:layout_marginEnd="60dp">
74
-
75
- <androidx.cardview.widget.CardView
76
- android:layout_width="70dp"
77
- android:layout_height="70dp"
78
- app:cardCornerRadius="35dp"
79
- app:cardElevation="8dp"
80
- app:cardBackgroundColor="#FF3B30">
81
-
82
- <ImageView
83
- android:layout_width="match_parent"
84
- android:layout_height="match_parent"
85
- android:src="@drawable/ic_call_end_white"
86
- android:padding="18dp"
87
- android:tint="#FFFFFF" />
88
-
89
- </androidx.cardview.widget.CardView>
90
-
91
- <TextView
92
- android:layout_width="wrap_content"
93
- android:layout_height="wrap_content"
94
- android:text="Decline"
95
- android:textColor="#FFFFFF"
96
- android:textSize="16sp"
97
- android:fontFamily="sans-serif-medium"
98
- android:layout_marginTop="10dp" />
99
-
100
- </LinearLayout>
101
-
102
- <!-- Accept Button -->
103
- <LinearLayout
104
- android:layout_width="wrap_content"
105
- android:layout_height="wrap_content"
106
- android:orientation="vertical"
107
- android:gravity="center"
108
- android:layout_marginStart="60dp">
109
-
110
- <androidx.cardview.widget.CardView
111
- android:layout_width="70dp"
112
- android:layout_height="70dp"
113
- app:cardCornerRadius="35dp"
114
- app:cardElevation="8dp"
115
- app:cardBackgroundColor="#4CD964">
116
-
117
- <ImageView
118
- android:layout_width="match_parent"
119
- android:layout_height="match_parent"
120
- android:src="@drawable/ic_call_answer_white"
121
- android:padding="18dp"
122
- android:tint="#FFFFFF" />
123
-
124
- </androidx.cardview.widget.CardView>
125
-
126
- <TextView
127
- android:layout_width="wrap_content"
128
- android:layout_height="wrap_content"
129
- android:text="Accept"
130
- android:textColor="#FFFFFF"
131
- android:textSize="16sp"
132
- android:fontFamily="sans-serif-medium"
133
- android:layout_marginTop="10dp" />
134
-
135
- </LinearLayout>
136
-
137
- </LinearLayout>
138
-
139
- </androidx.constraintlayout.widget.ConstraintLayout>