diff --git a/flutter/lib/main.dart b/flutter/lib/main.dart index 42c08da..8c960c5 100644 --- a/flutter/lib/main.dart +++ b/flutter/lib/main.dart @@ -12,6 +12,7 @@ import 'package:simplecloudnotifier/nav_layout.dart'; import 'package:simplecloudnotifier/state/app_bar_state.dart'; import 'package:simplecloudnotifier/state/app_theme.dart'; import 'package:simplecloudnotifier/state/application_log.dart'; +import 'package:simplecloudnotifier/state/fb_message.dart'; import 'package:simplecloudnotifier/state/globals.dart'; import 'package:simplecloudnotifier/state/request_log.dart'; import 'package:simplecloudnotifier/state/app_auth.dart'; @@ -40,6 +41,7 @@ void main() async { Hive.registerAdapter(SCNLogLevelAdapter()); Hive.registerAdapter(MessageAdapter()); Hive.registerAdapter(ChannelAdapter()); + Hive.registerAdapter(FBMessageAdapter()); print('[INIT] Load Hive...'); @@ -81,6 +83,16 @@ void main() async { ApplicationLog.error('Failed to open Hive-Box: scn-channel-cache' + exc.toString(), trace: trace); } + print('[INIT] Load Hive...'); + + try { + await Hive.openBox('scn-fb-messages'); + } catch (exc, trace) { + Hive.deleteBoxFromDisk('scn-fb-messages'); + await Hive.openBox('scn-fb-messages'); + ApplicationLog.error('Failed to open Hive-Box: scn-fb-messages' + exc.toString(), trace: trace); + } + print('[INIT] Load AppAuth...'); final appAuth = AppAuth(); // ensure UserAccount is loaded @@ -128,6 +140,9 @@ void main() async { } catch (exc, trace) { ApplicationLog.error('Failed to get+set firebase token: ' + exc.toString(), trace: trace); } + + FirebaseMessaging.onBackgroundMessage(_onBackgroundMessage); + FirebaseMessaging.onMessage.listen(_onForegroundMessage); } else { print('[INIT] Skip Firebase init (Platform == Linux)...'); } @@ -146,6 +161,33 @@ void main() async { ); } +class SCNApp extends StatelessWidget { + SCNApp({super.key}); + + @override + Widget build(BuildContext context) { + return ToastificationWrapper( + config: ToastificationConfig( + itemWidth: 440, + marginBuilder: (alignment) => EdgeInsets.symmetric(vertical: 64), + animationDuration: Duration(milliseconds: 200), + ), + child: Consumer( + builder: (context, appTheme, child) => MaterialApp( + title: 'SimpleCloudNotifier', + navigatorObservers: [Navi.routeObserver, Navi.modalRouteObserver], + theme: ThemeData( + //TODO color settings + colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue, brightness: appTheme.darkMode ? Brightness.dark : Brightness.light), + useMaterial3: true, + ), + home: SCNNavLayout(), + ), + ), + ); + } +} + void setFirebaseToken(String fcmToken) async { final acc = AppAuth(); @@ -182,29 +224,18 @@ void setFirebaseToken(String fcmToken) async { } } -class SCNApp extends StatelessWidget { - SCNApp({super.key}); - - @override - Widget build(BuildContext context) { - return ToastificationWrapper( - config: ToastificationConfig( - itemWidth: 440, - marginBuilder: (alignment) => EdgeInsets.symmetric(vertical: 64), - animationDuration: Duration(milliseconds: 200), - ), - child: Consumer( - builder: (context, appTheme, child) => MaterialApp( - title: 'SimpleCloudNotifier', - navigatorObservers: [Navi.routeObserver, Navi.modalRouteObserver], - theme: ThemeData( - //TODO color settings - colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue, brightness: appTheme.darkMode ? Brightness.dark : Brightness.light), - useMaterial3: true, - ), - home: SCNNavLayout(), - ), - ), - ); - } +Future _onBackgroundMessage(RemoteMessage message) async { + await _receiveMessage(message, false); +} + +void _onForegroundMessage(RemoteMessage message) { + _receiveMessage(message, true); +} + +Future _receiveMessage(RemoteMessage message, bool foreground) async { + // ensure init + Hive.openBox('scn-logs'); + + ApplicationLog.info('Received FB message (${foreground ? 'foreground' : 'background'}): ${message.messageId ?? 'NULL'}'); + FBMessageLog.insert(message); } diff --git a/flutter/lib/pages/debug/debug_persistence.dart b/flutter/lib/pages/debug/debug_persistence.dart index e02a52f..44d3328 100644 --- a/flutter/lib/pages/debug/debug_persistence.dart +++ b/flutter/lib/pages/debug/debug_persistence.dart @@ -6,6 +6,8 @@ import 'package:simplecloudnotifier/models/message.dart'; import 'package:simplecloudnotifier/pages/debug/debug_persistence_hive.dart'; import 'package:simplecloudnotifier/pages/debug/debug_persistence_sharedprefs.dart'; import 'package:simplecloudnotifier/state/application_log.dart'; +import 'package:simplecloudnotifier/state/fb_message.dart'; +import 'package:simplecloudnotifier/state/interfaces.dart'; import 'package:simplecloudnotifier/state/request_log.dart'; import 'package:simplecloudnotifier/utils/navi.dart'; @@ -31,98 +33,56 @@ class _DebugPersistencePageState extends State { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Card.outlined( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: GestureDetector( - onTap: () { - Navi.push(context, () => DebugSharedPrefPage(sharedPref: prefs!)); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(width: 30, child: Text('')), - Expanded(child: Text('Shared Preferences', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)), - SizedBox(width: 40, child: Text('${prefs?.getKeys().length.toString()}', textAlign: TextAlign.end)), - ], - ), - ), - ), - ), - Card.outlined( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: GestureDetector( - onTap: () { - Navi.push(context, () => DebugHiveBoxPage(boxName: 'scn-requests', box: Hive.box('scn-requests'))); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(width: 30, child: Text('')), - Expanded(child: Text('Hive [scn-requests]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)), - SizedBox(width: 40, child: Text('${Hive.box('scn-requests').length.toString()}', textAlign: TextAlign.end)), - ], - ), - ), - ), - ), - Card.outlined( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: GestureDetector( - onTap: () { - Navi.push(context, () => DebugHiveBoxPage(boxName: 'scn-requests', box: Hive.box('scn-logs'))); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(width: 30, child: Text('')), - Expanded(child: Text('Hive [scn-logs]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)), - SizedBox(width: 40, child: Text('${Hive.box('scn-logs').length.toString()}', textAlign: TextAlign.end)), - ], - ), - ), - ), - ), - Card.outlined( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: GestureDetector( - onTap: () { - Navi.push(context, () => DebugHiveBoxPage(boxName: 'scn-message-cache', box: Hive.box('scn-message-cache'))); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(width: 30, child: Text('')), - Expanded(child: Text('Hive [scn-message-cache]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)), - SizedBox(width: 40, child: Text('${Hive.box('scn-message-cache').length.toString()}', textAlign: TextAlign.end)), - ], - ), - ), - ), - ), - Card.outlined( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: GestureDetector( - onTap: () { - Navi.push(context, () => DebugHiveBoxPage(boxName: 'scn-channel-cache', box: Hive.box('scn-channel-cache'))); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(width: 30, child: Text('')), - Expanded(child: Text('Hive [scn-channel-cache]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)), - SizedBox(width: 40, child: Text('${Hive.box('scn-channel-cache').length.toString()}', textAlign: TextAlign.end)), - ], - ), - ), - ), - ), + _buildSharedPrefCard(context), + _buildHiveCard(context, () => Hive.box('scn-requests'), 'scn-requests'), + _buildHiveCard(context, () => Hive.box('scn-logs'), 'scn-logs'), + _buildHiveCard(context, () => Hive.box('scn-message-cache'), 'scn-message-cache'), + _buildHiveCard(context, () => Hive.box('scn-channel-cache'), 'scn-channel-cache'), + _buildHiveCard(context, () => Hive.box('scn-fb-messages'), 'scn-fb-messages'), ], ), ); } + + Widget _buildSharedPrefCard(BuildContext context) { + return Card.outlined( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: GestureDetector( + onTap: () { + Navi.push(context, () => DebugSharedPrefPage(sharedPref: prefs!)); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(width: 30, child: Text('')), + Expanded(child: Text('Shared Preferences', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)), + SizedBox(width: 40, child: Text('${prefs?.getKeys().length.toString()}', textAlign: TextAlign.end)), + ], + ), + ), + ), + ); + } + + Widget _buildHiveCard(BuildContext context, Box Function() boxFunc, String boxname) { + return Card.outlined( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: GestureDetector( + onTap: () { + Navi.push(context, () => DebugHiveBoxPage(boxName: boxname, box: Hive.box(boxname))); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(width: 30, child: Text('')), + Expanded(child: Text('Hive [$boxname]', style: TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center)), + SizedBox(width: 40, child: Text('${boxFunc().length.toString()}', textAlign: TextAlign.end)), + ], + ), + ), + ), + ); + } } diff --git a/flutter/lib/state/fb_message.dart b/flutter/lib/state/fb_message.dart new file mode 100644 index 0000000..b57e372 --- /dev/null +++ b/flutter/lib/state/fb_message.dart @@ -0,0 +1,236 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:simplecloudnotifier/state/interfaces.dart'; + +part 'fb_message.g.dart'; + +class FBMessageLog { + //TODO max size, auto clear old + + static void insert(RemoteMessage msg) { + Hive.box('scn-fb-messages').add(FBMessage.fromRemoteMessage(msg)); + } +} + +@HiveType(typeId: 106) +class FBMessage extends HiveObject implements FieldDebuggable { + @HiveField(0) + final String? senderId; + @HiveField(1) + final String? category; + @HiveField(2) + final String? collapseKey; + @HiveField(3) + final bool contentAvailable; + @HiveField(4) + final Map data; + @HiveField(5) + final String? from; + @HiveField(6) + final String? messageId; + @HiveField(7) + final String? messageType; + @HiveField(8) + final bool mutableContent; + @HiveField(9) + final RemoteNotification? notification; + @HiveField(10) + final DateTime? sentTime; + @HiveField(11) + final String? threadId; + @HiveField(12) + final int? ttl; + + @HiveField(20) + final String? notificationAndroidChannelId; + @HiveField(21) + final String? notificationAndroidClickAction; + @HiveField(22) + final String? notificationAndroidColor; + @HiveField(23) + final int? notificationAndroidCount; + @HiveField(24) + final String? notificationAndroidImageUrl; + @HiveField(25) + final String? notificationAndroidLink; + @HiveField(26) + final AndroidNotificationPriority? notificationAndroidPriority; + @HiveField(27) + final String? notificationAndroidSmallIcon; + @HiveField(28) + final String? notificationAndroidSound; + @HiveField(29) + final String? notificationAndroidTicker; + @HiveField(30) + final AndroidNotificationVisibility? notificationAndroidVisibility; + @HiveField(31) + final String? notificationAndroidTag; + + @HiveField(40) + final String? notificationAppleBadge; + @HiveField(41) + final AppleNotificationSound? notificationAppleSound; + @HiveField(42) + final String? notificationAppleImageUrl; + @HiveField(43) + final String? notificationAppleSubtitle; + @HiveField(44) + final List? notificationAppleSubtitleLocArgs; + @HiveField(45) + final String? notificationAppleSubtitleLocKey; + + @HiveField(50) + final String? notificationWebAnalyticsLabel; + @HiveField(51) + final String? notificationWebImage; + @HiveField(52) + final String? notificationWebLink; + + @HiveField(60) + final String? notificationTitle; + @HiveField(61) + final List? notificationTitleLocArgs; + @HiveField(62) + final String? notificationTitleLocKey; + @HiveField(63) + final String? notificationBody; + @HiveField(64) + final List? notificationBodyLocArgs; + @HiveField(65) + final String? notificationBodyLocKey; + + FBMessage({ + required this.senderId, + required this.category, + required this.collapseKey, + required this.contentAvailable, + required this.data, + required this.from, + required this.messageId, + required this.messageType, + required this.mutableContent, + required this.notification, + required this.sentTime, + required this.threadId, + required this.ttl, + required this.notificationAndroidChannelId, + required this.notificationAndroidClickAction, + required this.notificationAndroidColor, + required this.notificationAndroidCount, + required this.notificationAndroidImageUrl, + required this.notificationAndroidLink, + required this.notificationAndroidPriority, + required this.notificationAndroidSmallIcon, + required this.notificationAndroidSound, + required this.notificationAndroidTicker, + required this.notificationAndroidVisibility, + required this.notificationAndroidTag, + required this.notificationAppleBadge, + required this.notificationAppleSound, + required this.notificationAppleImageUrl, + required this.notificationAppleSubtitle, + required this.notificationAppleSubtitleLocArgs, + required this.notificationAppleSubtitleLocKey, + required this.notificationWebAnalyticsLabel, + required this.notificationWebImage, + required this.notificationWebLink, + required this.notificationTitle, + required this.notificationTitleLocArgs, + required this.notificationTitleLocKey, + required this.notificationBody, + required this.notificationBodyLocArgs, + required this.notificationBodyLocKey, + }); + + FBMessage.fromRemoteMessage(RemoteMessage rmsg) + : this.senderId = rmsg.senderId, + this.category = rmsg.category, + this.collapseKey = rmsg.collapseKey, + this.contentAvailable = rmsg.contentAvailable, + this.data = rmsg.data.map((key, value) => MapEntry(key, value?.toString() ?? '')), + this.from = rmsg.from, + this.messageId = rmsg.messageId, + this.messageType = rmsg.messageType, + this.mutableContent = rmsg.mutableContent, + this.notification = rmsg.notification, + this.sentTime = rmsg.sentTime, + this.threadId = rmsg.threadId, + this.ttl = rmsg.ttl, + this.notificationAndroidChannelId = rmsg.notification?.android?.channelId, + this.notificationAndroidClickAction = rmsg.notification?.android?.clickAction, + this.notificationAndroidColor = rmsg.notification?.android?.color, + this.notificationAndroidCount = rmsg.notification?.android?.count, + this.notificationAndroidImageUrl = rmsg.notification?.android?.imageUrl, + this.notificationAndroidLink = rmsg.notification?.android?.link, + this.notificationAndroidPriority = rmsg.notification?.android?.priority, + this.notificationAndroidSmallIcon = rmsg.notification?.android?.smallIcon, + this.notificationAndroidSound = rmsg.notification?.android?.sound, + this.notificationAndroidTicker = rmsg.notification?.android?.ticker, + this.notificationAndroidVisibility = rmsg.notification?.android?.visibility, + this.notificationAndroidTag = rmsg.notification?.android?.tag, + this.notificationAppleBadge = rmsg.notification?.apple?.badge, + this.notificationAppleSound = rmsg.notification?.apple?.sound, + this.notificationAppleImageUrl = rmsg.notification?.apple?.imageUrl, + this.notificationAppleSubtitle = rmsg.notification?.apple?.subtitle, + this.notificationAppleSubtitleLocArgs = rmsg.notification?.apple?.subtitleLocArgs, + this.notificationAppleSubtitleLocKey = rmsg.notification?.apple?.subtitleLocKey, + this.notificationWebAnalyticsLabel = rmsg.notification?.web?.analyticsLabel, + this.notificationWebImage = rmsg.notification?.web?.image, + this.notificationWebLink = rmsg.notification?.web?.link, + this.notificationTitle = rmsg.notification?.title, + this.notificationTitleLocArgs = rmsg.notification?.titleLocArgs, + this.notificationTitleLocKey = rmsg.notification?.titleLocKey, + this.notificationBody = rmsg.notification?.body, + this.notificationBodyLocArgs = rmsg.notification?.bodyLocArgs, + this.notificationBodyLocKey = rmsg.notification?.bodyLocKey {} + + @override + String toString() { + return 'FBMessage[${this.messageId ?? 'NULL'}]'; + } + + List<(String, String)> debugFieldList() { + return [ + ('senderId', this.senderId ?? ''), + ('category', this.category ?? ''), + ('collapseKey', this.collapseKey ?? ''), + ('contentAvailable', this.contentAvailable.toString()), + ('data', this.data.toString()), + ('from', this.from ?? ''), + ('messageId', this.messageId ?? ''), + ('messageType', this.messageType ?? ''), + ('mutableContent', this.mutableContent.toString()), + ('notification', this.notification?.toString() ?? ''), + ('sentTime', this.sentTime?.toString() ?? ''), + ('threadId', this.threadId ?? ''), + ('ttl', this.ttl?.toString() ?? ''), + ('notification.Android.ChannelId', this.notificationAndroidChannelId ?? ''), + ('notification.Android.ClickAction', this.notificationAndroidClickAction ?? ''), + ('notification.Android.Color', this.notificationAndroidColor ?? ''), + ('notification.Android.Count', this.notificationAndroidCount?.toString() ?? ''), + ('notification.Android.ImageUrl', this.notificationAndroidImageUrl ?? ''), + ('notification.Android.Link', this.notificationAndroidLink ?? ''), + ('notification.Android.Priority', this.notificationAndroidPriority?.toString() ?? ''), + ('notification.Android.SmallIcon', this.notificationAndroidSmallIcon ?? ''), + ('notification.Android.Sound', this.notificationAndroidSound ?? ''), + ('notification.Android.Ticker', this.notificationAndroidTicker ?? ''), + ('notification.Android.Visibility', this.notificationAndroidVisibility?.toString() ?? ''), + ('notification.Android.Tag', this.notificationAndroidTag ?? ''), + ('notification.Apple.Badge', this.notificationAppleBadge ?? ''), + ('notification.Apple.Sound', this.notificationAppleSound?.toString() ?? ''), + ('notification.Apple.ImageUrl', this.notificationAppleImageUrl ?? ''), + ('notification.Apple.Subtitle', this.notificationAppleSubtitle ?? ''), + ('notification.Apple.SubtitleLocArgs', this.notificationAppleSubtitleLocArgs?.toString() ?? ''), + ('notification.Apple.SubtitleLocKey', this.notificationAppleSubtitleLocKey ?? ''), + ('notification.Web.AnalyticsLabel', this.notificationWebAnalyticsLabel ?? ''), + ('notification.Web.Image', this.notificationWebImage ?? ''), + ('notification.Web.Link', this.notificationWebLink ?? ''), + ('notification.Title', this.notificationTitle ?? ''), + ('notification.TitleLocArgs', this.notificationTitleLocArgs?.toString() ?? ''), + ('notification.TitleLocKey', this.notificationTitleLocKey ?? ''), + ('notification.Body', this.notificationBody ?? ''), + ('notification.BodyLocArgs', this.notificationBodyLocArgs?.toString() ?? ''), + ('notification.BodyLocKey', this.notificationBodyLocKey ?? ''), + ]; + } +} diff --git a/flutter/lib/state/fb_message.g.dart b/flutter/lib/state/fb_message.g.dart new file mode 100644 index 0000000..e10f640 --- /dev/null +++ b/flutter/lib/state/fb_message.g.dart @@ -0,0 +1,159 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'fb_message.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class FBMessageAdapter extends TypeAdapter { + @override + final int typeId = 106; + + @override + FBMessage read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return FBMessage( + senderId: fields[0] as String?, + category: fields[1] as String?, + collapseKey: fields[2] as String?, + contentAvailable: fields[3] as bool, + data: (fields[4] as Map).cast(), + from: fields[5] as String?, + messageId: fields[6] as String?, + messageType: fields[7] as String?, + mutableContent: fields[8] as bool, + notification: fields[9] as RemoteNotification?, + sentTime: fields[10] as DateTime?, + threadId: fields[11] as String?, + ttl: fields[12] as int?, + notificationAndroidChannelId: fields[20] as String?, + notificationAndroidClickAction: fields[21] as String?, + notificationAndroidColor: fields[22] as String?, + notificationAndroidCount: fields[23] as int?, + notificationAndroidImageUrl: fields[24] as String?, + notificationAndroidLink: fields[25] as String?, + notificationAndroidPriority: fields[26] as AndroidNotificationPriority?, + notificationAndroidSmallIcon: fields[27] as String?, + notificationAndroidSound: fields[28] as String?, + notificationAndroidTicker: fields[29] as String?, + notificationAndroidVisibility: + fields[30] as AndroidNotificationVisibility?, + notificationAndroidTag: fields[31] as String?, + notificationAppleBadge: fields[40] as String?, + notificationAppleSound: fields[41] as AppleNotificationSound?, + notificationAppleImageUrl: fields[42] as String?, + notificationAppleSubtitle: fields[43] as String?, + notificationAppleSubtitleLocArgs: (fields[44] as List?)?.cast(), + notificationAppleSubtitleLocKey: fields[45] as String?, + notificationWebAnalyticsLabel: fields[50] as String?, + notificationWebImage: fields[51] as String?, + notificationWebLink: fields[52] as String?, + notificationTitle: fields[60] as String?, + notificationTitleLocArgs: (fields[61] as List?)?.cast(), + notificationTitleLocKey: fields[62] as String?, + notificationBody: fields[63] as String?, + notificationBodyLocArgs: (fields[64] as List?)?.cast(), + notificationBodyLocKey: fields[65] as String?, + ); + } + + @override + void write(BinaryWriter writer, FBMessage obj) { + writer + ..writeByte(40) + ..writeByte(0) + ..write(obj.senderId) + ..writeByte(1) + ..write(obj.category) + ..writeByte(2) + ..write(obj.collapseKey) + ..writeByte(3) + ..write(obj.contentAvailable) + ..writeByte(4) + ..write(obj.data) + ..writeByte(5) + ..write(obj.from) + ..writeByte(6) + ..write(obj.messageId) + ..writeByte(7) + ..write(obj.messageType) + ..writeByte(8) + ..write(obj.mutableContent) + ..writeByte(9) + ..write(obj.notification) + ..writeByte(10) + ..write(obj.sentTime) + ..writeByte(11) + ..write(obj.threadId) + ..writeByte(12) + ..write(obj.ttl) + ..writeByte(20) + ..write(obj.notificationAndroidChannelId) + ..writeByte(21) + ..write(obj.notificationAndroidClickAction) + ..writeByte(22) + ..write(obj.notificationAndroidColor) + ..writeByte(23) + ..write(obj.notificationAndroidCount) + ..writeByte(24) + ..write(obj.notificationAndroidImageUrl) + ..writeByte(25) + ..write(obj.notificationAndroidLink) + ..writeByte(26) + ..write(obj.notificationAndroidPriority) + ..writeByte(27) + ..write(obj.notificationAndroidSmallIcon) + ..writeByte(28) + ..write(obj.notificationAndroidSound) + ..writeByte(29) + ..write(obj.notificationAndroidTicker) + ..writeByte(30) + ..write(obj.notificationAndroidVisibility) + ..writeByte(31) + ..write(obj.notificationAndroidTag) + ..writeByte(40) + ..write(obj.notificationAppleBadge) + ..writeByte(41) + ..write(obj.notificationAppleSound) + ..writeByte(42) + ..write(obj.notificationAppleImageUrl) + ..writeByte(43) + ..write(obj.notificationAppleSubtitle) + ..writeByte(44) + ..write(obj.notificationAppleSubtitleLocArgs) + ..writeByte(45) + ..write(obj.notificationAppleSubtitleLocKey) + ..writeByte(50) + ..write(obj.notificationWebAnalyticsLabel) + ..writeByte(51) + ..write(obj.notificationWebImage) + ..writeByte(52) + ..write(obj.notificationWebLink) + ..writeByte(60) + ..write(obj.notificationTitle) + ..writeByte(61) + ..write(obj.notificationTitleLocArgs) + ..writeByte(62) + ..write(obj.notificationTitleLocKey) + ..writeByte(63) + ..write(obj.notificationBody) + ..writeByte(64) + ..write(obj.notificationBodyLocArgs) + ..writeByte(65) + ..write(obj.notificationBodyLocKey); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is FBMessageAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +}