✨ Better attachment fullscreen (support exif meta)
This commit is contained in:
parent
aa17a5d52a
commit
6e00a99803
@ -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"
|
||||
}
|
||||
|
@ -482,5 +482,6 @@
|
||||
"shareImage": "分享图片",
|
||||
"shareImageFooter": "上 Solar Network 看更多有趣帖子",
|
||||
"fileSavedAt": "文件保存于 @path",
|
||||
"showIp": "显示 IP 地址"
|
||||
"showIp": "显示 IP 地址",
|
||||
"shotOn": "由 @device 拍摄"
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
});
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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: [
|
||||
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}',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -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(
|
||||
|
@ -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: [
|
||||
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,
|
||||
),
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user