All checks were successful
Create Release / build (push) Successful in 4s
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>
108 lines
2.8 KiB
Dart
108 lines
2.8 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import '../models/user.dart';
|
|
import '../services/api_client.dart';
|
|
import '../services/auth_service.dart';
|
|
import '../services/voice_service.dart';
|
|
import '../services/push_notification_service.dart';
|
|
import '../services/sse_service.dart';
|
|
|
|
enum AuthState { unauthenticated, authenticating, authenticated }
|
|
|
|
class AuthProvider extends ChangeNotifier {
|
|
final ApiClient _apiClient;
|
|
late final AuthService _authService;
|
|
late final VoiceService _voiceService;
|
|
late final PushNotificationService _pushService;
|
|
late final SseService _sseService;
|
|
|
|
AuthState _state = AuthState.unauthenticated;
|
|
User? _user;
|
|
String? _error;
|
|
|
|
AuthState get state => _state;
|
|
User? get user => _user;
|
|
String? get error => _error;
|
|
VoiceService get voiceService => _voiceService;
|
|
SseService get sseService => _sseService;
|
|
ApiClient get apiClient => _apiClient;
|
|
|
|
AuthProvider(this._apiClient) {
|
|
_authService = AuthService(_apiClient);
|
|
_voiceService = VoiceService(_apiClient);
|
|
_pushService = PushNotificationService(_apiClient);
|
|
_sseService = SseService(_apiClient);
|
|
|
|
_apiClient.onForceLogout = _handleForceLogout;
|
|
}
|
|
|
|
Future<void> tryRestoreSession() async {
|
|
final restored = await _authService.tryRestoreSession();
|
|
if (restored) {
|
|
_state = AuthState.authenticated;
|
|
await _initializeServices();
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
Future<void> login(String serverUrl, String username, String password) async {
|
|
_state = AuthState.authenticating;
|
|
_error = null;
|
|
notifyListeners();
|
|
|
|
try {
|
|
_user = await _authService.login(serverUrl, username, password);
|
|
_state = AuthState.authenticated;
|
|
await _initializeServices();
|
|
} catch (e) {
|
|
_state = AuthState.unauthenticated;
|
|
_error = e.toString().replaceFirst('Exception: ', '');
|
|
}
|
|
notifyListeners();
|
|
}
|
|
|
|
Future<void> _initializeServices() async {
|
|
try {
|
|
await _pushService.initialize();
|
|
} catch (_) {}
|
|
try {
|
|
await _voiceService.initialize();
|
|
} catch (_) {}
|
|
try {
|
|
await _sseService.connect();
|
|
} catch (_) {}
|
|
}
|
|
|
|
Future<void> logout() async {
|
|
_voiceService.dispose();
|
|
_sseService.disconnect();
|
|
await _authService.logout();
|
|
|
|
_state = AuthState.unauthenticated;
|
|
_user = null;
|
|
_error = null;
|
|
|
|
// Re-create services for potential re-login
|
|
_voiceService = VoiceService(_apiClient);
|
|
_pushService = PushNotificationService(_apiClient);
|
|
_sseService = SseService(_apiClient);
|
|
|
|
notifyListeners();
|
|
}
|
|
|
|
void _handleForceLogout() {
|
|
_state = AuthState.unauthenticated;
|
|
_user = null;
|
|
_error = 'Session expired. Please log in again.';
|
|
_sseService.disconnect();
|
|
notifyListeners();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_authService.dispose();
|
|
_voiceService.dispose();
|
|
_sseService.dispose();
|
|
super.dispose();
|
|
}
|
|
}
|