Compare commits

..

No commits in common. "88587c10da459ce0b1969fc52228e049b291857b" and "aa17a5d52a8bd95a49d970a867b3de17782059ce" have entirely different histories.

10 changed files with 41 additions and 193 deletions

View File

@ -486,7 +486,5 @@
"shareImage": "Share as image",
"shareImageFooter": "Only on the Solar Network",
"fileSavedAt": "File saved at @path",
"showIp": "Show IP Address",
"shotOn": "Shot on @device",
"unread": "Unread"
"showIp": "Show IP Address"
}

View File

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

View File

@ -199,8 +199,6 @@ 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,15 +1,7 @@
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,
'interactive.subscription': Icons.subscriptions,
'interactive.feedback': Icons.add_reaction,
'messaging.callStart': Icons.call_received,
};
@JsonSerializable()
class Notification {
int id;
@ -17,13 +9,11 @@ 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;
@ -33,13 +23,11 @@ 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,13 +16,11 @@ 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(),
);
@ -34,13 +32,11 @@ 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,6 +18,12 @@ 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;
@ -29,6 +35,7 @@ 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;
}
@ -55,11 +62,7 @@ class NotificationProvider extends GetxController {
await client.put('/notifications/read', {'messages': markList});
}
nty.notifications.value = nty.notifications.map((x) {
x.readAt = DateTime.now();
return x;
}).toList();
nty.notifications.refresh();
nty.notifications.clear();
isBusy.value = false;
}
@ -83,8 +86,7 @@ class NotificationProvider extends GetxController {
await client.put('/notifications/read/${element.id}', {});
nty.notifications[0].readAt = DateTime.now();
nty.notifications.refresh();
nty.notifications.removeAt(index);
isBusy.value = false;
}

View File

@ -1,14 +1,7 @@
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:solian/models/notification.dart';
import 'package:solian/models/post.dart';
import 'package:solian/providers/notifications.dart';
import 'package:solian/router.dart';
import 'package:solian/widgets/loading_indicator.dart';
import 'package:solian/widgets/markdown_text_content.dart';
import 'package:solian/widgets/posts/post_item.dart';
import 'package:solian/widgets/relative_date.dart';
import 'package:uuid/uuid.dart';
class NotificationScreen extends StatefulWidget {
@ -83,7 +76,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
return ClipRect(
child: Dismissible(
direction: element.readAt == null
? DismissDirection.horizontal
? DismissDirection.vertical
: DismissDirection.none,
key: Key(const Uuid().v4()),
background: Container(
@ -102,92 +95,18 @@ class _NotificationScreenState extends State<NotificationScreen> {
child:
const Icon(Icons.check, color: Colors.white),
),
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 28,
vertical: 16,
child: ListTile(
contentPadding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 8,
),
child: Row(
title: Text(element.title),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(NotificationTopicIcons[element.topic]),
const Gap(16),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
if (element.readAt == null)
Badge(
label: Row(
children: [
const Icon(
Icons.new_releases_outlined,
color: Colors.white,
size: 12,
),
const Gap(4),
Text('unread'.tr),
],
),
).paddingOnly(bottom: 4),
Text(
element.title,
style: Theme.of(context)
.textTheme
.titleMedium,
),
if (element.subtitle != null)
Text(
element.subtitle!,
style: Theme.of(context)
.textTheme
.titleSmall,
),
if (element.subtitle != null)
const Gap(4),
MarkdownTextContent(
content: element.body,
isAutoWarp: true,
isSelectable: true,
parentId:
'notification-${element.id}',
),
if ([
'interactive.feedback',
'interactive.subscription'
].contains(element.topic) &&
element.metadata?['related_post'] !=
null)
_PostRelatedNotificationWidget(
metadata: element.metadata!,
),
const Gap(8),
Opacity(
opacity: 0.75,
child: Row(
children: [
RelativeDate(
element.createdAt,
style: TextStyle(fontSize: 12),
),
const Gap(4),
Text(
'·',
style: TextStyle(fontSize: 12),
),
const Gap(4),
RelativeDate(
element.createdAt,
style: TextStyle(fontSize: 12),
isFull: true,
),
],
),
),
],
),
),
Text(element.subtitle!),
Text(element.body),
],
),
),
@ -242,31 +161,3 @@ class NotificationButton extends StatelessWidget {
});
}
}
class _PostRelatedNotificationWidget extends StatelessWidget {
final Map<String, dynamic> metadata;
const _PostRelatedNotificationWidget({super.key, required this.metadata});
@override
Widget build(BuildContext context) {
return GestureDetector(
child: Card(
margin: const EdgeInsets.symmetric(vertical: 4),
child: PostItem(
item: Post.fromJson(metadata['related_post']),
isCompact: true,
).paddingAll(8),
),
onTap: () {
final data = Post.fromJson(metadata['related_post']);
Navigator.pop(context);
AppRouter.instance.pushNamed(
'postDetail',
pathParameters: {'id': data.id.toString()},
extra: data,
);
},
);
}
}

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

View File

@ -8,7 +8,6 @@ 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';
@ -104,10 +103,9 @@ class _AttachmentFullScreenState extends State<AttachmentFullScreen> {
@override
Widget build(BuildContext context) {
final metaTextStyle = GoogleFonts.roboto(
final metaTextStyle = TextStyle(
fontSize: 12,
color: _unFocusColor,
height: 1,
);
return DismissiblePage(
@ -241,43 +239,27 @@ 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?['exif']?['Model'] != null)
if (widget.item.metadata?['width'] != null &&
widget.item.metadata?['height'] != null)
Text(
'shotOn'.trParams({
'device': widget.item.metadata?['exif']
?['Model']
}),
'${widget.item.metadata?['width']}x${widget.item.metadata?['height']}',
style: metaTextStyle,
).paddingOnly(right: 2),
if (widget.item.metadata?['exif']?['ShutterSpeed'] !=
null)
),
if (widget.item.metadata?['ratio'] != null)
Text(
widget.item.metadata?['exif']?['ShutterSpeed'],
'${_getRatio().toPrecision(2)}',
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.metadata?['width']}x${widget.item.metadata?['height']}',
widget.item.mimetype,
style: metaTextStyle,
),
],

View File

@ -4,25 +4,20 @@ import 'package:timeago/timeago.dart';
class RelativeDate extends StatelessWidget {
final DateTime date;
final TextStyle? style;
final bool isFull;
const RelativeDate(this.date, {super.key, this.style, this.isFull = false});
const RelativeDate(this.date, {super.key, this.isFull = false});
@override
Widget build(BuildContext context) {
if (isFull) {
return Text(
DateFormat('y/M/d HH:mm').format(date),
style: style,
);
return Text(DateFormat('y/M/d HH:mm').format(date));
}
return Text(
format(
date,
locale: 'en_short',
),
style: style,
);
}
}