import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'dart:typed_data'; class CallService { static const MethodChannel _channel = MethodChannel('call_service'); static String? currentPhoneNumber; static bool _isCallPageVisible = false; static final GlobalKey navigatorKey = GlobalKey(); // Private constructor CallService._privateConstructor() { _initializeMethodCallHandler(); } // Singleton instance static final CallService _instance = CallService._privateConstructor(); // Factory constructor to return the same instance factory CallService() { return _instance; } void _initializeMethodCallHandler() { _channel.setMethodCallHandler((call) async { final context = navigatorKey.currentContext; print('CallService: Received method ${call.method} with args ${call.arguments}'); if (context == null) { print('CallService: Navigator context is null, cannot navigate'); return; } switch (call.method) { case "callAdded": _handleCallAdded(context, call.arguments); break; case "callStateChanged": _handleCallStateChanged(context, call.arguments); break; case "callEnded": case "callRemoved": _handleCallEnded(context); break; } }); } void _handleCallAdded(BuildContext context, dynamic arguments) { final phoneNumber = arguments["callId"] as String; final state = arguments["state"] as String; currentPhoneNumber = phoneNumber.replaceFirst('tel:', ''); print('CallService: Call added, number: $currentPhoneNumber, state: $state'); if (state == "ringing") { _navigateToIncomingCallPage(context); } else { _navigateToCallPage(context); } } void _handleCallStateChanged(BuildContext context, dynamic arguments) { final state = arguments["state"] as String; print('CallService: State changed to $state'); if (state == "disconnected" || state == "disconnecting") { _closeCallPage(context); } else if (state == "active" || state == "dialing") { _navigateToCallPage(context); } else if (state == "ringing") { _navigateToIncomingCallPage(context); } } void _handleCallEnded(BuildContext context) { print('CallService: Call ended/removed'); _closeCallPage(context); currentPhoneNumber = null; } void _navigateToCallPage(BuildContext context) { if (_isCallPageVisible && ModalRoute.of(context)?.settings.name == '/call') { print('CallService: CallPage already visible, skipping navigation'); return; } print('CallService: Navigating to CallPage'); Navigator.pushReplacementNamed( context, '/call', arguments: { 'displayName': currentPhoneNumber!, 'phoneNumber': currentPhoneNumber!, 'thumbnail': null, } ).then((_) { _isCallPageVisible = false; }); _isCallPageVisible = true; } void _navigateToIncomingCallPage(BuildContext context) { if (_isCallPageVisible && ModalRoute.of(context)?.settings.name == '/incoming_call') { print('CallService: IncomingCallPage already visible, skipping navigation'); return; } print('CallService: Navigating to IncomingCallPage'); Navigator.pushNamed( context, '/incoming_call', arguments: { 'displayName': currentPhoneNumber!, 'phoneNumber': currentPhoneNumber!, 'thumbnail': null, } ).then((_) { _isCallPageVisible = false; }); _isCallPageVisible = true; } void _closeCallPage(BuildContext context) { if (!_isCallPageVisible) { print('CallService: CallPage not visible, skipping pop'); return; } if (Navigator.canPop(context)) { print('CallService: Popping CallPage'); Navigator.pop(context); _isCallPageVisible = false; } } Future makeGsmCall( BuildContext context, { required String phoneNumber, String? displayName, Uint8List? thumbnail, }) async { try { currentPhoneNumber = phoneNumber; print('CallService: Making GSM call to $phoneNumber'); final result = await _channel.invokeMethod('makeGsmCall', {"phoneNumber": phoneNumber}); print('CallService: makeGsmCall result: $result'); if (result["status"] != "calling") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to initiate call")), ); } } catch (e) { print("CallService: Error making call: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error making call: $e")), ); rethrow; } } Future hangUpCall(BuildContext context) async { try { print('CallService: Hanging up call'); final result = await _channel.invokeMethod('hangUpCall'); print('CallService: hangUpCall result: $result'); if (result["status"] != "ended") { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Failed to end call")), ); } } catch (e) { print("CallService: Error hanging up call: $e"); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Error hanging up call: $e")), ); rethrow; } } }