Skip to content

Commit 0377bfd

Browse files
mikaelwillsMikael Wills
andauthored
Extracting UI responsibility, named parameters, android dismissal fix (#189)
Co-authored-by: Mikael Wills <[email protected]>
1 parent c5ee757 commit 0377bfd

File tree

6 files changed

+187
-170
lines changed

6 files changed

+187
-170
lines changed

README.md

Lines changed: 64 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
[![Financial Contributors on Open Collective](https://opencollective.com/flutter-webrtc/all/badge.svg?label=financial+contributors)](https://opencollective.com/flutter-webrtc) [![pub package](https://img.shields.io/pub/v/callkeep.svg)](https://pub.dartlang.org/packages/callkeep) [![slack](https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=brightgreen)](https://join.slack.com/t/flutterwebrtc/shared_invite/zt-q83o7y1s-FExGLWEvtkPKM8ku_F8cEQ)
44

5-
* iOS CallKit and Android ConnectionService for Flutter
6-
* Support FCM and PushKit
5+
- iOS CallKit and Android ConnectionService for Flutter
6+
- Support FCM and PushKit
77

88
> Keep in mind Callkit is banned in China, so if you want your app in the chinese AppStore consider include a basic alternative for notifying calls (ex. FCM notifications with sound).
99
@@ -36,7 +36,7 @@ final callSetup = <String, dynamic>{
3636
'channelName': 'Foreground service for my app',
3737
'notificationTitle': 'My app is running on background',
3838
'notificationIcon': 'mipmap/ic_notification_launcher',
39-
},
39+
},
4040
},
4141
};
4242
@@ -53,7 +53,7 @@ Callkeep offers some events to handle native actions during a call.
5353

5454
These events are quite crucial because they act as an intermediate between the native calling UI and your call P-C-M.
5555

56-
What does it mean?
56+
What does it mean?
5757

5858
Assuming your application already implements some calling system (RTC, Voip, or whatever) with its own calling UI, you are using some basic controls:
5959

@@ -72,32 +72,37 @@ Assuming your application already implements some calling system (RTC, Voip, or
7272
Then you handle the action:
7373

7474
```dart
75-
Function(CallKeepPerformAnswerCallAction) answerAction = (event) async {
75+
Future<void> answerCall(CallKeepPerformAnswerCallAction event) async {
7676
print('CallKeepPerformAnswerCallAction ${event.callUUID}');
7777
// notify to your call P-C-M the answer action
7878
};
7979
80-
Function(CallKeepPerformEndCallAction) endAction = (event) async {
80+
Future<void> endCall(CallKeepPerformEndCallAction event) async {
8181
print('CallKeepPerformEndCallAction ${event.callUUID}');
8282
// notify to your call P-C-M the end action
8383
};
8484
85-
Function(CallKeepDidPerformSetMutedCallAction) setMuted = (event) async {
85+
Future<void> didPerformSetMutedCallAction(CallKeepDidPerformSetMutedCallAction event) async {
8686
print('CallKeepDidPerformSetMutedCallAction ${event.callUUID}');
8787
// notify to your call P-C-M the muted switch action
8888
};
8989
90-
Function(CallKeepDidToggleHoldAction) onHold = (event) async {
90+
Future<void> didToggleHoldCallAction(CallKeepDidToggleHoldAction event) async {
9191
print('CallKeepDidToggleHoldAction ${event.callUUID}');
9292
// notify to your call P-C-M the hold switch action
9393
};
9494
```
9595

9696
```dart
97-
callKeep.on(CallKeepDidToggleHoldAction(), onHold);
98-
callKeep.on(CallKeepPerformAnswerCallAction(), answerAction);
99-
callKeep.on(CallKeepPerformEndCallAction(), endAction);
100-
callKeep.on(CallKeepDidPerformSetMutedCallAction(), setMuted);
97+
98+
@override
99+
void initState() {
100+
super.initState();
101+
callKeep.on<CallKeepDidDisplayIncomingCall>(didDisplayIncomingCall);
102+
callKeep.on<CallKeepPerformAnswerCallAction>(answerCall);
103+
callKeep.on<CallKeepPerformEndCallAction>(endCall);
104+
callKeep.on<CallKeepDidToggleHoldAction>(didToggleHoldCallAction);
105+
}
101106
```
102107

103108
## Display incoming calls in foreground, background or terminate state
@@ -120,7 +125,7 @@ Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
120125
print(e);
121126
}
122127
}
123-
128+
124129
// then process your remote message looking for some call uuid
125130
// and display any incoming call
126131
}
@@ -134,11 +139,11 @@ A payload data example:
134139

135140
```json
136141
{
137-
"uuid": "xxxxx-xxxxx-xxxxx-xxxxx",
138-
"caller_id": "+0123456789",
139-
"caller_name": "Draco",
140-
"caller_id_type": "number",
141-
"has_video": "false"
142+
"uuid": "xxxxx-xxxxx-xxxxx-xxxxx",
143+
"caller_id": "+0123456789",
144+
"caller_name": "Draco",
145+
"caller_id_type": "number",
146+
"has_video": "false"
142147
}
143148
```
144149

@@ -172,11 +177,11 @@ Future<void> showIncomingCall(
172177
var callerName = remoteMessage.payload()["caller_name"] as String;
173178
var uuid = remoteMessage.payload()["uuid"] as String;
174179
var hasVideo = remoteMessage.payload()["has_video"] == "true";
175-
176-
callKeep.on(CallKeepDidToggleHoldAction(), onHold);
177-
callKeep.on(CallKeepPerformAnswerCallAction(), answerAction);
178-
callKeep.on(CallKeepPerformEndCallAction(), endAction);
179-
callKeep.on(CallKeepDidPerformSetMutedCallAction(), setMuted);
180+
181+
callKeep.on<CallKeepDidToggleHoldAction>(onHold);
182+
callKeep.on<CallKeepPerformAnswerCallAction>(answerAction);
183+
callKeep.on<CallKeepPerformEndCallAction>(endAction);
184+
callKeep.on<CallKeepDidPerformSetMutedCallAction>(setMuted);
180185
181186
print('backgroundMessage: displayIncomingCall ($uuid)');
182187
@@ -207,6 +212,40 @@ Future<void> closeIncomingCall(
207212
}
208213
```
209214

215+
Pass in your own dialog UI for permissions alerts
216+
217+
````dart
218+
showAlertDialog: () async {
219+
final BuildContext context = navigatorKey.currentContext!;
220+
221+
return await showDialog<bool>(
222+
context: context,
223+
barrierDismissible: false,
224+
builder: (BuildContext context) {
225+
return AlertDialog(
226+
title: const Text('Permissions Required'),
227+
content: const Text(
228+
'This application needs to access your phone accounts'),
229+
actions: <Widget>[
230+
TextButton(
231+
child: const Text('Cancel'),
232+
onPressed: () => Navigator.of(context).pop(false),
233+
),
234+
TextButton(
235+
child: const Text('OK'),
236+
onPressed: () => Navigator.of(context).pop(true),
237+
),
238+
],
239+
);
240+
},
241+
) ??
242+
false;
243+
},
244+
```
245+
246+
247+
248+
210249
### FAQ
211250
212251
> I don't receive the incoming call
@@ -216,9 +255,9 @@ Remember FCM push messages not always works due to data-only messages are classi
216255
217256
> How can I manage the call if the app is terminated and the device is locked?
218257
219-
Even in this scenario, the `backToForeground()` method will open the app and your call P-C-M will be able to work.
220-
258+
Even in this scenario, the `backToForeground()` method will open the app and your call P-C-M will be able to work.
221259
222260
## push test tool
223261
224262
Please refer to the [Push Toolkit](/tools/) to test callkeep offline push.
263+
````

android/src/main/java/io/wazo/callkeep/VoiceConnection.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ public void onAnswer(int videoState) {
154154

155155
private void onAnswered() {
156156
initCall();
157+
setCurrent();
157158
sendCallRequestToActivity(ACTION_ANSWER_CALL, connectionData);
158159
}
159160

example/lib/main.dart

Lines changed: 72 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -57,34 +57,32 @@ Future<dynamic> myBackgroundMessageHandler(RemoteMessage message) {
5757
});
5858
if (!_callKeepInited) {
5959
_callKeep.setup(
60-
null,
61-
<String, dynamic>{
62-
'ios': {
63-
'appName': 'CallKeepDemo',
64-
},
65-
'android': {
66-
'alertTitle': 'Permissions required',
67-
'alertDescription':
68-
'This application needs to access your phone accounts',
69-
'cancelButton': 'Cancel',
70-
'okButton': 'ok',
71-
'foregroundService': {
72-
'channelId': 'com.company.my',
73-
'channelName': 'Foreground service for my app',
74-
'notificationTitle': 'My app is running on background',
75-
'notificationIcon':
76-
'Path to the resource icon of the notification',
77-
},
60+
showAlertDialog: null,
61+
options: <String, dynamic>{
62+
'ios': {
63+
'appName': 'CallKeepDemo',
64+
},
65+
'android': {
66+
'additionalPermissions': [
67+
'android.permission.CALL_PHONE',
68+
'android.permission.READ_PHONE_NUMBERS'
69+
],
70+
'foregroundService': {
71+
'channelId': 'com.example.call-kit-test',
72+
'channelName': 'callKitTest',
73+
'notificationTitle': 'My app is running on background',
74+
'notificationIcon': 'Path to the resource icon of the notification',
7875
},
7976
},
80-
backgroundMode: true);
77+
},
78+
);
8179
_callKeepInited = true;
8280
}
8381

8482
logger.d('backgroundMessage: displayIncomingCall ($callerId)');
8583
_callKeep.displayIncomingCall(
86-
callUUID,
87-
callerId,
84+
uuid: callUUID,
85+
handle: callerId,
8886
callerName: callerName,
8987
hasVideo: hasVideo,
9088
);
@@ -220,7 +218,8 @@ class MyAppState extends State<HomePage> {
220218
logger
221219
.d('[didReceiveStartCallAction] $callUUID, number: ${callData.handle}');
222220

223-
_callKeep.startCall(callUUID, call.number, call.number);
221+
_callKeep.startCall(
222+
uuid: callUUID, handle: call.number, callerName: call.number);
224223

225224
Timer(const Duration(seconds: 1), () {
226225
logger.d('[setCurrentCallActive] $callUUID, number: ${callData.handle}');
@@ -263,14 +262,14 @@ class MyAppState extends State<HomePage> {
263262
}
264263

265264
Future<void> setOnHold(String callUUID, bool held) async {
266-
_callKeep.setOnHold(callUUID, held);
265+
_callKeep.setOnHold(uuid: callUUID, shouldHold: held);
267266
final String handle = calls[callUUID]?.number ?? "No Number";
268267
logger.d('[setOnHold: $held] $callUUID, number: $handle');
269268
setCallHeld(callUUID, held);
270269
}
271270

272271
Future<void> setMutedCall(String callUUID, bool muted) async {
273-
_callKeep.setMutedCall(callUUID, muted);
272+
_callKeep.setMutedCall(uuid: callUUID, shouldMute: muted);
274273
final String handle = calls[callUUID]?.number ?? "No Number";
275274
logger.d('[setMutedCall: $muted] $callUUID, number: $handle');
276275
setCallMuted(callUUID, muted);
@@ -280,9 +279,11 @@ class MyAppState extends State<HomePage> {
280279
final String number = calls[callUUID]?.number ?? "No Number";
281280
// Workaround because Android doesn't display well displayName, se we have to switch ...
282281
if (isIOS) {
283-
_callKeep.updateDisplay(callUUID, callerName: 'New Name', handle: number);
282+
_callKeep.updateDisplay(
283+
uuid: callUUID, callerName: 'New Name', handle: number);
284284
} else {
285-
_callKeep.updateDisplay(callUUID, callerName: number, handle: 'New Name');
285+
_callKeep.updateDisplay(
286+
uuid: callUUID, callerName: number, handle: 'New Name');
286287
}
287288

288289
logger.d('[updateDisplay: $number] $callUUID');
@@ -302,7 +303,7 @@ class MyAppState extends State<HomePage> {
302303
logger.d('Display incoming call now');
303304
final bool hasPhoneAccount = await _callKeep.hasPhoneAccount();
304305
if (!hasPhoneAccount) {
305-
await _callKeep.hasDefaultPhoneAccount(context, <String, dynamic>{
306+
await _callKeep.hasDefaultPhoneAccount(<String, dynamic>{
306307
'alertTitle': 'Permissions required',
307308
'alertDescription':
308309
'This application needs to access your phone accounts',
@@ -318,8 +319,8 @@ class MyAppState extends State<HomePage> {
318319
}
319320

320321
logger.d('[displayIncomingCall] $callUUID number: $number');
321-
_callKeep.displayIncomingCall(callUUID, number,
322-
handleType: 'number', hasVideo: false);
322+
_callKeep.displayIncomingCall(
323+
uuid: callUUID, handle: number, handleType: 'number', hasVideo: false);
323324
}
324325

325326
void didDisplayIncomingCall(CallKeepDidDisplayIncomingCall event) {
@@ -347,29 +348,51 @@ class MyAppState extends State<HomePage> {
347348
_callKeep.on<CallKeepDidPerformDTMFAction>(didPerformDTMFAction);
348349
_callKeep.on<CallKeepDidReceiveStartCallAction>(didReceiveStartCallAction);
349350
_callKeep.on<CallKeepDidToggleHoldAction>(didToggleHoldCallAction);
350-
_callKeep.on<CallKeepDidPerformSetMutedCallAction>(didPerformSetMutedCallAction);
351+
_callKeep
352+
.on<CallKeepDidPerformSetMutedCallAction>(didPerformSetMutedCallAction);
351353
_callKeep.on<CallKeepPerformEndCallAction>(endCall);
352354
_callKeep.on<CallKeepPushKitToken>(onPushKitToken);
353355

354-
_callKeep.setup(context, <String, dynamic>{
355-
'ios': {
356-
'appName': 'CallKeepDemo',
357-
},
358-
'android': {
359-
'alertTitle': 'Permissions required',
360-
'alertDescription':
361-
'This application needs to access your phone accounts',
362-
'cancelButton': 'Cancel',
363-
'okButton': 'ok',
364-
'foregroundService': {
365-
'channelId': 'com.company.my',
366-
'channelName': 'Foreground service for my app',
367-
'notificationId': 5005,
368-
'notificationTitle': 'My app is running on background',
369-
'notificationIcon': 'Path to the resource icon of the notification',
356+
_callKeep.setup(
357+
showAlertDialog: () => showDialog<bool>(
358+
context: context,
359+
barrierDismissible: false,
360+
builder: (BuildContext context) {
361+
return AlertDialog(
362+
title: const Text('Permissions Required'),
363+
content: const Text(
364+
'This application needs to access your phone accounts'),
365+
actions: <Widget>[
366+
TextButton(
367+
child: const Text('Cancel'),
368+
onPressed: () => Navigator.of(context).pop(false),
369+
),
370+
TextButton(
371+
child: const Text('OK'),
372+
onPressed: () => Navigator.of(context).pop(true),
373+
),
374+
],
375+
);
376+
},
377+
).then((value) => value ?? false),
378+
options: <String, dynamic>{
379+
'ios': {
380+
'appName': 'CallKeepDemo',
381+
},
382+
'android': {
383+
'additionalPermissions': [
384+
'android.permission.CALL_PHONE',
385+
'android.permission.READ_PHONE_NUMBERS'
386+
],
387+
'foregroundService': {
388+
'channelId': 'com.example.call-kit-test',
389+
'channelName': 'callKitTest',
390+
'notificationTitle': 'My app is running on background',
391+
'notificationIcon': 'Path to the resource icon of the notification',
392+
},
370393
},
371394
},
372-
});
395+
);
373396

374397
if (Platform.isIOS) iOSPermission();
375398

@@ -394,8 +417,8 @@ class MyAppState extends State<HomePage> {
394417
calls[callUUID] = Call(callerId);
395418
});
396419
_callKeep.displayIncomingCall(
397-
callUUID,
398-
callerId,
420+
uuid: callUUID,
421+
handle: callerId,
399422
callerName: callerName,
400423
hasVideo: hasVideo,
401424
);

ios/Classes/CallKeep.m

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,6 @@ - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPush
212212
- (NSString *)createUUID {
213213
CFUUIDRef uuidObject = CFUUIDCreate(kCFAllocatorDefault);
214214
NSString *uuidStr = (NSString *)CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuidObject));
215-
CFUUIDBytes bytes = CFUUIDGetUUIDBytes(uuidObject);
216215
CFRelease(uuidObject);
217216
return [uuidStr lowercaseString];
218217
}

0 commit comments

Comments
 (0)