feat(videocall): handle device response via WebSocket and show ringing state

This commit is contained in:
2026-04-27 14:12:39 +02:00
parent b5d12f31a1
commit f54bf24417
17 changed files with 99 additions and 22 deletions

View File

@@ -66,7 +66,7 @@ final class CallHistoryProvider
}
}
String _$callHistoryHash() => r'f5b99a06bc69f62660d1cf7b66648a279724f216';
String _$callHistoryHash() => r'86542b22355569620e6c9d58091b6580103b852e';
final class CallHistoryFamily extends $Family
with $FunctionalFamilyOverride<FutureOr<List<CallHistoryEntity>>, String> {

View File

@@ -8,6 +8,7 @@ enum VideocallErrorEvent {
cameraPermission,
microphonePermission,
generic,
deviceBusy,
}
enum VideocallSuccessEvent {

View File

@@ -1,8 +1,10 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:legacy_device_state/legacy_device_state.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:sf_infrastructure/sf_infrastructure.dart';
import 'package:sf_shared/sf_shared.dart';
import 'package:videocall_sdk/videocall_sdk.dart' hide VideocallState;
@@ -34,6 +36,7 @@ class VideocallController extends _$VideocallController {
StreamSubscription<({int reason, String description})>? _callRemoveSub;
StreamSubscription<VideocallItem>? _missedCallSub;
StreamSubscription<VideocallClientState>? _clientStateSub;
StreamSubscription<WebSocketEvent>? _webSocketSub;
bool _wasInCall = false;
@override
@@ -54,6 +57,7 @@ class VideocallController extends _$VideocallController {
_callRemoveSub?.cancel();
_missedCallSub?.cancel();
_clientStateSub?.cancel();
_webSocketSub?.cancel();
if (_wasInCall) {
_callService.hangUp();
_deviceService.stopAudio();
@@ -136,6 +140,10 @@ class VideocallController extends _$VideocallController {
);
_missedCallSub = _callService.missedCallStream.listen(_onMissedCall);
_clientStateSub = _client.stateStream.listen(_onClientStateChange);
_webSocketSub = ref
.read(webSocketServiceProvider)
.events
.listen(_onWebSocketEvent);
}
String _sanitize(String value) =>
@@ -514,6 +522,28 @@ class VideocallController extends _$VideocallController {
}
}
void _onWebSocketEvent(WebSocketEvent event) {
if (!ref.mounted) return;
if (event is VideoCallRequestResponseEvent) {
debugPrint('[Videocall] WS video_call_request_response: ${event.status}');
if (event.isOk) {
state = state.copyWith(isDeviceRinging: true);
} else {
_callService.hangUp();
_deviceService.stopAudio();
_deviceService.stopCamera();
state = state.copyWith(
screenMode: VideocallScreenMode.idle,
errorEvent: VideocallErrorEvent.deviceBusy,
currentCall: null,
localCanvas: null,
remoteCanvas: null,
isDeviceRinging: false,
);
}
}
}
void clearError() {
state = state.copyWith(errorEvent: null);
}

View File

@@ -42,7 +42,7 @@ final class VideocallControllerProvider
}
String _$videocallControllerHash() =>
r'e94071625d05927701912826102a5b526b72140b';
r'7338ee15f5325ce3e77837ce6bd70d97fbd52b5c';
abstract class _$VideocallController extends $Notifier<VideocallState> {
VideocallState build();

View File

@@ -18,6 +18,7 @@ abstract class VideocallState with _$VideocallState {
@Default(true) bool isMicEnabled,
@Default(true) bool isSpeakerEnabled,
@Default(true) bool isFrontCamera,
@Default(false) bool isDeviceRinging,
@Default(false) bool isRemoteVideoAvailable,
VideocallItem? currentCall,
JCMediaDeviceVideoCanvas? localCanvas,

View File

@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$VideocallState {
String get deviceId; String get localUserId; String get remoteUserId; VideocallScreenMode get screenMode; VideocallChatType get chatType; bool get isSdkReady; bool get isMicEnabled; bool get isSpeakerEnabled; bool get isFrontCamera; bool get isRemoteVideoAvailable; VideocallItem? get currentCall; JCMediaDeviceVideoCanvas? get localCanvas; JCMediaDeviceVideoCanvas? get remoteCanvas; VideocallErrorEvent? get errorEvent; VideocallSuccessEvent? get successEvent;
String get deviceId; String get localUserId; String get remoteUserId; VideocallScreenMode get screenMode; VideocallChatType get chatType; bool get isSdkReady; bool get isMicEnabled; bool get isSpeakerEnabled; bool get isFrontCamera; bool get isDeviceRinging; bool get isRemoteVideoAvailable; VideocallItem? get currentCall; JCMediaDeviceVideoCanvas? get localCanvas; JCMediaDeviceVideoCanvas? get remoteCanvas; VideocallErrorEvent? get errorEvent; VideocallSuccessEvent? get successEvent;
/// Create a copy of VideocallState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,16 +25,16 @@ $VideocallStateCopyWith<VideocallState> get copyWith => _$VideocallStateCopyWith
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is VideocallState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.localUserId, localUserId) || other.localUserId == localUserId)&&(identical(other.remoteUserId, remoteUserId) || other.remoteUserId == remoteUserId)&&(identical(other.screenMode, screenMode) || other.screenMode == screenMode)&&(identical(other.chatType, chatType) || other.chatType == chatType)&&(identical(other.isSdkReady, isSdkReady) || other.isSdkReady == isSdkReady)&&(identical(other.isMicEnabled, isMicEnabled) || other.isMicEnabled == isMicEnabled)&&(identical(other.isSpeakerEnabled, isSpeakerEnabled) || other.isSpeakerEnabled == isSpeakerEnabled)&&(identical(other.isFrontCamera, isFrontCamera) || other.isFrontCamera == isFrontCamera)&&(identical(other.isRemoteVideoAvailable, isRemoteVideoAvailable) || other.isRemoteVideoAvailable == isRemoteVideoAvailable)&&(identical(other.currentCall, currentCall) || other.currentCall == currentCall)&&(identical(other.localCanvas, localCanvas) || other.localCanvas == localCanvas)&&(identical(other.remoteCanvas, remoteCanvas) || other.remoteCanvas == remoteCanvas)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)&&(identical(other.successEvent, successEvent) || other.successEvent == successEvent));
return identical(this, other) || (other.runtimeType == runtimeType&&other is VideocallState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.localUserId, localUserId) || other.localUserId == localUserId)&&(identical(other.remoteUserId, remoteUserId) || other.remoteUserId == remoteUserId)&&(identical(other.screenMode, screenMode) || other.screenMode == screenMode)&&(identical(other.chatType, chatType) || other.chatType == chatType)&&(identical(other.isSdkReady, isSdkReady) || other.isSdkReady == isSdkReady)&&(identical(other.isMicEnabled, isMicEnabled) || other.isMicEnabled == isMicEnabled)&&(identical(other.isSpeakerEnabled, isSpeakerEnabled) || other.isSpeakerEnabled == isSpeakerEnabled)&&(identical(other.isFrontCamera, isFrontCamera) || other.isFrontCamera == isFrontCamera)&&(identical(other.isDeviceRinging, isDeviceRinging) || other.isDeviceRinging == isDeviceRinging)&&(identical(other.isRemoteVideoAvailable, isRemoteVideoAvailable) || other.isRemoteVideoAvailable == isRemoteVideoAvailable)&&(identical(other.currentCall, currentCall) || other.currentCall == currentCall)&&(identical(other.localCanvas, localCanvas) || other.localCanvas == localCanvas)&&(identical(other.remoteCanvas, remoteCanvas) || other.remoteCanvas == remoteCanvas)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)&&(identical(other.successEvent, successEvent) || other.successEvent == successEvent));
}
@override
int get hashCode => Object.hash(runtimeType,deviceId,localUserId,remoteUserId,screenMode,chatType,isSdkReady,isMicEnabled,isSpeakerEnabled,isFrontCamera,isRemoteVideoAvailable,currentCall,localCanvas,remoteCanvas,errorEvent,successEvent);
int get hashCode => Object.hash(runtimeType,deviceId,localUserId,remoteUserId,screenMode,chatType,isSdkReady,isMicEnabled,isSpeakerEnabled,isFrontCamera,isDeviceRinging,isRemoteVideoAvailable,currentCall,localCanvas,remoteCanvas,errorEvent,successEvent);
@override
String toString() {
return 'VideocallState(deviceId: $deviceId, localUserId: $localUserId, remoteUserId: $remoteUserId, screenMode: $screenMode, chatType: $chatType, isSdkReady: $isSdkReady, isMicEnabled: $isMicEnabled, isSpeakerEnabled: $isSpeakerEnabled, isFrontCamera: $isFrontCamera, isRemoteVideoAvailable: $isRemoteVideoAvailable, currentCall: $currentCall, localCanvas: $localCanvas, remoteCanvas: $remoteCanvas, errorEvent: $errorEvent, successEvent: $successEvent)';
return 'VideocallState(deviceId: $deviceId, localUserId: $localUserId, remoteUserId: $remoteUserId, screenMode: $screenMode, chatType: $chatType, isSdkReady: $isSdkReady, isMicEnabled: $isMicEnabled, isSpeakerEnabled: $isSpeakerEnabled, isFrontCamera: $isFrontCamera, isDeviceRinging: $isDeviceRinging, isRemoteVideoAvailable: $isRemoteVideoAvailable, currentCall: $currentCall, localCanvas: $localCanvas, remoteCanvas: $remoteCanvas, errorEvent: $errorEvent, successEvent: $successEvent)';
}
@@ -45,7 +45,7 @@ abstract mixin class $VideocallStateCopyWith<$Res> {
factory $VideocallStateCopyWith(VideocallState value, $Res Function(VideocallState) _then) = _$VideocallStateCopyWithImpl;
@useResult
$Res call({
String deviceId, String localUserId, String remoteUserId, VideocallScreenMode screenMode, VideocallChatType chatType, bool isSdkReady, bool isMicEnabled, bool isSpeakerEnabled, bool isFrontCamera, bool isRemoteVideoAvailable, VideocallItem? currentCall, JCMediaDeviceVideoCanvas? localCanvas, JCMediaDeviceVideoCanvas? remoteCanvas, VideocallErrorEvent? errorEvent, VideocallSuccessEvent? successEvent
String deviceId, String localUserId, String remoteUserId, VideocallScreenMode screenMode, VideocallChatType chatType, bool isSdkReady, bool isMicEnabled, bool isSpeakerEnabled, bool isFrontCamera, bool isDeviceRinging, bool isRemoteVideoAvailable, VideocallItem? currentCall, JCMediaDeviceVideoCanvas? localCanvas, JCMediaDeviceVideoCanvas? remoteCanvas, VideocallErrorEvent? errorEvent, VideocallSuccessEvent? successEvent
});
@@ -62,7 +62,7 @@ class _$VideocallStateCopyWithImpl<$Res>
/// Create a copy of VideocallState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? deviceId = null,Object? localUserId = null,Object? remoteUserId = null,Object? screenMode = null,Object? chatType = null,Object? isSdkReady = null,Object? isMicEnabled = null,Object? isSpeakerEnabled = null,Object? isFrontCamera = null,Object? isRemoteVideoAvailable = null,Object? currentCall = freezed,Object? localCanvas = freezed,Object? remoteCanvas = freezed,Object? errorEvent = freezed,Object? successEvent = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? deviceId = null,Object? localUserId = null,Object? remoteUserId = null,Object? screenMode = null,Object? chatType = null,Object? isSdkReady = null,Object? isMicEnabled = null,Object? isSpeakerEnabled = null,Object? isFrontCamera = null,Object? isDeviceRinging = null,Object? isRemoteVideoAvailable = null,Object? currentCall = freezed,Object? localCanvas = freezed,Object? remoteCanvas = freezed,Object? errorEvent = freezed,Object? successEvent = freezed,}) {
return _then(_self.copyWith(
deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,localUserId: null == localUserId ? _self.localUserId : localUserId // ignore: cast_nullable_to_non_nullable
@@ -73,6 +73,7 @@ as VideocallChatType,isSdkReady: null == isSdkReady ? _self.isSdkReady : isSdkRe
as bool,isMicEnabled: null == isMicEnabled ? _self.isMicEnabled : isMicEnabled // ignore: cast_nullable_to_non_nullable
as bool,isSpeakerEnabled: null == isSpeakerEnabled ? _self.isSpeakerEnabled : isSpeakerEnabled // ignore: cast_nullable_to_non_nullable
as bool,isFrontCamera: null == isFrontCamera ? _self.isFrontCamera : isFrontCamera // ignore: cast_nullable_to_non_nullable
as bool,isDeviceRinging: null == isDeviceRinging ? _self.isDeviceRinging : isDeviceRinging // ignore: cast_nullable_to_non_nullable
as bool,isRemoteVideoAvailable: null == isRemoteVideoAvailable ? _self.isRemoteVideoAvailable : isRemoteVideoAvailable // ignore: cast_nullable_to_non_nullable
as bool,currentCall: freezed == currentCall ? _self.currentCall : currentCall // ignore: cast_nullable_to_non_nullable
as VideocallItem?,localCanvas: freezed == localCanvas ? _self.localCanvas : localCanvas // ignore: cast_nullable_to_non_nullable
@@ -176,10 +177,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String deviceId, String localUserId, String remoteUserId, VideocallScreenMode screenMode, VideocallChatType chatType, bool isSdkReady, bool isMicEnabled, bool isSpeakerEnabled, bool isFrontCamera, bool isRemoteVideoAvailable, VideocallItem? currentCall, JCMediaDeviceVideoCanvas? localCanvas, JCMediaDeviceVideoCanvas? remoteCanvas, VideocallErrorEvent? errorEvent, VideocallSuccessEvent? successEvent)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String deviceId, String localUserId, String remoteUserId, VideocallScreenMode screenMode, VideocallChatType chatType, bool isSdkReady, bool isMicEnabled, bool isSpeakerEnabled, bool isFrontCamera, bool isDeviceRinging, bool isRemoteVideoAvailable, VideocallItem? currentCall, JCMediaDeviceVideoCanvas? localCanvas, JCMediaDeviceVideoCanvas? remoteCanvas, VideocallErrorEvent? errorEvent, VideocallSuccessEvent? successEvent)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _VideocallState() when $default != null:
return $default(_that.deviceId,_that.localUserId,_that.remoteUserId,_that.screenMode,_that.chatType,_that.isSdkReady,_that.isMicEnabled,_that.isSpeakerEnabled,_that.isFrontCamera,_that.isRemoteVideoAvailable,_that.currentCall,_that.localCanvas,_that.remoteCanvas,_that.errorEvent,_that.successEvent);case _:
return $default(_that.deviceId,_that.localUserId,_that.remoteUserId,_that.screenMode,_that.chatType,_that.isSdkReady,_that.isMicEnabled,_that.isSpeakerEnabled,_that.isFrontCamera,_that.isDeviceRinging,_that.isRemoteVideoAvailable,_that.currentCall,_that.localCanvas,_that.remoteCanvas,_that.errorEvent,_that.successEvent);case _:
return orElse();
}
@@ -197,10 +198,10 @@ return $default(_that.deviceId,_that.localUserId,_that.remoteUserId,_that.screen
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String deviceId, String localUserId, String remoteUserId, VideocallScreenMode screenMode, VideocallChatType chatType, bool isSdkReady, bool isMicEnabled, bool isSpeakerEnabled, bool isFrontCamera, bool isRemoteVideoAvailable, VideocallItem? currentCall, JCMediaDeviceVideoCanvas? localCanvas, JCMediaDeviceVideoCanvas? remoteCanvas, VideocallErrorEvent? errorEvent, VideocallSuccessEvent? successEvent) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String deviceId, String localUserId, String remoteUserId, VideocallScreenMode screenMode, VideocallChatType chatType, bool isSdkReady, bool isMicEnabled, bool isSpeakerEnabled, bool isFrontCamera, bool isDeviceRinging, bool isRemoteVideoAvailable, VideocallItem? currentCall, JCMediaDeviceVideoCanvas? localCanvas, JCMediaDeviceVideoCanvas? remoteCanvas, VideocallErrorEvent? errorEvent, VideocallSuccessEvent? successEvent) $default,) {final _that = this;
switch (_that) {
case _VideocallState():
return $default(_that.deviceId,_that.localUserId,_that.remoteUserId,_that.screenMode,_that.chatType,_that.isSdkReady,_that.isMicEnabled,_that.isSpeakerEnabled,_that.isFrontCamera,_that.isRemoteVideoAvailable,_that.currentCall,_that.localCanvas,_that.remoteCanvas,_that.errorEvent,_that.successEvent);case _:
return $default(_that.deviceId,_that.localUserId,_that.remoteUserId,_that.screenMode,_that.chatType,_that.isSdkReady,_that.isMicEnabled,_that.isSpeakerEnabled,_that.isFrontCamera,_that.isDeviceRinging,_that.isRemoteVideoAvailable,_that.currentCall,_that.localCanvas,_that.remoteCanvas,_that.errorEvent,_that.successEvent);case _:
throw StateError('Unexpected subclass');
}
@@ -217,10 +218,10 @@ return $default(_that.deviceId,_that.localUserId,_that.remoteUserId,_that.screen
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String deviceId, String localUserId, String remoteUserId, VideocallScreenMode screenMode, VideocallChatType chatType, bool isSdkReady, bool isMicEnabled, bool isSpeakerEnabled, bool isFrontCamera, bool isRemoteVideoAvailable, VideocallItem? currentCall, JCMediaDeviceVideoCanvas? localCanvas, JCMediaDeviceVideoCanvas? remoteCanvas, VideocallErrorEvent? errorEvent, VideocallSuccessEvent? successEvent)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String deviceId, String localUserId, String remoteUserId, VideocallScreenMode screenMode, VideocallChatType chatType, bool isSdkReady, bool isMicEnabled, bool isSpeakerEnabled, bool isFrontCamera, bool isDeviceRinging, bool isRemoteVideoAvailable, VideocallItem? currentCall, JCMediaDeviceVideoCanvas? localCanvas, JCMediaDeviceVideoCanvas? remoteCanvas, VideocallErrorEvent? errorEvent, VideocallSuccessEvent? successEvent)? $default,) {final _that = this;
switch (_that) {
case _VideocallState() when $default != null:
return $default(_that.deviceId,_that.localUserId,_that.remoteUserId,_that.screenMode,_that.chatType,_that.isSdkReady,_that.isMicEnabled,_that.isSpeakerEnabled,_that.isFrontCamera,_that.isRemoteVideoAvailable,_that.currentCall,_that.localCanvas,_that.remoteCanvas,_that.errorEvent,_that.successEvent);case _:
return $default(_that.deviceId,_that.localUserId,_that.remoteUserId,_that.screenMode,_that.chatType,_that.isSdkReady,_that.isMicEnabled,_that.isSpeakerEnabled,_that.isFrontCamera,_that.isDeviceRinging,_that.isRemoteVideoAvailable,_that.currentCall,_that.localCanvas,_that.remoteCanvas,_that.errorEvent,_that.successEvent);case _:
return null;
}
@@ -232,7 +233,7 @@ return $default(_that.deviceId,_that.localUserId,_that.remoteUserId,_that.screen
class _VideocallState implements VideocallState {
const _VideocallState({this.deviceId = '', this.localUserId = '', this.remoteUserId = '', this.screenMode = VideocallScreenMode.idle, this.chatType = VideocallChatType.single, this.isSdkReady = false, this.isMicEnabled = true, this.isSpeakerEnabled = true, this.isFrontCamera = true, this.isRemoteVideoAvailable = false, this.currentCall, this.localCanvas, this.remoteCanvas, this.errorEvent, this.successEvent});
const _VideocallState({this.deviceId = '', this.localUserId = '', this.remoteUserId = '', this.screenMode = VideocallScreenMode.idle, this.chatType = VideocallChatType.single, this.isSdkReady = false, this.isMicEnabled = true, this.isSpeakerEnabled = true, this.isFrontCamera = true, this.isDeviceRinging = false, this.isRemoteVideoAvailable = false, this.currentCall, this.localCanvas, this.remoteCanvas, this.errorEvent, this.successEvent});
@override@JsonKey() final String deviceId;
@@ -244,6 +245,7 @@ class _VideocallState implements VideocallState {
@override@JsonKey() final bool isMicEnabled;
@override@JsonKey() final bool isSpeakerEnabled;
@override@JsonKey() final bool isFrontCamera;
@override@JsonKey() final bool isDeviceRinging;
@override@JsonKey() final bool isRemoteVideoAvailable;
@override final VideocallItem? currentCall;
@override final JCMediaDeviceVideoCanvas? localCanvas;
@@ -261,16 +263,16 @@ _$VideocallStateCopyWith<_VideocallState> get copyWith => __$VideocallStateCopyW
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _VideocallState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.localUserId, localUserId) || other.localUserId == localUserId)&&(identical(other.remoteUserId, remoteUserId) || other.remoteUserId == remoteUserId)&&(identical(other.screenMode, screenMode) || other.screenMode == screenMode)&&(identical(other.chatType, chatType) || other.chatType == chatType)&&(identical(other.isSdkReady, isSdkReady) || other.isSdkReady == isSdkReady)&&(identical(other.isMicEnabled, isMicEnabled) || other.isMicEnabled == isMicEnabled)&&(identical(other.isSpeakerEnabled, isSpeakerEnabled) || other.isSpeakerEnabled == isSpeakerEnabled)&&(identical(other.isFrontCamera, isFrontCamera) || other.isFrontCamera == isFrontCamera)&&(identical(other.isRemoteVideoAvailable, isRemoteVideoAvailable) || other.isRemoteVideoAvailable == isRemoteVideoAvailable)&&(identical(other.currentCall, currentCall) || other.currentCall == currentCall)&&(identical(other.localCanvas, localCanvas) || other.localCanvas == localCanvas)&&(identical(other.remoteCanvas, remoteCanvas) || other.remoteCanvas == remoteCanvas)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)&&(identical(other.successEvent, successEvent) || other.successEvent == successEvent));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _VideocallState&&(identical(other.deviceId, deviceId) || other.deviceId == deviceId)&&(identical(other.localUserId, localUserId) || other.localUserId == localUserId)&&(identical(other.remoteUserId, remoteUserId) || other.remoteUserId == remoteUserId)&&(identical(other.screenMode, screenMode) || other.screenMode == screenMode)&&(identical(other.chatType, chatType) || other.chatType == chatType)&&(identical(other.isSdkReady, isSdkReady) || other.isSdkReady == isSdkReady)&&(identical(other.isMicEnabled, isMicEnabled) || other.isMicEnabled == isMicEnabled)&&(identical(other.isSpeakerEnabled, isSpeakerEnabled) || other.isSpeakerEnabled == isSpeakerEnabled)&&(identical(other.isFrontCamera, isFrontCamera) || other.isFrontCamera == isFrontCamera)&&(identical(other.isDeviceRinging, isDeviceRinging) || other.isDeviceRinging == isDeviceRinging)&&(identical(other.isRemoteVideoAvailable, isRemoteVideoAvailable) || other.isRemoteVideoAvailable == isRemoteVideoAvailable)&&(identical(other.currentCall, currentCall) || other.currentCall == currentCall)&&(identical(other.localCanvas, localCanvas) || other.localCanvas == localCanvas)&&(identical(other.remoteCanvas, remoteCanvas) || other.remoteCanvas == remoteCanvas)&&(identical(other.errorEvent, errorEvent) || other.errorEvent == errorEvent)&&(identical(other.successEvent, successEvent) || other.successEvent == successEvent));
}
@override
int get hashCode => Object.hash(runtimeType,deviceId,localUserId,remoteUserId,screenMode,chatType,isSdkReady,isMicEnabled,isSpeakerEnabled,isFrontCamera,isRemoteVideoAvailable,currentCall,localCanvas,remoteCanvas,errorEvent,successEvent);
int get hashCode => Object.hash(runtimeType,deviceId,localUserId,remoteUserId,screenMode,chatType,isSdkReady,isMicEnabled,isSpeakerEnabled,isFrontCamera,isDeviceRinging,isRemoteVideoAvailable,currentCall,localCanvas,remoteCanvas,errorEvent,successEvent);
@override
String toString() {
return 'VideocallState(deviceId: $deviceId, localUserId: $localUserId, remoteUserId: $remoteUserId, screenMode: $screenMode, chatType: $chatType, isSdkReady: $isSdkReady, isMicEnabled: $isMicEnabled, isSpeakerEnabled: $isSpeakerEnabled, isFrontCamera: $isFrontCamera, isRemoteVideoAvailable: $isRemoteVideoAvailable, currentCall: $currentCall, localCanvas: $localCanvas, remoteCanvas: $remoteCanvas, errorEvent: $errorEvent, successEvent: $successEvent)';
return 'VideocallState(deviceId: $deviceId, localUserId: $localUserId, remoteUserId: $remoteUserId, screenMode: $screenMode, chatType: $chatType, isSdkReady: $isSdkReady, isMicEnabled: $isMicEnabled, isSpeakerEnabled: $isSpeakerEnabled, isFrontCamera: $isFrontCamera, isDeviceRinging: $isDeviceRinging, isRemoteVideoAvailable: $isRemoteVideoAvailable, currentCall: $currentCall, localCanvas: $localCanvas, remoteCanvas: $remoteCanvas, errorEvent: $errorEvent, successEvent: $successEvent)';
}
@@ -281,7 +283,7 @@ abstract mixin class _$VideocallStateCopyWith<$Res> implements $VideocallStateCo
factory _$VideocallStateCopyWith(_VideocallState value, $Res Function(_VideocallState) _then) = __$VideocallStateCopyWithImpl;
@override @useResult
$Res call({
String deviceId, String localUserId, String remoteUserId, VideocallScreenMode screenMode, VideocallChatType chatType, bool isSdkReady, bool isMicEnabled, bool isSpeakerEnabled, bool isFrontCamera, bool isRemoteVideoAvailable, VideocallItem? currentCall, JCMediaDeviceVideoCanvas? localCanvas, JCMediaDeviceVideoCanvas? remoteCanvas, VideocallErrorEvent? errorEvent, VideocallSuccessEvent? successEvent
String deviceId, String localUserId, String remoteUserId, VideocallScreenMode screenMode, VideocallChatType chatType, bool isSdkReady, bool isMicEnabled, bool isSpeakerEnabled, bool isFrontCamera, bool isDeviceRinging, bool isRemoteVideoAvailable, VideocallItem? currentCall, JCMediaDeviceVideoCanvas? localCanvas, JCMediaDeviceVideoCanvas? remoteCanvas, VideocallErrorEvent? errorEvent, VideocallSuccessEvent? successEvent
});
@@ -298,7 +300,7 @@ class __$VideocallStateCopyWithImpl<$Res>
/// Create a copy of VideocallState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? deviceId = null,Object? localUserId = null,Object? remoteUserId = null,Object? screenMode = null,Object? chatType = null,Object? isSdkReady = null,Object? isMicEnabled = null,Object? isSpeakerEnabled = null,Object? isFrontCamera = null,Object? isRemoteVideoAvailable = null,Object? currentCall = freezed,Object? localCanvas = freezed,Object? remoteCanvas = freezed,Object? errorEvent = freezed,Object? successEvent = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? deviceId = null,Object? localUserId = null,Object? remoteUserId = null,Object? screenMode = null,Object? chatType = null,Object? isSdkReady = null,Object? isMicEnabled = null,Object? isSpeakerEnabled = null,Object? isFrontCamera = null,Object? isDeviceRinging = null,Object? isRemoteVideoAvailable = null,Object? currentCall = freezed,Object? localCanvas = freezed,Object? remoteCanvas = freezed,Object? errorEvent = freezed,Object? successEvent = freezed,}) {
return _then(_VideocallState(
deviceId: null == deviceId ? _self.deviceId : deviceId // ignore: cast_nullable_to_non_nullable
as String,localUserId: null == localUserId ? _self.localUserId : localUserId // ignore: cast_nullable_to_non_nullable
@@ -309,6 +311,7 @@ as VideocallChatType,isSdkReady: null == isSdkReady ? _self.isSdkReady : isSdkRe
as bool,isMicEnabled: null == isMicEnabled ? _self.isMicEnabled : isMicEnabled // ignore: cast_nullable_to_non_nullable
as bool,isSpeakerEnabled: null == isSpeakerEnabled ? _self.isSpeakerEnabled : isSpeakerEnabled // ignore: cast_nullable_to_non_nullable
as bool,isFrontCamera: null == isFrontCamera ? _self.isFrontCamera : isFrontCamera // ignore: cast_nullable_to_non_nullable
as bool,isDeviceRinging: null == isDeviceRinging ? _self.isDeviceRinging : isDeviceRinging // ignore: cast_nullable_to_non_nullable
as bool,isRemoteVideoAvailable: null == isRemoteVideoAvailable ? _self.isRemoteVideoAvailable : isRemoteVideoAvailable // ignore: cast_nullable_to_non_nullable
as bool,currentCall: freezed == currentCall ? _self.currentCall : currentCall // ignore: cast_nullable_to_non_nullable
as VideocallItem?,localCanvas: freezed == localCanvas ? _self.localCanvas : localCanvas // ignore: cast_nullable_to_non_nullable

View File

@@ -44,6 +44,7 @@ class _VideocallScreenState extends ConsumerState<VideocallScreen> {
VideocallErrorEvent.missedCall => I18n.videocallErrorMissedCall,
VideocallErrorEvent.cameraPermission => I18n.videocallErrorCamera,
VideocallErrorEvent.microphonePermission => I18n.videocallErrorMic,
VideocallErrorEvent.deviceBusy => I18n.videocallErrorDeviceBusy,
VideocallErrorEvent.network || VideocallErrorEvent.generic => I18n.errorGeneric,
};
showErrorDialog(context, key);
@@ -282,7 +283,9 @@ class _ActiveCallView extends StatelessWidget {
),
if (isOutgoing)
Text(
context.translate(I18n.videocallConnecting),
state.isDeviceRinging
? context.translate(I18n.videocallDeviceRinging)
: context.translate(I18n.videocallConnecting),
style: TextStyle(
color: Colors.white.withValues(alpha: 0.7),
fontSize: 12,

View File

@@ -8,17 +8,24 @@ class CallStatusIndicator extends StatelessWidget {
super.key,
required this.screenMode,
required this.remoteUserId,
required this.isDeviceRinging,
required this.onCancel,
});
final VideocallScreenMode screenMode;
final String remoteUserId;
final bool isDeviceRinging;
final VoidCallback onCancel;
@override
Widget build(BuildContext context) {
final (text, showCancel) = switch (screenMode) {
VideocallScreenMode.outgoing => (context.translate(I18n.videocallCalling, args: {'userId': remoteUserId}), true),
VideocallScreenMode.outgoing => (
isDeviceRinging
? context.translate(I18n.videocallDeviceRinging)
: context.translate(I18n.videocallCalling, args: {'userId': remoteUserId}),
true,
),
VideocallScreenMode.incoming => (context.translate(I18n.videocallUserCalling, args: {'userId': remoteUserId}), false),
_ => ('', false),
};

View File

@@ -168,6 +168,17 @@ class VideoCallRoomCountEvent extends WebSocketEvent {
}) : super(type: 'video-call-room-count');
}
class VideoCallRequestResponseEvent extends WebSocketEvent {
final String status;
const VideoCallRequestResponseEvent({
required this.status,
required super.timestamp,
}) : super(type: 'video_call_request_response');
bool get isOk => status == 'ok';
}
class UnknownEvent extends WebSocketEvent {
final Map<String, dynamic> rawData;

View File

@@ -41,6 +41,13 @@ WebSocketEvent? parseWebSocketEvent(String raw) {
? (jsonDecode(rawData) as Map<String, dynamic>? ?? {})
: (rawData as Map<String, dynamic>? ?? {});
if (type == 'video_call_request_response') {
return VideoCallRequestResponseEvent(
status: json['status'] as String? ?? '',
timestamp: timestamp,
);
}
return switch (type) {
'position' => PositionEvent(
positioningData: data['positioning_data'] as Map<String, dynamic>? ?? {},

View File

@@ -711,6 +711,8 @@
"videocallErrorCamera": "Kameraberechtigung erforderlich",
"videocallErrorMic": "Mikrofonberechtigung erforderlich",
"videocallErrorMissedCall": "Verpasster Anruf",
"videocallErrorDeviceBusy": "Das Gerät ist beschäftigt",
"videocallDeviceRinging": "Gerät klingelt...",
"videocallCallEnded": "Anruf beendet",
"videocallGroupCall": "Gruppen-Videoanruf",
"videocallDisclaimer": "*Für einen optimalen Videoanruf stellen Sie sicher, dass Ihre Uhr über mobile Daten mit Internetverbindung, guter Abdeckung oder WLAN-Verbindung verfügt.",

View File

@@ -898,6 +898,8 @@
"videocallErrorCamera": "Camera permission required",
"videocallErrorMic": "Microphone permission required",
"videocallErrorMissedCall": "Missed call",
"videocallErrorDeviceBusy": "Device is busy",
"videocallDeviceRinging": "Device ringing...",
"videocallCallEnded": "Call ended",
"videocallGroupCall": "Group video call",
"videocallDisclaimer": "*For an optimal video call, make sure your watch has mobile data with Internet connection, good coverage or WiFi connection.",

View File

@@ -899,6 +899,8 @@
"videocallErrorCamera": "Se requiere permiso de cámara",
"videocallErrorMic": "Se requiere permiso de micrófono",
"videocallErrorMissedCall": "Llamada perdida",
"videocallErrorDeviceBusy": "El dispositivo está ocupado",
"videocallDeviceRinging": "Dispositivo sonando...",
"videocallCallEnded": "Llamada finalizada",
"videocallGroupCall": "Videollamada grupal",
"videocallDisclaimer": "*Para una llamada de video óptima, asegúrate de que tu reloj tenga datos móviles con conexión a Internet, buena cobertura o conexión wifi.",

View File

@@ -711,6 +711,8 @@
"videocallErrorCamera": "Autorisation de la caméra requise",
"videocallErrorMic": "Autorisation du microphone requise",
"videocallErrorMissedCall": "Appel manqué",
"videocallErrorDeviceBusy": "L'appareil est occupé",
"videocallDeviceRinging": "Appareil en train de sonner...",
"videocallCallEnded": "Appel terminé",
"videocallGroupCall": "Appel vidéo de groupe",
"videocallDisclaimer": "*Pour un appel vidéo optimal, assurez-vous que votre montre dispose de données mobiles avec connexion Internet, bonne couverture ou connexion WiFi.",

View File

@@ -711,6 +711,8 @@
"videocallErrorCamera": "Permesso fotocamera richiesto",
"videocallErrorMic": "Permesso microfono richiesto",
"videocallErrorMissedCall": "Chiamata persa",
"videocallErrorDeviceBusy": "Il dispositivo è occupato",
"videocallDeviceRinging": "Dispositivo in chiamata...",
"videocallCallEnded": "Chiamata terminata",
"videocallGroupCall": "Videochiamata di gruppo",
"videocallDisclaimer": "*Per una videochiamata ottimale, assicurati che il tuo orologio abbia dati mobili con connessione Internet, buona copertura o connessione WiFi.",

View File

@@ -711,6 +711,8 @@
"videocallErrorCamera": "Permissão de câmara necessária",
"videocallErrorMic": "Permissão de microfone necessária",
"videocallErrorMissedCall": "Chamada perdida",
"videocallErrorDeviceBusy": "O dispositivo está ocupado",
"videocallDeviceRinging": "Dispositivo a tocar...",
"videocallCallEnded": "Chamada terminada",
"videocallGroupCall": "Videochamada em grupo",
"videocallDisclaimer": "*Para uma videochamada ideal, certifique-se de que o seu relógio tenha dados móveis com conexão à Internet, boa cobertura ou conexão WiFi.",

View File

@@ -1032,6 +1032,8 @@ class I18n {
static const String videocallErrorCamera = 'videocallErrorCamera';
static const String videocallErrorMic = 'videocallErrorMic';
static const String videocallErrorMissedCall = 'videocallErrorMissedCall';
static const String videocallErrorDeviceBusy = 'videocallErrorDeviceBusy';
static const String videocallDeviceRinging = 'videocallDeviceRinging';
static const String videocallErrorSdkInit = 'videocallErrorSdkInit';
static const String videocallHangUp = 'videocallHangUp';
static const String videocallIncomingAudio = 'videocallIncomingAudio';