hiennguyen92 / flutter_callkit_incoming

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

Showing duplicate call in andriod #531

Open ankit-deligence3112 opened 6 months ago

ankit-deligence3112 commented 6 months ago

some time its show again same call when user pick or decline the call , i also check the call length and firebase message coming time ,why its happened can you resolve this ? my code is // ignore_for_file: use_build_context_synchronously import 'dart:convert';

import 'package:agora_uikit/agora_uikit.dart'; import 'package:bspotz/app/modules/authentication/datasource/models/main_user.dart'; import 'package:bspotz/app/modules/authentication/presentation/providers/user_provider.dart'; import 'package:bspotz/app/modules/explore/datasource/model/map_markers_model.dart'; import 'package:bspotz/app/modules/explore/presentation/pages/explore_map.dart'; import 'package:bspotz/app/modules/home/presentation/pages/main_events_page.dart'; import 'package:bspotz/app/modules/profile/presentation/pages/user_profile.dart'; import 'package:bspotz/app/modules/subscribedevent/presentation/pages/subscribed_events_page.dart'; import 'package:bspotz/app/modules/userDetails/presentation/bloc/user_details_bloc.dart'; import 'package:bspotz/app/modules/wallet/presentation/pages/wallet_details_screen.dart'; import 'package:bspotz/app/widgets/custom_snackbar.dart'; import 'package:bspotz/core/routes/app_paths.dart'; import 'package:bspotz/core/utils/foreground_notification.dart'; import 'package:bspotz/core/utils/show_case_repo.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_callkit_incoming/entities/call_event.dart'; import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; import 'package:provider/provider.dart'; import 'package:uuid/uuid.dart';

// ignore: depend_on_referenced_packages import '../../../calling/presentation/pages/call.dart'; import '../../../chat/datasource/firebase_messeging_repo.dart'; import '../../../chat/presentation/screens/chat_page.dart'; import '../../../explore/presentation/bloc/fetch_location_bloc.dart'; import '../../../registration/datasource/repo/jwt_repo.dart'; import '../../../userDetails/datasource/repo/user_details_repo.dart'; import '../../datasource/repo/notification.dart'; import '../widgets/bottom_navigation.dart';

@pragma('vm:entry-point') Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { debugPrint("Handling a background message: ${message.messageId}");

if (message.data['type'] == 'Call') { NotificationData.showCallkitIncoming( message.data['channel_id'], message.data['senderName'], message.notification?.body, message.data['senderPicture'], message.data['duration'], message.data['time'], const Uuid().v4(), ); } }

var fcmInstance = FirebaseMessaging.instance;

class HomePage extends StatefulWidget { const HomePage({super.key});

@override State createState() => _HomePageState(); }

class _HomePageState extends State with WidgetsBindingObserver { int _currentIndex = 0; final JwtRepoImpl _jwtRepoImpl = JwtRepoImpl(); final PageController _pageController = PageController(initialPage: 0); CollectionReference callRef = FirebaseFirestore.instance.collection("calls"); late final Uuid _uuid; String? currentUuid; // Set to store notification IDs that have been displayed Set<String?> shownNotificationForegroundIds = {};

@override void initState() { _uuid = const Uuid(); currentUuid = ""; context.read().add(RequestUserDetails()); WidgetsBinding.instance.addObserver(this); FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

initFirebase(context);
listenerEvent(onEvent);
checkAndNavigationCallingPageFromTerminated();

super.initState();

}

@override void dispose() { WidgetsBinding.instance.removeObserver(this); _pageController.dispose();

super.dispose();

}

initFirebase(BuildContext context) async { await Firebase.initializeApp(); await fcmInstance.setForegroundNotificationPresentationOptions( alert: true, badge: true, sound: true); currentUuid = _uuid.v4(); NotificationSettings settings = await fcmInstance.requestPermission( alert: true, announcement: false, badge: true, carPlay: false, criticalAlert: false, provisional: false, sound: true, );

if (settings.authorizationStatus == AuthorizationStatus.authorized) {
  // debugPrint('User granted permission');
} else if (settings.authorizationStatus ==
    AuthorizationStatus.provisional) {
  // debugPrint('User granted provisional permission');
} else {
  // debugPrint('User declined or has not accepted permission');
}

// when user tap on msg fron terminated state
FirebaseMessaging.instance.getInitialMessage().then((message) async {
  // debugPrint("RemoteMessage  ${message?.data}");
  // Generate a unique notification ID
  String? notificationId = message?.messageId;
  // Check if the notification ID has already been shown
  if (shownNotificationForegroundIds.contains(notificationId)) {
    // If the ID has already been shown, do not process the notification
    return;
  }
  // Add the notification ID to the set of shown notification IDs
  shownNotificationForegroundIds.add(notificationId);
  if (message != null) {
    bool iscallling =
        await NotificationData.checkcallState(message.data['channel_id']);
    if (message.data['type'] == 'Call' && iscallling) {
      debugPrint("how much time called background");
      NotificationData.showCallkitIncoming(
          message.data['channel_id'],
          message.data['senderName'],
          message.notification?.body,
          message.data['senderPicture'],
          message.data['duration'],
          message.data['time'],
          currentUuid!);
    } else if (message.data['type'] == 'Call' && !iscallling) {
      Navigator.pushReplacementNamed(context, AppPaths.home,
          arguments: "notification");
    } else {
      MapMarkersModel second = MapMarkersModel(
          name: message.data['senderName'],
          picture: message.data['senderPicture'],
          userId: message.data['userId'],
          deviceName: message.data['device']);
      String? jwTtoken = await _jwtRepoImpl.fetchJwtToken();
      var result = await UserDetailsImpl().userDetails(token: jwTtoken);
      final currentUser = UserModel.fromJson(result);
      Provider.of<UserProvider>(context, listen: false).mainUser =
          currentUser;
      Navigator.push(
          context,
          MaterialPageRoute(
              builder: (context) => ChatPage(
                    fromMap: false,
                    currentUser:
                        Provider.of<UserProvider>(context, listen: false)
                            .mainUser!,
                    second: second,
                  )));
    }
  } else {}
});

// when user tap on notification in background state
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
  debugPrint("background mode ${message.data['channel_id']}");
  // Generate a unique notification ID
  String? notificationId = message.messageId;
  // Check if the notification ID has already been shown
  if (shownNotificationForegroundIds.contains(notificationId)) {
    // If the ID has already been shown, do not process the notification
    return;
  }
  // Add the notification ID to the set of shown notification IDs
  shownNotificationForegroundIds.add(notificationId);
  bool iscallling =
      await NotificationData.checkcallState(message.data['channel_id']);
  if (message.data['type'] == 'Call' && iscallling) {
    debugPrint("how much time called background opened");
    NotificationData.showCallkitIncoming(
        message.data['channel_id'],
        message.data['senderName'],
        message.notification?.body,
        message.data['senderPicture'],
        message.data['duration'],
        message.data['time'],
        currentUuid!);

    // Handle the call based on the call type
  } else if (message.data['type'] == 'Call' && !iscallling) {
    Navigator.pushReplacementNamed(context, AppPaths.home,
        arguments: "notification");
  } else {
    MapMarkersModel second = MapMarkersModel(
        name: message.data['senderName'],
        picture: message.data['senderPicture'],
        userId: message.data['userId'],
        deviceName: message.data['device']);
    debugPrint("notification data is ${second.userId}");

    Navigator.push(
        context,
        MaterialPageRoute(
            builder: (context) => ChatPage(
                  fromMap: false,
                  currentUser:
                      Provider.of<UserProvider>(context, listen: false)
                          .mainUser!,
                  second: second,
                )));
  }
});

// when app is opened

FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
  debugPrint("foreground mode ${message.data['channel_id']}");
  debugPrint(
      'Message title: ${message.notification?.title}, body: ${message.notification?.body}, data: ${message.data}');
  // Generate a unique notification ID
  String? notificationId = message.messageId;
  // Check if the notification ID has already been shown
  if (shownNotificationForegroundIds.contains(notificationId)) {
    // If the ID has already been shown, do not process the notification
    return;
  }
  // Add the notification ID to the set of shown notification IDs
  shownNotificationForegroundIds.add(notificationId);
  if (message.data['type'] == 'Call') {
    debugPrint("how much time called foreground");
    NotificationData.showCallkitIncoming(
        message.data['channel_id'],
        message.data['senderName'],
        message.notification?.body,
        message.data['senderPicture'],
        message.data['duration'],
        message.data['time'],
        currentUuid!);
  } else {
    ForegroundNotification.showForegroundNotification(message, context);
  }
});

}

String textEvents = ""; onEvent(event) { if (!mounted) return; setState(() { textEvents += "${event.toString()}\n"; }); }

// For listening event fron callkit incoming Future listenerEvent(Function? callback) async { try { FlutterCallkitIncoming.onEvent.listen((event) async { debugPrint('HOME: $event');

    switch (event!.event) {
      case Event.actionCallIncoming:
        break;
      case Event.actionCallStart:
        // getCurrentCall();
        // await getDevicePushTokenVoIP();
        break;
      case Event.actionCallAccept:
        await checkAndNavigationCallingPage(
            event.body['extra']['channelId'],
            event.body['number'] ?? '',
            event.body['extra']['duration']);
        break;
      case Event.actionCallDecline:
        await FlutterCallkitIncoming.endAllCalls();
        await callRef
            .doc(event.body['extra']['channelId'])
            .update({'response': 'Decline'});

        debugPrint(
            'decilne incoming dart------------------------------------');

        break;
      case Event.actionCallEnded:
        try {
          await callRef
              .doc(event.body['extra']['channelId'])
              .update({'response': 'Decline'});
          debugPrint('completed call------------------------------------');
        } catch (e) {
          rethrow;
        }

        await FlutterCallkitIncoming.endCall(event.body['id']);
        await FlutterCallkitIncoming.endAllCalls();
        break;
      case Event.actionCallTimeout:
        await FlutterCallkitIncoming.endCall(event.body['id']);
        await FlutterCallkitIncoming.endAllCalls();

        await callRef
            .doc(event.body['extra']['channelId'])
            .update({'response': 'Not-answer'});
        debugPrint(
            'decilne incoming dart------------------------------------');
        break;
      case Event.actionCallCallback:
        break;
      case Event.actionCallToggleHold:
        break;
      case Event.actionCallToggleMute:
        break;
      case Event.actionCallToggleDmtf:
        break;
      case Event.actionCallToggleGroup:
        break;
      case Event.actionCallToggleAudioSession:
        break;
      case Event.actionDidUpdateDevicePushTokenVoip:
        break;
      case Event.actionCallCustom:
        break;
    }
    if (callback != null) {
      callback(event.toString());
    }
  });
} on Exception {
  rethrow;
}

}

// For checking user has granted notification permission or not Future checkNotificationPermission() async { NotificationSettings settings = await fcmInstance.requestPermission( alert: true, announcement: false, badge: true, carPlay: false, criticalAlert: false, provisional: false, sound: true, );

if (settings.authorizationStatus == AuthorizationStatus.authorized) {
  return true;
} else if (settings.authorizationStatus ==
    AuthorizationStatus.provisional) {
  return true;
} else {
  return false;
}

}

// When user pick the call from terminated state

checkAndNavigationCallingPageFromTerminated() async { var currentCall = await getCurrentCall(); bool isPermissionallowed = await checkNotificationPermission();

if (currentCall != null && isPermissionallowed) {
  Map<String, dynamic> callTimeMap = Map<String, dynamic>.from(
      json.decode(currentCall['extra']['callTime']));
  DateTime dateTime =
      DateTime.fromMillisecondsSinceEpoch(callTimeMap['_seconds'] * 1000);
  if (DateTime.now().difference(dateTime).inSeconds >= 0 &&
      DateTime.now().difference(dateTime).inSeconds <= 20) {
    debugPrint("current call is after terminating $currentCall");
    await callRef
        .doc(currentCall['extra']['channelId'])
        .update({'response': "Pickup"});
    debugPrint('call not  expired');
    Navigator.push(
        context,
        MaterialPageRoute(
            builder: (context) => CallPage(
                  imageurl: currentCall['avatar'],
                  currentUuid: currentUuid!,
                  callDurationInSec:
                      int.parse(currentCall['extra']['duration']),
                  callType: "VideoCall",
                  channelName: currentCall['extra']['channelId'],
                  role: ClientRoleType.clientRoleAudience,
                )));
  } else {
    debugPrint('call expired');
    await FlutterCallkitIncoming.endCall(currentCall['id']);
    await FlutterCallkitIncoming.endAllCalls();

    await callRef
        .doc(currentCall['extra']['channelId'])
        .update({'response': 'Not-answer'});
  }
} else {
  debugPrint('call null');
}

}

// when user pick the call from opened app or background to navigate on call screen

Future checkAndNavigationCallingPage( String channelId, String callType, String callDurationInSec) async { var currentCall = await getCurrentCall(); if (currentCall != null) { Map<String, dynamic> callTimeMap = Map<String, dynamic>.from( json.decode(currentCall['extra']['callTime'])); DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(callTimeMap['_seconds'] * 1000); if (DateTime.now().difference(dateTime).inSeconds >= 0 && DateTime.now().difference(dateTime).inSeconds <= 20) { debugPrint('call not expired'); await callRef.doc(channelId).update({'response': "Pickup"}); Navigator.push( context, MaterialPageRoute( builder: (context) => CallPage( currentUuid: currentUuid!, imageurl: currentCall['avatar'], callDurationInSec: int.parse(callDurationInSec), callType: callType, channelName: channelId, role: ClientRoleType.clientRoleAudience, ))); } else { debugPrint('call expired'); await callRef .doc(currentCall['extra']['channelId']) .update({'response': 'Not-answer'}); await FlutterCallkitIncoming.endCall(currentCall['id']); await FlutterCallkitIncoming.endAllCalls(); } } else { debugPrint('call null'); } }

Future getCurrentCall() async { //check current call from pushkit if possible var calls = await FlutterCallkitIncoming.activeCalls(); debugPrint('calls$calls'); if (calls is List) { if (calls.isNotEmpty) { debugPrint("call length is ${calls.length}"); currentUuid = calls[0]['id']; return calls[0]; } else { currentUuid = ""; return; } } }

// add your page for calling in bottom navigation bar

final List _pages = [ AllEventsPage( searchShowCase: ShowCaseRepo.searchShowCase, languageShowCase: ShowCaseRepo.languageShowCase, drawerShowCase: ShowCaseRepo.drawerShowCase, ), const EventListPage(), ExploreScreen( searchUserKey: ShowCaseRepo.searchUserShowCase, ), WalletDetailScreen( walletrefreshCase: ShowCaseRepo.walletRefershCase, paybuttonShowCase: ShowCaseRepo.paybuttonShowCase, ), const UserProfileScreen() ];

// for handing and changing page content ontap of tab

void onTabTapped(int index) async { switch (index) { case 0: WidgetsBinding.instance.addPostFrameCallback(() { ShowCaseRepo.callShowCase( context: context, showcaseKey: "homePage", widgetIds: [ ShowCaseRepo.drawerShowCase, ShowCaseRepo.searchShowCase, ShowCaseRepo.languageShowCase ]); });

    break;
  case 2:
    WidgetsBinding.instance.addPostFrameCallback((_) {
      ShowCaseRepo.callShowCase(
          context: context,
          showcaseKey: "mapPage",
          widgetIds: [
            ShowCaseRepo.searchUserShowCase,
          ]);
    });
    break;
  case 3:
    WidgetsBinding.instance.addPostFrameCallback((_) {
      ShowCaseRepo.callShowCase(
          context: context,
          showcaseKey: "walletPage",
          widgetIds: [
            ShowCaseRepo.walletRefershCase,
            ShowCaseRepo.paybuttonShowCase,
          ]);
    });
}
setState(() {
  _currentIndex = index;
});

}

@override Widget build(BuildContext context) { return BlocListener<UserDetailsBloc, UserDetailsState>( listener: (context, state) { if (state is UserDetailsSuccess) { final currentUser = UserModel.fromJson(state.result); Provider.of(context, listen: false).mainUser = currentUser; bool isEnabled = Provider.of(context, listen: false) .mainUser ?.showOnMap ?? false; if (isEnabled) { context.read().add(ListenLocationStream()); } debugPrint(" user id is ${UserModel.fromJson(state.result).userId}"); fcmInstance.getToken().then((token) async { // debugPrint('token $token'); try { if (!currentUser.fcmTokens.contains(token)) { String? jwTtoken = await _jwtRepoImpl.fetchJwtToken(); await FCMRepositoryImpl().updateFCMToken( token: jwTtoken!, body: {'fcmToken': token}); debugPrint('Device Token FCM: $token'); } } catch (e) { debugPrint(e.toString()); } }); } if (state is UserDetailsFailed) { CustomSnackbar.showSnackBar(state.msg, context); } }, child: PopScope( canPop: false, child: Scaffold( body: IndexedStack( index: _currentIndex, children: _pages, ), bottomNavigationBar: CustomBottomNavigationBar( currentIndex: _currentIndex, onTabTapped: _onTabTapped, ), ), ), ); } }

WorkNedoPrograMax commented 6 months ago

Same issue

kevin-do-zimplistic commented 3 months ago

_uuid = const Uuid()

Don't do this, the uuid should be from the server. If you don't have an server, try hard code it, the duplicate will stop.