forked from eatvio/eatvio_app
feat(video_calls): fixed online indicator and in app notifications for video call
This commit is contained in:
@@ -9,4 +9,5 @@ const List<String> planTypeValues = ['fit', 'slim', 'wellness', 'behavior'];
|
||||
const newMessagesChannelName = 'private-new-chat-messages';
|
||||
const newNotificationsChannelName = 'private-message-events';
|
||||
const directChatChannelName = 'private-direct-chat';
|
||||
const presenceDirectChatChannelName = 'presence-direct-chat';
|
||||
const thresholdForPagination = 150;
|
||||
|
||||
@@ -236,6 +236,9 @@ class OtherRoutes {
|
||||
conversationUuid: extra['conversationUuid'] as String,
|
||||
counterpartName: extra['counterpartName'] as String?,
|
||||
counterpartAvatarUrl: extra['counterpartAvatarUrl'] as String?,
|
||||
counterpartUserId: extra['counterpartUserId'] as int?,
|
||||
counterpartIsOnline: extra['counterpartIsOnline'] as bool?,
|
||||
counterpartLastSeenText: extra['counterpartLastSeenText'] as String?,
|
||||
localParticipantName: extra['localParticipantName'] as String?,
|
||||
entryMode: _videoCallEntryModeFromRouteExtra(extra['entryMode']),
|
||||
onBackToChat: extra['onBackToChat'] as void Function()?,
|
||||
|
||||
@@ -26,6 +26,13 @@ class WsManager {
|
||||
PrivateChannel? _privateNotificationChannel;
|
||||
PrivateChannel? _privateMessagesChannel;
|
||||
PrivateChannel? _privateDirectChatChannel;
|
||||
PresenceChannel? _presenceDirectChatChannel;
|
||||
|
||||
StreamSubscription? _presenceMemberAddedSubscription;
|
||||
StreamSubscription? _presenceMemberRemovedSubscription;
|
||||
StreamSubscription? _presenceSubscriptionSucceededSubscription;
|
||||
final List<_PresenceDirectChatListener> _presenceDirectChatListeners = [];
|
||||
bool _presenceChannelEventsBound = false;
|
||||
|
||||
StreamSubscription? _lifecycleSubscription;
|
||||
StreamSubscription? _eventSubscription;
|
||||
@@ -175,6 +182,107 @@ class WsManager {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> initPresenceDirectChatChannel(String chatId) async {
|
||||
final token = await TokenStorage.getTokenFromStorage();
|
||||
_presenceDirectChatChannel = client?.presenceChannel(
|
||||
'$presenceDirectChatChannelName.$chatId',
|
||||
authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate.forPresenceChannel(
|
||||
authorizationEndpoint: Uri.parse('${sl<AppConfig>().baseUrl}/broadcasting/auth'),
|
||||
headers: {
|
||||
'Authorization': 'Bearer $token',
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void addPresenceDirectChatListener({
|
||||
required int counterpartUserId,
|
||||
required void Function(bool isOnline) onCounterpartOnlineChanged,
|
||||
}) {
|
||||
final channel = _presenceDirectChatChannel;
|
||||
if (channel == null) return;
|
||||
|
||||
_presenceDirectChatListeners.add(
|
||||
_PresenceDirectChatListener(
|
||||
counterpartUserId: counterpartUserId,
|
||||
onCounterpartOnlineChanged: onCounterpartOnlineChanged,
|
||||
),
|
||||
);
|
||||
|
||||
_bindPresenceChannelEventsIfNeeded();
|
||||
channel.subscribe();
|
||||
_emitCounterpartPresence(counterpartUserId);
|
||||
}
|
||||
|
||||
void removePresenceDirectChatListener(
|
||||
void Function(bool isOnline) onCounterpartOnlineChanged,
|
||||
) {
|
||||
_presenceDirectChatListeners.removeWhere(
|
||||
(listener) => listener.onCounterpartOnlineChanged == onCounterpartOnlineChanged,
|
||||
);
|
||||
if (_presenceDirectChatListeners.isEmpty) {
|
||||
unsubscribePresenceDirectChatChannel();
|
||||
}
|
||||
}
|
||||
|
||||
void _bindPresenceChannelEventsIfNeeded() {
|
||||
if (_presenceChannelEventsBound) return;
|
||||
final channel = _presenceDirectChatChannel;
|
||||
if (channel == null) return;
|
||||
|
||||
_presenceChannelEventsBound = true;
|
||||
|
||||
_presenceSubscriptionSucceededSubscription =
|
||||
channel.whenSubscriptionSucceeded().listen((_) {
|
||||
final counterpartIds = _presenceDirectChatListeners
|
||||
.map((listener) => listener.counterpartUserId)
|
||||
.toSet();
|
||||
for (final counterpartUserId in counterpartIds) {
|
||||
_emitCounterpartPresence(counterpartUserId);
|
||||
}
|
||||
});
|
||||
_presenceMemberAddedSubscription = channel.whenMemberAdded().listen((event) {
|
||||
final memberUserId = _parsePresenceMemberUserId(event.data);
|
||||
if (memberUserId == null) return;
|
||||
_notifyPresenceListeners(memberUserId, isOnline: true);
|
||||
});
|
||||
_presenceMemberRemovedSubscription = channel.whenMemberRemoved().listen((event) {
|
||||
final memberUserId = _parsePresenceMemberUserId(event.data);
|
||||
if (memberUserId == null) return;
|
||||
_notifyPresenceListeners(memberUserId, isOnline: false);
|
||||
});
|
||||
}
|
||||
|
||||
void _emitCounterpartPresence(int counterpartUserId) {
|
||||
final channel = _presenceDirectChatChannel;
|
||||
if (channel == null) return;
|
||||
final members = channel.state?.members;
|
||||
if (members == null) return;
|
||||
final isOnline = members.getMemberInfo(counterpartUserId.toString()) != null;
|
||||
_notifyPresenceListeners(counterpartUserId, isOnline: isOnline);
|
||||
}
|
||||
|
||||
void _notifyPresenceListeners(int counterpartUserId, {required bool isOnline}) {
|
||||
for (final listener in _presenceDirectChatListeners) {
|
||||
if (listener.counterpartUserId == counterpartUserId) {
|
||||
listener.onCounterpartOnlineChanged(isOnline);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int? _parsePresenceMemberUserId(String? data) {
|
||||
if (data == null || data.isEmpty) return null;
|
||||
try {
|
||||
final decoded = jsonDecode(data);
|
||||
if (decoded is! Map) return null;
|
||||
final rawId = decoded['user_id'] ?? decoded['id'];
|
||||
if (rawId == null) return null;
|
||||
return int.tryParse(rawId.toString());
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void _subscribe() {
|
||||
_privateMessagesChannel?.subscribe();
|
||||
_privateNotificationChannel?.subscribe();
|
||||
@@ -193,6 +301,19 @@ class WsManager {
|
||||
_privateDirectChatChannel?.unsubscribe();
|
||||
}
|
||||
|
||||
void unsubscribePresenceDirectChatChannel() {
|
||||
_presenceSubscriptionSucceededSubscription?.cancel();
|
||||
_presenceMemberAddedSubscription?.cancel();
|
||||
_presenceMemberRemovedSubscription?.cancel();
|
||||
_presenceSubscriptionSucceededSubscription = null;
|
||||
_presenceMemberAddedSubscription = null;
|
||||
_presenceMemberRemovedSubscription = null;
|
||||
_presenceDirectChatListeners.clear();
|
||||
_presenceChannelEventsBound = false;
|
||||
_presenceDirectChatChannel?.unsubscribe();
|
||||
_presenceDirectChatChannel = null;
|
||||
}
|
||||
|
||||
Future<void> clear() async {
|
||||
await client?.disconnect();
|
||||
await _lifecycleSubscription?.cancel();
|
||||
@@ -204,6 +325,7 @@ class WsManager {
|
||||
client = null;
|
||||
userId = null;
|
||||
_privateDirectChatChannel?.unsubscribe();
|
||||
unsubscribePresenceDirectChatChannel();
|
||||
_privateMessagesChannel?.unsubscribe();
|
||||
_privateNotificationChannel?.unsubscribe();
|
||||
_privateDirectChatChannel = null;
|
||||
@@ -211,3 +333,13 @@ class WsManager {
|
||||
_privateNotificationChannel = null;
|
||||
}
|
||||
}
|
||||
|
||||
class _PresenceDirectChatListener {
|
||||
const _PresenceDirectChatListener({
|
||||
required this.counterpartUserId,
|
||||
required this.onCounterpartOnlineChanged,
|
||||
});
|
||||
|
||||
final int counterpartUserId;
|
||||
final void Function(bool isOnline) onCounterpartOnlineChanged;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user