From 85a1dd30539a3b25270cf688d71b36b0ffa010e3 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sat, 23 Nov 2024 16:55:23 +0800 Subject: [PATCH] :sparkles: Notification screen --- assets/translations/en.json | 13 +- assets/translations/zh.json | 13 +- lib/providers/navigation.dart | 5 + lib/router.dart | 8 + lib/screens/notification.dart | 244 ++++++++++ lib/types/notification.dart | 26 ++ lib/types/notification.freezed.dart | 438 ++++++++++++++++++ lib/types/notification.g.dart | 46 ++ .../navigation/app_drawer_navigation.dart | 2 +- 9 files changed, 792 insertions(+), 3 deletions(-) create mode 100644 lib/screens/notification.dart create mode 100644 lib/types/notification.dart create mode 100644 lib/types/notification.freezed.dart create mode 100644 lib/types/notification.g.dart 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 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 createState() => _NotificationScreenState(); +} + +class _NotificationScreenState extends State { + bool _isBusy = false; + bool _isFirstLoading = true; + bool _isSubmitting = false; + + final List _notifications = List.empty(growable: true); + int? _totalCount; + + static const Map kNotificationTopicIcons = { + 'passport.security.alert': Symbols.gpp_maybe, + 'interactive.subscription': Symbols.subscriptions, + 'interactive.feedback': Symbols.add_reaction, + 'messaging.callStart': Symbols.call_received, + }; + + Future _fetchNotifications() async { + setState(() => _isBusy = true); + + try { + final sn = context.read(); + 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() ?? + [], + ); + } 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 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(); + 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(); + 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 metadata, + required int priority, + required int? senderId, + required int accountId, + required DateTime? readAt, + }) = _SnNotification; + + factory SnNotification.fromJson(Map 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 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 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 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 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 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 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, + 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 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, + 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 metadata = const {}, + required this.priority, + required this.senderId, + required this.accountId, + required this.readAt}) + : _metadata = metadata; + + factory _$SnNotificationImpl.fromJson(Map 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 _metadata; + @override + @JsonKey() + Map 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 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 metadata, + required final int priority, + required final int? senderId, + required final int accountId, + required final DateTime? readAt}) = _$SnNotificationImpl; + + factory _SnNotification.fromJson(Map 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 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 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? ?? 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 _$$SnNotificationImplToJson( + _$SnNotificationImpl instance) => + { + '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 { Widget build(BuildContext context) { final nav = context.watch(); - final backgroundColor = ResponsiveBreakpoints.of(context).largerThan(MOBILE) + final backgroundColor = ResponsiveBreakpoints.of(context).largerThan(TABLET) ? Colors.transparent : null;