Skip to content

Commit

Permalink
feat: push notification enhancements (#580)
Browse files Browse the repository at this point in the history
* push notification enhancements

* added changelog

* changelog tweak

* fix for accepting call

* changed name to id
  • Loading branch information
Brazol authored Feb 9, 2024
1 parent 6941db5 commit d81d0ed
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 42 deletions.
6 changes: 4 additions & 2 deletions packages/stream_video/lib/src/call/call.dart
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,8 @@ class Call {
Future<Result<None>> reject() async {
final state = this.state.value;
final status = state.status;
if (status is! CallStatusIncoming || status.acceptedByMe) {
if ((status is! CallStatusIncoming || status.acceptedByMe) &&
status is! CallStatusOutgoing) {
_logger.w(() => '[rejectCall] rejected (invalid status): $status');
return Result.error('invalid status: $status');
}
Expand Down Expand Up @@ -502,6 +503,7 @@ class Call {
if (result.isFailure) {
_logger.e(() => '[join] waiting failed: $result');

await reject();
_stateManager.lifecycleCallTimeout(const CallTimeout());

return result;
Expand Down Expand Up @@ -1050,7 +1052,7 @@ class Call {
final response = await _coordinatorClient.getOrCreateCall(
callCid: callCid,
ringing: ringing,
members: memberIds.map((id) {
members: {...memberIds, currentUserId}.map((id) {
return MemberRequest(
userId: id,
role: 'admin',
Expand Down
6 changes: 6 additions & 0 deletions packages/stream_video/lib/src/call/call_ringing_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
enum CallRingingState {
ended,
rejected,
accepted,
ringing,
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ part 'call_kit_events.dart';
/// [PushNotificationManager].
typedef PNManagerProvider = PushNotificationManager Function(
CoordinatorClient client,
StreamVideo streamVideo,
);

/// Interface for managing push notifications related to call events.
Expand Down
94 changes: 84 additions & 10 deletions packages/stream_video/lib/src/stream_video.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:uuid/uuid.dart';

import '../open_api/video/coordinator/api.dart' as open;
import 'call/call.dart';
import 'call/call_ringing_state.dart';
import 'coordinator/coordinator_client.dart';
import 'coordinator/models/coordinator_events.dart';
import 'coordinator/open_api/coordinator_client_open_api.dart';
Expand Down Expand Up @@ -141,7 +142,8 @@ class StreamVideo {
);

// Initialize the push notification manager if the provider is provided.
pushNotificationManager = pushNotificationManagerProvider?.call(_client);
pushNotificationManager =
pushNotificationManagerProvider?.call(_client, this);

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

var callId = const Uuid().v4();
var callType = 'default';

final splitCid = callCid.split(':');
if (splitCid.length == 2) {
callType = splitCid.first;
callId = splitCid.last;
}

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

unawaited(
manager.showIncomingCall(
uuid: callId,
handle: createdById,
nameCaller: createdByName,
callCid: callCid,
),
);
final callRingingState =
await getCallRingingState(type: callType, id: callId);

switch (callRingingState) {
case CallRingingState.ringing:
unawaited(
manager.showIncomingCall(
uuid: callId,
handle: createdById,
nameCaller: createdByName,
callCid: callCid,
),
);
return true;
case CallRingingState.accepted:
return false;
case CallRingingState.rejected:
return false;
case CallRingingState.ended:
unawaited(
manager.showMissedCall(
uuid: callId,
handle: createdById,
nameCaller: createdByName,
callCid: callCid,
),
);
return false;
}
}

Future<CallRingingState> getCallRingingState({
required String type,
required String id,
}) async {
final call = makeCall(type: type, id: id);
final callResult = await call.get();

return true;
return callResult.fold(
failure: (failure) {
_logger.e(() => '[getCallRingingState] failed: $failure');
return CallRingingState.ended;
},
success: (success) {
final callData = success.data;

if (callData.metadata.details.endedAt != null) {
_logger.e(() => '[getCallRingingState] call already ended');

return CallRingingState.ended;
}

if (callData.metadata.session.acceptedBy
.containsKey(_state.currentUser.id)) {
_logger.e(() => '[getCallRingingState] call already accepted');
return CallRingingState.accepted;
}

if (callData.metadata.session.rejectedBy
.containsKey(_state.currentUser.id)) {
_logger.e(() => '[getCallRingingState] call already rejected');
return CallRingingState.rejected;
}

final otherMembers = callData.metadata.members.keys.toList()
..remove(_state.currentUser.id);
if (callData.metadata.session.rejectedBy.keys
.toSet()
.containsAll(otherMembers)) {
_logger.e(
() =>
'[getCallRingingState] call already rejected by all other members',
);
return CallRingingState.rejected;
}

return CallRingingState.ringing;
},
);
}

/// Consumes incoming voIP call and returns the [Call] object.
Expand Down
1 change: 1 addition & 0 deletions packages/stream_video/lib/stream_video.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export 'open_api/video/coordinator/api.dart';
export 'src/action/participant_action.dart';
export 'src/call/call.dart';
export 'src/call/call_connect_options.dart';
export 'src/call/call_ringing_state.dart';
export 'src/call_state.dart';
export 'src/coordinator/coordinator_client.dart';
export 'src/coordinator/models/coordinator_events.dart';
Expand Down
14 changes: 14 additions & 0 deletions packages/stream_video_flutter/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
## Upcoming

🐞 Fixed

* Various fixes to call ringing and push notifications
- 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
- 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
- fixes call ringing cancelation when caller timed out while calling
* Fixed background image for incoming/outgoing call screens when `participant.image` is invalid
* Fixed action tap callback on Android call notification
* Fixes possible crashes for Android SDKs versions <26
* Fixed screen sharing on iOS when screen sharing mode was switched between `in-app` and `broadcast`
* Changed the version range of `intl` package to >=0.18.1 <=0.19.0 because it was causing isses with other packages

✅ Added

* Added `custom` field to `CallParticipantState` with custom user data.

## 0.3.1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const _idCallKitIncoming = 2;
const _idCallEnded = 3;
const _idCallAccepted = 4;
const _idCallKitAcceptDecline = 5;
const _idCallRejected = 6;

/// Implementation of [PushNotificationManager] for Stream Video.
class StreamVideoPushNotificationManager implements PushNotificationManager {
Expand All @@ -30,7 +31,7 @@ class StreamVideoPushNotificationManager implements PushNotificationManager {
BackgroundVoipCallHandler? backgroundVoipCallHandler,
StreamVideoPushParams? pushParams,
}) {
return (CoordinatorClient client) {
return (CoordinatorClient client, StreamVideo streamVideo) {
final params = _defaultPushParams.merge(pushParams);

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

return StreamVideoPushNotificationManager._(
client: client,
streamVideo: streamVideo,
iosPushProvider: iosPushProvider,
androidPushProvider: androidPushProvider,
pushParams: params,
Expand All @@ -53,22 +55,68 @@ class StreamVideoPushNotificationManager implements PushNotificationManager {

StreamVideoPushNotificationManager._({
required CoordinatorClient client,
required StreamVideo streamVideo,
required this.iosPushProvider,
required this.androidPushProvider,
required this.pushParams,
this.callerCustomizationCallback,
}) : _client = client {
//if there are active calls (for iOS) when connecting, subscribe to end call event
subscribeToEvents() {
_subscriptions.add(
_idCallEnded,
client.events.on<CoordinatorCallEndedEvent>(
(event) {
FlutterCallkitIncoming.endCall(event.callCid.id);
},
),
);

_subscriptions.add(
_idCallRejected,
client.events.on<CoordinatorCallRejectedEvent>(
(event) async {
final callRingingState = await streamVideo.getCallRingingState(
type: event.callCid.type, id: event.callCid.id);

switch (callRingingState) {
case CallRingingState.accepted:
case CallRingingState.rejected:
case CallRingingState.ended:
FlutterCallkitIncoming.endCall(event.callCid.id);
case CallRingingState.ringing:
break;
}
},
),
);

_subscriptions.add(
_idCallAccepted,
client.events.on<CoordinatorCallAcceptedEvent>(
(event) async {
final callRingingState = await streamVideo.getCallRingingState(
type: event.callCid.type, id: event.callCid.id);

switch (callRingingState) {
case CallRingingState.accepted:
case CallRingingState.rejected:
case CallRingingState.ended:
await FlutterCallkitIncoming.silenceEvents();
await FlutterCallkitIncoming.endCall(event.callCid.id);
await Future<void>.delayed(const Duration(milliseconds: 300));
await FlutterCallkitIncoming.unsilenceEvents();
case CallRingingState.ringing:
break;
}
},
),
);
}

//if there are active calls (for iOS) when connecting, subscribe to events as if the call was incoming
FlutterCallkitIncoming.activeCalls().then((value) {
if (value is List && value.isNotEmpty) {
_subscriptions.add(
_idCallEnded,
client.events.on<CoordinatorCallEndedEvent>(
(event) {
FlutterCallkitIncoming.endCall(event.callCid.id);
},
),
);
subscribeToEvents();
}
});

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

_subscriptions.add(
_idCallEnded,
client.events.on<CoordinatorCallEndedEvent>(
(event) {
FlutterCallkitIncoming.endCall(event.callCid.id);
},
),
);

_subscriptions.add(
_idCallAccepted,
client.events.on<CoordinatorCallAcceptedEvent>(
(event) async {
await FlutterCallkitIncoming.silenceEvents();
await FlutterCallkitIncoming.endCall(event.callCid.id);
await Future<void>.delayed(const Duration(milliseconds: 300));
await FlutterCallkitIncoming.unsilenceEvents();
},
),
);
subscribeToEvents();
},
),
);
Expand All @@ -119,6 +148,7 @@ class StreamVideoPushNotificationManager implements PushNotificationManager {

_subscriptions.cancel(_idCallAccepted);
_subscriptions.cancel(_idCallEnded);
_subscriptions.cancel(_idCallRejected);
},
),
);
Expand Down

0 comments on commit d81d0ed

Please sign in to comment.