handlePushNotification function
- @pragma('vm:entry-point')
- RemoteMessage message
Initializes the FlutterCallkitIncoming
and displays an incoming call
notification, if the provided message
is about a call.
Must be a top level function, as intended to be used as a Firebase Cloud Messaging notification background handler.
Implementation
@pragma('vm:entry-point')
Future<void> handlePushNotification(RemoteMessage message) async {
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
Log.debug('handlePushNotification($message)', 'main');
final String? tag = message.notification?.android?.tag;
final bool isCall =
tag?.endsWith('_call') == true || tag?.endsWith('-call') == true;
if (isCall && message.data['chatId'] != null) {
SharedPreferences? prefs;
CredentialsDriftProvider? credentialsProvider;
AccountDriftProvider? accountProvider;
GraphQlProvider? provider;
StreamSubscription? subscription;
try {
FlutterCallkitIncoming.onEvent.listen((CallEvent? event) async {
switch (event!.event) {
case Event.actionCallAccept:
await prefs?.setString('answeredCall', message.data['chatId']);
break;
case Event.actionCallDecline:
await provider?.declineChatCall(ChatId(message.data['chatId']));
break;
case Event.actionCallEnded:
case Event.actionCallTimeout:
subscription?.cancel();
provider?.disconnect();
break;
case Event.actionCallCallback:
// TODO: Handle.
break;
default:
break;
}
});
// TODO: Use stored in [ApplicationSettings] language here.
await L10n.init();
await FlutterCallkitIncoming.showCallkitIncoming(
CallKitParams(
id: message.data['chatId'],
nameCaller: message.notification?.title ?? 'gapopa',
appName: 'Gapopa',
avatar: '', // TODO: Add avatar to FCM notifications.
handle: message.data['chatId'],
type: 0,
textAccept: 'btn_accept'.l10n,
textDecline: 'btn_decline'.l10n,
duration: 30000,
extra: {'chatId': message.data['chatId']},
headers: {'platform': 'flutter'},
android: AndroidParams(
isCustomNotification: true,
isShowLogo: false,
ringtonePath: 'ringtone',
backgroundColor: '#0955fa',
backgroundUrl: '', // TODO: Add avatar to FCM notifications.
actionColor: '#4CAF50',
textColor: '#ffffff',
incomingCallNotificationChannelName: 'label_incoming_call'.l10n,
missedCallNotificationChannelName: 'label_chat_call_missed'.l10n,
isShowCallID: true,
isShowFullLockedScreen: true,
),
),
);
await Config.init();
final common = CommonDriftProvider.from(CommonDatabase());
credentialsProvider = CredentialsDriftProvider(common);
accountProvider = AccountDriftProvider(common);
await credentialsProvider.init();
await accountProvider.init();
final UserId? userId = accountProvider.userId;
final Credentials? credentials =
userId != null ? await credentialsProvider.read(userId) : null;
if (credentials != null) {
provider = GraphQlProvider();
provider.token = credentials.access.secret;
provider.reconnect();
subscription = provider
.chatEvents(ChatId(message.data['chatId']), null, () => null)
.listen((e) async {
var events = ChatEvents$Subscription.fromJson(e.data!).chatEvents;
if (events.$$typename == 'ChatEventsVersioned') {
var mixin =
events
as ChatEvents$Subscription$ChatEvents$ChatEventsVersioned;
for (var e in mixin.events) {
if (e.$$typename == 'EventChatCallFinished') {
await FlutterCallkitIncoming.endCall(
(message.data['chatId'] as String).base62ToUuid(),
);
} else if (e.$$typename == 'EventChatCallMemberJoined') {
var node =
e
as ChatEventsVersionedMixin$Events$EventChatCallMemberJoined;
if (node.user.id == credentials.userId) {
await FlutterCallkitIncoming.endCall(
(message.data['chatId'] as String).base62ToUuid(),
);
}
} else if (e.$$typename == 'EventChatCallDeclined') {
var node =
e as ChatEventsVersionedMixin$Events$EventChatCallDeclined;
if (node.user.id == credentials.userId) {
await FlutterCallkitIncoming.endCall(
(message.data['chatId'] as String).base62ToUuid(),
);
}
}
}
}
});
prefs = await SharedPreferences.getInstance();
await prefs.remove('answeredCall');
}
// Remove the incoming call notification after a reasonable amount of
// time for a better UX.
await Future.delayed(30.seconds);
await FlutterCallkitIncoming.endCall(
(message.data['chatId'] as String).base62ToUuid(),
);
} catch (_) {
provider?.disconnect();
subscription?.cancel();
await FlutterCallkitIncoming.endCall(
(message.data['chatId'] as String).base62ToUuid(),
);
}
} else {
// If message contains no notification (it's a background notification),
// then try canceling the notifications with the provided thread, if any, or
// otherwise a single one, if data contains a tag.
if (message.notification == null ||
(message.notification?.title == 'Canceled' &&
message.notification?.body == null)) {
final String? tag = message.data['tag'];
final String? thread = message.data['thread'];
if (PlatformUtils.isAndroid) {
final FlutterLocalNotificationsPlugin plugin =
FlutterLocalNotificationsPlugin();
Future.delayed(const Duration(milliseconds: 16), () async {
final notifications = await plugin.getActiveNotifications();
for (var e in notifications) {
if (e.tag?.contains(thread ?? tag ?? '.....') == true) {
plugin.cancel(e.id ?? 0, tag: e.tag);
}
}
});
} else if (PlatformUtils.isIOS) {
if (thread != null) {
await IosUtils.cancelNotificationsContaining(thread);
} else if (tag != null) {
await IosUtils.cancelNotification(tag);
}
}
}
// If payload contains a `ChatId` in it, then try sending a single
// [GraphQlProvider.chatItems] query to mark the chat as delivered.
//
// Note, that on iOS this behaviour is done via separate Notification
// Service Extension, as this code isn't guaranteed to be invoked at all,
// especially for visual notifications.
if (PlatformUtils.isAndroid) {
final String? chatId = message.data['thread'] ?? message.data['chatId'];
if (chatId != null) {
await Config.init();
final common = CommonDriftProvider.from(CommonDatabase());
final credentialsProvider = CredentialsDriftProvider(common);
final accountProvider = AccountDriftProvider(common);
await credentialsProvider.init();
await accountProvider.init();
final UserId? userId = accountProvider.userId;
final Credentials? credentials =
userId != null ? await credentialsProvider.read(userId) : null;
if (credentials != null) {
final provider = GraphQlProvider();
provider.token = credentials.access.secret;
try {
await provider.chatItems(ChatId(chatId), first: 1);
} catch (e) {
// No-op.
}
provider.disconnect();
}
}
}
}
}