@qusaieilouti99/call-manager 0.1.98 → 0.1.100

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.
@@ -4,12 +4,14 @@ package com.margelo.nitro.qusaieilouti99.callmanager
4
4
  import android.app.Activity
5
5
  import android.app.KeyguardManager
6
6
  import android.content.Context
7
+ import android.graphics.Bitmap
7
8
  import android.graphics.BitmapFactory
8
9
  import android.graphics.Color
9
10
  import android.graphics.RenderEffect
10
11
  import android.graphics.Shader
11
- import android.graphics.drawable.Drawable
12
+ import android.graphics.Typeface
12
13
  import android.graphics.drawable.GradientDrawable
14
+ import android.graphics.drawable.RippleDrawable
13
15
  import android.os.Build
14
16
  import android.os.Bundle
15
17
  import android.os.Handler
@@ -24,19 +26,20 @@ import android.widget.FrameLayout
24
26
  import android.widget.ImageView
25
27
  import android.widget.LinearLayout
26
28
  import android.widget.TextView
27
- import androidx.core.content.ContextCompat
28
29
  import java.net.HttpURLConnection
29
30
  import java.net.URL
30
31
 
31
32
  class CallActivity : Activity(), CallEngine.CallEndListener {
32
33
 
33
- private enum class FinishReason { ANSWER, DECLINE, TIMEOUT,
34
- MANUAL_DISMISS, EXTERNAL_END }
34
+ private enum class FinishReason {
35
+ ANSWER, DECLINE, TIMEOUT, MANUAL_DISMISS, EXTERNAL_END
36
+ }
35
37
  private var finishReason: FinishReason? = null
36
- private var callId: String = ""
37
- private var callType: String = "Audio"
38
+ private var callId = ""
39
+ private var callType = "Audio"
40
+
38
41
  private val timeoutMs = 60_000L
39
- private val handler = Handler(Looper.getMainLooper())
42
+ private val uiHandler = Handler(Looper.getMainLooper())
40
43
  private val timeoutRunnable = Runnable {
41
44
  finishReason = FinishReason.TIMEOUT
42
45
  CallEngine.stopRingtone()
@@ -52,7 +55,6 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
52
55
  )
53
56
  setupLockScreenBypass(isSamsungBypass)
54
57
 
55
- // Read params
56
58
  callId = intent.getStringExtra("callId") ?: ""
57
59
  callType = intent.getStringExtra("callType") ?: "Audio"
58
60
  val callerName = intent.getStringExtra("callerName") ?: "Unknown"
@@ -60,7 +62,7 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
60
62
 
61
63
  CallEngine.registerCallEndListener(this)
62
64
  buildUi(callerName, avatarUrl)
63
- handler.postDelayed(timeoutRunnable, timeoutMs)
65
+ uiHandler.postDelayed(timeoutRunnable, timeoutMs)
64
66
  Log.d(TAG, "CallActivity setup complete for callId=$callId")
65
67
  }
66
68
 
@@ -72,18 +74,17 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
72
74
  )
73
75
  }
74
76
 
75
- // 1) Background ImageView + optional blur
77
+ // 1) Full-screen background + blur
76
78
  val bg = ImageView(this).apply {
77
79
  layoutParams = FrameLayout.LayoutParams(
78
80
  ViewGroup.LayoutParams.MATCH_PARENT,
79
81
  ViewGroup.LayoutParams.MATCH_PARENT
80
82
  )
81
83
  scaleType = ImageView.ScaleType.CENTER_CROP
82
- // apply RenderEffect blur on API31+
83
84
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
84
85
  setRenderEffect(
85
86
  RenderEffect.createBlurEffect(
86
- 25f, 25f, Shader.TileMode.CLAMP
87
+ 50f, 50f, Shader.TileMode.CLAMP
87
88
  )
88
89
  )
89
90
  }
@@ -100,12 +101,12 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
100
101
  setBackgroundColor(Color.parseColor("#80000000"))
101
102
  })
102
103
 
103
- // 3) Top call-type label
104
- val callTypeLabel = TextView(this).apply {
105
- text = when (callType.lowercase()) {
106
- "video" -> "Incoming video call"
107
- else -> "Incoming audio call"
108
- }
104
+ // 3) “Incoming ... call with extra top padding (48dp)
105
+ val typeLabel = TextView(this).apply {
106
+ text = if (callType.equals("video", true))
107
+ "Incoming video call"
108
+ else
109
+ "Incoming audio call"
109
110
  setTextColor(Color.WHITE)
110
111
  setTextSize(TypedValue.COMPLEX_UNIT_SP, 18f)
111
112
  gravity = Gravity.CENTER
@@ -114,55 +115,38 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
114
115
  ViewGroup.LayoutParams.WRAP_CONTENT
115
116
  ).apply {
116
117
  gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
117
- topMargin = dp(40)
118
+ topMargin = dp(48)
118
119
  }
119
120
  }
120
- root.addView(callTypeLabel)
121
+ root.addView(typeLabel)
121
122
 
122
- // 4) Center content: profile + name
123
- val centerCol = LinearLayout(this).apply {
123
+ // 4) Avatar + name in top half
124
+ val avatarSection = LinearLayout(this).apply {
124
125
  orientation = LinearLayout.VERTICAL
125
126
  gravity = Gravity.CENTER_HORIZONTAL
126
127
  layoutParams = FrameLayout.LayoutParams(
127
128
  ViewGroup.LayoutParams.MATCH_PARENT,
128
129
  ViewGroup.LayoutParams.WRAP_CONTENT
129
130
  ).apply {
130
- gravity = Gravity.CENTER
131
- topMargin = dp(20)
132
- }
133
- }
134
-
135
- // Profile circle
136
- val profile = ImageView(this).apply {
137
- val sz = dp(140)
138
- layoutParams = LinearLayout.LayoutParams(sz, sz)
139
- scaleType = ImageView.ScaleType.CENTER_CROP
140
- background = GradientDrawable().apply {
141
- shape = GradientDrawable.OVAL
142
- setColor(Color.LTGRAY)
131
+ gravity = Gravity.TOP or Gravity.CENTER_HORIZONTAL
132
+ topMargin = dp(100)
143
133
  }
144
- // enable clipping to our oval background
145
- clipToOutline = true
146
134
  }
147
- centerCol.addView(profile)
148
- loadAvatar(profile, avatarUrl)
149
-
150
- // Caller name
151
- centerCol.addView(TextView(this).apply {
135
+ avatarSection.addView(createAvatarView(name, avatarUrl))
136
+ avatarSection.addView(TextView(this).apply {
152
137
  text = name
153
138
  setTextColor(Color.WHITE)
154
139
  setTextSize(TypedValue.COMPLEX_UNIT_SP, 28f)
155
- typeface = android.graphics.Typeface.DEFAULT_BOLD
140
+ typeface = Typeface.DEFAULT_BOLD
156
141
  gravity = Gravity.CENTER
157
142
  layoutParams = LinearLayout.LayoutParams(
158
143
  ViewGroup.LayoutParams.WRAP_CONTENT,
159
144
  ViewGroup.LayoutParams.WRAP_CONTENT
160
- ).apply { topMargin = dp(24) }
145
+ ).apply { topMargin = dp(16) }
161
146
  })
147
+ root.addView(avatarSection)
162
148
 
163
- root.addView(centerCol)
164
-
165
- // 5) Bottom actions
149
+ // 5) Bottom buttons with extra bottom padding (64dp)
166
150
  val actions = LinearLayout(this).apply {
167
151
  orientation = LinearLayout.HORIZONTAL
168
152
  gravity = Gravity.CENTER
@@ -171,53 +155,129 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
171
155
  ViewGroup.LayoutParams.WRAP_CONTENT
172
156
  ).apply {
173
157
  gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
174
- bottomMargin = dp(48)
158
+ bottomMargin = dp(64)
175
159
  }
176
160
  }
177
-
178
- val declineBtn = createCircleButton(
179
- android.R.drawable.ic_menu_close_clear_cancel,
180
- Color.parseColor("#F44336")
181
- ).apply { setOnClickListener { onDecline() } }
182
-
183
- val answerBtn = createCircleButton(
184
- android.R.drawable.ic_menu_call,
185
- Color.parseColor("#4CAF50")
186
- ).apply { setOnClickListener { onAnswer() } }
187
-
188
- actions.addView(declineBtn)
161
+ actions.addView(
162
+ createCircleButton(
163
+ android.R.drawable.ic_menu_close_clear_cancel,
164
+ Color.parseColor("#F44336")
165
+ ).apply { setOnClickListener { onDecline() } }
166
+ )
189
167
  actions.addView(View(this).apply {
190
168
  layoutParams = LinearLayout.LayoutParams(dp(60), 0)
191
169
  })
192
- actions.addView(answerBtn)
170
+ actions.addView(
171
+ createCircleButton(
172
+ android.R.drawable.ic_menu_call,
173
+ Color.parseColor("#4CAF50")
174
+ ).apply { setOnClickListener { onAnswer() } }
175
+ )
193
176
  root.addView(actions)
194
177
 
195
178
  setContentView(root)
196
179
  }
197
180
 
198
- // Plain HTTP loader + setImageBitmap
199
- private fun loadAvatar(iv: ImageView, url: String?) {
200
- if (url.isNullOrEmpty()) return
181
+ private fun createAvatarView(name: String, url: String?): FrameLayout {
182
+ val size = dp(140)
183
+ // Gradient purple background
184
+ val gradientBg = GradientDrawable(
185
+ GradientDrawable.Orientation.TL_BR,
186
+ intArrayOf(
187
+ Color.parseColor("#8E24AA"),
188
+ Color.parseColor("#CE93D8")
189
+ )
190
+ ).apply { shape = GradientDrawable.OVAL }
191
+
192
+ val container = FrameLayout(this).apply {
193
+ layoutParams = LinearLayout.LayoutParams(size, size)
194
+ background = gradientBg
195
+ clipToOutline = true
196
+ }
197
+
198
+ // ImageView (only visible when URL != null)
199
+ val iv = ImageView(this).apply {
200
+ layoutParams = FrameLayout.LayoutParams(
201
+ ViewGroup.LayoutParams.MATCH_PARENT,
202
+ ViewGroup.LayoutParams.MATCH_PARENT
203
+ )
204
+ scaleType = ImageView.ScaleType.CENTER_CROP
205
+ }
206
+ container.addView(iv)
207
+
208
+ // Initials TextView
209
+ val initials = TextView(this).apply {
210
+ layoutParams = FrameLayout.LayoutParams(
211
+ ViewGroup.LayoutParams.MATCH_PARENT,
212
+ ViewGroup.LayoutParams.MATCH_PARENT
213
+ )
214
+ gravity = Gravity.CENTER
215
+ setTextColor(Color.WHITE)
216
+ setTextSize(TypedValue.COMPLEX_UNIT_SP, 32f)
217
+ typeface = Typeface.DEFAULT_BOLD
218
+ text = getInitials(name)
219
+ }
220
+ container.addView(initials)
221
+
222
+ if (url.isNullOrEmpty()) {
223
+ iv.visibility = View.GONE
224
+ initials.visibility = View.VISIBLE
225
+ } else {
226
+ iv.visibility = View.VISIBLE
227
+ initials.visibility = View.GONE
228
+ loadAvatar(iv, url)
229
+ }
230
+
231
+ return container
232
+ }
233
+
234
+ private fun getInitials(fullName: String): String {
235
+ val parts = fullName.trim().split("\\s+".toRegex())
236
+ return when (parts.size) {
237
+ 0 -> ""
238
+ 1 -> parts[0].substring(0, 1).uppercase()
239
+ else -> (parts[0][0].toString() + parts[1][0].toString()).uppercase()
240
+ }
241
+ }
242
+
243
+ private fun loadAvatar(iv: ImageView, url: String) {
201
244
  Thread {
202
245
  try {
203
246
  val conn = URL(url).openConnection() as HttpURLConnection
204
247
  conn.doInput = true; conn.connect()
205
248
  val bmp = BitmapFactory.decodeStream(conn.inputStream)
206
249
  runOnUiThread { iv.setImageBitmap(bmp) }
207
- } catch (_: Exception) { /* ignore */ }
250
+ } catch (_: Exception) { }
208
251
  }.start()
209
252
  }
210
253
 
211
- // Same loader for background
212
254
  private fun loadAndBlurBackground(iv: ImageView, url: String?) {
213
- if (url.isNullOrEmpty()) return
214
255
  Thread {
215
- try {
216
- val conn = URL(url).openConnection() as HttpURLConnection
217
- conn.doInput = true; conn.connect()
218
- val bmp = BitmapFactory.decodeStream(conn.inputStream)
219
- runOnUiThread { iv.setImageBitmap(bmp) }
220
- } catch (_: Exception) { /* ignore */ }
256
+ val bmp: Bitmap? = try {
257
+ if (!url.isNullOrEmpty()) {
258
+ val c = URL(url).openConnection() as HttpURLConnection
259
+ c.doInput = true; c.connect()
260
+ BitmapFactory.decodeStream(c.inputStream)
261
+ } else {
262
+ BitmapFactory.decodeResource(
263
+ resources, R.drawable.default_call_bg
264
+ )
265
+ }
266
+ } catch (_: Exception) { null }
267
+ bmp ?: return@Thread
268
+
269
+ val finalBmp = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
270
+ // heavier blur via 16× down/up-scale
271
+ val factor = 16
272
+ val w = bmp.width / factor
273
+ val h = bmp.height / factor
274
+ val small = Bitmap.createScaledBitmap(bmp, w, h, true)
275
+ Bitmap.createScaledBitmap(small, bmp.width, bmp.height, true)
276
+ } else {
277
+ bmp
278
+ }
279
+
280
+ runOnUiThread { iv.setImageBitmap(finalBmp) }
221
281
  }.start()
222
282
  }
223
283
 
@@ -225,9 +285,11 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
225
285
  val size = dp(70)
226
286
  return FrameLayout(this).apply {
227
287
  layoutParams = LinearLayout.LayoutParams(size, size)
228
- isClickable = true; isFocusable = true
229
- foreground = getRipple()
230
- // colored circle
288
+ isClickable = true
289
+ isFocusable = true
290
+ foreground = makeCircleRipple()
291
+
292
+ // circular background
231
293
  addView(View(context).apply {
232
294
  layoutParams = FrameLayout.LayoutParams(
233
295
  ViewGroup.LayoutParams.MATCH_PARENT,
@@ -238,6 +300,7 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
238
300
  setColor(bgColor)
239
301
  }
240
302
  })
303
+
241
304
  // icon
242
305
  addView(ImageView(context).apply {
243
306
  layoutParams = FrameLayout.LayoutParams(dp(36), dp(36))
@@ -248,12 +311,15 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
248
311
  }
249
312
  }
250
313
 
251
- private fun getRipple(): Drawable? {
252
- val tv = TypedValue()
253
- theme.resolveAttribute(
254
- android.R.attr.selectableItemBackgroundBorderless, tv, true
314
+ private fun makeCircleRipple(): RippleDrawable {
315
+ val mask = GradientDrawable().apply {
316
+ shape = GradientDrawable.OVAL
317
+ setColor(Color.WHITE)
318
+ }
319
+ val color = android.content.res.ColorStateList.valueOf(
320
+ Color.parseColor("#33FFFFFF")
255
321
  )
256
- return ContextCompat.getDrawable(this, tv.resourceId)
322
+ return RippleDrawable(color, null, mask)
257
323
  }
258
324
 
259
325
  private fun onAnswer() {
@@ -272,14 +338,10 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
272
338
  finishCallActivity()
273
339
  }
274
340
 
275
- private fun dp(v: Int): Int = TypedValue.applyDimension(
276
- TypedValue.COMPLEX_UNIT_DIP, v.toFloat(),
277
- resources.displayMetrics
278
- ).toInt()
279
-
280
341
  private fun setupLockScreenBypass(isSamsung: Boolean) {
281
342
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
282
- setShowWhenLocked(true); setTurnScreenOn(true)
343
+ setShowWhenLocked(true)
344
+ setTurnScreenOn(true)
283
345
  } else {
284
346
  window.addFlags(
285
347
  WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
@@ -294,19 +356,16 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
294
356
  WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
295
357
  WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
296
358
  )
297
- val km = getSystemService(Context.KEYGUARD_SERVICE)
298
- as KeyguardManager
299
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
300
- km.requestDismissKeyguard(this,
359
+ (getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager)
360
+ .requestDismissKeyguard(this,
301
361
  object : KeyguardManager.KeyguardDismissCallback() {
302
362
  override fun onDismissSucceeded() {
303
363
  Log.d(TAG, "Samsung keyguard dismissed")
304
364
  }
305
365
  override fun onDismissError() {
306
- Log.w(TAG, "Samsung keyguard dismiss error")
366
+ Log.w(TAG, "Keyguard dismiss error")
307
367
  }
308
368
  })
309
- }
310
369
  }
311
370
  }
312
371
 
@@ -328,7 +387,7 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
328
387
  override fun onDestroy() {
329
388
  super.onDestroy()
330
389
  CallEngine.unregisterCallEndListener(this)
331
- handler.removeCallbacks(timeoutRunnable)
390
+ uiHandler.removeCallbacks(timeoutRunnable)
332
391
  if (finishReason != FinishReason.ANSWER) {
333
392
  CallEngine.stopRingtone()
334
393
  CallEngine.cancelIncomingCallUI()
@@ -344,6 +403,12 @@ class CallActivity : Activity(), CallEngine.CallEndListener {
344
403
  }
345
404
  }
346
405
 
406
+ private fun dp(v: Int): Int = TypedValue.applyDimension(
407
+ TypedValue.COMPLEX_UNIT_DIP,
408
+ v.toFloat(),
409
+ resources.displayMetrics
410
+ ).toInt()
411
+
347
412
  companion object {
348
413
  private const val TAG = "CallActivity"
349
414
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qusaieilouti99/call-manager",
3
- "version": "0.1.98",
3
+ "version": "0.1.100",
4
4
  "description": "Call manager",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",