diff --git a/packages/stream_chat_v1/android/app/src/main/AndroidManifest.xml b/packages/stream_chat_v1/android/app/src/main/AndroidManifest.xml
index fa7cbe4..6f96e79 100644
--- a/packages/stream_chat_v1/android/app/src/main/AndroidManifest.xml
+++ b/packages/stream_chat_v1/android/app/src/main/AndroidManifest.xml
@@ -44,5 +44,11 @@
+
+
+
\ No newline at end of file
diff --git a/packages/stream_chat_v1/android/app/src/main/res/drawable-anydpi-v24/ic_notification.xml b/packages/stream_chat_v1/android/app/src/main/res/drawable-anydpi-v24/ic_notification.xml
new file mode 100644
index 0000000..b953a9c
--- /dev/null
+++ b/packages/stream_chat_v1/android/app/src/main/res/drawable-anydpi-v24/ic_notification.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
diff --git a/packages/stream_chat_v1/android/app/src/main/res/drawable-anydpi-v24/ic_notification_in_app.xml b/packages/stream_chat_v1/android/app/src/main/res/drawable-anydpi-v24/ic_notification_in_app.xml
new file mode 100644
index 0000000..3c858bb
--- /dev/null
+++ b/packages/stream_chat_v1/android/app/src/main/res/drawable-anydpi-v24/ic_notification_in_app.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
diff --git a/packages/stream_chat_v1/android/app/src/main/res/drawable-hdpi/ic_notification.png b/packages/stream_chat_v1/android/app/src/main/res/drawable-hdpi/ic_notification.png
new file mode 100644
index 0000000..89924d6
Binary files /dev/null and b/packages/stream_chat_v1/android/app/src/main/res/drawable-hdpi/ic_notification.png differ
diff --git a/packages/stream_chat_v1/android/app/src/main/res/drawable-hdpi/ic_notification_in_app.png b/packages/stream_chat_v1/android/app/src/main/res/drawable-hdpi/ic_notification_in_app.png
new file mode 100644
index 0000000..738f9dd
Binary files /dev/null and b/packages/stream_chat_v1/android/app/src/main/res/drawable-hdpi/ic_notification_in_app.png differ
diff --git a/packages/stream_chat_v1/android/app/src/main/res/drawable-mdpi/ic_notification.png b/packages/stream_chat_v1/android/app/src/main/res/drawable-mdpi/ic_notification.png
new file mode 100644
index 0000000..bc99f1b
Binary files /dev/null and b/packages/stream_chat_v1/android/app/src/main/res/drawable-mdpi/ic_notification.png differ
diff --git a/packages/stream_chat_v1/android/app/src/main/res/drawable-mdpi/ic_notification_in_app.png b/packages/stream_chat_v1/android/app/src/main/res/drawable-mdpi/ic_notification_in_app.png
new file mode 100644
index 0000000..e983bbd
Binary files /dev/null and b/packages/stream_chat_v1/android/app/src/main/res/drawable-mdpi/ic_notification_in_app.png differ
diff --git a/packages/stream_chat_v1/android/app/src/main/res/drawable-xhdpi/ic_notification.png b/packages/stream_chat_v1/android/app/src/main/res/drawable-xhdpi/ic_notification.png
new file mode 100644
index 0000000..c75ff08
Binary files /dev/null and b/packages/stream_chat_v1/android/app/src/main/res/drawable-xhdpi/ic_notification.png differ
diff --git a/packages/stream_chat_v1/android/app/src/main/res/drawable-xhdpi/ic_notification_in_app.png b/packages/stream_chat_v1/android/app/src/main/res/drawable-xhdpi/ic_notification_in_app.png
new file mode 100644
index 0000000..1eec20b
Binary files /dev/null and b/packages/stream_chat_v1/android/app/src/main/res/drawable-xhdpi/ic_notification_in_app.png differ
diff --git a/packages/stream_chat_v1/android/app/src/main/res/drawable-xxhdpi/ic_notification.png b/packages/stream_chat_v1/android/app/src/main/res/drawable-xxhdpi/ic_notification.png
new file mode 100644
index 0000000..f65b654
Binary files /dev/null and b/packages/stream_chat_v1/android/app/src/main/res/drawable-xxhdpi/ic_notification.png differ
diff --git a/packages/stream_chat_v1/android/app/src/main/res/drawable-xxhdpi/ic_notification_in_app.png b/packages/stream_chat_v1/android/app/src/main/res/drawable-xxhdpi/ic_notification_in_app.png
new file mode 100644
index 0000000..c0c3912
Binary files /dev/null and b/packages/stream_chat_v1/android/app/src/main/res/drawable-xxhdpi/ic_notification_in_app.png differ
diff --git a/packages/stream_chat_v1/lib/app.dart b/packages/stream_chat_v1/lib/app.dart
index 3e95328..4b7ed04 100644
--- a/packages/stream_chat_v1/lib/app.dart
+++ b/packages/stream_chat_v1/lib/app.dart
@@ -8,6 +8,7 @@ import 'package:example/state/init_data.dart';
import 'package:example/utils/app_config.dart';
import 'package:example/utils/local_notification_observer.dart';
import 'package:example/utils/localizations.dart';
+import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
@@ -15,11 +16,63 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
+import 'package:firebase_core/firebase_core.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_localizations/stream_chat_localizations.dart';
import 'package:stream_chat_persistence/stream_chat_persistence.dart';
import 'package:streaming_shared_preferences/streaming_shared_preferences.dart';
+import 'firebase_options.dart';
+
+@pragma('vm:entry-point')
+Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
+ await Firebase.initializeApp(
+ options: DefaultFirebaseOptions.currentPlatform,
+ );
+ debugPrint('[onBackgroundMessage] #firebase; message: ${message.toMap()}');
+
+ final data = message.data;
+ if (data['type'] != 'message.new') {
+ return;
+ }
+ String? apiKey, userId, token;
+ if (!kIsWeb) {
+ const secureStorage = FlutterSecureStorage();
+ apiKey = await secureStorage.read(key: kStreamApiKey);
+ userId = await secureStorage.read(key: kStreamUserId);
+ token = await secureStorage.read(key: kStreamToken);
+ }
+ debugPrint('[onBackgroundMessage] #firebase; apiKey: $apiKey, userId: $userId, token: $token');
+ if (userId == null || token == null) {
+ return;
+ }
+ final client = buildStreamChatClient(apiKey ?? kDefaultStreamApiKey);
+ final persistenceClient = StreamChatPersistenceClient();
+ await persistenceClient.connect(userId);
+
+ await client.connectUser(
+ User(id: userId),
+ token,
+ connectWebSocket: false,
+ );
+
+ final messageId = data['id'];
+ final cid = data['cid'];
+ final response = await client.getMessage(messageId);
+ debugPrint('[onBackgroundMessage] #firebase; response: $response');
+ await persistenceClient.updateMessages(cid, [response.message]);
+ debugPrint('[onBackgroundMessage] #firebase; saved');
+
+}
+
+Future _firebaseMessagingForegroundHandler(RemoteMessage message) async {
+ debugPrint('[onForegroundMessage] #firebase; message: ${message.toMap()}');
+}
+
+Future _firebaseMessagingOpenedHandler(RemoteMessage message) async {
+ debugPrint('[onOpenedMessage] #firebase; message: ${message.toMap()}');
+}
+
final chatPersistentClient = StreamChatPersistenceClient(
logLevel: Level.SEVERE,
connectionMode: ConnectionMode.regular,
@@ -28,7 +81,7 @@ final chatPersistentClient = StreamChatPersistenceClient(
void _sampleAppLogHandler(LogRecord record) async {
if (kDebugMode) StreamChatClient.defaultLogHandler(record);
- // report errors to setnry.io
+ // report errors to sentry.io
if (record.error != null || record.stackTrace != null) {
await Sentry.captureException(
record.error,
@@ -62,6 +115,9 @@ class _StreamChatSampleAppState extends State
with SplashScreenStateMixin, TickerProviderStateMixin {
final InitNotifier _initNotifier = InitNotifier();
+ final firebaseSubscriptions = >[];
+ StreamSubscription? userIdSubscription;
+
Future _initConnection() async {
String? apiKey, userId, token;
@@ -71,7 +127,7 @@ class _StreamChatSampleAppState extends State
userId = await secureStorage.read(key: kStreamUserId);
token = await secureStorage.read(key: kStreamToken);
}
-
+ debugPrint('[initConnection] #firebase; apiKey: $apiKey, userId: $userId, token: $token');
final client = buildStreamChatClient(apiKey ?? kDefaultStreamApiKey);
if (userId != null && token != null) {
@@ -86,6 +142,77 @@ class _StreamChatSampleAppState extends State
return InitData(client, prefs);
}
+ Future _initFirebaseMessaging(StreamChatClient client) async {
+ userIdSubscription?.cancel();
+ userIdSubscription = client.state.currentUserStream
+ .map((it) => it?.id)
+ .distinct()
+ .listen((userId) async {
+ debugPrint('[onUserIdSet] #firebase; userId: "$userId"');
+
+ if (userId != null) {
+ FirebaseMessaging.onBackgroundMessage(
+ _firebaseMessagingBackgroundHandler);
+ firebaseSubscriptions.add(FirebaseMessaging.onMessage
+ .listen(_firebaseMessagingForegroundHandler));
+ firebaseSubscriptions
+ .add(FirebaseMessaging.onMessageOpenedApp.listen((message) async {
+ debugPrint(
+ '[onOpenedMessage] #firebase; message: ${message.toMap()}');
+ final channelCid = (message.data['cid'] as String?) ?? '';
+ final channelType = (message.data['channel_type'] as String?) ?? '';
+ final channelId = (message.data['channel_id'] as String?) ?? '';
+ debugPrint('[onOpenedMessage] #firebase; channelCid; $channelCid, channelType: $channelType, channelId: $channelId');
+ var channel = client.state.channels[channelCid];
+ debugPrint('[onOpenedMessage] #firebase; channel1: $channel');
+ if (channel == null) {
+ channel = client.channel(
+ channelId,
+ id: channelId,
+ );
+ final state = await channel.watch();
+ debugPrint('[onOpenedMessage] #firebase; channelState: $state');
+ }
+ debugPrint('[onOpenedMessage] #firebase; channel2: $channel');
+
+ debugPrint('[onOpenedMessage] #firebase; #1');
+ WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
+ if (channel == null) {
+ debugPrint('[onOpenedMessage] #firebase; rejected (channel is null)');
+ return;
+ }
+ try {
+ debugPrint('[onOpenedMessage] #firebase; #2: ${_navigatorKey.currentContext}');
+ final router = GoRouter.of(_navigatorKey.currentContext!);
+ debugPrint('[onOpenedMessage] #firebase; #3: $router');
+
+ router.pushNamed(
+ Routes.CHANNEL_PAGE.name,
+ params: Routes.CHANNEL_PAGE.params(channel),
+ );
+ debugPrint('[onOpenedMessage] #firebase; #4');
+ } catch (e, stk) {
+ debugPrint('[onOpenedMessage] #firebase; failed: $e; $stk');
+ }
+ });
+
+ }));
+ firebaseSubscriptions.add(
+ FirebaseMessaging.instance.onTokenRefresh.listen((token) async {
+ debugPrint('[onTokenRefresh] #firebase; token: "$token"');
+ await client.addDevice(token, PushProvider.firebase);
+ debugPrint('[onTokenRefresh] #firebase; token set: $token');
+ }));
+ final token = await FirebaseMessaging.instance.getToken();
+ debugPrint('[initFirebaseMessaging] #firebase; token: "$token"');
+ if (token != null) {
+ await client.addDevice(token, PushProvider.firebase);
+ debugPrint('[initFirebaseMessaging] #firebase; token set: $token');
+ }
+ }
+ });
+ }
+
@override
void initState() {
final timeOfStartMs = DateTime.now().millisecondsSinceEpoch;
@@ -100,19 +227,38 @@ class _StreamChatSampleAppState extends State
if (now - timeOfStartMs > 1500) {
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
+ debugPrint('[forwardAnimations] #firebase; context1: $context');
+ debugPrint('[forwardAnimations] #firebase; _navigatorKey.currentContext1: ${_navigatorKey.currentContext}');
forwardAnimations();
+ final router = GoRouter.of(_navigatorKey.currentContext!);
+ debugPrint('[forwardAnimations] #firebase; router1: ${router}');
});
} else {
Future.delayed(const Duration(milliseconds: 1500)).then((value) {
+ debugPrint('[forwardAnimations] #firebase; context2: $context');
+ debugPrint('[forwardAnimations] #firebase; _navigatorKey.currentContext2: ${_navigatorKey.currentContext}');
forwardAnimations();
+ final router = GoRouter.of(_navigatorKey.currentContext!);
+ debugPrint('[forwardAnimations] #firebase; router2: ${router}');
});
}
+ _initFirebaseMessaging(initData.client);
},
);
super.initState();
}
+ @override
+ void dispose() {
+ super.dispose();
+ userIdSubscription?.cancel();
+ for (final subscription in firebaseSubscriptions) {
+ unawaited(subscription.cancel());
+ }
+ firebaseSubscriptions.clear();
+ }
+
final GlobalKey _navigatorKey = GlobalKey();
LocalNotificationObserver? localNotificationObserver;
@@ -154,6 +300,7 @@ class _StreamChatSampleAppState extends State
@override
Widget build(BuildContext context) {
+ debugPrint('[AppState.build] #firebase; context: $context');
return Stack(
alignment: Alignment.center,
children: [
diff --git a/packages/stream_chat_v1/lib/main.dart b/packages/stream_chat_v1/lib/main.dart
index 40c5399..2211b9f 100644
--- a/packages/stream_chat_v1/lib/main.dart
+++ b/packages/stream_chat_v1/lib/main.dart
@@ -1,35 +1,21 @@
import 'dart:async';
+import 'package:example/app.dart';
import 'package:example/utils/app_config.dart';
+import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
-import 'package:firebase_core/firebase_core.dart';
-import 'package:firebase_messaging/firebase_messaging.dart';
-
-
-import 'package:example/app.dart';
import 'firebase_options.dart';
-@pragma('vm:entry-point')
-Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
- print("#firebase; PN_2 ");
- await Firebase.initializeApp();
-
- // If you're going to use other Firebase services in the background, such as Firestore,
- // make sure you call `initializeApp` before using other Firebase services.
- print('[setupPushNotifications] newMessage: ${message.toMap()}');
-}
-
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
- print("#firebase; PN_1");
- await Firebase.initializeApp();
- Firebase.apps.forEach((it) {
- print("#firebase; app: $it");
- });
- FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
+ debugPrint("#firebase; PN_1");
+ await Firebase.initializeApp(
+ options: DefaultFirebaseOptions.currentPlatform,
+ );
+
/// Captures errors reported by the Flutter framework.
FlutterError.onError = (FlutterErrorDetails details) {
if (kDebugMode) {
diff --git a/packages/stream_chat_v1/lib/pages/channel_list_page.dart b/packages/stream_chat_v1/lib/pages/channel_list_page.dart
index c045e0b..f4139e4 100644
--- a/packages/stream_chat_v1/lib/pages/channel_list_page.dart
+++ b/packages/stream_chat_v1/lib/pages/channel_list_page.dart
@@ -69,6 +69,7 @@ class _ChannelListPageState extends State {
@override
Widget build(BuildContext context) {
+ print(">>>>>>>>>> ChannelListPage");
final user = StreamChat.of(context).currentUser;
if (user == null) {
return const Offstage();
@@ -76,8 +77,10 @@ class _ChannelListPageState extends State {
return Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
appBar: StreamChannelListHeader(
- onNewChatButtonTap: () =>
- GoRouter.of(context).pushNamed(Routes.NEW_CHAT.name),
+ onNewChatButtonTap: () {
+ print(">>>>>>>>>> onNewChatButtonTap");
+ GoRouter.of(context).pushNamed(Routes.NEW_CHAT.name);
+ },
preNavigationCallback: () =>
FocusScope.of(context).requestFocus(FocusNode()),
),
@@ -220,6 +223,7 @@ class LeftDrawer extends StatelessWidget {
.withOpacity(.5),
),
onTap: () {
+ print(">>>>>>>>>> NEW_CHAT");
Navigator.of(context).pop();
GoRouter.of(context).pushNamed(Routes.NEW_CHAT.name);
},
@@ -238,6 +242,7 @@ class LeftDrawer extends StatelessWidget {
.withOpacity(.5),
),
onTap: () {
+ print(">>>>>>>>>> NEW_GROUP_CHAT");
Navigator.of(context).pop();
GoRouter.of(context).pushNamed(Routes.NEW_GROUP_CHAT.name);
},
@@ -253,6 +258,7 @@ class LeftDrawer extends StatelessWidget {
alignment: Alignment.bottomCenter,
child: ListTile(
onTap: () async {
+ print(">>>>>>>>>> CHOOSE_USER");
final client = StreamChat.of(context).client;
final router = GoRouter.of(context);
final initNotifier = context.read();
diff --git a/packages/stream_chat_v1/lib/pages/channel_page.dart b/packages/stream_chat_v1/lib/pages/channel_page.dart
index e2714ee..652f266 100644
--- a/packages/stream_chat_v1/lib/pages/channel_page.dart
+++ b/packages/stream_chat_v1/lib/pages/channel_page.dart
@@ -47,12 +47,14 @@ class _ChannelPageState extends State {
@override
Widget build(BuildContext context) {
+ print(">>>>>>>>>> CHANNEL PAGE");
return Scaffold(
backgroundColor: StreamChatTheme.of(context).colorTheme.appBg,
appBar: StreamChannelHeader(
showTypingIndicator: false,
onBackPressed: () => GoRouter.of(context).pop(),
onImageTap: () async {
+ print(">>>>>>>>>> onImageTap");
final channel = StreamChannel.of(context).channel;
final router = GoRouter.of(context);
@@ -62,6 +64,7 @@ class _ChannelPageState extends State {
(element) => element.user!.id != currentUser!.id,
);
if (otherUser != null) {
+ print(">>>>>>>>>> GROUP_INFO_SCREEN otherUser");
router.pushNamed(
Routes.CHAT_INFO_SCREEN.name,
params: Routes.CHAT_INFO_SCREEN.params(channel),
@@ -69,6 +72,7 @@ class _ChannelPageState extends State {
);
}
} else {
+ print(">>>>>>>>>> GROUP_INFO_SCREEN");
GoRouter.of(context).pushNamed(
Routes.GROUP_INFO_SCREEN.name,
params: Routes.GROUP_INFO_SCREEN.params(channel),
diff --git a/packages/stream_chat_v1/lib/utils/notifications_service.dart b/packages/stream_chat_v1/lib/utils/notifications_service.dart
index a02d2ee..35e21a5 100644
--- a/packages/stream_chat_v1/lib/utils/notifications_service.dart
+++ b/packages/stream_chat_v1/lib/utils/notifications_service.dart
@@ -21,7 +21,7 @@ void showLocalNotification(
if (event.message == null) return;
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
const initializationSettingsAndroid =
- AndroidInitializationSettings('launch_background');
+ AndroidInitializationSettings('ic_notification_in_app');
const initializationSettingsIOS = IOSInitializationSettings();
const initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
@@ -33,6 +33,8 @@ void showLocalNotification(
await flutterLocalNotificationsPlugin.initialize(
initializationSettings,
onSelectNotification: (channelCid) async {
+ debugPrint("[onSelectNotification] #firebase; channelCid: $channelCid");
+ debugPrint("[onSelectNotification] #firebase; context: $context");
if (channelCid != null) {
final client = StreamChat.of(context).client;
final router = GoRouter.of(context);