im so tired man

This commit is contained in:
Wowieee4
2026-05-28 11:27:06 +07:00
parent 66da2473e1
commit 6272ece15d
86 changed files with 1759 additions and 4090 deletions
@@ -0,0 +1,3 @@
Call application layer.
This layer owns call state orchestration. The current route keeps a compatibility screen while call side effects are delegated to core services.
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../app/injection_container.dart';
import '../../core/errors/friendly_error.dart';
import '../../core/services/call_service.dart';
import '../../core/services/haptic_service.dart';
import '../../core/services/tts_service.dart';
@@ -63,65 +64,71 @@ class _CallScreenState extends State<CallScreen>
unawaited(_finishRemoteEnded());
});
try {
final invite = await callService.startPairedCall();
if (!mounted) return;
if (invite == null) {
final invite = await runFriendly<Map<String, dynamic>>(
() => callService.startPairedCall(),
onError: _failCall,
fallback: 'Panggilan gagal. Server tidak merespons.',
);
if (!mounted) return;
if (invite == null) {
if (_phase != _CallPhase.failed) {
_failCall('Panggilan gagal. Pastikan sudah pairing dan server aktif.');
return;
}
_otherId = _asInt(invite['receiverId']);
_activeChannel = invite['channelName']?.toString();
setState(() => _phase = _CallPhase.calling);
sl<TtsService>().speak(
'Panggilan dikirim ke ${widget.targetLabel}. Menunggu jawaban.',
);
_startAcceptedPolling();
_ringTimeout?.cancel();
_ringTimeout = Timer(const Duration(seconds: 45), () {
if (!mounted || _phase == _CallPhase.connected) return;
_failCall('Panggilan tidak dijawab.');
});
} catch (_) {
if (!mounted) return;
_failCall('Panggilan gagal. Server tidak merespons.');
return;
}
_otherId = _asInt(invite['receiverId']);
_activeChannel = invite['channelName']?.toString();
setState(() => _phase = _CallPhase.calling);
sl<TtsService>().speak(
'Panggilan dikirim ke ${widget.targetLabel}. Menunggu jawaban.',
);
_startAcceptedPolling();
_ringTimeout?.cancel();
_ringTimeout = Timer(const Duration(seconds: 45), () {
if (!mounted || _phase == _CallPhase.connected) return;
_failCall('Panggilan tidak dijawab.');
});
}
void _startAcceptedPolling() {
_acceptedPoll?.cancel();
_acceptedPoll = Timer.periodic(const Duration(seconds: 2), (_) async {
if (!mounted || _activeChannel == null) return;
try {
final state = await sl<CallService>()
final state = await runFriendly<Map<String, dynamic>>(
() => sl<CallService>()
.getCallState(_activeChannel)
.timeout(const Duration(seconds: 3));
final status = state?['status']?.toString();
if (status == 'ENDED') {
await _finishRemoteEnded();
return;
}
if (status == 'ACCEPTED') {
_markRemoteConnected();
return;
}
final accepted = await sl<CallService>()
.getAcceptedCall()
.timeout(const Duration(seconds: 3));
if (accepted?['type']?.toString() != 'CALL_ACCEPTED') return;
final channel = accepted?['channelName']?.toString();
if (_activeChannel != null &&
channel != null &&
channel.isNotEmpty &&
channel != _activeChannel) {
return;
}
_markRemoteConnected();
} catch (_) {
// Keep ringing; a short network hiccup should not cancel the call UI.
.timeout(const Duration(seconds: 3)),
onError: (_) {},
fallback: 'Polling panggilan gagal.',
);
if (state == null) return;
final status = state['status']?.toString();
if (status == 'ENDED') {
await _finishRemoteEnded();
return;
}
if (status == 'ACCEPTED') {
_markRemoteConnected();
return;
}
final accepted = await runFriendly<Map<String, dynamic>>(
() => sl<CallService>()
.getAcceptedCall()
.timeout(const Duration(seconds: 3)),
onError: (_) {},
fallback: 'Polling panggilan diterima gagal.',
);
if (accepted?['type']?.toString() != 'CALL_ACCEPTED') return;
final channel = accepted?['channelName']?.toString();
if (_activeChannel != null &&
channel != null &&
channel.isNotEmpty &&
channel != _activeChannel) {
return;
}
_markRemoteConnected();
});
}
@@ -319,7 +326,12 @@ class _IncomingCallScreenState extends State<IncomingCallScreen> {
setState(() => _responding = true);
sl<TtsService>().speak('Menerima panggilan.');
final joined = await _joinIncomingChannel();
final joined = await runFriendly<bool>(
() => _joinIncomingChannel(),
onError: (_) {},
fallback: 'Panggilan gagal tersambung.',
) ??
false;
if (!mounted) return;
if (!joined || _joinedChannel == null || widget.callerId == null) {
setState(() {
@@ -350,14 +362,16 @@ class _IncomingCallScreenState extends State<IncomingCallScreen> {
_statePoll?.cancel();
_statePoll = Timer.periodic(const Duration(seconds: 2), (_) async {
if (!mounted || _joinedChannel == null) return;
try {
final state = await sl<CallService>()
final state = await runFriendly<Map<String, dynamic>>(
() => sl<CallService>()
.getCallState(_joinedChannel)
.timeout(const Duration(seconds: 3));
if (state?['status']?.toString() == 'ENDED') {
await _finishIncomingRemoteEnded();
}
} catch (_) {}
.timeout(const Duration(seconds: 3)),
onError: (_) {},
fallback: 'Polling panggilan masuk gagal.',
);
if (state?['status']?.toString() == 'ENDED') {
await _finishIncomingRemoteEnded();
}
});
}
@@ -0,0 +1,3 @@
Call data layer.
This layer is reserved for call remote data sources and repository implementations over `/shared/call/**`.
@@ -0,0 +1,3 @@
Call domain layer.
This layer is reserved for call session entities, repository contracts, and call use cases.