implemented chat long-polling and optimistic updates, centralized notification management, optimized avatar caching
This commit is contained in:
@@ -1,11 +1,19 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||
import '../widget/debug/debug_tile.dart';
|
||||
import '../widget/debug/json_viewer.dart';
|
||||
import '../widget/info_dialog.dart';
|
||||
import 'notification_tasks.dart';
|
||||
|
||||
// `vm:entry-point` on the class so AOT tree-shaking doesn't drop it: the
|
||||
// FCM background handler runs in a fresh isolate that looks up the class
|
||||
// by name from native code.
|
||||
@pragma('vm:entry-point')
|
||||
class NotificationController {
|
||||
// Notification display is handled by the Firebase SDK using server-generated payloads.
|
||||
@pragma('vm:entry-point')
|
||||
@@ -17,8 +25,21 @@ class NotificationController {
|
||||
RemoteMessage message,
|
||||
BuildContext context,
|
||||
) async {
|
||||
NotificationTasks.updateProviders(context);
|
||||
final pushToken = _extractChatToken(message);
|
||||
final activeToken = context.read<ChatBloc>().state.data?.currentToken ?? '';
|
||||
final chatIsOpen =
|
||||
pushToken != null && pushToken.isNotEmpty && pushToken == activeToken;
|
||||
|
||||
NotificationTasks.updateBadgeCount(message);
|
||||
|
||||
if (chatIsOpen) {
|
||||
// Long-poll already fetches the message and moves the marker; just
|
||||
// dismiss any tray entry that slipped through foreground rendering.
|
||||
unawaited(NotificationTasks.clearNotificationsForChat(pushToken));
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationTasks.updateProviders(context);
|
||||
}
|
||||
|
||||
static Future<void> onAppOpenedByNotification(
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:eraser/eraser.dart';
|
||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_app_badge/flutter_app_badge.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../routing/app_routes.dart';
|
||||
import '../state/app/modules/chat/bloc/chat_bloc.dart';
|
||||
import '../state/app/modules/chat_list/bloc/chat_list_bloc.dart';
|
||||
import 'notification_service.dart';
|
||||
|
||||
class NotificationTasks {
|
||||
static void updateBadgeCount(RemoteMessage notification) {
|
||||
@@ -14,9 +18,43 @@ class NotificationTasks {
|
||||
);
|
||||
}
|
||||
|
||||
/// Per-chat tag scheme. MUST match the Notify backend, which sets this
|
||||
/// value on `AndroidNotification.setTag` AND `apns-collapse-id`.
|
||||
static String chatTag(String chatToken) => 'talk_$chatToken';
|
||||
|
||||
/// Removes tray notifications belonging to [chatToken]. Eraser handles
|
||||
/// iOS (where the plugin's `getActiveNotifications` returns null ids
|
||||
/// for FCM posts and can't cancel them); the local-notifications sweep
|
||||
/// handles Android and acts as a fallback while Eraser's native side
|
||||
/// isn't built in yet.
|
||||
static Future<void> clearNotificationsForChat(String chatToken) async {
|
||||
final tag = chatTag(chatToken);
|
||||
try {
|
||||
await Eraser.clearAppNotificationsByTag(tag);
|
||||
} on MissingPluginException {
|
||||
// Eraser native code not yet linked — needs flutter clean + run.
|
||||
} on Object catch (e) {
|
||||
log('Eraser($tag) failed: $e');
|
||||
}
|
||||
try {
|
||||
final plugin = NotificationService().flutterLocalNotificationsPlugin;
|
||||
final actives = await plugin.getActiveNotifications();
|
||||
for (final n in actives) {
|
||||
final id = n.id;
|
||||
if (id == null) continue;
|
||||
if (n.tag == tag) await plugin.cancel(id: id, tag: n.tag);
|
||||
}
|
||||
} on Object catch (e) {
|
||||
log('Active-notification sweep failed: $e');
|
||||
}
|
||||
}
|
||||
|
||||
/// Refreshes the chat list. Deliberately does NOT touch [ChatBloc] —
|
||||
/// the open chat view manages its own state via long-poll, and refreshing
|
||||
/// it here would re-fetch the last-opened chat with setReadMarker=on
|
||||
/// even if the user has already left.
|
||||
static void updateProviders(BuildContext context) {
|
||||
context.read<ChatListBloc>().refresh();
|
||||
context.read<ChatBloc>().refresh();
|
||||
}
|
||||
|
||||
/// Switches to the Talk tab. If [chatToken] is provided, also schedules
|
||||
|
||||
Reference in New Issue
Block a user