Skip to content

Commit d81d0ed

Browse files
authored
feat: push notification enhancements (#580)
* push notification enhancements * added changelog * changelog tweak * fix for accepting call * changed name to id
1 parent 6941db5 commit d81d0ed

File tree

7 files changed

+170
-42
lines changed

7 files changed

+170
-42
lines changed

packages/stream_video/lib/src/call/call.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,8 @@ class Call {
371371
Future<Result<None>> reject() async {
372372
final state = this.state.value;
373373
final status = state.status;
374-
if (status is! CallStatusIncoming || status.acceptedByMe) {
374+
if ((status is! CallStatusIncoming || status.acceptedByMe) &&
375+
status is! CallStatusOutgoing) {
375376
_logger.w(() => '[rejectCall] rejected (invalid status): $status');
376377
return Result.error('invalid status: $status');
377378
}
@@ -502,6 +503,7 @@ class Call {
502503
if (result.isFailure) {
503504
_logger.e(() => '[join] waiting failed: $result');
504505

506+
await reject();
505507
_stateManager.lifecycleCallTimeout(const CallTimeout());
506508

507509
return result;
@@ -1050,7 +1052,7 @@ class Call {
10501052
final response = await _coordinatorClient.getOrCreateCall(
10511053
callCid: callCid,
10521054
ringing: ringing,
1053-
members: memberIds.map((id) {
1055+
members: {...memberIds, currentUserId}.map((id) {
10541056
return MemberRequest(
10551057
userId: id,
10561058
role: 'admin',
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
enum CallRingingState {
2+
ended,
3+
rejected,
4+
accepted,
5+
ringing,
6+
}

packages/stream_video/lib/src/push_notification/push_notification_manager.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ part 'call_kit_events.dart';
1212
/// [PushNotificationManager].
1313
typedef PNManagerProvider = PushNotificationManager Function(
1414
CoordinatorClient client,
15+
StreamVideo streamVideo,
1516
);
1617

1718
/// Interface for managing push notifications related to call events.

packages/stream_video/lib/src/stream_video.dart

Lines changed: 84 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:uuid/uuid.dart';
55

66
import '../open_api/video/coordinator/api.dart' as open;
77
import 'call/call.dart';
8+
import 'call/call_ringing_state.dart';
89
import 'coordinator/coordinator_client.dart';
910
import 'coordinator/models/coordinator_events.dart';
1011
import 'coordinator/open_api/coordinator_client_open_api.dart';
@@ -141,7 +142,8 @@ class StreamVideo {
141142
);
142143

143144
// Initialize the push notification manager if the provider is provided.
144-
pushNotificationManager = pushNotificationManagerProvider?.call(_client);
145+
pushNotificationManager =
146+
pushNotificationManagerProvider?.call(_client, this);
145147

146148
_state.user.value = user;
147149
final tokenProvider = switch (user.type) {
@@ -574,24 +576,96 @@ class StreamVideo {
574576
if (callCid == null) return false;
575577

576578
var callId = const Uuid().v4();
579+
var callType = 'default';
580+
577581
final splitCid = callCid.split(':');
578582
if (splitCid.length == 2) {
583+
callType = splitCid.first;
579584
callId = splitCid.last;
580585
}
581586

582587
final createdById = payload['created_by_id'] as String?;
583588
final createdByName = payload['created_by_display_name'] as String?;
584589

585-
unawaited(
586-
manager.showIncomingCall(
587-
uuid: callId,
588-
handle: createdById,
589-
nameCaller: createdByName,
590-
callCid: callCid,
591-
),
592-
);
590+
final callRingingState =
591+
await getCallRingingState(type: callType, id: callId);
592+
593+
switch (callRingingState) {
594+
case CallRingingState.ringing:
595+
unawaited(
596+
manager.showIncomingCall(
597+
uuid: callId,
598+
handle: createdById,
599+
nameCaller: createdByName,
600+
callCid: callCid,
601+
),
602+
);
603+
return true;
604+
case CallRingingState.accepted:
605+
return false;
606+
case CallRingingState.rejected:
607+
return false;
608+
case CallRingingState.ended:
609+
unawaited(
610+
manager.showMissedCall(
611+
uuid: callId,
612+
handle: createdById,
613+
nameCaller: createdByName,
614+
callCid: callCid,
615+
),
616+
);
617+
return false;
618+
}
619+
}
620+
621+
Future<CallRingingState> getCallRingingState({
622+
required String type,
623+
required String id,
624+
}) async {
625+
final call = makeCall(type: type, id: id);
626+
final callResult = await call.get();
593627

594-
return true;
628+
return callResult.fold(
629+
failure: (failure) {
630+
_logger.e(() => '[getCallRingingState] failed: $failure');
631+
return CallRingingState.ended;
632+
},
633+
success: (success) {
634+
final callData = success.data;
635+
636+
if (callData.metadata.details.endedAt != null) {
637+
_logger.e(() => '[getCallRingingState] call already ended');
638+
639+
return CallRingingState.ended;
640+
}
641+
642+
if (callData.metadata.session.acceptedBy
643+
.containsKey(_state.currentUser.id)) {
644+
_logger.e(() => '[getCallRingingState] call already accepted');
645+
return CallRingingState.accepted;
646+
}
647+
648+
if (callData.metadata.session.rejectedBy
649+
.containsKey(_state.currentUser.id)) {
650+
_logger.e(() => '[getCallRingingState] call already rejected');
651+
return CallRingingState.rejected;
652+
}
653+
654+
final otherMembers = callData.metadata.members.keys.toList()
655+
..remove(_state.currentUser.id);
656+
if (callData.metadata.session.rejectedBy.keys
657+
.toSet()
658+
.containsAll(otherMembers)) {
659+
_logger.e(
660+
() =>
661+
'[getCallRingingState] call already rejected by all other members',
662+
);
663+
return CallRingingState.rejected;
664+
}
665+
666+
return CallRingingState.ringing;
667+
},
668+
);
595669
}
596670

597671
/// Consumes incoming voIP call and returns the [Call] object.

packages/stream_video/lib/stream_video.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export 'open_api/video/coordinator/api.dart';
44
export 'src/action/participant_action.dart';
55
export 'src/call/call.dart';
66
export 'src/call/call_connect_options.dart';
7+
export 'src/call/call_ringing_state.dart';
78
export 'src/call_state.dart';
89
export 'src/coordinator/coordinator_client.dart';
910
export 'src/coordinator/models/coordinator_events.dart';

packages/stream_video_flutter/CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
## Upcoming
22

3+
🐞 Fixed
4+
5+
* Various fixes to call ringing and push notifications
6+
- fixes call ringing cancelation when app is terminated on iOS (requires additional setup - check Step 6 of the [APNS integration](https://getstream.io/video/docs/flutter/advanced/adding_ringing_and_callkit/#integrating-apns-for-ios)) in our documentations
7+
- fixes late push notification handling on Android, where already ended call was ringing if the device was offline and the push was delivered with a delay
8+
- fixes call ringing cancelation when caller timed out while calling
9+
* Fixed background image for incoming/outgoing call screens when `participant.image` is invalid
10+
* Fixed action tap callback on Android call notification
11+
* Fixes possible crashes for Android SDKs versions <26
12+
* Fixed screen sharing on iOS when screen sharing mode was switched between `in-app` and `broadcast`
13+
* Changed the version range of `intl` package to >=0.18.1 <=0.19.0 because it was causing isses with other packages
14+
15+
✅ Added
16+
317
* Added `custom` field to `CallParticipantState` with custom user data.
418

519
## 0.3.1

packages/stream_video_push_notification/lib/src/stream_video_push_notification.dart

Lines changed: 60 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const _idCallKitIncoming = 2;
1717
const _idCallEnded = 3;
1818
const _idCallAccepted = 4;
1919
const _idCallKitAcceptDecline = 5;
20+
const _idCallRejected = 6;
2021

2122
/// Implementation of [PushNotificationManager] for Stream Video.
2223
class StreamVideoPushNotificationManager implements PushNotificationManager {
@@ -30,7 +31,7 @@ class StreamVideoPushNotificationManager implements PushNotificationManager {
3031
BackgroundVoipCallHandler? backgroundVoipCallHandler,
3132
StreamVideoPushParams? pushParams,
3233
}) {
33-
return (CoordinatorClient client) {
34+
return (CoordinatorClient client, StreamVideo streamVideo) {
3435
final params = _defaultPushParams.merge(pushParams);
3536

3637
if (CurrentPlatform.isIos) {
@@ -43,6 +44,7 @@ class StreamVideoPushNotificationManager implements PushNotificationManager {
4344

4445
return StreamVideoPushNotificationManager._(
4546
client: client,
47+
streamVideo: streamVideo,
4648
iosPushProvider: iosPushProvider,
4749
androidPushProvider: androidPushProvider,
4850
pushParams: params,
@@ -53,22 +55,68 @@ class StreamVideoPushNotificationManager implements PushNotificationManager {
5355

5456
StreamVideoPushNotificationManager._({
5557
required CoordinatorClient client,
58+
required StreamVideo streamVideo,
5659
required this.iosPushProvider,
5760
required this.androidPushProvider,
5861
required this.pushParams,
5962
this.callerCustomizationCallback,
6063
}) : _client = client {
61-
//if there are active calls (for iOS) when connecting, subscribe to end call event
64+
subscribeToEvents() {
65+
_subscriptions.add(
66+
_idCallEnded,
67+
client.events.on<CoordinatorCallEndedEvent>(
68+
(event) {
69+
FlutterCallkitIncoming.endCall(event.callCid.id);
70+
},
71+
),
72+
);
73+
74+
_subscriptions.add(
75+
_idCallRejected,
76+
client.events.on<CoordinatorCallRejectedEvent>(
77+
(event) async {
78+
final callRingingState = await streamVideo.getCallRingingState(
79+
type: event.callCid.type, id: event.callCid.id);
80+
81+
switch (callRingingState) {
82+
case CallRingingState.accepted:
83+
case CallRingingState.rejected:
84+
case CallRingingState.ended:
85+
FlutterCallkitIncoming.endCall(event.callCid.id);
86+
case CallRingingState.ringing:
87+
break;
88+
}
89+
},
90+
),
91+
);
92+
93+
_subscriptions.add(
94+
_idCallAccepted,
95+
client.events.on<CoordinatorCallAcceptedEvent>(
96+
(event) async {
97+
final callRingingState = await streamVideo.getCallRingingState(
98+
type: event.callCid.type, id: event.callCid.id);
99+
100+
switch (callRingingState) {
101+
case CallRingingState.accepted:
102+
case CallRingingState.rejected:
103+
case CallRingingState.ended:
104+
await FlutterCallkitIncoming.silenceEvents();
105+
await FlutterCallkitIncoming.endCall(event.callCid.id);
106+
await Future<void>.delayed(const Duration(milliseconds: 300));
107+
await FlutterCallkitIncoming.unsilenceEvents();
108+
case CallRingingState.ringing:
109+
break;
110+
}
111+
},
112+
),
113+
);
114+
}
115+
116+
//if there are active calls (for iOS) when connecting, subscribe to events as if the call was incoming
62117
FlutterCallkitIncoming.activeCalls().then((value) {
63118
if (value is List && value.isNotEmpty) {
64-
_subscriptions.add(
65-
_idCallEnded,
66-
client.events.on<CoordinatorCallEndedEvent>(
67-
(event) {
68-
FlutterCallkitIncoming.endCall(event.callCid.id);
69-
},
70-
),
71-
);
119+
subscribeToEvents();
72120
}
73121
});
74122

@@ -81,26 +129,7 @@ class StreamVideoPushNotificationManager implements PushNotificationManager {
81129
client.openConnection();
82130
}
83131

84-
_subscriptions.add(
85-
_idCallEnded,
86-
client.events.on<CoordinatorCallEndedEvent>(
87-
(event) {
88-
FlutterCallkitIncoming.endCall(event.callCid.id);
89-
},
90-
),
91-
);
92-
93-
_subscriptions.add(
94-
_idCallAccepted,
95-
client.events.on<CoordinatorCallAcceptedEvent>(
96-
(event) async {
97-
await FlutterCallkitIncoming.silenceEvents();
98-
await FlutterCallkitIncoming.endCall(event.callCid.id);
99-
await Future<void>.delayed(const Duration(milliseconds: 300));
100-
await FlutterCallkitIncoming.unsilenceEvents();
101-
},
102-
),
103-
);
132+
subscribeToEvents();
104133
},
105134
),
106135
);
@@ -119,6 +148,7 @@ class StreamVideoPushNotificationManager implements PushNotificationManager {
119148

120149
_subscriptions.cancel(_idCallAccepted);
121150
_subscriptions.cancel(_idCallEnded);
151+
_subscriptions.cancel(_idCallRejected);
122152
},
123153
),
124154
);

0 commit comments

Comments
 (0)