From ab5df9fd282e47dd2ee4f5c183a4654a060573ee Mon Sep 17 00:00:00 2001 From: Andrew nuark G Date: Thu, 9 Apr 2026 12:22:18 +0700 Subject: [PATCH] feat: add custom notification title and message for VoIP calls (also stopCallService method) --- .../LiblinphoneFlutterPlugin.kt | 21 ++++++++-- .../LinphoneVoipService.kt | 16 +++++--- .../liblinphone_flutter_dialer_sip_24px.xml | 10 +++++ ios/Classes/LiblinphoneFlutterPlugin.swift | 3 ++ lib/liblinphone_flutter.dart | 25 +++++++++-- lib/liblinphone_flutter_method_channel.dart | 41 ++++++++++++++----- ...iblinphone_flutter_platform_interface.dart | 16 +++++++- 7 files changed, 106 insertions(+), 26 deletions(-) create mode 100644 android/src/main/res/drawable/liblinphone_flutter_dialer_sip_24px.xml diff --git a/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LiblinphoneFlutterPlugin.kt b/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LiblinphoneFlutterPlugin.kt index 4b8e5ea..89962c3 100644 --- a/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LiblinphoneFlutterPlugin.kt +++ b/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LiblinphoneFlutterPlugin.kt @@ -139,8 +139,11 @@ class LiblinphoneFlutterPlugin : FlutterPlugin, ActivityAware, MethodCallHandler try { val callTo = call.argument("callTo")!! val isVideoEnabled = call.argument("isVideoEnabled")!! + val notificationTitle = call.argument("notificationTitle")!! + val notificationMessage = call.argument("notificationMessage")!! + linphoneBridge.makeCall(callTo, isVideoEnabled) - startCallService() + startCallService(notificationTitle, notificationMessage) result.success(true) } catch (e: Exception) { Log.e(TAG, "makeCall: ${e.message}") @@ -149,14 +152,17 @@ class LiblinphoneFlutterPlugin : FlutterPlugin, ActivityAware, MethodCallHandler } "answerCall" -> { + val notificationTitle = call.argument("notificationTitle")!! + val notificationMessage = call.argument("notificationMessage")!! + val res = linphoneBridge.answerCall() - if (res) startCallService() + if (res) startCallService(notificationTitle, notificationMessage) result.success(res) } "hangupCall" -> { val res = linphoneBridge.hangupCall() - if (res) startCallService() + if (res) stopCallService() result.success(res) } @@ -201,6 +207,11 @@ class LiblinphoneFlutterPlugin : FlutterPlugin, ActivityAware, MethodCallHandler } } + "stopCallService" -> { + stopCallService() + result.success(true) + } + else -> { result.notImplemented() } @@ -243,9 +254,11 @@ class LiblinphoneFlutterPlugin : FlutterPlugin, ActivityAware, MethodCallHandler return null } - private fun startCallService() { + private fun startCallService(title: String, message: String) { try { val intent = Intent(activity.applicationContext, LinphoneVoipService::class.java) + intent.putExtra("lvs_key_name", title) + intent.putExtra("lvs_key_message", message) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { activity.startForegroundService(intent) } else { diff --git a/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LinphoneVoipService.kt b/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LinphoneVoipService.kt index edc29e5..55ef1aa 100644 --- a/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LinphoneVoipService.kt +++ b/android/src/main/kotlin/xyz/nuark/liblinphone_flutter/LinphoneVoipService.kt @@ -25,7 +25,11 @@ class LinphoneVoipService : Service() { } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - val notification = createNotification() + val bundle: Bundle? = intent?.extras + val notificationTitle = bundle?.getString("lvs_key_name") ?: "Voice Call" + val notificationMessage = bundle?.getString("lvs_key_message") ?: "Call in progress" + + val notification = createNotification(notificationTitle, notificationMessage) // Android 14+ requires explicit service type in startForeground if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { @@ -51,7 +55,7 @@ class LinphoneVoipService : Service() { val channel = NotificationChannel( channelId, "Active VoIP Call", - NotificationManager.IMPORTANCE_LOW + NotificationManager.IMPORTANCE_HIGH ).apply { setShowBadge(false) setSound(null, null) @@ -61,11 +65,11 @@ class LinphoneVoipService : Service() { } } - private fun createNotification(): Notification { + private fun createNotification(title: String, message: String): Notification { return NotificationCompat.Builder(this, channelId) - .setContentTitle("Voice Call") - .setContentText("Call in progress") - .setSmallIcon(android.R.drawable.ic_menu_call) + .setContentTitle(title) + .setContentText(message) + .setSmallIcon(R.drawable.liblinphone_flutter_dialer_sip_24px) .setOngoing(true) .setCategory(NotificationCompat.CATEGORY_CALL) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) diff --git a/android/src/main/res/drawable/liblinphone_flutter_dialer_sip_24px.xml b/android/src/main/res/drawable/liblinphone_flutter_dialer_sip_24px.xml new file mode 100644 index 0000000..c28f499 --- /dev/null +++ b/android/src/main/res/drawable/liblinphone_flutter_dialer_sip_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/ios/Classes/LiblinphoneFlutterPlugin.swift b/ios/Classes/LiblinphoneFlutterPlugin.swift index 401756b..3de8996 100644 --- a/ios/Classes/LiblinphoneFlutterPlugin.swift +++ b/ios/Classes/LiblinphoneFlutterPlugin.swift @@ -224,6 +224,9 @@ public class LiblinphoneFlutterPlugin: NSObject, FlutterPlugin { let success = linphoneBridge.sendDtmf(tone: tone) result(success) + case "stopCallService": + result(true) + default: result(FlutterMethodNotImplemented) } diff --git a/lib/liblinphone_flutter.dart b/lib/liblinphone_flutter.dart index da21d2d..22d7366 100644 --- a/lib/liblinphone_flutter.dart +++ b/lib/liblinphone_flutter.dart @@ -46,11 +46,25 @@ class LiblinphoneFlutter { Future unregister() async => LiblinphoneFlutterPlatform.instance.unregister(); - Future makeCall(String callTo, bool isVideoEnabled) async => - LiblinphoneFlutterPlatform.instance.makeCall(callTo, isVideoEnabled); + Future makeCall( + String callTo, + bool isVideoEnabled, + String notificationTitle, + String notificationMessage, + ) async => LiblinphoneFlutterPlatform.instance.makeCall( + callTo, + isVideoEnabled, + notificationTitle, + notificationMessage, + ); - Future answerCall() async => - LiblinphoneFlutterPlatform.instance.answerCall(); + Future answerCall( + String notificationTitle, + String notificationMessage, + ) async => LiblinphoneFlutterPlatform.instance.answerCall( + notificationTitle, + notificationMessage, + ); Future hangupCall() async => LiblinphoneFlutterPlatform.instance.hangupCall(); @@ -73,4 +87,7 @@ class LiblinphoneFlutter { Future sendDtmf(String tone) async => LiblinphoneFlutterPlatform.instance.sendDtmf(tone); + + Future stopCallService(String tone) async => + LiblinphoneFlutterPlatform.instance.stopCallService(); } diff --git a/lib/liblinphone_flutter_method_channel.dart b/lib/liblinphone_flutter_method_channel.dart index d6d4044..c9173ca 100644 --- a/lib/liblinphone_flutter_method_channel.dart +++ b/lib/liblinphone_flutter_method_channel.dart @@ -42,16 +42,31 @@ class MethodChannelLiblinphoneFlutter extends LiblinphoneFlutterPlatform { } @override - Future makeCall(String callTo, bool isVideoEnabled) async { - return (await methodChannel.invokeMethod( - 'makeCall', - {'callTo': callTo, 'isVideoEnabled': isVideoEnabled}, - ))!; + Future makeCall( + String callTo, + bool isVideoEnabled, + String notificationTitle, + String notificationMessage, + ) async { + return (await methodChannel + .invokeMethod('makeCall', { + 'callTo': callTo, + 'isVideoEnabled': isVideoEnabled, + 'notificationTitle': notificationTitle, + 'notificationMessage': notificationMessage, + }))!; } @override - Future answerCall() async { - return (await methodChannel.invokeMethod('answerCall'))!; + Future answerCall( + String notificationTitle, + String notificationMessage, + ) async { + return (await methodChannel + .invokeMethod('answerCall', { + 'notificationTitle': notificationTitle, + 'notificationMessage': notificationMessage, + }))!; } @override @@ -92,8 +107,14 @@ class MethodChannelLiblinphoneFlutter extends LiblinphoneFlutterPlatform { @override Future sendDtmf(String tone) async { - return (await methodChannel.invokeMethod('sendDtmf', { - 'tone': tone, - }))!; + return (await methodChannel.invokeMethod( + 'sendDtmf', + {'tone': tone}, + ))!; + } + + @override + Future stopCallService() async { + return (await methodChannel.invokeMethod('stopCallService'))!; } } diff --git a/lib/liblinphone_flutter_platform_interface.dart b/lib/liblinphone_flutter_platform_interface.dart index 9c41f91..06e991b 100644 --- a/lib/liblinphone_flutter_platform_interface.dart +++ b/lib/liblinphone_flutter_platform_interface.dart @@ -46,11 +46,19 @@ abstract class LiblinphoneFlutterPlatform extends PlatformInterface { throw UnimplementedError('unregister() has not been implemented.'); } - Future makeCall(String callTo, bool isVideoEnabled) { + Future makeCall( + String callTo, + bool isVideoEnabled, + String notificationTitle, + String notificationMessage, + ) { throw UnimplementedError('makeCall() has not been implemented.'); } - Future answerCall() { + Future answerCall( + String notificationTitle, + String notificationMessage, + ) { throw UnimplementedError('answerCall() has not been implemented.'); } @@ -85,4 +93,8 @@ abstract class LiblinphoneFlutterPlatform extends PlatformInterface { Future sendDtmf(String tone) { throw UnimplementedError('sendDtmf() has not been implemented.'); } + + Future stopCallService() async { + throw UnimplementedError('stopCallService() has not been implemented.'); + } }