diff --git a/assets/translations/en.json b/assets/translations/en.json
index e22c61f..02d4e7d 100644
--- a/assets/translations/en.json
+++ b/assets/translations/en.json
@@ -22,6 +22,7 @@
   "screenRealm": "Realm",
   "screenRealmManage": "Edit Realm",
   "screenRealmNew": "New Realm",
+  "screenNotification": "Notification",
   "dialogOkay": "Okay",
   "dialogCancel": "Cancel",
   "dialogConfirm": "Confirm",
@@ -174,5 +175,15 @@
   },
   "addAttachmentFromAlbum": "Add from album",
   "addAttachmentFromClipboard": "Paste file",
-  "attachmentPastedImage": "Pasted Image"
+  "attachmentPastedImage": "Pasted Image",
+  "notificationUnread": "未读",
+  "notificationRead": "已读",
+  "notificationMarkAllRead": "Mark all notifications as read",
+  "notificationMarkAllReadDescription": "Are you sure you want to mark all notifications as read? This operation is irreversible.",
+  "notificationMarkAllReadPrompt": {
+    "zero": "Marked 0 notification as read.",
+    "one": "Marked {} notification as read.",
+    "other": "Marked {} notifications as read."
+  },
+  "notificationMarkOneReadPrompt": "Marked notification {} as read."
 }
diff --git a/assets/translations/zh.json b/assets/translations/zh.json
index 4ce2c6b..04c84fd 100644
--- a/assets/translations/zh.json
+++ b/assets/translations/zh.json
@@ -22,6 +22,7 @@
   "screenRealm": "领域",
   "screenRealmManage": "编辑领域",
   "screenRealmNew": "新建领域",
+  "screenNotification": "通知",
   "dialogOkay": "好的",
   "dialogCancel": "取消",
   "dialogConfirm": "确认",
@@ -174,5 +175,15 @@
   },
   "addAttachmentFromAlbum": "从相册中添加附件",
   "addAttachmentFromClipboard": "粘贴附件",
-  "attachmentPastedImage" : "粘贴的图片"
+  "attachmentPastedImage" : "粘贴的图片",
+  "notificationUnread": "未读",
+  "notificationRead": "已读",
+  "notificationMarkAllRead": "已读所有通知",
+  "notificationMarkAllReadDescription": "您确定要将所有通知设置为已读吗?该操作不可撤销。",
+  "notificationMarkAllReadPrompt": {
+    "zero": "已将 0 个通知标记为已读。",
+    "one": "已将 {} 个通知标记为已读。",
+    "other": "已将 {} 个通知标记为已读。"
+  },
+  "notificationMarkOneReadPrompt": "已将通知 {} 标记为已读。"
 }
diff --git a/lib/providers/navigation.dart b/lib/providers/navigation.dart
index d5bf27a..dbed59e 100644
--- a/lib/providers/navigation.dart
+++ b/lib/providers/navigation.dart
@@ -63,6 +63,11 @@ class NavigationProvider extends ChangeNotifier {
       screen: 'album',
       label: 'screenAlbum',
     ),
+    AppNavDestination(
+      icon: Icon(Symbols.notifications, weight: 400, opticalSize: 20),
+      screen: 'notification',
+      label: 'screenNotification',
+    ),
   ];
   static const List<String> kDefaultPinnedDestination = [
     'home',
diff --git a/lib/router.dart b/lib/router.dart
index 35d5c78..1fff01c 100644
--- a/lib/router.dart
+++ b/lib/router.dart
@@ -14,6 +14,7 @@ import 'package:surface/screens/chat/manage.dart';
 import 'package:surface/screens/chat/room.dart';
 import 'package:surface/screens/explore.dart';
 import 'package:surface/screens/home.dart';
+import 'package:surface/screens/notification.dart';
 import 'package:surface/screens/post/post_detail.dart';
 import 'package:surface/screens/post/post_editor.dart';
 import 'package:surface/screens/realm.dart';
@@ -175,6 +176,13 @@ final _appRoutes = [
           child: const AlbumScreen(),
         ),
       ),
+      GoRoute(
+        path: '/notification',
+        name: 'notification',
+        pageBuilder: (context, state) => NoTransitionPage(
+          child: const NotificationScreen(),
+        ),
+      ),
     ],
   ),
   ShellRoute(
diff --git a/lib/screens/notification.dart b/lib/screens/notification.dart
new file mode 100644
index 0000000..5e693a1
--- /dev/null
+++ b/lib/screens/notification.dart
@@ -0,0 +1,244 @@
+import 'dart:math' as math;
+
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
+import 'package:gap/gap.dart';
+import 'package:material_symbols_icons/symbols.dart';
+import 'package:provider/provider.dart';
+import 'package:relative_time/relative_time.dart';
+import 'package:styled_widget/styled_widget.dart';
+import 'package:surface/providers/sn_network.dart';
+import 'package:surface/types/notification.dart';
+import 'package:surface/types/post.dart';
+import 'package:surface/widgets/dialog.dart';
+import 'package:surface/widgets/loading_indicator.dart';
+import 'package:surface/widgets/markdown_content.dart';
+import 'package:surface/widgets/post/post_item.dart';
+import 'package:very_good_infinite_list/very_good_infinite_list.dart';
+
+class NotificationScreen extends StatefulWidget {
+  const NotificationScreen({super.key});
+
+  @override
+  State<NotificationScreen> createState() => _NotificationScreenState();
+}
+
+class _NotificationScreenState extends State<NotificationScreen> {
+  bool _isBusy = false;
+  bool _isFirstLoading = true;
+  bool _isSubmitting = false;
+
+  final List<SnNotification> _notifications = List.empty(growable: true);
+  int? _totalCount;
+
+  static const Map<String, IconData> kNotificationTopicIcons = {
+    'passport.security.alert': Symbols.gpp_maybe,
+    'interactive.subscription': Symbols.subscriptions,
+    'interactive.feedback': Symbols.add_reaction,
+    'messaging.callStart': Symbols.call_received,
+  };
+
+  Future<void> _fetchNotifications() async {
+    setState(() => _isBusy = true);
+
+    try {
+      final sn = context.read<SnNetworkProvider>();
+      final resp = await sn.client.get('/cgi/id/notifications?take=10');
+      _totalCount = resp.data['count'];
+      _notifications.addAll(
+        resp.data['data']
+                ?.map((e) => SnNotification.fromJson(e))
+                .cast<SnNotification>() ??
+            [],
+      );
+    } catch (err) {
+      if (!mounted) return;
+      context.showErrorDialog(err);
+    } finally {
+      _isFirstLoading = false;
+      setState(() => _isBusy = false);
+    }
+  }
+
+  void _markAllAsRead() async {
+    if (_notifications.isEmpty) return;
+
+    final confirm = await context.showConfirmDialog(
+      'notificationMarkAllRead'.tr(),
+      'notificationMarkAllReadDescription'.tr(),
+    );
+    if (!confirm) return;
+
+    if (!mounted) return;
+    setState(() => _isSubmitting = true);
+
+    List<int> markList = List.empty(growable: true);
+    for (final element in _notifications) {
+      if (element.id <= 0) continue;
+      if (element.readAt != null) continue;
+      markList.add(element.id);
+    }
+
+    try {
+      final sn = context.read<SnNetworkProvider>();
+      await sn.client.put('/cgi/id/notifications/read', data: {
+        'messages': markList,
+      });
+      _notifications.clear();
+      _fetchNotifications();
+
+      if (!mounted) return;
+      context.showSnackbar(
+        'notificationMarkAllReadPrompt'.plural(markList.length),
+      );
+    } catch (err) {
+      if (!mounted) return;
+      context.showErrorDialog(err);
+    } finally {
+      setState(() => _isSubmitting = false);
+    }
+  }
+
+  void _markOneAsRead(SnNotification notification) async {
+    if (notification.readAt != null) return;
+
+    setState(() => _isSubmitting = true);
+
+    try {
+      final sn = context.read<SnNetworkProvider>();
+      await sn.client.put('/cgi/id/notifications/read/${notification.id}');
+      _notifications.clear();
+      _fetchNotifications();
+
+      if (!mounted) return;
+      context.showSnackbar(
+        'notificationMarkOneReadPrompt'.tr(args: ['#${notification.id}']),
+      );
+    } catch (err) {
+      if (!mounted) return;
+      context.showErrorDialog(err);
+    } finally {
+      setState(() => _isSubmitting = false);
+    }
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _fetchNotifications();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text('screenNotification').tr(),
+        actions: [
+          IconButton(
+            icon: const Icon(Icons.checklist),
+            onPressed: _isSubmitting ? null : _markAllAsRead,
+          ),
+        ],
+      ),
+      body: Column(
+        children: [
+          LoadingIndicator(isActive: _isFirstLoading),
+          Expanded(
+            child: RefreshIndicator(
+              onRefresh: () {
+                _notifications.clear();
+                return _fetchNotifications();
+              },
+              child: InfiniteList(
+                padding: EdgeInsets.only(
+                  top: 16,
+                  bottom: math.max(MediaQuery.of(context).padding.bottom, 16),
+                ),
+                itemCount: _notifications.length,
+                onFetchData: () {
+                  _fetchNotifications();
+                },
+                isLoading: _isBusy,
+                hasReachedMax: _totalCount != null &&
+                    _notifications.length >= _totalCount!,
+                itemBuilder: (context, idx) {
+                  final nty = _notifications[idx];
+                  return Row(
+                    crossAxisAlignment: CrossAxisAlignment.start,
+                    children: [
+                      Icon(kNotificationTopicIcons[nty.topic]),
+                      const Gap(16),
+                      Expanded(
+                        child: Column(
+                          crossAxisAlignment: CrossAxisAlignment.start,
+                          children: [
+                            if (nty.readAt == null)
+                              StyledWidget(Badge(
+                                label: Text('notificationUnread').tr(),
+                              )).padding(bottom: 4),
+                            Text(
+                              nty.title,
+                              style: Theme.of(context).textTheme.titleMedium,
+                            ),
+                            if (nty.subtitle != null)
+                              Text(
+                                nty.subtitle!,
+                                style: Theme.of(context).textTheme.titleSmall,
+                              ),
+                            if (nty.subtitle != null) const Gap(4),
+                            MarkdownTextContent(
+                              content: nty.body,
+                              isAutoWarp: true,
+                              isSelectable: true,
+                            ),
+                            if ([
+                                  'interactive.feedback',
+                                  'interactive.subscription'
+                                ].contains(nty.topic) &&
+                                nty.metadata['related_post'] != null)
+                              PostItem(
+                                data: SnPost.fromJson(
+                                  nty.metadata['related_post']!,
+                                ),
+                              ),
+                            const Gap(8),
+                            Row(
+                              children: [
+                                Text(
+                                  DateFormat('yy/MM/dd').format(nty.createdAt),
+                                ).fontSize(12),
+                                const Gap(4),
+                                Text(
+                                  '·',
+                                  style: TextStyle(fontSize: 12),
+                                ),
+                                const Gap(4),
+                                Text(
+                                  RelativeTime(context).format(nty.createdAt),
+                                ).fontSize(12),
+                              ],
+                            ).opacity(0.75),
+                          ],
+                        ),
+                      ),
+                      const Gap(16),
+                      IconButton(
+                        icon: const Icon(Symbols.check),
+                        padding: EdgeInsets.all(0),
+                        visualDensity:
+                            const VisualDensity(horizontal: -4, vertical: -4),
+                        onPressed:
+                            _isSubmitting ? null : () => _markOneAsRead(nty),
+                      ),
+                    ],
+                  ).padding(horizontal: 16);
+                },
+                separatorBuilder: (_, __) => const Divider(),
+              ),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/types/notification.dart b/lib/types/notification.dart
new file mode 100644
index 0000000..d878b3c
--- /dev/null
+++ b/lib/types/notification.dart
@@ -0,0 +1,26 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'notification.freezed.dart';
+part 'notification.g.dart';
+
+@freezed
+class SnNotification with _$SnNotification {
+  const factory SnNotification({
+    required int id,
+    required DateTime createdAt,
+    required DateTime updatedAt,
+    required DateTime? deletedAt,
+    required String topic,
+    required String title,
+    required String? subtitle,
+    required String body,
+    @Default({}) Map<String, dynamic> metadata,
+    required int priority,
+    required int? senderId,
+    required int accountId,
+    required DateTime? readAt,
+  }) = _SnNotification;
+
+  factory SnNotification.fromJson(Map<String, dynamic> json) =>
+      _$SnNotificationFromJson(json);
+}
diff --git a/lib/types/notification.freezed.dart b/lib/types/notification.freezed.dart
new file mode 100644
index 0000000..75c295e
--- /dev/null
+++ b/lib/types/notification.freezed.dart
@@ -0,0 +1,438 @@
+// coverage:ignore-file
+// GENERATED CODE - DO NOT MODIFY BY HAND
+// ignore_for_file: type=lint
+// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
+
+part of 'notification.dart';
+
+// **************************************************************************
+// FreezedGenerator
+// **************************************************************************
+
+T _$identity<T>(T value) => value;
+
+final _privateConstructorUsedError = UnsupportedError(
+    'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
+
+SnNotification _$SnNotificationFromJson(Map<String, dynamic> json) {
+  return _SnNotification.fromJson(json);
+}
+
+/// @nodoc
+mixin _$SnNotification {
+  int get id => throw _privateConstructorUsedError;
+  DateTime get createdAt => throw _privateConstructorUsedError;
+  DateTime get updatedAt => throw _privateConstructorUsedError;
+  DateTime? get deletedAt => throw _privateConstructorUsedError;
+  String get topic => throw _privateConstructorUsedError;
+  String get title => throw _privateConstructorUsedError;
+  String? get subtitle => throw _privateConstructorUsedError;
+  String get body => throw _privateConstructorUsedError;
+  Map<String, dynamic> get metadata => throw _privateConstructorUsedError;
+  int get priority => throw _privateConstructorUsedError;
+  int? get senderId => throw _privateConstructorUsedError;
+  int get accountId => throw _privateConstructorUsedError;
+  DateTime? get readAt => throw _privateConstructorUsedError;
+
+  /// Serializes this SnNotification to a JSON map.
+  Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
+
+  /// Create a copy of SnNotification
+  /// with the given fields replaced by the non-null parameter values.
+  @JsonKey(includeFromJson: false, includeToJson: false)
+  $SnNotificationCopyWith<SnNotification> get copyWith =>
+      throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $SnNotificationCopyWith<$Res> {
+  factory $SnNotificationCopyWith(
+          SnNotification value, $Res Function(SnNotification) then) =
+      _$SnNotificationCopyWithImpl<$Res, SnNotification>;
+  @useResult
+  $Res call(
+      {int id,
+      DateTime createdAt,
+      DateTime updatedAt,
+      DateTime? deletedAt,
+      String topic,
+      String title,
+      String? subtitle,
+      String body,
+      Map<String, dynamic> metadata,
+      int priority,
+      int? senderId,
+      int accountId,
+      DateTime? readAt});
+}
+
+/// @nodoc
+class _$SnNotificationCopyWithImpl<$Res, $Val extends SnNotification>
+    implements $SnNotificationCopyWith<$Res> {
+  _$SnNotificationCopyWithImpl(this._value, this._then);
+
+  // ignore: unused_field
+  final $Val _value;
+  // ignore: unused_field
+  final $Res Function($Val) _then;
+
+  /// Create a copy of SnNotification
+  /// with the given fields replaced by the non-null parameter values.
+  @pragma('vm:prefer-inline')
+  @override
+  $Res call({
+    Object? id = null,
+    Object? createdAt = null,
+    Object? updatedAt = null,
+    Object? deletedAt = freezed,
+    Object? topic = null,
+    Object? title = null,
+    Object? subtitle = freezed,
+    Object? body = null,
+    Object? metadata = null,
+    Object? priority = null,
+    Object? senderId = freezed,
+    Object? accountId = null,
+    Object? readAt = freezed,
+  }) {
+    return _then(_value.copyWith(
+      id: null == id
+          ? _value.id
+          : id // ignore: cast_nullable_to_non_nullable
+              as int,
+      createdAt: null == createdAt
+          ? _value.createdAt
+          : createdAt // ignore: cast_nullable_to_non_nullable
+              as DateTime,
+      updatedAt: null == updatedAt
+          ? _value.updatedAt
+          : updatedAt // ignore: cast_nullable_to_non_nullable
+              as DateTime,
+      deletedAt: freezed == deletedAt
+          ? _value.deletedAt
+          : deletedAt // ignore: cast_nullable_to_non_nullable
+              as DateTime?,
+      topic: null == topic
+          ? _value.topic
+          : topic // ignore: cast_nullable_to_non_nullable
+              as String,
+      title: null == title
+          ? _value.title
+          : title // ignore: cast_nullable_to_non_nullable
+              as String,
+      subtitle: freezed == subtitle
+          ? _value.subtitle
+          : subtitle // ignore: cast_nullable_to_non_nullable
+              as String?,
+      body: null == body
+          ? _value.body
+          : body // ignore: cast_nullable_to_non_nullable
+              as String,
+      metadata: null == metadata
+          ? _value.metadata
+          : metadata // ignore: cast_nullable_to_non_nullable
+              as Map<String, dynamic>,
+      priority: null == priority
+          ? _value.priority
+          : priority // ignore: cast_nullable_to_non_nullable
+              as int,
+      senderId: freezed == senderId
+          ? _value.senderId
+          : senderId // ignore: cast_nullable_to_non_nullable
+              as int?,
+      accountId: null == accountId
+          ? _value.accountId
+          : accountId // ignore: cast_nullable_to_non_nullable
+              as int,
+      readAt: freezed == readAt
+          ? _value.readAt
+          : readAt // ignore: cast_nullable_to_non_nullable
+              as DateTime?,
+    ) as $Val);
+  }
+}
+
+/// @nodoc
+abstract class _$$SnNotificationImplCopyWith<$Res>
+    implements $SnNotificationCopyWith<$Res> {
+  factory _$$SnNotificationImplCopyWith(_$SnNotificationImpl value,
+          $Res Function(_$SnNotificationImpl) then) =
+      __$$SnNotificationImplCopyWithImpl<$Res>;
+  @override
+  @useResult
+  $Res call(
+      {int id,
+      DateTime createdAt,
+      DateTime updatedAt,
+      DateTime? deletedAt,
+      String topic,
+      String title,
+      String? subtitle,
+      String body,
+      Map<String, dynamic> metadata,
+      int priority,
+      int? senderId,
+      int accountId,
+      DateTime? readAt});
+}
+
+/// @nodoc
+class __$$SnNotificationImplCopyWithImpl<$Res>
+    extends _$SnNotificationCopyWithImpl<$Res, _$SnNotificationImpl>
+    implements _$$SnNotificationImplCopyWith<$Res> {
+  __$$SnNotificationImplCopyWithImpl(
+      _$SnNotificationImpl _value, $Res Function(_$SnNotificationImpl) _then)
+      : super(_value, _then);
+
+  /// Create a copy of SnNotification
+  /// with the given fields replaced by the non-null parameter values.
+  @pragma('vm:prefer-inline')
+  @override
+  $Res call({
+    Object? id = null,
+    Object? createdAt = null,
+    Object? updatedAt = null,
+    Object? deletedAt = freezed,
+    Object? topic = null,
+    Object? title = null,
+    Object? subtitle = freezed,
+    Object? body = null,
+    Object? metadata = null,
+    Object? priority = null,
+    Object? senderId = freezed,
+    Object? accountId = null,
+    Object? readAt = freezed,
+  }) {
+    return _then(_$SnNotificationImpl(
+      id: null == id
+          ? _value.id
+          : id // ignore: cast_nullable_to_non_nullable
+              as int,
+      createdAt: null == createdAt
+          ? _value.createdAt
+          : createdAt // ignore: cast_nullable_to_non_nullable
+              as DateTime,
+      updatedAt: null == updatedAt
+          ? _value.updatedAt
+          : updatedAt // ignore: cast_nullable_to_non_nullable
+              as DateTime,
+      deletedAt: freezed == deletedAt
+          ? _value.deletedAt
+          : deletedAt // ignore: cast_nullable_to_non_nullable
+              as DateTime?,
+      topic: null == topic
+          ? _value.topic
+          : topic // ignore: cast_nullable_to_non_nullable
+              as String,
+      title: null == title
+          ? _value.title
+          : title // ignore: cast_nullable_to_non_nullable
+              as String,
+      subtitle: freezed == subtitle
+          ? _value.subtitle
+          : subtitle // ignore: cast_nullable_to_non_nullable
+              as String?,
+      body: null == body
+          ? _value.body
+          : body // ignore: cast_nullable_to_non_nullable
+              as String,
+      metadata: null == metadata
+          ? _value._metadata
+          : metadata // ignore: cast_nullable_to_non_nullable
+              as Map<String, dynamic>,
+      priority: null == priority
+          ? _value.priority
+          : priority // ignore: cast_nullable_to_non_nullable
+              as int,
+      senderId: freezed == senderId
+          ? _value.senderId
+          : senderId // ignore: cast_nullable_to_non_nullable
+              as int?,
+      accountId: null == accountId
+          ? _value.accountId
+          : accountId // ignore: cast_nullable_to_non_nullable
+              as int,
+      readAt: freezed == readAt
+          ? _value.readAt
+          : readAt // ignore: cast_nullable_to_non_nullable
+              as DateTime?,
+    ));
+  }
+}
+
+/// @nodoc
+@JsonSerializable()
+class _$SnNotificationImpl implements _SnNotification {
+  const _$SnNotificationImpl(
+      {required this.id,
+      required this.createdAt,
+      required this.updatedAt,
+      required this.deletedAt,
+      required this.topic,
+      required this.title,
+      required this.subtitle,
+      required this.body,
+      final Map<String, dynamic> metadata = const {},
+      required this.priority,
+      required this.senderId,
+      required this.accountId,
+      required this.readAt})
+      : _metadata = metadata;
+
+  factory _$SnNotificationImpl.fromJson(Map<String, dynamic> json) =>
+      _$$SnNotificationImplFromJson(json);
+
+  @override
+  final int id;
+  @override
+  final DateTime createdAt;
+  @override
+  final DateTime updatedAt;
+  @override
+  final DateTime? deletedAt;
+  @override
+  final String topic;
+  @override
+  final String title;
+  @override
+  final String? subtitle;
+  @override
+  final String body;
+  final Map<String, dynamic> _metadata;
+  @override
+  @JsonKey()
+  Map<String, dynamic> get metadata {
+    if (_metadata is EqualUnmodifiableMapView) return _metadata;
+    // ignore: implicit_dynamic_type
+    return EqualUnmodifiableMapView(_metadata);
+  }
+
+  @override
+  final int priority;
+  @override
+  final int? senderId;
+  @override
+  final int accountId;
+  @override
+  final DateTime? readAt;
+
+  @override
+  String toString() {
+    return 'SnNotification(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, topic: $topic, title: $title, subtitle: $subtitle, body: $body, metadata: $metadata, priority: $priority, senderId: $senderId, accountId: $accountId, readAt: $readAt)';
+  }
+
+  @override
+  bool operator ==(Object other) {
+    return identical(this, other) ||
+        (other.runtimeType == runtimeType &&
+            other is _$SnNotificationImpl &&
+            (identical(other.id, id) || other.id == id) &&
+            (identical(other.createdAt, createdAt) ||
+                other.createdAt == createdAt) &&
+            (identical(other.updatedAt, updatedAt) ||
+                other.updatedAt == updatedAt) &&
+            (identical(other.deletedAt, deletedAt) ||
+                other.deletedAt == deletedAt) &&
+            (identical(other.topic, topic) || other.topic == topic) &&
+            (identical(other.title, title) || other.title == title) &&
+            (identical(other.subtitle, subtitle) ||
+                other.subtitle == subtitle) &&
+            (identical(other.body, body) || other.body == body) &&
+            const DeepCollectionEquality().equals(other._metadata, _metadata) &&
+            (identical(other.priority, priority) ||
+                other.priority == priority) &&
+            (identical(other.senderId, senderId) ||
+                other.senderId == senderId) &&
+            (identical(other.accountId, accountId) ||
+                other.accountId == accountId) &&
+            (identical(other.readAt, readAt) || other.readAt == readAt));
+  }
+
+  @JsonKey(includeFromJson: false, includeToJson: false)
+  @override
+  int get hashCode => Object.hash(
+      runtimeType,
+      id,
+      createdAt,
+      updatedAt,
+      deletedAt,
+      topic,
+      title,
+      subtitle,
+      body,
+      const DeepCollectionEquality().hash(_metadata),
+      priority,
+      senderId,
+      accountId,
+      readAt);
+
+  /// Create a copy of SnNotification
+  /// with the given fields replaced by the non-null parameter values.
+  @JsonKey(includeFromJson: false, includeToJson: false)
+  @override
+  @pragma('vm:prefer-inline')
+  _$$SnNotificationImplCopyWith<_$SnNotificationImpl> get copyWith =>
+      __$$SnNotificationImplCopyWithImpl<_$SnNotificationImpl>(
+          this, _$identity);
+
+  @override
+  Map<String, dynamic> toJson() {
+    return _$$SnNotificationImplToJson(
+      this,
+    );
+  }
+}
+
+abstract class _SnNotification implements SnNotification {
+  const factory _SnNotification(
+      {required final int id,
+      required final DateTime createdAt,
+      required final DateTime updatedAt,
+      required final DateTime? deletedAt,
+      required final String topic,
+      required final String title,
+      required final String? subtitle,
+      required final String body,
+      final Map<String, dynamic> metadata,
+      required final int priority,
+      required final int? senderId,
+      required final int accountId,
+      required final DateTime? readAt}) = _$SnNotificationImpl;
+
+  factory _SnNotification.fromJson(Map<String, dynamic> json) =
+      _$SnNotificationImpl.fromJson;
+
+  @override
+  int get id;
+  @override
+  DateTime get createdAt;
+  @override
+  DateTime get updatedAt;
+  @override
+  DateTime? get deletedAt;
+  @override
+  String get topic;
+  @override
+  String get title;
+  @override
+  String? get subtitle;
+  @override
+  String get body;
+  @override
+  Map<String, dynamic> get metadata;
+  @override
+  int get priority;
+  @override
+  int? get senderId;
+  @override
+  int get accountId;
+  @override
+  DateTime? get readAt;
+
+  /// Create a copy of SnNotification
+  /// with the given fields replaced by the non-null parameter values.
+  @override
+  @JsonKey(includeFromJson: false, includeToJson: false)
+  _$$SnNotificationImplCopyWith<_$SnNotificationImpl> get copyWith =>
+      throw _privateConstructorUsedError;
+}
diff --git a/lib/types/notification.g.dart b/lib/types/notification.g.dart
new file mode 100644
index 0000000..078d378
--- /dev/null
+++ b/lib/types/notification.g.dart
@@ -0,0 +1,46 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'notification.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+_$SnNotificationImpl _$$SnNotificationImplFromJson(Map<String, dynamic> json) =>
+    _$SnNotificationImpl(
+      id: (json['id'] as num).toInt(),
+      createdAt: DateTime.parse(json['created_at'] as String),
+      updatedAt: DateTime.parse(json['updated_at'] as String),
+      deletedAt: json['deleted_at'] == null
+          ? null
+          : DateTime.parse(json['deleted_at'] as String),
+      topic: json['topic'] as String,
+      title: json['title'] as String,
+      subtitle: json['subtitle'] as String?,
+      body: json['body'] as String,
+      metadata: json['metadata'] as Map<String, dynamic>? ?? const {},
+      priority: (json['priority'] as num).toInt(),
+      senderId: (json['sender_id'] as num?)?.toInt(),
+      accountId: (json['account_id'] as num).toInt(),
+      readAt: json['read_at'] == null
+          ? null
+          : DateTime.parse(json['read_at'] as String),
+    );
+
+Map<String, dynamic> _$$SnNotificationImplToJson(
+        _$SnNotificationImpl instance) =>
+    <String, dynamic>{
+      'id': instance.id,
+      'created_at': instance.createdAt.toIso8601String(),
+      'updated_at': instance.updatedAt.toIso8601String(),
+      'deleted_at': instance.deletedAt?.toIso8601String(),
+      'topic': instance.topic,
+      'title': instance.title,
+      'subtitle': instance.subtitle,
+      'body': instance.body,
+      'metadata': instance.metadata,
+      'priority': instance.priority,
+      'sender_id': instance.senderId,
+      'account_id': instance.accountId,
+      'read_at': instance.readAt?.toIso8601String(),
+    };
diff --git a/lib/widgets/navigation/app_drawer_navigation.dart b/lib/widgets/navigation/app_drawer_navigation.dart
index 079f896..0de5379 100644
--- a/lib/widgets/navigation/app_drawer_navigation.dart
+++ b/lib/widgets/navigation/app_drawer_navigation.dart
@@ -29,7 +29,7 @@ class _AppNavigationDrawerState extends State<AppNavigationDrawer> {
   Widget build(BuildContext context) {
     final nav = context.watch<NavigationProvider>();
 
-    final backgroundColor = ResponsiveBreakpoints.of(context).largerThan(MOBILE)
+    final backgroundColor = ResponsiveBreakpoints.of(context).largerThan(TABLET)
         ? Colors.transparent
         : null;