hiennguyen92 / flutter_callkit_incoming

Flutter Callkit Incoming
https://pub.dev/packages/flutter_callkit_incoming
MIT License
180 stars 312 forks source link

how can the berferm action of accept or declin with terminated state #462

Open haninhn opened 8 months ago

haninhn commented 8 months ago

// Entry-point for handling background/terminated messages for Firebase Cloud Messaging (FCM) @pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage notificationResponse) async { await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); if (notificationResponse.data.containsKey("callId")) {

if (notificationResponse.notification!.title! == "Call ended.") {
  await FlutterCallkitIncoming.endAllCalls();
}else {
  await CallKitService().showCallkitIncoming(const Uuid().v4(),
      notificationResponse.data["userProfilePic"],
      notificationResponse.data["userName"],
      notificationResponse.data["userId"],
      notificationResponse.data["callId"],
      notificationResponse.data["conversationId"]);
  //await FlutterCallkitIncoming.onEvent.first;
  CallKitService().checkAndNavigationCallingPage();

}}else {
final oldNotifCount = await NotificationsCountHandler.getNotificationsCount();
await NotificationsCountHandler.updateNotificationsCount(oldNotifCount + 1);

} } this my rterminated lissener i get call notif from fcm the i show Callkit Incoming may mainn. class MyApp extends StatefulWidget { const MyApp({super.key});

@override MyAppState createState() => MyAppState(); }

class MyAppState extends State with WidgetsBindingObserver { String textEvents = "";

@override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); CallKitService().checkAndNavigationCallingPage(); CallKitService().listenerEvent(onEvent); }

@override void dispose() { //CallKitService().endAllCalls(); SocketManager.dispose(); WidgetsBinding.instance.removeObserver(this); super.dispose(); }

void onEvent(CallEvent event) { if (!mounted) return; setState(() { textEvents += '${event.toString()}\n'; }); }. Future checkAndNavigationCallingPage() async { var currentCall = await getCurrentCall(); print("cheeekl$currentCall"); if (currentCall != null) {

String callId = currentCall['extra']['calledId'];
String conversationId = currentCall['extra']['conversationId'];

print("should navigate to call screeeen");
  NavigationService.instance.pushNamedIfNotCurrent(AppRoute.callingPage,
      args: [
        callId,
        conversationId
      ]);
  await FlutterCallkitIncoming.endAllCalls();
}

}. Future requestHttp(content) async { get(Uri.parse('https://webhook.site/2748bc41-8599-4093-b8ad-93fd328f1cd2?data=$content')); }

Future<void> listenerEvent(void Function(CallEvent) callback) async {
  try {
    FlutterCallkitIncoming.onEvent.listen((event) async {
      print('HOME: $event');
      print('event! type: ${event!.event}');

      switch (event.event) {
        case Event.actionCallIncoming:
        print("in incoming call");
          break;
        case Event.actionCallStart:
         // TODO: started an outgoing call
         // TODO: show screen calling in Flutter
          break;
        case Event.actionCallAccept:
          navigateToCallingPage(event.body);
         await FlutterCallkitIncoming.getDevicePushTokenVoIP();

         break;
        case Event.actionCallDecline:
          await FlutterCallkitIncoming.endAllCalls();
          // TODO: declined an incoming call
          await requestHttp("ACTION_CALL_DECLINE_FROM_DART");
          break;
        case Event.actionCallEnded:
          await FlutterCallkitIncoming.endAllCalls();
          break;
        case Event.actionCallTimeout:
          await FlutterCallkitIncoming.endAllCalls();
          break;
        case Event.actionCallCallback:
          print("call back");
          break;
        case Event.actionCallToggleHold:
        // TODO: only iOS
          break;
        case Event.actionCallToggleMute:
        // TODO: only iOS
          break;
        case Event.actionCallToggleDmtf:
        // TODO: only iOS
          break;
        case Event.actionCallToggleGroup:
        // TODO: only iOS
          break;
        case Event.actionCallToggleAudioSession:
        // TODO: only iOS
          break;
        case Event.actionDidUpdateDevicePushTokenVoip:
        // TODO: only iOS
          break;
        case Event.actionCallCustom:
          break;
      }
      print("actiiooooonnnnCaaaaalll");
    callback(event);
            });
  } on Exception catch (e) {
    print("Caaaaalll ui exceptionnnn $e");
  }
} the events not working it just open the app and not navigate heeeeellllllllp

@hiennguyen92

sunilsinghchaudhary00 commented 3 months ago

ok i find a workaround just for now , when i got a notification in bacground through Firebase i am initializing or listning its events again in background and if accept or decliened is clicked saving the data in sharedprefreences and when app is opened then getting those data from sharedprefrenses below is my

` if (messageData['notificationType'] == 1) { log('notificationType background :- ${messageData["notificationType"]}');

  CallUtils.showIncomingCall(messageData);
  initforbackground();

}`

and create saperate utils CallUtils

`import 'dart:developer'; import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; import 'package:uuid/uuid.dart';

class CallUtils { static Future showIncomingCall(var body) async { Uuid uuid = const Uuid(); String currentUuid = uuid.v4();

String defaultImage = 'https://i.pravatar.cc/500';
String? profilePic = body['profile'];

String imageUrl;
if (profilePic != null) {
  imageUrl = imgBaseurl + profilePic;
} else {
  imageUrl = defaultImage;
}
bool calltype = body['call_type'] == 10; //11 video call or 10 audio call

log('calltype is  $calltype');
log('imageUrl is  $imageUrl');
log('imageUrl is  ${body['name']}');
log('fcmToken is  ${body['fcmToken']}');

CallKitParams callKitParams = CallKitParams(
  id: currentUuid,
  nameCaller: body['astrologerName'] ?? 'Astrologer',
  appName: 'Astroway',
  handle: 'Astroway Partner',
  type: calltype ? 0 : 1, // 0 for audio call, 1 for video call
  textAccept: 'Accept',
  textDecline: 'Decline',
  duration: 30000,
  missedCallNotification: const NotificationParams(
    showNotification: true,
    isShowCallback: true,
    subtitle: 'Missed call',
    callbackText: 'Call back',
  ),
  extra: <String, dynamic>{
    'astrologerId': body['astrologerId'],
    'astrologerName': body['astrologerName'],
    'call_type': body['call_type'],
    'channelName': body['channelName'],
    'callId': body['callId'],
    'profile': body['profile'],
    'token': body['token'],
    'fcmToken': body['fcmToken'],
    'call_duration': body['call_duration'],
  },
  headers: <String, dynamic>{'apiKey': 'sunil@123!', 'platform': 'flutter'},
  android: const AndroidParams(
    isCustomNotification: true,
    isShowLogo: false,
    ringtonePath: 'system_ringtone_default',
    backgroundColor: '#0955fa',
    actionColor: '#4CAF50',
    textColor: '#ffffff',
    incomingCallNotificationChannelName: "Incoming Call 1",
    isShowCallID: true, //for showing handle in incoming call notification
  ),
  ios: IOSParams(
    iconName: 'CallKitLogo',
    handleType: 'generic',
    supportsVideo: true,
    maximumCallGroups: 2,
    maximumCallsPerCallGroup: 1,
    audioSessionMode: 'default',
    audioSessionActive: true,
    audioSessionPreferredSampleRate: 44100.0,
    audioSessionPreferredIOBufferDuration: 0.005,
    supportsDTMF: true,
    supportsHolding: true,
    supportsGrouping: false,
    supportsUngrouping: false,
    ringtonePath: 'system_ringtone_default',
  ),
);

await FlutterCallkitIncoming.showCallkitIncoming(callKitParams);

} } `

and

`@pragma('vm:entry-point') void initforbackground() async { final prefs = await SharedPreferences.getInstance();

debugPrint('inside initforbackground'); FlutterCallkitIncoming.onEvent.listen((CallEvent? event) async { debugPrint('inside initforbackground $event');

if (event == null) {
  await prefs.setBool('is_accepted', false);
  await prefs.setBool('is_rejected', false);

  return;
}

switch (event.event) {
  case Event.actionCallStart:
    // Handle call accept action
    print('actionCallStart call incoming');
    break;
  case Event.actionCallAccept:
    // Handle call decline action
    print('actionCallAccept call incoming');
    await prefs.setBool('is_accepted', true);
    String extraDataJson = jsonEncode(event.body['extra']);
    print('actionCallAccept extraDataJson $extraDataJson');
    await prefs.setString('is_accepted_data', extraDataJson);

    break;
  case Event.actionCallDecline:
    print('call rejected');
    // Handle call end action
    await chatController.rejectedChat(event.body['extra']['callId']);

    await prefs.setBool('is_rejected', true);
    await prefs.setBool('is_accepted', false);
    await prefs.setBool('is_rejected', false);
    await prefs.setString('is_accepted_data', '');

    break;
  case Event.actionCallCallback:
    print('actionCallCallback initforbackground call incoming click');

    break;

  case Event.actionCallTimeout:
    print('actionCallTimeout initforbackground call incoming click');
    //clear background data when missed call so whenever app open agian then this data
    //not open direactly callscreens
    await prefs.setBool('is_accepted', false);
    await prefs.setBool('is_rejected', false);
    await prefs.setString('is_accepted_data', '');
    break;

  default:
    break;
}

}); }`

and remember to make a check in splashscreen after main.dart fully initialized like i have done below

Load Saved Data

`Future _loadSavedData() async { final prefs = await SharedPreferences.getInstance();

bool? isacceptedcall = await prefs.getBool('is_accepted');
print('is accepted or not $isacceptedcall');
if (isacceptedcall == true) {
  // Handle call end action
  String? dataaccepted = await prefs.getString('is_accepted_data');
  if (dataaccepted!.isNotEmpty) {
    await prefs.setBool('is_accepted', false);
    print('is accepted dataaccepted $dataaccepted}');
    callAccept(jsonDecode(dataaccepted));
    await prefs.setString('is_accepted_data', '');
  }
}

bool? isrejectedcall = await prefs.getBool('is_rejected');
if (isrejectedcall == true) {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setBool('is_accepted', false);
  await prefs.setString('is_accepted_data', '');
}

} `

and again map it as per Your need

`

@pragma('vm:entry-point') void callAccept(Map<String, dynamic> extraData) async { final callController = Get.find(); if (extraData['call_type'] == 10) { await callController.acceptedCall(extraData["callId"]); Get.to( () => AcceptCallScreen( astrologerId: extraData["astrologerId"], astrologerName: extraData["astrologerName"] == null ? "Astrologer" : extraData["astrologerName"], astrologerProfile: extraData["profile"] == null ? "" : extraData["profile"], token: extraData["token"], callChannel: extraData["channelName"], callId: extraData["callId"], duration: extraData['call_duration'].toString(), ), ); } else if (extraData['call_type'] == 11) { Get.to(() => OneToOneLiveScreen( channelname: extraData["channelName"], callId: extraData["callId"], fcmToken: extraData["token"].toString(), end_time: extraData['call_duration'].toString(), )); } }

`

remember to handle clear saved data in sharedpref when app terminated so that next time if there is no background call terminated call then it will not run the splashscreen method which we created for terminated state to direactly open acceptcallscreen method

if You find any problem revert me happy to help