Skip to content

Commit 9093c2f

Browse files
authored
feat: Added customization of incoming/outgoing call screen (#513)
* handling push params in iOS native call registration * full customization * changelog added
1 parent 112f9c0 commit 9093c2f

16 files changed

+331
-93
lines changed

development.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
- [x] Deeplinking
3333
- [ ] Tap to focus (flutter_webrtc) [Kanat]
3434
- [ ] Push notifications [Maciej]
35-
- - [ ] Customization of the notification
36-
- - [ ] Customizing call.kt ringing calls
35+
- - [x] Customization of the incoming/outgoing call screens
36+
- - [x] Customizing CallKit ringing calls
3737
- - [ ] Bug app crashes when notification button is tapped
3838
- [ ] Native packages refactor [Maciej]
3939
- - [ ] Create the native projects in stream_video_flutter

docusaurus/docs/Flutter/05-advanced/02-ringing.mdx

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ a push notification is received.
9393
- Replace `yourUserCredentialsGetMethod()` with your implementation to get logged in user credentials
9494
- For `androidPushProvider` use the provider name created in [Step 2](#Step-2---Upload-the-Firebase-Credentials-to-Stream)
9595
- For `iosPushProvider` use the provider name we will create later in [APN integration](#Integrating-APNs-for-iOS​)
96+
- Add app icon Asset in Xcode for displaying in CallKit screen dedicated button (named `IconMask` in the code below). [See details here](https://developer.apple.com/documentation/callkit/cxproviderconfiguration/2274376-icontemplateimagedata)
9697

9798
```dart
9899
// As this runs in a separate isolate, we need to setup the app again.

packages/stream_video_flutter/lib/src/call_screen/call_container.dart

+16
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@ typedef CallContentBuilder = Widget Function(
2525
CallState callState,
2626
);
2727

28+
/// Builder used to create a custom widget for participants avatars.
29+
typedef ParticipantsAvatarBuilder = Widget Function(
30+
BuildContext context,
31+
Call call,
32+
CallState callState,
33+
List<UserInfo> participants,
34+
);
35+
36+
/// Builder used to create a custom widget for participants display names.
37+
typedef ParticipantsDisplayNameBuilder = Widget Function(
38+
BuildContext context,
39+
Call call,
40+
CallState callState,
41+
List<UserInfo> participants,
42+
);
43+
2844
/// Represents different call content based on the call state.
2945
class StreamCallContainer extends StatefulWidget {
3046
/// Creates a new instance of [StreamCallContainer].

packages/stream_video_flutter/lib/src/call_screen/incoming_call/incoming_call_content.dart

+35-13
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class StreamIncomingCallContent extends StatefulWidget {
2323
this.singleParticipantTextStyle,
2424
this.multipleParticipantTextStyle,
2525
this.callingLabelTextStyle,
26+
this.participantsAvatarBuilder,
27+
this.participantsDisplayNameBuilder,
2628
});
2729

2830
/// Represents a call.
@@ -58,6 +60,12 @@ class StreamIncomingCallContent extends StatefulWidget {
5860
/// Text style for the calling label.
5961
final TextStyle? callingLabelTextStyle;
6062

63+
/// Builder used to create a custom widget for participants avatars.
64+
final ParticipantsAvatarBuilder? participantsAvatarBuilder;
65+
66+
/// Builder used to create a custom widget for participants display names.
67+
final ParticipantsDisplayNameBuilder? participantsDisplayNameBuilder;
68+
6169
@override
6270
State<StreamIncomingCallContent> createState() =>
6371
_StreamIncomingCallContentState();
@@ -93,19 +101,33 @@ class _StreamIncomingCallContentState extends State<StreamIncomingCallContent> {
93101
mainAxisAlignment: MainAxisAlignment.center,
94102
children: [
95103
const Spacer(),
96-
ParticipantAvatars(
97-
participants: users,
98-
singleParticipantAvatarTheme: singleParticipantAvatarTheme,
99-
multipleParticipantAvatarTheme: multipleParticipantAvatarTheme,
100-
),
101-
Padding(
102-
padding: const EdgeInsets.symmetric(horizontal: 64, vertical: 32),
103-
child: CallingParticipants(
104-
participants: users,
105-
singleParticipantTextStyle: singleParticipantTextStyle,
106-
multipleParticipantTextStyle: multipleParticipantTextStyle,
107-
),
108-
),
104+
widget.participantsAvatarBuilder?.call(
105+
context,
106+
widget.call,
107+
widget.callState,
108+
users,
109+
) ??
110+
ParticipantAvatars(
111+
participants: users,
112+
singleParticipantAvatarTheme: singleParticipantAvatarTheme,
113+
multipleParticipantAvatarTheme:
114+
multipleParticipantAvatarTheme,
115+
),
116+
widget.participantsDisplayNameBuilder?.call(
117+
context,
118+
widget.call,
119+
widget.callState,
120+
users,
121+
) ??
122+
Padding(
123+
padding:
124+
const EdgeInsets.symmetric(horizontal: 64, vertical: 32),
125+
child: CallingParticipants(
126+
participants: users,
127+
singleParticipantTextStyle: singleParticipantTextStyle,
128+
multipleParticipantTextStyle: multipleParticipantTextStyle,
129+
),
130+
),
109131
Text(
110132
// TODO hardcoded text
111133
'Incoming Call...',

packages/stream_video_flutter/lib/src/call_screen/outgoing_call/outgoing_call_content.dart

+37-13
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:ffi';
2+
13
import 'package:flutter/material.dart';
24

35
import '../../../stream_video_flutter.dart';
@@ -23,6 +25,8 @@ class StreamOutgoingCallContent extends StatefulWidget {
2325
this.singleParticipantTextStyle,
2426
this.multipleParticipantTextStyle,
2527
this.callingLabelTextStyle,
28+
this.participantsAvatarBuilder,
29+
this.participantsDisplayNameBuilder,
2630
});
2731

2832
/// Represents a call.
@@ -55,6 +59,12 @@ class StreamOutgoingCallContent extends StatefulWidget {
5559
/// Text style for the calling label.
5660
final TextStyle? callingLabelTextStyle;
5761

62+
/// Builder used to create a custom widget for participants avatars.
63+
final ParticipantsAvatarBuilder? participantsAvatarBuilder;
64+
65+
/// Builder used to create a custom widget for participants display names.
66+
final ParticipantsDisplayNameBuilder? participantsDisplayNameBuilder;
67+
5868
@override
5969
State<StreamOutgoingCallContent> createState() =>
6070
_StreamOutgoingCallContentState();
@@ -90,19 +100,33 @@ class _StreamOutgoingCallContentState extends State<StreamOutgoingCallContent> {
90100
mainAxisAlignment: MainAxisAlignment.center,
91101
children: [
92102
const Spacer(),
93-
ParticipantAvatars(
94-
participants: participants,
95-
singleParticipantAvatarTheme: singleParticipantAvatarTheme,
96-
multipleParticipantAvatarTheme: multipleParticipantAvatarTheme,
97-
),
98-
Padding(
99-
padding: const EdgeInsets.symmetric(horizontal: 64, vertical: 32),
100-
child: CallingParticipants(
101-
participants: participants,
102-
singleParticipantTextStyle: singleParticipantTextStyle,
103-
multipleParticipantTextStyle: multipleParticipantTextStyle,
104-
),
105-
),
103+
widget.participantsAvatarBuilder?.call(
104+
context,
105+
widget.call,
106+
widget.callState,
107+
participants,
108+
) ??
109+
ParticipantAvatars(
110+
participants: participants,
111+
singleParticipantAvatarTheme: singleParticipantAvatarTheme,
112+
multipleParticipantAvatarTheme:
113+
multipleParticipantAvatarTheme,
114+
),
115+
widget.participantsDisplayNameBuilder?.call(
116+
context,
117+
widget.call,
118+
widget.callState,
119+
participants,
120+
) ??
121+
Padding(
122+
padding:
123+
const EdgeInsets.symmetric(horizontal: 64, vertical: 32),
124+
child: CallingParticipants(
125+
participants: participants,
126+
singleParticipantTextStyle: singleParticipantTextStyle,
127+
multipleParticipantTextStyle: multipleParticipantTextStyle,
128+
),
129+
),
106130
Text(
107131
'Calling…',
108132
style: callingLabelTextStyle,
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,23 @@
11
# This file tracks properties of this Flutter project.
22
# Used by Flutter tool to assess capabilities and perform upgrades etc.
33
#
4-
# This file should be version controlled.
4+
# This file should be version controlled and should not be manually edited.
55

66
version:
7-
revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
8-
channel: stable
7+
revision: "2f708eb8396e362e280fac22cf171c2cb467343c"
8+
channel: "stable"
99

1010
project_type: plugin
1111

1212
# Tracks metadata for the flutter migrate command
1313
migration:
1414
platforms:
1515
- platform: root
16-
create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
17-
base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
18-
- platform: android
19-
create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
20-
base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
16+
create_revision: 2f708eb8396e362e280fac22cf171c2cb467343c
17+
base_revision: 2f708eb8396e362e280fac22cf171c2cb467343c
2118
- platform: ios
22-
create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
23-
base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849
19+
create_revision: 2f708eb8396e362e280fac22cf171c2cb467343c
20+
base_revision: 2f708eb8396e362e280fac22cf171c2cb467343c
2421

2522
# User provided section
2623

@@ -30,4 +27,4 @@ migration:
3027
# Files that are not part of the templates will be ignored by default.
3128
unmanaged_files:
3229
- 'lib/main.dart'
33-
- 'ios/Runner.xcodeproj/project.pbxproj'
30+
- 'ios/Runner.xcodeproj/project.pbxproj'

packages/stream_video_push_notification/CHANGELOG.md

+25
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
1+
## Upcoming
2+
3+
✅ Added
4+
5+
* `incomingCallerNameOverride` and `incomingCallerHandlerOverride` to `StreamVideoPushParams` to allow customization of CallKit call screen
6+
* `participantsAvatarBuilder` and `participantsDisplayNameBuilder` to `StreamOutgoingCallContent` and `StreamIncomingCallContent` to allow customiztion of Incoming and Outgoing call screens
7+
8+
Example usage:
9+
```dart
10+
StreamCallContainer(
11+
...
12+
outgoingCallBuilder: (context, call, callState) =>
13+
StreamOutgoingCallContent(
14+
call: call,
15+
callState: callState,
16+
participantsDisplayNameBuilder:
17+
(context, call, callState, participants) => your widget here,
18+
),
19+
)
20+
```
21+
22+
🐞 Fixed
23+
24+
* App icon in CallKit screen is now visible in dedicated button when correctly configured.
25+
126
## 0.1.1
227

328
* Aligned version with other Stream Video packages

packages/stream_video_push_notification/ios/Classes/StreamVideoPKDelegateManager.swift

+25-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import Foundation
22
import PushKit
3+
import flutter_callkit_incoming
34

45
public class StreamVideoPKDelegateManager: NSObject, PKPushRegistryDelegate {
56
public static let shared = StreamVideoPKDelegateManager()
67

78
private var pushRegistry: PKPushRegistry?
9+
private var defaultData: [String: Any]?
810

911
private override init() {
1012
super.init()
@@ -15,9 +17,12 @@ public class StreamVideoPKDelegateManager: NSObject, PKPushRegistryDelegate {
1517
pushRegistry?.delegate = self
1618
pushRegistry?.desiredPushTypes = [.voIP]
1719
}
20+
21+
public func initData(data: [String: Any]) {
22+
defaultData = data
23+
}
1824

1925
// MARK: - PKPushRegistryDelegate
20-
2126
@objc public func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
2227
let deviceToken = pushCredentials.token.map { String(format: "%02x", $0) }.joined()
2328
return StreamVideoPushNotificationPlugin.setDevicePushTokenVoIP(deviceToken: deviceToken)
@@ -32,19 +37,32 @@ public class StreamVideoPKDelegateManager: NSObject, PKPushRegistryDelegate {
3237
guard type == .voIP else {
3338
return completion()
3439
}
35-
40+
41+
let defaultCallText = "Unknown Caller"
42+
3643
// Prepare call kit incoming notification.
3744
let streamDict = payload.dictionaryPayload["stream"] as? [String: Any]
3845
let callCid = streamDict?["call_cid"] as? String ?? ""
46+
3947
let createdByName = streamDict?["created_by_display_name"] as? String
4048
let createdById = streamDict?["created_by_id"] as? String
41-
49+
50+
let data: StreamVideoPushParams
51+
if let jsonData = defaultData {
52+
data = StreamVideoPushParams(args: jsonData)
53+
} else {
54+
data = StreamVideoPushParams(args: [String: Any]())
55+
}
56+
57+
data.callKitData.uuid = UUID().uuidString
58+
data.callKitData.nameCaller = data.incomingCallerNameOverride ?? createdByName ?? defaultCallText
59+
data.callKitData.handle = data.incomingCallerHandlerOverride ?? createdById ?? defaultCallText
60+
data.callKitData.type = 1 //video
61+
data.callKitData.extra = ["callCid": callCid]
62+
4263
// Show call incoming notification.
4364
StreamVideoPushNotificationPlugin.showIncomingCall(
44-
uuid: UUID().uuidString,
45-
callCid: callCid,
46-
handle: createdById,
47-
nameCaller: createdByName,
65+
data: data.callKitData,
4866
fromPushKit: true
4967
)
5068

packages/stream_video_push_notification/ios/Classes/StreamVideoPushNotificationPlugin.swift

+20-33
Original file line numberDiff line numberDiff line change
@@ -5,54 +5,41 @@ import flutter_callkit_incoming
55
public class StreamVideoPushNotificationPlugin: NSObject, FlutterPlugin {
66

77
public static func register(with registrar: FlutterPluginRegistrar) {
8-
// no-op
8+
let channel = FlutterMethodChannel(name: "stream_video_push_notifications", binaryMessenger: registrar.messenger())
9+
let instance = StreamVideoPushNotificationPlugin()
10+
11+
registrar.addMethodCallDelegate(instance, channel: channel)
912
}
13+
14+
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
15+
switch call.method {
16+
case "initData":
17+
if let arguments = call.arguments as? [String: Any] {
18+
StreamVideoPKDelegateManager.shared.initData(data: arguments)
19+
result(nil)
20+
} else {
21+
result(FlutterError(code: "INVALID_ARGUMENT", message: "Invalid argument", details: nil))
22+
}
23+
default:
24+
result(FlutterMethodNotImplemented)
25+
}
26+
}
1027

1128
@objc public static func setDevicePushTokenVoIP(deviceToken: String) {
1229
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP(deviceToken)
1330
}
1431

1532
@objc public static func startOutgoingCall(
16-
uuid: String,
17-
callCid: String,
18-
avatar: String? = nil,
19-
handle: String? = nil,
20-
nameCaller: String? = nil,
21-
hasVideo: Bool = true,
33+
data: flutter_callkit_incoming.Data,
2234
fromPushKit: Bool
2335
) {
24-
let defaultCallText = "Unknown Caller"
25-
26-
let data = flutter_callkit_incoming.Data(
27-
id: uuid,
28-
nameCaller: nameCaller ?? defaultCallText,
29-
handle: handle ?? defaultCallText,
30-
type: hasVideo ? 1 : 0
31-
)
32-
data.extra = ["callCid": callCid]
33-
3436
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.startCall(data, fromPushKit: fromPushKit)
3537
}
3638

3739
@objc public static func showIncomingCall(
38-
uuid: String,
39-
callCid: String,
40-
avatar: String? = nil,
41-
handle: String? = nil,
42-
nameCaller: String? = nil,
43-
hasVideo: Bool = true,
40+
data: flutter_callkit_incoming.Data,
4441
fromPushKit: Bool
4542
) {
46-
let defaultCallText = "Unknown Caller"
47-
48-
let data = flutter_callkit_incoming.Data(
49-
id: uuid,
50-
nameCaller: nameCaller ?? defaultCallText,
51-
handle: handle ?? defaultCallText,
52-
type: hasVideo ? 1 : 0
53-
)
54-
data.extra = ["callCid": callCid]
55-
5643
SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: fromPushKit)
5744
}
5845

0 commit comments

Comments
 (0)