From 6e00a9980346521c8b8b2c41d1adf05d144c9218 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Wed, 16 Oct 2024 22:16:03 +0800 Subject: [PATCH] :sparkles: Better attachment fullscreen (support exif meta) --- assets/locales/en_us.json | 3 +- assets/locales/zh_cn.json | 3 +- lib/bootstrapper.dart | 2 + lib/models/notification.dart | 9 ++++ lib/models/notification.g.dart | 4 ++ lib/providers/notifications.dart | 7 --- lib/screens/account/notification.dart | 50 +++++++++++++++---- lib/screens/dashboard.dart | 6 +-- .../attachments/attachment_fullscreen.dart | 48 ++++++++++++------ 9 files changed, 95 insertions(+), 37 deletions(-) diff --git a/assets/locales/en_us.json b/assets/locales/en_us.json index d6692e9..4558578 100644 --- a/assets/locales/en_us.json +++ b/assets/locales/en_us.json @@ -486,5 +486,6 @@ "shareImage": "Share as image", "shareImageFooter": "Only on the Solar Network", "fileSavedAt": "File saved at @path", - "showIp": "Show IP Address" + "showIp": "Show IP Address", + "shotOn": "Shot on @device" } diff --git a/assets/locales/zh_cn.json b/assets/locales/zh_cn.json index 99374d3..e2b1353 100644 --- a/assets/locales/zh_cn.json +++ b/assets/locales/zh_cn.json @@ -482,5 +482,6 @@ "shareImage": "分享图片", "shareImageFooter": "上 Solar Network 看更多有趣帖子", "fileSavedAt": "文件保存于 @path", - "showIp": "显示 IP 地址" + "showIp": "显示 IP 地址", + "shotOn": "由 @device 拍摄" } diff --git a/lib/bootstrapper.dart b/lib/bootstrapper.dart index 538fa32..ace7c22 100644 --- a/lib/bootstrapper.dart +++ b/lib/bootstrapper.dart @@ -199,6 +199,8 @@ class _BootstrapperShellState extends State { final AuthProvider auth = Get.find(); try { await Future.wait([ + if (auth.isAuthorized.isTrue) + Get.find().fetchNotification(), if (auth.isAuthorized.isTrue) Get.find().refreshRelativeList(), if (auth.isAuthorized.isTrue) diff --git a/lib/models/notification.dart b/lib/models/notification.dart index 0b82bb1..eb40c89 100755 --- a/lib/models/notification.dart +++ b/lib/models/notification.dart @@ -1,7 +1,12 @@ +import 'package:flutter/material.dart'; import 'package:json_annotation/json_annotation.dart'; part 'notification.g.dart'; +const Map NotificationTopicIcons = { + 'passport.security.alert': Icons.gpp_maybe, +}; + @JsonSerializable() class Notification { int id; @@ -9,11 +14,13 @@ class Notification { DateTime updatedAt; DateTime? deletedAt; DateTime? readAt; + String topic; String title; String? subtitle; String body; String? avatar; String? picture; + Map? metadata; int? senderId; int accountId; @@ -23,11 +30,13 @@ class Notification { required this.updatedAt, required this.deletedAt, required this.readAt, + required this.topic, required this.title, required this.subtitle, required this.body, required this.avatar, required this.picture, + required this.metadata, required this.senderId, required this.accountId, }); diff --git a/lib/models/notification.g.dart b/lib/models/notification.g.dart index f80239e..50c7134 100644 --- a/lib/models/notification.g.dart +++ b/lib/models/notification.g.dart @@ -16,11 +16,13 @@ Notification _$NotificationFromJson(Map json) => Notification( readAt: json['read_at'] == null ? null : DateTime.parse(json['read_at'] as String), + topic: json['topic'] as String, title: json['title'] as String, subtitle: json['subtitle'] as String?, body: json['body'] as String, avatar: json['avatar'] as String?, picture: json['picture'] as String?, + metadata: json['metadata'] as Map?, senderId: (json['sender_id'] as num?)?.toInt(), accountId: (json['account_id'] as num).toInt(), ); @@ -32,11 +34,13 @@ Map _$NotificationToJson(Notification instance) => 'updated_at': instance.updatedAt.toIso8601String(), 'deleted_at': instance.deletedAt?.toIso8601String(), 'read_at': instance.readAt?.toIso8601String(), + 'topic': instance.topic, 'title': instance.title, 'subtitle': instance.subtitle, 'body': instance.body, 'avatar': instance.avatar, 'picture': instance.picture, + 'metadata': instance.metadata, 'sender_id': instance.senderId, 'account_id': instance.accountId, }; diff --git a/lib/providers/notifications.dart b/lib/providers/notifications.dart index 1664b62..a93b3a6 100644 --- a/lib/providers/notifications.dart +++ b/lib/providers/notifications.dart @@ -18,12 +18,6 @@ class NotificationProvider extends GetxController { RxList notifications = List.empty(growable: true).obs; - @override - void onInit() { - super.onInit(); - fetchNotification(); - } - Future fetchNotification() async { final AuthProvider auth = Get.find(); if (auth.isAuthorized.isFalse) return; @@ -35,7 +29,6 @@ class NotificationProvider extends GetxController { final result = PaginationResult.fromJson(resp.body); final data = result.data?.map((x) => Notification.fromJson(x)).toList(); if (data != null) { - print(data.map((x) => x.toJson())); notifications.addAll(data); notificationUnread.value = data.where((x) => x.readAt == null).length; } diff --git a/lib/screens/account/notification.dart b/lib/screens/account/notification.dart index b765abd..3e3fb73 100644 --- a/lib/screens/account/notification.dart +++ b/lib/screens/account/notification.dart @@ -1,7 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; import 'package:get/get.dart'; +import 'package:solian/models/notification.dart'; import 'package:solian/providers/notifications.dart'; import 'package:solian/widgets/loading_indicator.dart'; +import 'package:solian/widgets/markdown_text_content.dart'; import 'package:uuid/uuid.dart'; class NotificationScreen extends StatefulWidget { @@ -76,7 +79,7 @@ class _NotificationScreenState extends State { return ClipRect( child: Dismissible( direction: element.readAt == null - ? DismissDirection.vertical + ? DismissDirection.horizontal : DismissDirection.none, key: Key(const Uuid().v4()), background: Container( @@ -95,18 +98,45 @@ class _NotificationScreenState extends State { child: const Icon(Icons.check, color: Colors.white), ), - child: ListTile( - contentPadding: const EdgeInsets.symmetric( - horizontal: 24, - vertical: 8, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 28, + vertical: 16, ), - title: Text(element.title), - subtitle: Column( + child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (element.subtitle != null) - Text(element.subtitle!), - Text(element.body), + Icon(NotificationTopicIcons[element.topic]), + const Gap(12), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + element.title, + style: Theme.of(context) + .textTheme + .titleMedium, + ), + if (element.subtitle != null) + Text( + element.subtitle!, + style: Theme.of(context) + .textTheme + .titleSmall, + ), + const Gap(4), + MarkdownTextContent( + content: element.body, + isAutoWarp: true, + isSelectable: true, + parentId: + 'notification-${element.id}', + ), + ], + ), + ), ], ), ), diff --git a/lib/screens/dashboard.dart b/lib/screens/dashboard.dart index f139d2c..8e7b4f0 100644 --- a/lib/screens/dashboard.dart +++ b/lib/screens/dashboard.dart @@ -46,7 +46,7 @@ class _DashboardScreenState extends State { Theme.of(context).colorScheme.onSurface.withOpacity(0.75); List get _pendingNotifications => - List.from(_nty.notifications) + List.from(_nty.notifications.where((x) => x.readAt == null)) ..sort((a, b) => b.createdAt.compareTo(a.createdAt)); List? _currentPosts; @@ -254,7 +254,7 @@ class _DashboardScreenState extends State { ), Text( 'notificationUnreadCount'.trParams({ - 'count': _nty.notifications.length.toString(), + 'count': _pendingNotifications.length.toString(), }), ), ], @@ -272,7 +272,7 @@ class _DashboardScreenState extends State { ), ], ).paddingOnly(left: 18, right: 18, bottom: 8), - if (_nty.notifications.isNotEmpty) + if (_pendingNotifications.isNotEmpty) SizedBox( height: 76, child: ListView.separated( diff --git a/lib/widgets/attachments/attachment_fullscreen.dart b/lib/widgets/attachments/attachment_fullscreen.dart index 0a8947e..d5b266e 100644 --- a/lib/widgets/attachments/attachment_fullscreen.dart +++ b/lib/widgets/attachments/attachment_fullscreen.dart @@ -8,6 +8,7 @@ import 'package:flutter_animate/flutter_animate.dart'; import 'package:gal/gal.dart'; import 'package:gap/gap.dart'; import 'package:get/get.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:solian/exts.dart'; import 'package:solian/models/attachment.dart'; import 'package:solian/platform.dart'; @@ -103,9 +104,10 @@ class _AttachmentFullScreenState extends State { @override Widget build(BuildContext context) { - final metaTextStyle = TextStyle( + final metaTextStyle = GoogleFonts.roboto( fontSize: 12, color: _unFocusColor, + height: 1, ); return DismissiblePage( @@ -239,27 +241,43 @@ class _AttachmentFullScreenState extends State { child: Wrap( spacing: 6, children: [ - Text( - '#${widget.item.rid}', - style: metaTextStyle, - ), - if (widget.item.metadata?['width'] != null && - widget.item.metadata?['height'] != null) + if (widget.item.metadata?['exif'] == null) Text( - '${widget.item.metadata?['width']}x${widget.item.metadata?['height']}', + '#${widget.item.rid}', style: metaTextStyle, ), - if (widget.item.metadata?['ratio'] != null) + if (widget.item.metadata?['exif']?['Model'] != null) Text( - '${_getRatio().toPrecision(2)}', + 'shotOn'.trParams({ + 'device': widget.item.metadata?['exif'] + ?['Model'] + }), + style: metaTextStyle, + ).paddingOnly(right: 2), + if (widget.item.metadata?['exif']?['ShutterSpeed'] != + null) + Text( + widget.item.metadata?['exif']?['ShutterSpeed'], + style: metaTextStyle, + ).paddingOnly(right: 2), + if (widget.item.metadata?['exif']?['ISO'] != null) + Text( + 'ISO${widget.item.metadata?['exif']?['ISO']}', + style: metaTextStyle, + ).paddingOnly(right: 2), + if (widget.item.metadata?['exif']?['Megapixels'] != + null) + Text( + '${widget.item.metadata?['exif']?['Megapixels']}MP', + style: metaTextStyle, + ) + else + Text( + widget.item.size.formatBytes(), style: metaTextStyle, ), Text( - widget.item.size.formatBytes(), - style: metaTextStyle, - ), - Text( - widget.item.mimetype, + '${widget.item.metadata?['width']}x${widget.item.metadata?['height']}', style: metaTextStyle, ), ],