Compare commits
No commits in common. "88587c10da459ce0b1969fc52228e049b291857b" and "aa17a5d52a8bd95a49d970a867b3de17782059ce" have entirely different histories.
88587c10da
...
aa17a5d52a
@ -486,7 +486,5 @@
|
|||||||
"shareImage": "Share as image",
|
"shareImage": "Share as image",
|
||||||
"shareImageFooter": "Only on the Solar Network",
|
"shareImageFooter": "Only on the Solar Network",
|
||||||
"fileSavedAt": "File saved at @path",
|
"fileSavedAt": "File saved at @path",
|
||||||
"showIp": "Show IP Address",
|
"showIp": "Show IP Address"
|
||||||
"shotOn": "Shot on @device",
|
|
||||||
"unread": "Unread"
|
|
||||||
}
|
}
|
||||||
|
@ -482,7 +482,5 @@
|
|||||||
"shareImage": "分享图片",
|
"shareImage": "分享图片",
|
||||||
"shareImageFooter": "上 Solar Network 看更多有趣帖子",
|
"shareImageFooter": "上 Solar Network 看更多有趣帖子",
|
||||||
"fileSavedAt": "文件保存于 @path",
|
"fileSavedAt": "文件保存于 @path",
|
||||||
"showIp": "显示 IP 地址",
|
"showIp": "显示 IP 地址"
|
||||||
"shotOn": "由 @device 拍摄",
|
|
||||||
"unread": "未读"
|
|
||||||
}
|
}
|
||||||
|
@ -199,8 +199,6 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
|
|||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
try {
|
try {
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
if (auth.isAuthorized.isTrue)
|
|
||||||
Get.find<NotificationProvider>().fetchNotification(),
|
|
||||||
if (auth.isAuthorized.isTrue)
|
if (auth.isAuthorized.isTrue)
|
||||||
Get.find<RelationshipProvider>().refreshRelativeList(),
|
Get.find<RelationshipProvider>().refreshRelativeList(),
|
||||||
if (auth.isAuthorized.isTrue)
|
if (auth.isAuthorized.isTrue)
|
||||||
|
@ -1,15 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
part 'notification.g.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()
|
@JsonSerializable()
|
||||||
class Notification {
|
class Notification {
|
||||||
int id;
|
int id;
|
||||||
@ -17,13 +9,11 @@ class Notification {
|
|||||||
DateTime updatedAt;
|
DateTime updatedAt;
|
||||||
DateTime? deletedAt;
|
DateTime? deletedAt;
|
||||||
DateTime? readAt;
|
DateTime? readAt;
|
||||||
String topic;
|
|
||||||
String title;
|
String title;
|
||||||
String? subtitle;
|
String? subtitle;
|
||||||
String body;
|
String body;
|
||||||
String? avatar;
|
String? avatar;
|
||||||
String? picture;
|
String? picture;
|
||||||
Map<String, dynamic>? metadata;
|
|
||||||
int? senderId;
|
int? senderId;
|
||||||
int accountId;
|
int accountId;
|
||||||
|
|
||||||
@ -33,13 +23,11 @@ class Notification {
|
|||||||
required this.updatedAt,
|
required this.updatedAt,
|
||||||
required this.deletedAt,
|
required this.deletedAt,
|
||||||
required this.readAt,
|
required this.readAt,
|
||||||
required this.topic,
|
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.subtitle,
|
required this.subtitle,
|
||||||
required this.body,
|
required this.body,
|
||||||
required this.avatar,
|
required this.avatar,
|
||||||
required this.picture,
|
required this.picture,
|
||||||
required this.metadata,
|
|
||||||
required this.senderId,
|
required this.senderId,
|
||||||
required this.accountId,
|
required this.accountId,
|
||||||
});
|
});
|
||||||
|
@ -16,13 +16,11 @@ Notification _$NotificationFromJson(Map<String, dynamic> json) => Notification(
|
|||||||
readAt: json['read_at'] == null
|
readAt: json['read_at'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json['read_at'] as String),
|
: DateTime.parse(json['read_at'] as String),
|
||||||
topic: json['topic'] as String,
|
|
||||||
title: json['title'] as String,
|
title: json['title'] as String,
|
||||||
subtitle: json['subtitle'] as String?,
|
subtitle: json['subtitle'] as String?,
|
||||||
body: json['body'] as String,
|
body: json['body'] as String,
|
||||||
avatar: json['avatar'] as String?,
|
avatar: json['avatar'] as String?,
|
||||||
picture: json['picture'] as String?,
|
picture: json['picture'] as String?,
|
||||||
metadata: json['metadata'] as Map<String, dynamic>?,
|
|
||||||
senderId: (json['sender_id'] as num?)?.toInt(),
|
senderId: (json['sender_id'] as num?)?.toInt(),
|
||||||
accountId: (json['account_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(),
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
'deleted_at': instance.deletedAt?.toIso8601String(),
|
'deleted_at': instance.deletedAt?.toIso8601String(),
|
||||||
'read_at': instance.readAt?.toIso8601String(),
|
'read_at': instance.readAt?.toIso8601String(),
|
||||||
'topic': instance.topic,
|
|
||||||
'title': instance.title,
|
'title': instance.title,
|
||||||
'subtitle': instance.subtitle,
|
'subtitle': instance.subtitle,
|
||||||
'body': instance.body,
|
'body': instance.body,
|
||||||
'avatar': instance.avatar,
|
'avatar': instance.avatar,
|
||||||
'picture': instance.picture,
|
'picture': instance.picture,
|
||||||
'metadata': instance.metadata,
|
|
||||||
'sender_id': instance.senderId,
|
'sender_id': instance.senderId,
|
||||||
'account_id': instance.accountId,
|
'account_id': instance.accountId,
|
||||||
};
|
};
|
||||||
|
@ -18,6 +18,12 @@ class NotificationProvider extends GetxController {
|
|||||||
RxList<Notification> notifications =
|
RxList<Notification> notifications =
|
||||||
List<Notification>.empty(growable: true).obs;
|
List<Notification>.empty(growable: true).obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
fetchNotification();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> fetchNotification() async {
|
Future<void> fetchNotification() async {
|
||||||
final AuthProvider auth = Get.find();
|
final AuthProvider auth = Get.find();
|
||||||
if (auth.isAuthorized.isFalse) return;
|
if (auth.isAuthorized.isFalse) return;
|
||||||
@ -29,6 +35,7 @@ class NotificationProvider extends GetxController {
|
|||||||
final result = PaginationResult.fromJson(resp.body);
|
final result = PaginationResult.fromJson(resp.body);
|
||||||
final data = result.data?.map((x) => Notification.fromJson(x)).toList();
|
final data = result.data?.map((x) => Notification.fromJson(x)).toList();
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
|
print(data.map((x) => x.toJson()));
|
||||||
notifications.addAll(data);
|
notifications.addAll(data);
|
||||||
notificationUnread.value = data.where((x) => x.readAt == null).length;
|
notificationUnread.value = data.where((x) => x.readAt == null).length;
|
||||||
}
|
}
|
||||||
@ -55,11 +62,7 @@ class NotificationProvider extends GetxController {
|
|||||||
await client.put('/notifications/read', {'messages': markList});
|
await client.put('/notifications/read', {'messages': markList});
|
||||||
}
|
}
|
||||||
|
|
||||||
nty.notifications.value = nty.notifications.map((x) {
|
nty.notifications.clear();
|
||||||
x.readAt = DateTime.now();
|
|
||||||
return x;
|
|
||||||
}).toList();
|
|
||||||
nty.notifications.refresh();
|
|
||||||
|
|
||||||
isBusy.value = false;
|
isBusy.value = false;
|
||||||
}
|
}
|
||||||
@ -83,8 +86,7 @@ class NotificationProvider extends GetxController {
|
|||||||
|
|
||||||
await client.put('/notifications/read/${element.id}', {});
|
await client.put('/notifications/read/${element.id}', {});
|
||||||
|
|
||||||
nty.notifications[0].readAt = DateTime.now();
|
nty.notifications.removeAt(index);
|
||||||
nty.notifications.refresh();
|
|
||||||
|
|
||||||
isBusy.value = false;
|
isBusy.value = false;
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:gap/gap.dart';
|
|
||||||
import 'package:get/get.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/providers/notifications.dart';
|
||||||
import 'package:solian/router.dart';
|
|
||||||
import 'package:solian/widgets/loading_indicator.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';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class NotificationScreen extends StatefulWidget {
|
class NotificationScreen extends StatefulWidget {
|
||||||
@ -83,7 +76,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
return ClipRect(
|
return ClipRect(
|
||||||
child: Dismissible(
|
child: Dismissible(
|
||||||
direction: element.readAt == null
|
direction: element.readAt == null
|
||||||
? DismissDirection.horizontal
|
? DismissDirection.vertical
|
||||||
: DismissDirection.none,
|
: DismissDirection.none,
|
||||||
key: Key(const Uuid().v4()),
|
key: Key(const Uuid().v4()),
|
||||||
background: Container(
|
background: Container(
|
||||||
@ -102,92 +95,18 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
|||||||
child:
|
child:
|
||||||
const Icon(Icons.check, color: Colors.white),
|
const Icon(Icons.check, color: Colors.white),
|
||||||
),
|
),
|
||||||
child: Container(
|
child: ListTile(
|
||||||
padding: const EdgeInsets.symmetric(
|
contentPadding: const EdgeInsets.symmetric(
|
||||||
horizontal: 28,
|
horizontal: 24,
|
||||||
vertical: 16,
|
vertical: 8,
|
||||||
),
|
),
|
||||||
child: Row(
|
title: Text(element.title),
|
||||||
|
subtitle: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
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)
|
if (element.subtitle != null)
|
||||||
Text(
|
Text(element.subtitle!),
|
||||||
element.subtitle!,
|
Text(element.body),
|
||||||
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,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -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,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -46,7 +46,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
|
|||||||
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
|
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
|
||||||
|
|
||||||
List<Notification> get _pendingNotifications =>
|
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));
|
..sort((a, b) => b.createdAt.compareTo(a.createdAt));
|
||||||
|
|
||||||
List<Post>? _currentPosts;
|
List<Post>? _currentPosts;
|
||||||
@ -254,7 +254,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'notificationUnreadCount'.trParams({
|
'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),
|
).paddingOnly(left: 18, right: 18, bottom: 8),
|
||||||
if (_pendingNotifications.isNotEmpty)
|
if (_nty.notifications.isNotEmpty)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 76,
|
height: 76,
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
|
@ -8,7 +8,6 @@ import 'package:flutter_animate/flutter_animate.dart';
|
|||||||
import 'package:gal/gal.dart';
|
import 'package:gal/gal.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
|
||||||
import 'package:solian/exts.dart';
|
import 'package:solian/exts.dart';
|
||||||
import 'package:solian/models/attachment.dart';
|
import 'package:solian/models/attachment.dart';
|
||||||
import 'package:solian/platform.dart';
|
import 'package:solian/platform.dart';
|
||||||
@ -104,10 +103,9 @@ class _AttachmentFullScreenState extends State<AttachmentFullScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final metaTextStyle = GoogleFonts.roboto(
|
final metaTextStyle = TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: _unFocusColor,
|
color: _unFocusColor,
|
||||||
height: 1,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return DismissiblePage(
|
return DismissiblePage(
|
||||||
@ -241,43 +239,27 @@ class _AttachmentFullScreenState extends State<AttachmentFullScreen> {
|
|||||||
child: Wrap(
|
child: Wrap(
|
||||||
spacing: 6,
|
spacing: 6,
|
||||||
children: [
|
children: [
|
||||||
if (widget.item.metadata?['exif'] == null)
|
|
||||||
Text(
|
Text(
|
||||||
'#${widget.item.rid}',
|
'#${widget.item.rid}',
|
||||||
style: metaTextStyle,
|
style: metaTextStyle,
|
||||||
),
|
),
|
||||||
if (widget.item.metadata?['exif']?['Model'] != null)
|
if (widget.item.metadata?['width'] != null &&
|
||||||
|
widget.item.metadata?['height'] != null)
|
||||||
Text(
|
Text(
|
||||||
'shotOn'.trParams({
|
'${widget.item.metadata?['width']}x${widget.item.metadata?['height']}',
|
||||||
'device': widget.item.metadata?['exif']
|
|
||||||
?['Model']
|
|
||||||
}),
|
|
||||||
style: metaTextStyle,
|
style: metaTextStyle,
|
||||||
).paddingOnly(right: 2),
|
),
|
||||||
if (widget.item.metadata?['exif']?['ShutterSpeed'] !=
|
if (widget.item.metadata?['ratio'] != null)
|
||||||
null)
|
|
||||||
Text(
|
Text(
|
||||||
widget.item.metadata?['exif']?['ShutterSpeed'],
|
'${_getRatio().toPrecision(2)}',
|
||||||
style: metaTextStyle,
|
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(
|
Text(
|
||||||
widget.item.size.formatBytes(),
|
widget.item.size.formatBytes(),
|
||||||
style: metaTextStyle,
|
style: metaTextStyle,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${widget.item.metadata?['width']}x${widget.item.metadata?['height']}',
|
widget.item.mimetype,
|
||||||
style: metaTextStyle,
|
style: metaTextStyle,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -4,25 +4,20 @@ import 'package:timeago/timeago.dart';
|
|||||||
|
|
||||||
class RelativeDate extends StatelessWidget {
|
class RelativeDate extends StatelessWidget {
|
||||||
final DateTime date;
|
final DateTime date;
|
||||||
final TextStyle? style;
|
|
||||||
final bool isFull;
|
final bool isFull;
|
||||||
|
|
||||||
const RelativeDate(this.date, {super.key, this.style, this.isFull = false});
|
const RelativeDate(this.date, {super.key, this.isFull = false});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (isFull) {
|
if (isFull) {
|
||||||
return Text(
|
return Text(DateFormat('y/M/d HH:mm').format(date));
|
||||||
DateFormat('y/M/d HH:mm').format(date),
|
|
||||||
style: style,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return Text(
|
return Text(
|
||||||
format(
|
format(
|
||||||
date,
|
date,
|
||||||
locale: 'en_short',
|
locale: 'en_short',
|
||||||
),
|
),
|
||||||
style: style,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user