import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:twilio_voice/twilio_voice.dart'; import '../models/call_info.dart'; import '../services/voice_service.dart'; class CallProvider extends ChangeNotifier { final VoiceService _voiceService; CallInfo _callInfo = const CallInfo(); Timer? _durationTimer; StreamSubscription? _eventSub; DateTime? _connectedAt; CallInfo get callInfo => _callInfo; CallProvider(this._voiceService) { _eventSub = _voiceService.callEvents.listen(_handleCallEvent); } void _handleCallEvent(CallEvent event) { switch (event) { case CallEvent.incoming: _callInfo = _callInfo.copyWith( state: CallState.ringing, ); break; case CallEvent.ringing: _callInfo = _callInfo.copyWith(state: CallState.connecting); break; case CallEvent.connected: _connectedAt = DateTime.now(); _callInfo = _callInfo.copyWith(state: CallState.connected); _startDurationTimer(); break; case CallEvent.callEnded: _stopDurationTimer(); _callInfo = const CallInfo(); // reset to idle break; case CallEvent.returningCall: _callInfo = _callInfo.copyWith(state: CallState.connecting); break; case CallEvent.reconnecting: break; case CallEvent.reconnected: break; default: break; } // Update caller info from active call final call = TwilioVoice.instance.call; final active = call.activeCall; if (active != null) { _callInfo = _callInfo.copyWith( callerNumber: active.from, ); // Fetch SID asynchronously call.getSid().then((sid) { if (sid != null && sid != _callInfo.callSid) { _callInfo = _callInfo.copyWith(callSid: sid); notifyListeners(); } }); } notifyListeners(); } void _startDurationTimer() { _durationTimer?.cancel(); _durationTimer = Timer.periodic(const Duration(seconds: 1), (_) { if (_connectedAt != null) { _callInfo = _callInfo.copyWith( duration: DateTime.now().difference(_connectedAt!), ); notifyListeners(); } }); } void _stopDurationTimer() { _durationTimer?.cancel(); _connectedAt = null; } Future answer() => _voiceService.answer(); Future reject() => _voiceService.reject(); Future hangUp() => _voiceService.hangUp(); Future toggleMute() async { final newMuted = !_callInfo.isMuted; await _voiceService.toggleMute(newMuted); _callInfo = _callInfo.copyWith(isMuted: newMuted); notifyListeners(); } Future toggleSpeaker() async { final newSpeaker = !_callInfo.isSpeakerOn; await _voiceService.toggleSpeaker(newSpeaker); _callInfo = _callInfo.copyWith(isSpeakerOn: newSpeaker); notifyListeners(); } Future sendDigits(String digits) => _voiceService.sendDigits(digits); Future makeCall(String number, {String? callerId}) async { _callInfo = _callInfo.copyWith( state: CallState.connecting, callerNumber: number, ); notifyListeners(); await _voiceService.makeCall(number, callerId: callerId); } Future holdCall() async { final sid = _callInfo.callSid; if (sid == null) return; await _voiceService.holdCall(sid); _callInfo = _callInfo.copyWith(isOnHold: true); notifyListeners(); } Future unholdCall() async { final sid = _callInfo.callSid; if (sid == null) return; await _voiceService.unholdCall(sid); _callInfo = _callInfo.copyWith(isOnHold: false); notifyListeners(); } Future transferCall(String target) async { final sid = _callInfo.callSid; if (sid == null) return; await _voiceService.transferCall(sid, target); } @override void dispose() { _stopDurationTimer(); _eventSub?.cancel(); super.dispose(); } }