|
|
|
|
@ -46,8 +46,42 @@ Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Android: receives callkit events while no UI listener is active (app killed / not on LandPage).
|
|
|
|
|
/// When the app is in the foreground, events go to `callingEvents` in land_page.dart instead.
|
|
|
|
|
@pragma('vm:entry-point')
|
|
|
|
|
Future<void> backgroundMessageCallback(h_push.RemoteMessage remoteMessage) async {
|
|
|
|
|
final payload = huaweiMessageData(remoteMessage);
|
|
|
|
|
if (payload == null || payload["type"] != "incoming_call") return;
|
|
|
|
|
// Start the callkit background engine so a Decline tapped while killed reaches the caller.
|
|
|
|
|
try {
|
|
|
|
|
await FlutterCallkitIncoming.onBackgroundMessage(onBackgroundMessage);
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
await handleDataMessage(payload, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Map<String, dynamic>? huaweiMessageData(h_push.RemoteMessage remoteMessage) {
|
|
|
|
|
try {
|
|
|
|
|
Map<String, dynamic>? map;
|
|
|
|
|
final raw = remoteMessage.data;
|
|
|
|
|
if (raw != null && raw.isNotEmpty) {
|
|
|
|
|
final decoded = json.decode(raw);
|
|
|
|
|
if (decoded is Map) map = Map<String, dynamic>.from(decoded);
|
|
|
|
|
}
|
|
|
|
|
map ??= remoteMessage.dataOfMap == null ? null : Map<String, dynamic>.from(remoteMessage.dataOfMap!);
|
|
|
|
|
if (map == null) return null;
|
|
|
|
|
|
|
|
|
|
if (map["type"] == null && map["data"] != null) {
|
|
|
|
|
final inner = map["data"] is String ? json.decode(map["data"]) : map["data"];
|
|
|
|
|
if (inner is Map) map = Map<String, dynamic>.from(inner);
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
for (final e in map.entries)
|
|
|
|
|
if (e.value != null) e.key: e.value is String ? e.value : (e.value is Map || e.value is List ? json.encode(e.value) : e.value.toString()),
|
|
|
|
|
};
|
|
|
|
|
} catch (e) {
|
|
|
|
|
print("HMS data parse error: $e");
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@pragma('vm:entry-point')
|
|
|
|
|
Future<void> onBackgroundMessage(CallEvent event) async {
|
|
|
|
|
switch (event) {
|
|
|
|
|
@ -59,16 +93,6 @@ Future<void> onBackgroundMessage(CallEvent event) async {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// iOS: the single app-wide subscription to [FlutterCallkitIncoming.onEvent], started in main().
|
|
|
|
|
///
|
|
|
|
|
/// When the app is killed, a VoIP push launches it in the background with no UI: the splash
|
|
|
|
|
/// animation never completes, LandPage is never built and its listener never exists, so a
|
|
|
|
|
/// Decline tapped on the CallKit screen was dropped and the caller kept ringing.
|
|
|
|
|
/// Starting this in main() means a listener exists before the user can respond.
|
|
|
|
|
///
|
|
|
|
|
/// It must be the ONLY onEvent subscription: every `onEvent` getter call creates a new
|
|
|
|
|
/// EventChannel stream, and listening again replaces the previous native handler.
|
|
|
|
|
/// LandPage therefore receives events through [uiHandler] instead of listening itself.
|
|
|
|
|
class CallKitEvents {
|
|
|
|
|
static StreamSubscription<CallEvent?>? _subscription;
|
|
|
|
|
|
|
|
|
|
@ -113,6 +137,7 @@ class FirebaseNotificationManger {
|
|
|
|
|
static String? voipToken;
|
|
|
|
|
static Timer? _missedCallTimer;
|
|
|
|
|
static String? _activeMissedCallId;
|
|
|
|
|
static StreamSubscription<h_push.RemoteMessage>? _hmsMessageSubscription;
|
|
|
|
|
|
|
|
|
|
static String? _acceptedCallInProgress;
|
|
|
|
|
|
|
|
|
|
@ -124,6 +149,7 @@ class FirebaseNotificationManger {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void endAcceptedCall() => _acceptedCallInProgress = null;
|
|
|
|
|
|
|
|
|
|
/// iOS only. The VoIP token is registered natively in AppDelegate (PushKit) and stored
|
|
|
|
|
/// by flutter_callkit_incoming. It is sent to the backend as `voIPToken` on chat login.
|
|
|
|
|
static Future<void> refreshVoipToken() async {
|
|
|
|
|
@ -159,11 +185,11 @@ class FirebaseNotificationManger {
|
|
|
|
|
if (!(await isGoogleServicesAvailable())) {
|
|
|
|
|
h_push.Push.enableLogger();
|
|
|
|
|
final result = await h_push.Push.setAutoInitEnabled(true);
|
|
|
|
|
h_push.Push.onMessageReceivedStream.listen(_onMessageReceived, onError: _onMessageReceiveError);
|
|
|
|
|
listenHmsForegroundMessages();
|
|
|
|
|
|
|
|
|
|
h_push.Push.getTokenStream.listen((hToken) {
|
|
|
|
|
// onToken(token);
|
|
|
|
|
// print("Huawei Token: ${hToken}");
|
|
|
|
|
print("Huawei Token: ${hToken}");
|
|
|
|
|
token = hToken;
|
|
|
|
|
}, onError: (e) {
|
|
|
|
|
print("Huawei TokenError" + e.toString());
|
|
|
|
|
@ -191,8 +217,17 @@ class FirebaseNotificationManger {
|
|
|
|
|
//print("pushToken:$token");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static Stream<h_push.RemoteMessage>? _hmsMessageStream;
|
|
|
|
|
static Stream<h_push.RemoteMessage> get hmsMessageStream => _hmsMessageStream ??= h_push.Push.onMessageReceivedStream.asBroadcastStream();
|
|
|
|
|
static void listenHmsForegroundMessages() {
|
|
|
|
|
_hmsMessageSubscription ??= hmsMessageStream.listen(_onMessageReceived, onError: _onMessageReceiveError);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void _onMessageReceived(h_push.RemoteMessage remoteMessage) {
|
|
|
|
|
print("onMessageReceivedStream:${remoteMessage.toMap()}");
|
|
|
|
|
final payload = huaweiMessageData(remoteMessage);
|
|
|
|
|
if (payload != null && payload["type"] == "incoming_call") {
|
|
|
|
|
handleDataMessage(payload, false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void _onMessageReceiveError(Object error) {
|
|
|
|
|
@ -332,6 +367,7 @@ class FirebaseNotificationManger {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!(await isGoogleServicesAvailable()) && Platform.isAndroid) {
|
|
|
|
|
listenHmsForegroundMessages();
|
|
|
|
|
var initialNotification = await h_push.Push.getInitialNotification();
|
|
|
|
|
if (initialNotification != null) {
|
|
|
|
|
Map<String, dynamic> remoteData = Map<String, dynamic>.from(initialNotification["extras"] as Map);
|
|
|
|
|
|