Add FCM push notifications, queue alerts, caller ID fixes, and auto-revert agent status
All checks were successful
Create Release / build (push) Successful in 6s
All checks were successful
Create Release / build (push) Successful in 6s
Server-side: - Add push credential auto-creation for FCM incoming call notifications - Add queue alert FCM notifications (data-only for background delivery) - Add queue alert cancellation on call accept/disconnect - Fix caller ID to show caller's number instead of Twilio number - Fix FCM token storage when refresh_token is null - Add pre_call_status tracking to revert agent status 30s after call ends - Add SSE fallback polling for mobile app connectivity Mobile app: - Add Android telecom permissions and phone account registration - Add VoiceFirebaseMessagingService for incoming call push handling - Add insistent queue alert notifications with custom sound - Fix caller number display on active call screen - Add caller ID selection dropdown on dashboard - Add phone numbers endpoint and provider support - Add unit tests for CallInfo, QueueState, and CallProvider - Remove local.properties from tracking, add .gitignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,13 +1,60 @@
|
||||
import 'dart:typed_data';
|
||||
import 'package:firebase_core/firebase_core.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'api_client.dart';
|
||||
|
||||
/// Notification ID for queue alerts (fixed so we can cancel it).
|
||||
const int _queueAlertNotificationId = 9001;
|
||||
|
||||
/// Background handler — must be top-level function.
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
||||
await Firebase.initializeApp();
|
||||
// VoIP pushes are handled natively by twilio_voice plugin.
|
||||
// Other data messages can show a local notification if needed.
|
||||
final data = message.data;
|
||||
final type = data['type'];
|
||||
|
||||
if (type == 'queue_alert') {
|
||||
await _showQueueAlertNotification(data);
|
||||
} else if (type == 'queue_alert_cancel') {
|
||||
final plugin = FlutterLocalNotificationsPlugin();
|
||||
await plugin.cancel(_queueAlertNotificationId);
|
||||
}
|
||||
// VoIP pushes handled natively by twilio_voice plugin.
|
||||
}
|
||||
|
||||
/// Show an insistent queue alert notification (works from background handler too).
|
||||
Future<void> _showQueueAlertNotification(Map<String, dynamic> data) async {
|
||||
final plugin = FlutterLocalNotificationsPlugin();
|
||||
|
||||
final title = data['title'] ?? 'Call Waiting';
|
||||
final body = data['body'] ?? 'New call in queue';
|
||||
|
||||
final androidDetails = AndroidNotificationDetails(
|
||||
'twp_queue_alerts',
|
||||
'Queue Alerts',
|
||||
channelDescription: 'Alerts when calls are waiting in queue',
|
||||
importance: Importance.max,
|
||||
priority: Priority.max,
|
||||
playSound: true,
|
||||
sound: const RawResourceAndroidNotificationSound('queue_alert'),
|
||||
enableVibration: true,
|
||||
vibrationPattern: Int64List.fromList([0, 500, 200, 500, 200, 500]),
|
||||
ongoing: true,
|
||||
autoCancel: false,
|
||||
category: AndroidNotificationCategory.alarm,
|
||||
additionalFlags: Int32List.fromList([4]), // FLAG_INSISTENT = 4
|
||||
fullScreenIntent: true,
|
||||
visibility: NotificationVisibility.public,
|
||||
);
|
||||
|
||||
await plugin.show(
|
||||
_queueAlertNotificationId,
|
||||
title,
|
||||
body,
|
||||
NotificationDetails(android: androidDetails),
|
||||
);
|
||||
}
|
||||
|
||||
class PushNotificationService {
|
||||
@@ -15,6 +62,9 @@ class PushNotificationService {
|
||||
final FirebaseMessaging _messaging = FirebaseMessaging.instance;
|
||||
final FlutterLocalNotificationsPlugin _localNotifications =
|
||||
FlutterLocalNotificationsPlugin();
|
||||
String? _fcmToken;
|
||||
|
||||
String? get fcmToken => _fcmToken;
|
||||
|
||||
PushNotificationService(this._api);
|
||||
|
||||
@@ -36,8 +86,12 @@ class PushNotificationService {
|
||||
|
||||
// Get and register FCM token
|
||||
final token = await _messaging.getToken();
|
||||
debugPrint('FCM token: ${token != null ? "${token.substring(0, 20)}..." : "NULL"}');
|
||||
if (token != null) {
|
||||
_fcmToken = token;
|
||||
await _registerToken(token);
|
||||
} else {
|
||||
debugPrint('FCM: Failed to get token - Firebase may not be configured correctly');
|
||||
}
|
||||
|
||||
// Listen for token refresh
|
||||
@@ -60,7 +114,19 @@ class PushNotificationService {
|
||||
// VoIP incoming_call is handled by twilio_voice natively
|
||||
if (type == 'incoming_call') return;
|
||||
|
||||
// Show local notification for other types (missed call, queue alert, etc.)
|
||||
// Queue alert — show insistent notification
|
||||
if (type == 'queue_alert') {
|
||||
_showQueueAlertNotification(data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue alert cancel — dismiss notification
|
||||
if (type == 'queue_alert_cancel') {
|
||||
_localNotifications.cancel(_queueAlertNotificationId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show local notification for other types (missed call, etc.)
|
||||
_localNotifications.show(
|
||||
message.hashCode,
|
||||
data['title'] ?? 'TWP Softphone',
|
||||
@@ -75,4 +141,9 @@ class PushNotificationService {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Cancel any active queue alert (called when agent accepts a call in-app).
|
||||
void cancelQueueAlert() {
|
||||
_localNotifications.cancel(_queueAlertNotificationId);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user