From 083975bcd9a0cff8cf98f78f07cb26451a7298f1 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 3 May 2024 16:35:28 +0800 Subject: [PATCH] :sparkles: Batch mark notify as read --- lib/i18n/app_en.arb | 2 + lib/i18n/app_zh.arb | 2 + lib/screens/notification.dart | 78 +++++++++++++++++++++++++++++------ 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/lib/i18n/app_en.arb b/lib/i18n/app_en.arb index fbba2ba..918b923 100644 --- a/lib/i18n/app_en.arb +++ b/lib/i18n/app_en.arb @@ -42,6 +42,8 @@ "notifyDone": "You're done!", "notifyDoneCaption": "There are no notifications unread for you.", "notifyListHint": "Pull to refresh, swipe to dismiss", + "notifyMarkAllRead": "Mark all as read", + "notifyMarkAllReadDone": "Marked all notifications as read", "friend": "Friend", "friendPending": "Pending", "friendActive": "Active", diff --git a/lib/i18n/app_zh.arb b/lib/i18n/app_zh.arb index c353e38..7525bd3 100644 --- a/lib/i18n/app_zh.arb +++ b/lib/i18n/app_zh.arb @@ -42,6 +42,8 @@ "notifyDone": "所有通知已读!", "notifyDoneCaption": "这里没有什么东西可以给你看的了~", "notifyListHint": "下拉以刷新,左滑来已读", + "notifyMarkAllRead": "将所有标记为已读", + "notifyMarkAllReadDone": "已将所有通知标记为已读", "friend": "好友", "friendPending": "请求中", "friendActive": "活跃的好友", diff --git a/lib/screens/notification.dart b/lib/screens/notification.dart index 259dcfa..43a113c 100644 --- a/lib/screens/notification.dart +++ b/lib/screens/notification.dart @@ -1,4 +1,7 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; +import 'package:flutter_animate/flutter_animate.dart'; import 'package:provider/provider.dart'; import 'package:solian/providers/auth.dart'; import 'package:solian/providers/notify.dart'; @@ -16,15 +19,55 @@ class NotificationScreen extends StatefulWidget { } class _NotificationScreenState extends State { + bool _isSubmitting = false; + + Future markAllRead() async { + setState(() => _isSubmitting = true); + + final auth = context.read(); + if (!await auth.isAuthorized()) { + setState(() => _isSubmitting = false); + return; + } + + final nty = context.read(); + List markList = List.empty(growable: true); + for (final element in nty.notifications) { + if (element.isRealtime) continue; + markList.add(element.id); + } + + var uri = getRequestUri('passport', '/api/notifications/batch/read'); + await auth.client!.put( + uri, + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({'messages': markList}), + ); + + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)!.notifyMarkAllReadDone), + )); + + await nty.fetch(auth); + + setState(() => _isSubmitting = false); + } + + @override + void initState() { + super.initState(); + + Future.delayed(Duration.zero, () { + final nty = context.read(); + nty.allRead(); + }); + } + @override Widget build(BuildContext context) { final auth = context.read(); final nty = context.watch(); - WidgetsBinding.instance.addPostFrameCallback((_) { - nty.allRead(); - }); - return IndentScaffold( noSafeArea: true, hideDrawer: true, @@ -33,6 +76,19 @@ class _NotificationScreenState extends State { onRefresh: () => nty.fetch(auth), child: CustomScrollView( slivers: [ + SliverToBoxAdapter( + child: _isSubmitting ? const LinearProgressIndicator().animate().scaleX() : Container(), + ), + if (nty.notifications.isNotEmpty) + SliverToBoxAdapter( + child: ListTile( + tileColor: Theme.of(context).colorScheme.secondaryContainer, + leading: const Icon(Icons.checklist), + title: Text(AppLocalizations.of(context)!.notifyMarkAllRead), + contentPadding: const EdgeInsets.symmetric(horizontal: 28), + onTap: _isSubmitting ? null : markAllRead, + ), + ), nty.notifications.isEmpty ? SliverToBoxAdapter( child: Container( @@ -41,8 +97,7 @@ class _NotificationScreenState extends State { child: ListTile( leading: const Icon(Icons.check), title: Text(AppLocalizations.of(context)!.notifyDone), - subtitle: Text( - AppLocalizations.of(context)!.notifyDoneCaption), + subtitle: Text(AppLocalizations.of(context)!.notifyDoneCaption), ), ), ) @@ -79,8 +134,7 @@ class NotificationItem extends StatelessWidget { final model.Notification item; final void Function()? onDismiss; - const NotificationItem( - {super.key, required this.index, required this.item, this.onDismiss}); + const NotificationItem({super.key, required this.index, required this.item, this.onDismiss}); bool get hasLinks => item.links != null && item.links!.isNotEmpty; @@ -94,8 +148,7 @@ class NotificationItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( - padding: const EdgeInsets.only( - left: 16, right: 16, top: 34, bottom: 12), + padding: const EdgeInsets.only(left: 16, right: 16, top: 34, bottom: 12), child: Text( 'Links', style: Theme.of(context).textTheme.headlineSmall, @@ -124,8 +177,7 @@ class NotificationItem extends StatelessWidget { ); } - Future markAsRead( - model.Notification element, BuildContext context) async { + Future markAsRead(model.Notification element, BuildContext context) async { if (element.isRealtime) return; final auth = context.read(); @@ -139,7 +191,7 @@ class NotificationItem extends StatelessWidget { @override Widget build(BuildContext context) { return Dismissible( - key: Key((DateTime.now().millisecondsSinceEpoch << 10).toString()), + key: Key('n${item.id}'), onDismissed: (direction) { markAsRead(item, context).then((value) { ScaffoldMessenger.of(context).showSnackBar(