All checks were successful
Create Release / build (push) Successful in 4s
- Fix queue queries in mobile API and SSE to use twp_group_members (matching browser phone) instead of twp_queue_assignments - Auto-create personal queues if user has no extension - Make all model JSON parsing null-safe (handle null, string ints, bools) - Add AutofillGroup and autofill hints to login form - Add outbound calling with dialpad bottom sheet on dashboard Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
90 lines
2.3 KiB
Dart
90 lines
2.3 KiB
Dart
import 'dart:async';
|
|
import 'package:twilio_voice/twilio_voice.dart';
|
|
import 'api_client.dart';
|
|
|
|
class VoiceService {
|
|
final ApiClient _api;
|
|
Timer? _tokenRefreshTimer;
|
|
String? _identity;
|
|
|
|
final StreamController<CallEvent> _callEventController =
|
|
StreamController<CallEvent>.broadcast();
|
|
Stream<CallEvent> get callEvents => _callEventController.stream;
|
|
|
|
VoiceService(this._api);
|
|
|
|
Future<void> initialize() async {
|
|
await _fetchAndRegisterToken();
|
|
|
|
TwilioVoice.instance.callEventsListener.listen((event) {
|
|
_callEventController.add(event);
|
|
});
|
|
|
|
// Refresh token every 50 minutes
|
|
_tokenRefreshTimer?.cancel();
|
|
_tokenRefreshTimer = Timer.periodic(
|
|
const Duration(minutes: 50),
|
|
(_) => _fetchAndRegisterToken(),
|
|
);
|
|
}
|
|
|
|
Future<void> _fetchAndRegisterToken() async {
|
|
try {
|
|
final response = await _api.dio.get('/voice/token');
|
|
final data = response.data;
|
|
final token = data['token'] as String;
|
|
_identity = data['identity'] as String;
|
|
await TwilioVoice.instance.setTokens(accessToken: token);
|
|
} catch (e) {
|
|
// Token fetch failed - will retry on next interval
|
|
}
|
|
}
|
|
|
|
String? get identity => _identity;
|
|
|
|
Future<void> answer() async {
|
|
await TwilioVoice.instance.call.answer();
|
|
}
|
|
|
|
Future<void> reject() async {
|
|
await TwilioVoice.instance.call.hangUp();
|
|
}
|
|
|
|
Future<void> hangUp() async {
|
|
await TwilioVoice.instance.call.hangUp();
|
|
}
|
|
|
|
Future<void> toggleMute(bool mute) async {
|
|
await TwilioVoice.instance.call.toggleMute(mute);
|
|
}
|
|
|
|
Future<void> toggleSpeaker(bool speaker) async {
|
|
await TwilioVoice.instance.call.toggleSpeaker(speaker);
|
|
}
|
|
|
|
Future<bool> makeCall(String to) async {
|
|
return await TwilioVoice.instance.call.place(to: to, from: _identity ?? '') ?? false;
|
|
}
|
|
|
|
Future<void> sendDigits(String digits) async {
|
|
await TwilioVoice.instance.call.sendDigits(digits);
|
|
}
|
|
|
|
Future<void> holdCall(String callSid) async {
|
|
await _api.dio.post('/calls/$callSid/hold');
|
|
}
|
|
|
|
Future<void> unholdCall(String callSid) async {
|
|
await _api.dio.post('/calls/$callSid/unhold');
|
|
}
|
|
|
|
Future<void> transferCall(String callSid, String target) async {
|
|
await _api.dio.post('/calls/$callSid/transfer', data: {'target': target});
|
|
}
|
|
|
|
void dispose() {
|
|
_tokenRefreshTimer?.cancel();
|
|
_callEventController.close();
|
|
}
|
|
}
|