Add TWP Softphone Flutter app and complete mobile backend API
Backend: Add /voice/token endpoint with AccessToken + VoiceGrant for
mobile VoIP, implement unhold_call() with call leg detection, wire FCM
push notifications into call queue and webhook missed call handlers,
add data-only FCM message support for Android background wake, and add
Twilio API Key / Push Credential settings fields.
Flutter app: Full softphone with Twilio Voice SDK integration, JWT auth
with auto-refresh, SSE real-time queue updates, FCM push notifications,
Material 3 UI with dashboard, active call screen, dialpad, and call
controls (mute/speaker/hold/transfer).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:01:23 -08:00
|
|
|
import 'dart:async';
|
2026-03-07 17:11:02 -08:00
|
|
|
import 'dart:io';
|
|
|
|
|
import 'package:dio/dio.dart';
|
2026-03-06 18:05:54 -08:00
|
|
|
import 'package:flutter/foundation.dart';
|
Add TWP Softphone Flutter app and complete mobile backend API
Backend: Add /voice/token endpoint with AccessToken + VoiceGrant for
mobile VoIP, implement unhold_call() with call leg detection, wire FCM
push notifications into call queue and webhook missed call handlers,
add data-only FCM message support for Android background wake, and add
Twilio API Key / Push Credential settings fields.
Flutter app: Full softphone with Twilio Voice SDK integration, JWT auth
with auto-refresh, SSE real-time queue updates, FCM push notifications,
Material 3 UI with dashboard, active call screen, dialpad, and call
controls (mute/speaker/hold/transfer).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:01:23 -08:00
|
|
|
import 'package:twilio_voice/twilio_voice.dart';
|
|
|
|
|
import 'api_client.dart';
|
|
|
|
|
|
|
|
|
|
class VoiceService {
|
|
|
|
|
final ApiClient _api;
|
|
|
|
|
Timer? _tokenRefreshTimer;
|
|
|
|
|
String? _identity;
|
2026-03-07 17:11:02 -08:00
|
|
|
String? _deviceToken;
|
|
|
|
|
StreamSubscription? _eventSubscription;
|
Add TWP Softphone Flutter app and complete mobile backend API
Backend: Add /voice/token endpoint with AccessToken + VoiceGrant for
mobile VoIP, implement unhold_call() with call leg detection, wire FCM
push notifications into call queue and webhook missed call handlers,
add data-only FCM message support for Android background wake, and add
Twilio API Key / Push Credential settings fields.
Flutter app: Full softphone with Twilio Voice SDK integration, JWT auth
with auto-refresh, SSE real-time queue updates, FCM push notifications,
Material 3 UI with dashboard, active call screen, dialpad, and call
controls (mute/speaker/hold/transfer).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:01:23 -08:00
|
|
|
|
|
|
|
|
final StreamController<CallEvent> _callEventController =
|
|
|
|
|
StreamController<CallEvent>.broadcast();
|
|
|
|
|
Stream<CallEvent> get callEvents => _callEventController.stream;
|
|
|
|
|
|
|
|
|
|
VoiceService(this._api);
|
|
|
|
|
|
2026-03-07 17:11:02 -08:00
|
|
|
Future<void> initialize({String? deviceToken}) async {
|
|
|
|
|
_deviceToken = deviceToken;
|
|
|
|
|
debugPrint('VoiceService.initialize: deviceToken=${deviceToken != null ? "present (${deviceToken.length} chars)" : "NULL"}');
|
|
|
|
|
|
|
|
|
|
// Request permissions (Android telecom requires these)
|
|
|
|
|
await TwilioVoice.instance.requestMicAccess();
|
|
|
|
|
if (!kIsWeb && Platform.isAndroid) {
|
|
|
|
|
await TwilioVoice.instance.requestReadPhoneStatePermission();
|
|
|
|
|
await TwilioVoice.instance.requestReadPhoneNumbersPermission();
|
|
|
|
|
await TwilioVoice.instance.requestCallPhonePermission();
|
|
|
|
|
await TwilioVoice.instance.requestManageOwnCallsPermission();
|
|
|
|
|
// Register phone account with Android telecom
|
|
|
|
|
// (enabling is handled by dashboard UI with a user-friendly dialog)
|
|
|
|
|
await TwilioVoice.instance.registerPhoneAccount();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch token and register
|
Add TWP Softphone Flutter app and complete mobile backend API
Backend: Add /voice/token endpoint with AccessToken + VoiceGrant for
mobile VoIP, implement unhold_call() with call leg detection, wire FCM
push notifications into call queue and webhook missed call handlers,
add data-only FCM message support for Android background wake, and add
Twilio API Key / Push Credential settings fields.
Flutter app: Full softphone with Twilio Voice SDK integration, JWT auth
with auto-refresh, SSE real-time queue updates, FCM push notifications,
Material 3 UI with dashboard, active call screen, dialpad, and call
controls (mute/speaker/hold/transfer).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:01:23 -08:00
|
|
|
await _fetchAndRegisterToken();
|
|
|
|
|
|
2026-03-07 17:11:02 -08:00
|
|
|
// Listen for call events (only once)
|
|
|
|
|
_eventSubscription ??= TwilioVoice.instance.callEventsListener.listen((event) {
|
|
|
|
|
if (!_callEventController.isClosed) {
|
|
|
|
|
_callEventController.add(event);
|
|
|
|
|
}
|
Add TWP Softphone Flutter app and complete mobile backend API
Backend: Add /voice/token endpoint with AccessToken + VoiceGrant for
mobile VoIP, implement unhold_call() with call leg detection, wire FCM
push notifications into call queue and webhook missed call handlers,
add data-only FCM message support for Android background wake, and add
Twilio API Key / Push Credential settings fields.
Flutter app: Full softphone with Twilio Voice SDK integration, JWT auth
with auto-refresh, SSE real-time queue updates, FCM push notifications,
Material 3 UI with dashboard, active call screen, dialpad, and call
controls (mute/speaker/hold/transfer).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:01:23 -08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 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;
|
2026-03-07 17:11:02 -08:00
|
|
|
await TwilioVoice.instance.setTokens(
|
|
|
|
|
accessToken: token,
|
|
|
|
|
deviceToken: _deviceToken ?? 'no-fcm',
|
|
|
|
|
);
|
Add TWP Softphone Flutter app and complete mobile backend API
Backend: Add /voice/token endpoint with AccessToken + VoiceGrant for
mobile VoIP, implement unhold_call() with call leg detection, wire FCM
push notifications into call queue and webhook missed call handlers,
add data-only FCM message support for Android background wake, and add
Twilio API Key / Push Credential settings fields.
Flutter app: Full softphone with Twilio Voice SDK integration, JWT auth
with auto-refresh, SSE real-time queue updates, FCM push notifications,
Material 3 UI with dashboard, active call screen, dialpad, and call
controls (mute/speaker/hold/transfer).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:01:23 -08:00
|
|
|
} catch (e) {
|
2026-03-06 18:05:54 -08:00
|
|
|
debugPrint('VoiceService._fetchAndRegisterToken error: $e');
|
2026-03-07 17:11:02 -08:00
|
|
|
if (e is DioException) debugPrint(' response: ${e.response?.data}');
|
Add TWP Softphone Flutter app and complete mobile backend API
Backend: Add /voice/token endpoint with AccessToken + VoiceGrant for
mobile VoIP, implement unhold_call() with call leg detection, wire FCM
push notifications into call queue and webhook missed call handlers,
add data-only FCM message support for Android background wake, and add
Twilio API Key / Push Credential settings fields.
Flutter app: Full softphone with Twilio Voice SDK integration, JWT auth
with auto-refresh, SSE real-time queue updates, FCM push notifications,
Material 3 UI with dashboard, active call screen, dialpad, and call
controls (mute/speaker/hold/transfer).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:01:23 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-06 18:05:54 -08:00
|
|
|
Future<bool> makeCall(String to, {String? callerId}) async {
|
|
|
|
|
try {
|
|
|
|
|
final extraOptions = <String, dynamic>{};
|
|
|
|
|
if (callerId != null && callerId.isNotEmpty) {
|
|
|
|
|
extraOptions['CallerId'] = callerId;
|
|
|
|
|
}
|
2026-03-07 17:11:02 -08:00
|
|
|
debugPrint('VoiceService.makeCall: to=$to, from=$_identity, extras=$extraOptions');
|
|
|
|
|
final result = await TwilioVoice.instance.call.place(
|
2026-03-06 18:05:54 -08:00
|
|
|
to: to,
|
|
|
|
|
from: _identity ?? '',
|
|
|
|
|
extraOptions: extraOptions,
|
|
|
|
|
) ?? false;
|
2026-03-07 17:11:02 -08:00
|
|
|
debugPrint('VoiceService.makeCall: result=$result');
|
|
|
|
|
return result;
|
2026-03-06 18:05:54 -08:00
|
|
|
} catch (e) {
|
|
|
|
|
debugPrint('VoiceService.makeCall error: $e');
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-03-06 15:32:22 -08:00
|
|
|
}
|
|
|
|
|
|
Add TWP Softphone Flutter app and complete mobile backend API
Backend: Add /voice/token endpoint with AccessToken + VoiceGrant for
mobile VoIP, implement unhold_call() with call leg detection, wire FCM
push notifications into call queue and webhook missed call handlers,
add data-only FCM message support for Android background wake, and add
Twilio API Key / Push Credential settings fields.
Flutter app: Full softphone with Twilio Voice SDK integration, JWT auth
with auto-refresh, SSE real-time queue updates, FCM push notifications,
Material 3 UI with dashboard, active call screen, dialpad, and call
controls (mute/speaker/hold/transfer).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:01:23 -08:00
|
|
|
Future<void> sendDigits(String digits) async {
|
|
|
|
|
await TwilioVoice.instance.call.sendDigits(digits);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-07 17:11:02 -08:00
|
|
|
Future<List<Map<String, dynamic>>> getQueueCalls(int queueId) async {
|
|
|
|
|
final response = await _api.dio.get('/queues/$queueId/calls');
|
|
|
|
|
return List<Map<String, dynamic>>.from(response.data['calls'] ?? []);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> acceptQueueCall(String callSid) async {
|
|
|
|
|
await _api.dio.post('/calls/$callSid/accept', data: {
|
|
|
|
|
'client_identity': _identity,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
Add TWP Softphone Flutter app and complete mobile backend API
Backend: Add /voice/token endpoint with AccessToken + VoiceGrant for
mobile VoIP, implement unhold_call() with call leg detection, wire FCM
push notifications into call queue and webhook missed call handlers,
add data-only FCM message support for Android background wake, and add
Twilio API Key / Push Credential settings fields.
Flutter app: Full softphone with Twilio Voice SDK integration, JWT auth
with auto-refresh, SSE real-time queue updates, FCM push notifications,
Material 3 UI with dashboard, active call screen, dialpad, and call
controls (mute/speaker/hold/transfer).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:01:23 -08:00
|
|
|
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();
|
2026-03-07 17:11:02 -08:00
|
|
|
_eventSubscription?.cancel();
|
|
|
|
|
_eventSubscription = null;
|
Add TWP Softphone Flutter app and complete mobile backend API
Backend: Add /voice/token endpoint with AccessToken + VoiceGrant for
mobile VoIP, implement unhold_call() with call leg detection, wire FCM
push notifications into call queue and webhook missed call handlers,
add data-only FCM message support for Android background wake, and add
Twilio API Key / Push Credential settings fields.
Flutter app: Full softphone with Twilio Voice SDK integration, JWT auth
with auto-refresh, SSE real-time queue updates, FCM push notifications,
Material 3 UI with dashboard, active call screen, dialpad, and call
controls (mute/speaker/hold/transfer).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 13:01:23 -08:00
|
|
|
_callEventController.close();
|
|
|
|
|
}
|
|
|
|
|
}
|