Skip to content

Commit b527de5

Browse files
Merge pull request #35 from ConnectyCube/feature/add_ios_support
Add iOS support
2 parents 8c34394 + ef79fcd commit b527de5

28 files changed

+2708
-426
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
## 2.0.0
2+
Completely reworked version. Reworked the way of interaction between the flutter app and native platforms.
3+
4+
Since this version you don't need any third-party plugins for working with push notifications anymore, cause all required functionality has already been integrated into the plugin.
5+
6+
**New**
7+
- Added iOS support
8+
- Added getting the subscription tokens (VoIP for the iOS and FCM for the Android)
9+
- Added customisation for ringtone, app icon, color accent (for Android)
10+
11+
**Fixes and improvements**
12+
- reworked callbacks `onCallRejectedWhenTerminated` and `onCallAcceptedWhenTerminated` now they will be fired even if the app is terminated or in the background
13+
- migrated to `EventChannel` for sending events from native platforms to the Flutter app
14+
115
## 0.1.0-dev.2
216

317
* Improved compatibility with projects which support Web platform.

README.md

Lines changed: 193 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,198 @@
1-
# connectycube_flutter_call_kit
1+
# ConnectyCube Flutter Call Kit plugin
22

3-
A Flutter plugin for displaying call screen when the app in the background or terminated.
3+
A Flutter plugin for displaying call screen when the app is in the background or terminated.
4+
It provides a complex solution for implementation the background calls feature in your app including getting token and displaying the Incoming call screen.
45

5-
At the current moment implemented support only for the Android platform
6+
## Supported platoforms
67

7-
<kbd><img alt="Flutter P2P Calls code sample, incoming call in background Android" src="https://developers.connectycube.com/docs/_images/code_samples/flutter/background_call_android.png" height="440" /></kbd>
8-
<kbd><img alt="Flutter P2P Calls code sample, incoming call locked Android" src="https://developers.connectycube.com/docs/_images/code_samples/flutter/background_call_android_locked.png" height="440" /></kbd>
8+
- Android
9+
- iOS
910

10-
The plugin shows Incoming call notification in the case when device unlocked or Incoming call screen in the case when the device locked.
11+
## Features
1112

12-
You can check how this plugin works in our [P2P Calls code sample](https://github.com/ConnectyCube/connectycube-flutter-samples/tree/master/p2p_call_sample).
13+
- access device token (FCM for Android and VoIP for iOS)
14+
- notifying the app about token refreshing via callback
15+
- displaying the Incoming call screen when push notification was delivered on the device
16+
- notifying the app about user action performed on the Incoming call screen (accept, reject, mute (for iOS))
17+
- providing the methods for manual managing of the Incoming screen including the manual showing the Incoming call screen
18+
- getting the data about the current call during the call session
19+
- some customizations according to your app needs (ringtone, icon, accent color(for Android))
20+
21+
22+
<kbd><img alt="Flutter P2P Calls code sample, incoming call in background Android" src="https://developers.connectycube.com/docs/_images/code_samples/flutter/background_call_android.png" height="440" /></kbd> <kbd><img alt="Flutter P2P Calls code sample, incoming call locked Android" src="https://developers.connectycube.com/docs/_images/code_samples/flutter/background_call_android_locked.png" height="440" /></kbd> <kbd><img alt="Flutter P2P Calls code sample, incoming call in background iOS" src="https://developers.connectycube.com/docs/_images/code_samples/flutter/background_call_ios.PNG" height="440" /></kbd>
23+
<kbd><img alt="Flutter P2P Calls code sample, incoming call locked iOS" src="https://developers.connectycube.com/docs/_images/code_samples/flutter/background_call_ios_locked.PNG" height="440" /></kbd>
24+
25+
## Configure your project
26+
27+
This plugin doesn't require complicated configs, just [connect it](https://pub.dev/packages/connectycube_flutter_call_kit/install) as usual flutter plugin to your app and do the next simple actions:
28+
29+
### Prepare Android
30+
31+
- add the Google services config file `google-services.json` by path `your_app/android/app/`
32+
- add next string at the end of your **build.gradle** file by path `your_app/android/app/build.gradle`:
33+
```groovy
34+
apply plugin: 'com.google.gms.google-services'
35+
```
36+
37+
### Prepare iOS
38+
39+
- add next strings to your **Info.plist** file by path `your_app/ios/Runner/Info.plist`:
40+
```
41+
<key>UIBackgroundModes</key>
42+
<array>
43+
<string>remote-notification</string>
44+
<string>voip</string>
45+
</array>
46+
```
47+
48+
## API and callbacks
49+
### Get token
50+
The plugin returns the VoIP token for the iOS platform and the FCM token for the Android platform.
51+
52+
Get token from the system:
53+
54+
```dart
55+
ConnectycubeFlutterCallKit.getToken().then((token) {
56+
// use received token for subscription on push notifications on your server
57+
});
58+
```
59+
60+
Listen to the refresh token event:
61+
```dart
62+
ConnectycubeFlutterCallKit.onTokenReceived = (token) {
63+
// use refreshed token for resubscription on your server
64+
};
65+
```
66+
### Customize the plugin
67+
We added a helpful method for customization the plugin according to your needs. At this moment you can customize the ringtone, icon, and color accent. Use the next method for it:
68+
69+
```dart
70+
ConnectycubeFlutterCallKit.instance.updateConfig(ringtone: 'custom_ringtone', icon: 'app_icon', color: '#07711e');
71+
```
72+
73+
### Show Incoming call notification
74+
75+
```dart
76+
P2PCallSession incomingCall; // the call received somewhere
77+
78+
CallEvent callEvent = CallEvent(
79+
sessionId: incomingCall.sessionId,
80+
callType: incomingCall.callType,
81+
callerId: incomingCall.callerId,
82+
callerName: 'Caller Name',
83+
opponentsIds: incomingCall.opponentsIds,
84+
userInfo: {'customParameter1': 'value1'});
85+
ConnectycubeFlutterCallKit.showCallNotification(callEvent);
86+
```
87+
88+
### Listen to the user action from the Incoming call screen:
89+
90+
#### Listen in the foreground
91+
92+
Add the listeners during initialization of the plugin:
93+
94+
```dart
95+
ConnectycubeFlutterCallKit.instance.init(
96+
onCallAccepted: _onCallAccepted,
97+
onCallRejected: _onCallRejected,
98+
);
99+
100+
Future<void> _onCallAccepted(CallEvent callEvent) async {
101+
// the call was accepted
102+
}
103+
104+
Future<void> _onCallRejected(CallEvent callEvent) async {
105+
// the call was rejected
106+
}
107+
```
108+
109+
#### Listen in the background or terminated state (Android only):
110+
111+
```dart
112+
ConnectycubeFlutterCallKit.onCallRejectedWhenTerminated = onCallRejectedWhenTerminated;
113+
ConnectycubeFlutterCallKit.onCallAcceptedWhenTerminated = onCallAcceptedWhenTerminated;
114+
```
115+
116+
!> Attention: the functions `onCallRejectedWhenTerminated` and `onCallAcceptedWhenTerminated` must be a top-level function and cannot be anonymous
117+
118+
### Get the call state
119+
120+
```dart
121+
var callState = await ConnectycubeFlutterCallKit.getCallState(sessionId: sessionId);
122+
```
123+
124+
### Get the call data
125+
```dart
126+
ConnectycubeFlutterCallKit.getCallData(sessionId: sessionId).then((callData) {
127+
128+
});
129+
```
130+
131+
### Get the id of the latest call
132+
133+
It is helpful for some cases to know the id of the last received call. You can get it via:
134+
135+
```dart
136+
var sessionId = await ConnectycubeFlutterCallKit.getLastCallId();
137+
```
138+
Then you can get the state of this call using `getCallState`.
139+
140+
### Notify the plugin about processing the call on the Flutter app side
141+
142+
For dismissing the Incoming call screen (or the Call Kit for iOS) you should notify the plugin about these events.
143+
Use next functions for it:
144+
145+
```dart
146+
ConnectycubeFlutterCallKit.reportCallAccepted(sessionId: uuid, callType: callType);
147+
ConnectycubeFlutterCallKit.reportCallEnded(sessionId: uuid);
148+
```
149+
150+
### Clear call data
151+
After finishing the call you can clear all data on the plugin side related to this call, call the next code for it
152+
153+
```dart
154+
await ConnectycubeFlutterCallKit.clearCallData(sessionId: sessionId);
155+
```
156+
157+
### Manage the app visibility on the lock screen (Android only)
158+
159+
In case you need to show your app after accepting the call from the lock screen you can do it using the method
160+
```dart
161+
ConnectycubeFlutterCallKit.setOnLockScreenVisibility(isVisible: true);
162+
```
163+
164+
After finishing that call you should hide your app under the lock screen, do it via
165+
```dart
166+
ConnectycubeFlutterCallKit.setOnLockScreenVisibility(isVisible: false);
167+
```
168+
169+
## Show Incoming call screen by push notification
170+
In case you want to display the Incoming call screen automatically by push notification you can do it easily. For it, the caller should send the push notification to all call members. This push notification should contain some required parameters. If you use the [Connectycube Flutter SDK](https://pub.dev/packages/connectycube_sdk), you can do it using the next code:
171+
172+
```dart
173+
CreateEventParams params = CreateEventParams();
174+
params.parameters = {
175+
'message': "Incoming ${currentCall.callType == CallType.VIDEO_CALL ? "Video" : "Audio"} call",
176+
'call_type': currentCall.callType,
177+
'session_id': currentCall.sessionId,
178+
'caller_id': currentCall.callerId,
179+
'caller_name': callerName,
180+
'call_opponents': currentCall.opponentsIds.join(','),
181+
'signal_type': 'startCall',
182+
'ios_voip': 1,
183+
};
184+
185+
params.notificationType = NotificationType.PUSH;
186+
params.environment = CubeEnvironment.DEVELOPMENT; // not important
187+
params.usersIds = currentCall.opponentsIds.toList();
188+
189+
createEvent(params.getEventForRequest()).then((cubeEvent) {
190+
// event was created
191+
}).catchError((error) {
192+
// something went wrong during event creation
193+
});
194+
```
195+
196+
For hiding the Incoming call screen via push notification use a similar request but with a different `signal_type`, it can be `'endCall'` or `'rejectCall'`.
197+
198+
You can check how this plugin works in our [P2P Calls code sample](https://github.com/ConnectyCube/connectycube-flutter-samples/tree/master/p2p_call_sample).

android/build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ buildscript {
1111
dependencies {
1212
classpath 'com.android.tools.build:gradle:3.5.0'
1313
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14+
classpath 'com.google.gms:google-services:4.3.10'
1415
}
1516
}
1617

@@ -41,4 +42,7 @@ android {
4142
dependencies {
4243
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
4344
implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0'
45+
46+
implementation platform('com.google.firebase:firebase-bom:29.0.3')
47+
implementation 'com.google.firebase:firebase-messaging-ktx'
4448
}

android/src/main/AndroidManifest.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,17 @@
1313
android:taskAffinity="com.connectycube.flutter.connectycube_flutter_call_kit.INCOMING_CALL_AFFINITY"
1414
android:theme="@android:style/Theme.Holo.NoActionBar"
1515
android:turnScreenOn="true" />
16+
<service
17+
android:name=".background_isolates.ConnectycubeFlutterBgPerformingService"
18+
android:permission="android.permission.BIND_JOB_SERVICE"
19+
android:exported="false"/>
20+
<service
21+
android:name=".ConnectycubeFCMService"
22+
android:exported="false">
23+
<intent-filter>
24+
<action android:name="com.google.firebase.MESSAGING_EVENT" />
25+
</intent-filter>
26+
</service>
27+
1628
</application>
1729
</manifest>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.connectycube.flutter.connectycube_flutter_call_kit
2+
3+
import android.content.Context
4+
import android.content.Intent
5+
import androidx.localbroadcastmanager.content.LocalBroadcastManager
6+
import com.connectycube.flutter.connectycube_flutter_call_kit.utils.ContextHolder
7+
import com.google.firebase.messaging.FirebaseMessagingService
8+
import com.google.firebase.messaging.RemoteMessage
9+
10+
class ConnectycubeFCMService : FirebaseMessagingService() {
11+
12+
override fun onMessageReceived(remoteMessage: RemoteMessage) {
13+
super.onMessageReceived(remoteMessage)
14+
ContextHolder.applicationContext = applicationContext
15+
16+
val data = remoteMessage.data
17+
if (data.containsKey("signal_type")) {
18+
when (data["signal_type"]) {
19+
"startCall" -> {
20+
processInviteCallEvent(applicationContext, data)
21+
}
22+
23+
"endCall" -> {
24+
processEndCallEvent(applicationContext, data)
25+
}
26+
27+
"rejectCall" -> {
28+
processEndCallEvent(applicationContext, data)
29+
}
30+
}
31+
32+
}
33+
}
34+
35+
private fun processEndCallEvent(applicationContext: Context, data: Map<String, String>) {
36+
val callId = data["session_id"] ?: return
37+
38+
39+
processCallEnded(applicationContext, callId)
40+
}
41+
42+
private fun processInviteCallEvent(applicationContext: Context, data: Map<String, String>) {
43+
val callId = data["session_id"]
44+
45+
if (callId == null || CALL_STATE_UNKNOWN != getCallState(
46+
applicationContext,
47+
callId
48+
)
49+
) {
50+
return
51+
}
52+
53+
val callType = data["call_type"]?.toInt()
54+
val callInitiatorId = data["caller_id"]?.toInt()
55+
val callInitiatorName = data["caller_name"]
56+
val callOpponentsString = data["call_opponents"]
57+
var callOpponents = ArrayList<Int>()
58+
if (callOpponentsString != null) {
59+
callOpponents = ArrayList(callOpponentsString.split(',').map { it.toInt() })
60+
}
61+
val userInfo = data["user_info"] ?: "{}"
62+
63+
if (callType == null || callInitiatorId == null || callInitiatorName == null || callOpponents.isEmpty()) {
64+
return
65+
}
66+
67+
showCallNotification(
68+
applicationContext,
69+
callId,
70+
callType,
71+
callInitiatorId,
72+
callInitiatorName,
73+
callOpponents,
74+
userInfo
75+
)
76+
77+
saveCallState(applicationContext, callId, CALL_STATE_PENDING)
78+
saveCallData(applicationContext, callId, data)
79+
saveCallId(applicationContext, callId)
80+
}
81+
82+
override fun onNewToken(token: String) {
83+
super.onNewToken(token)
84+
85+
LocalBroadcastManager.getInstance(applicationContext)
86+
.sendBroadcast(Intent(ACTION_TOKEN_REFRESHED).putExtra(EXTRA_PUSH_TOKEN, token))
87+
}
88+
}

0 commit comments

Comments
 (0)