forked from eatvio/eatvio_app
feat(video_calls): call ended push, recall button for message
This commit is contained in:
@@ -26,6 +26,10 @@ class VideoCallSessionCubit extends BaseCubit<VideoCallSessionState> {
|
||||
final VideoCallSystemPipCoordinator _systemPipCoordinator;
|
||||
bool _callScreenRouteVisible = false;
|
||||
bool _autoEnterInFlight = false;
|
||||
bool _counterpartPresentWhenLobbyReady = false;
|
||||
bool _sawEmptyLobbyWhenConnected = false;
|
||||
bool _lobbyReadyTracked = false;
|
||||
String? _lobbyReadyTrackedConversationUuid;
|
||||
Future<void>? _autoEnterFuture;
|
||||
Future<void>? _outgoingAutoEnterFuture;
|
||||
|
||||
@@ -72,10 +76,12 @@ class VideoCallSessionCubit extends BaseCubit<VideoCallSessionState> {
|
||||
// [VideoCallManager.startCall] briefly emits idle while replacing the room.
|
||||
return;
|
||||
}
|
||||
_resetLobbyReadyTracking();
|
||||
emit(const VideoCallSessionState());
|
||||
unawaited(_restoreSystemUiAfterCall());
|
||||
return;
|
||||
}
|
||||
_trackLobbyReadyCounterpartPresence(snapshot);
|
||||
final next = _sessionStateFromSnapshot(snapshot);
|
||||
emit(next);
|
||||
_scheduleAutoEnterIfNeeded(next);
|
||||
@@ -86,12 +92,47 @@ class VideoCallSessionCubit extends BaseCubit<VideoCallSessionState> {
|
||||
if (_isCallSessionInactive(snapshot)) {
|
||||
unawaited(_systemPipCoordinator.disarm());
|
||||
if (_callScreenRouteVisible) return;
|
||||
_resetLobbyReadyTracking();
|
||||
emit(const VideoCallSessionState());
|
||||
return;
|
||||
}
|
||||
_trackLobbyReadyCounterpartPresence(snapshot);
|
||||
emit(_sessionStateFromSnapshot(snapshot));
|
||||
}
|
||||
|
||||
void _resetLobbyReadyTracking() {
|
||||
_lobbyReadyTrackedConversationUuid = null;
|
||||
_counterpartPresentWhenLobbyReady = false;
|
||||
_sawEmptyLobbyWhenConnected = false;
|
||||
_lobbyReadyTracked = false;
|
||||
}
|
||||
|
||||
void _trackLobbyReadyCounterpartPresence(VideoCallManagerSnapshot snapshot) {
|
||||
final uuid = snapshot.conversationUuid;
|
||||
if (uuid.isEmpty) {
|
||||
_resetLobbyReadyTracking();
|
||||
return;
|
||||
}
|
||||
if (snapshot.connectPhase != VideoCallConnectPhase.connected) return;
|
||||
|
||||
if (!_lobbyReadyTracked || _lobbyReadyTrackedConversationUuid != uuid) {
|
||||
_lobbyReadyTracked = true;
|
||||
_lobbyReadyTrackedConversationUuid = uuid;
|
||||
_counterpartPresentWhenLobbyReady = snapshot.counterpartPresentInLobby;
|
||||
_sawEmptyLobbyWhenConnected = !snapshot.counterpartPresentInLobby;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_sawEmptyLobbyWhenConnected && snapshot.counterpartPresentInLobby) {
|
||||
_counterpartPresentWhenLobbyReady = false;
|
||||
_sawEmptyLobbyWhenConnected = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _notifyIncomingCallAcceptedIfNeeded(String conversationUuid) {
|
||||
return _videoCallManager.notifyIncomingCallAccepted(conversationUuid);
|
||||
}
|
||||
|
||||
/// Синхронизирует UI-состояние после [VideoCallManager.endCall] из notification /
|
||||
/// foreground task (без повторного disconnect).
|
||||
Future<void> onCallEndedExternally() async {
|
||||
@@ -144,7 +185,12 @@ class VideoCallSessionCubit extends BaseCubit<VideoCallSessionState> {
|
||||
!state.callStageEntered &&
|
||||
state.connectPhase != VideoCallConnectPhase.idle;
|
||||
|
||||
if (isIncomingAccepted && !isResumingActiveCall) {
|
||||
unawaited(_notifyIncomingCallAcceptedIfNeeded(conversationUuid));
|
||||
}
|
||||
|
||||
if (!isResumingActiveCall && !isSameCallConnecting) {
|
||||
_resetLobbyReadyTracking();
|
||||
await _systemPipCoordinator.disarm();
|
||||
}
|
||||
|
||||
@@ -421,6 +467,11 @@ class VideoCallSessionCubit extends BaseCubit<VideoCallSessionState> {
|
||||
if (state.connectPhase != VideoCallConnectPhase.connected) return;
|
||||
if (state.callStageEntered) return;
|
||||
|
||||
if (state.entryMode == VideoCallEntryMode.outgoing &&
|
||||
_counterpartPresentWhenLobbyReady) {
|
||||
unawaited(_notifyIncomingCallAcceptedIfNeeded(state.conversationUuid));
|
||||
}
|
||||
|
||||
final entered = await _videoCallManager.onEnterCallStage();
|
||||
if (!entered || isClosed) return;
|
||||
|
||||
|
||||
@@ -9,12 +9,67 @@ abstract final class VideoCallTopOverlay {
|
||||
'assets/icons/video_call/poor_connection.svg';
|
||||
static const String warningSvg = 'assets/icons/video_call/warning.svg';
|
||||
|
||||
static const double _poorConnectionIconSize = 44;
|
||||
static const double _poorConnectionGlyphSize = 22;
|
||||
|
||||
static OverlayEntry? _poorConnectionEntry;
|
||||
|
||||
static bool get isPoorConnectionIconVisible =>
|
||||
_poorConnectionEntry?.mounted ?? false;
|
||||
|
||||
static OverlayState? _resolveOverlayState(BuildContext context) {
|
||||
return VideoCallBannerOverlay.overlayState ??
|
||||
AppRouter.navigatorKey.currentState?.overlay ??
|
||||
Overlay.maybeOf(context, rootOverlay: true);
|
||||
}
|
||||
|
||||
/// Compact poor-connection indicator: red square with icon only.
|
||||
static void showPoorConnectionIcon(BuildContext context) {
|
||||
if (isPoorConnectionIconVisible) return;
|
||||
|
||||
hidePoorConnectionIcon();
|
||||
|
||||
final overlayState = _resolveOverlayState(context);
|
||||
late OverlayEntry entry;
|
||||
entry = OverlayEntry(
|
||||
builder: (ctx) {
|
||||
final topInset = MediaQuery.viewPaddingOf(ctx).top;
|
||||
return Positioned(
|
||||
left: 16,
|
||||
top: topInset + 8,
|
||||
width: _poorConnectionIconSize,
|
||||
height: _poorConnectionIconSize,
|
||||
child: Material(
|
||||
color: appTheme.strongRed,
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
elevation: 6,
|
||||
child: Center(
|
||||
child: SvgPicture.asset(
|
||||
poorConnectionSvg,
|
||||
width: _poorConnectionGlyphSize,
|
||||
height: _poorConnectionGlyphSize,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
_poorConnectionEntry = entry;
|
||||
if (overlayState == null) {
|
||||
_poorConnectionEntry = null;
|
||||
return;
|
||||
}
|
||||
overlayState.insert(entry);
|
||||
}
|
||||
|
||||
static void hidePoorConnectionIcon() {
|
||||
final entry = _poorConnectionEntry;
|
||||
_poorConnectionEntry = null;
|
||||
if (entry != null && entry.mounted) {
|
||||
entry.remove();
|
||||
}
|
||||
}
|
||||
|
||||
static OverlayEntry insertBanner(
|
||||
BuildContext context, {
|
||||
|
||||
Reference in New Issue
Block a user