Better attachment fullscreen (support exif meta)

This commit is contained in:
LittleSheep 2024-10-16 22:16:03 +08:00
parent aa17a5d52a
commit 6e00a99803
9 changed files with 95 additions and 37 deletions

View File

@ -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"
}

View File

@ -482,5 +482,6 @@
"shareImage": "分享图片",
"shareImageFooter": "上 Solar Network 看更多有趣帖子",
"fileSavedAt": "文件保存于 @path",
"showIp": "显示 IP 地址"
"showIp": "显示 IP 地址",
"shotOn": "由 @device 拍摄"
}

View File

@ -199,6 +199,8 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
final AuthProvider auth = Get.find();
try {
await Future.wait([
if (auth.isAuthorized.isTrue)
Get.find<NotificationProvider>().fetchNotification(),
if (auth.isAuthorized.isTrue)
Get.find<RelationshipProvider>().refreshRelativeList(),
if (auth.isAuthorized.isTrue)

View File

@ -1,7 +1,12 @@
import 'package:flutter/material.dart';
import 'package:json_annotation/json_annotation.dart';
part 'notification.g.dart';
const Map<String, IconData> 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<String, dynamic>? 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,
});

View File

@ -16,11 +16,13 @@ Notification _$NotificationFromJson(Map<String, dynamic> 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<String, dynamic>?,
senderId: (json['sender_id'] as num?)?.toInt(),
accountId: (json['account_id'] as num).toInt(),
);
@ -32,11 +34,13 @@ Map<String, dynamic> _$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,
};

View File

@ -18,12 +18,6 @@ class NotificationProvider extends GetxController {
RxList<Notification> notifications =
List<Notification>.empty(growable: true).obs;
@override
void onInit() {
super.onInit();
fetchNotification();
}
Future<void> 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;
}

View File

@ -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<NotificationScreen> {
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<NotificationScreen> {
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: [
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!),
Text(element.body),
Text(
element.subtitle!,
style: Theme.of(context)
.textTheme
.titleSmall,
),
const Gap(4),
MarkdownTextContent(
content: element.body,
isAutoWarp: true,
isSelectable: true,
parentId:
'notification-${element.id}',
),
],
),
),
],
),
),

View File

@ -46,7 +46,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
List<Notification> get _pendingNotifications =>
List<Notification>.from(_nty.notifications)
List<Notification>.from(_nty.notifications.where((x) => x.readAt == null))
..sort((a, b) => b.createdAt.compareTo(a.createdAt));
List<Post>? _currentPosts;
@ -254,7 +254,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
),
Text(
'notificationUnreadCount'.trParams({
'count': _nty.notifications.length.toString(),
'count': _pendingNotifications.length.toString(),
}),
),
],
@ -272,7 +272,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
),
],
).paddingOnly(left: 18, right: 18, bottom: 8),
if (_nty.notifications.isNotEmpty)
if (_pendingNotifications.isNotEmpty)
SizedBox(
height: 76,
child: ListView.separated(

View File

@ -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<AttachmentFullScreen> {
@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<AttachmentFullScreen> {
child: Wrap(
spacing: 6,
children: [
if (widget.item.metadata?['exif'] == null)
Text(
'#${widget.item.rid}',
style: metaTextStyle,
),
if (widget.item.metadata?['width'] != null &&
widget.item.metadata?['height'] != null)
if (widget.item.metadata?['exif']?['Model'] != null)
Text(
'${widget.item.metadata?['width']}x${widget.item.metadata?['height']}',
'shotOn'.trParams({
'device': widget.item.metadata?['exif']
?['Model']
}),
style: metaTextStyle,
),
if (widget.item.metadata?['ratio'] != null)
).paddingOnly(right: 2),
if (widget.item.metadata?['exif']?['ShutterSpeed'] !=
null)
Text(
'${_getRatio().toPrecision(2)}',
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.mimetype,
'${widget.item.metadata?['width']}x${widget.item.metadata?['height']}',
style: metaTextStyle,
),
],