✨ Flag posts
This commit is contained in:
parent
e8ded55055
commit
972b304969
@ -655,5 +655,14 @@
|
||||
"checkInResultTier2": "Worse",
|
||||
"checkInResultTier3": "Normal",
|
||||
"checkInResultTier4": "Better",
|
||||
"checkInResultTier5": "Best"
|
||||
"checkInResultTier5": "Best",
|
||||
"flagPostAction": "Flag the Post",
|
||||
"flagPost": "Flag objectionable content",
|
||||
"flagPostDescription": "If flagged users takes 50% or more of the views, the post will be collapsed. You cannot revoke the action.",
|
||||
"flaggedPost": "Post has been flagged.",
|
||||
"postViews": {
|
||||
"zero": "No views",
|
||||
"one": "{} view",
|
||||
"other": "{} views"
|
||||
}
|
||||
}
|
||||
|
@ -654,5 +654,14 @@
|
||||
"checkInResultTier2": "凶",
|
||||
"checkInResultTier3": "中平",
|
||||
"checkInResultTier4": "吉",
|
||||
"checkInResultTier5": "大吉"
|
||||
"checkInResultTier5": "大吉",
|
||||
"flagPostAction": "吹哨",
|
||||
"flagPost": "吹哨不良内容",
|
||||
"flagPostDescription": "吹哨不良内容,如果吹哨用户占浏览量的 50% 或以上,则帖子会被折叠。吹哨后不可撤销。",
|
||||
"flaggedPost": "哨子已经吹响。",
|
||||
"postViews": {
|
||||
"zero": "{} 次浏览",
|
||||
"one": "{} 次浏览",
|
||||
"other": "{} 次浏览"
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import 'dart:math' as math;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:relative_time/relative_time.dart';
|
||||
@ -59,10 +60,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
||||
final resp = await sn.client.get('/cgi/id/notifications?take=10');
|
||||
_totalCount = resp.data['count'];
|
||||
_notifications.addAll(
|
||||
resp.data['data']
|
||||
?.map((e) => SnNotification.fromJson(e))
|
||||
.cast<SnNotification>() ??
|
||||
[],
|
||||
resp.data['data']?.map((e) => SnNotification.fromJson(e)).cast<SnNotification>() ?? [],
|
||||
);
|
||||
nty.updateTray();
|
||||
} catch (err) {
|
||||
@ -188,8 +186,7 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
||||
_fetchNotifications();
|
||||
},
|
||||
isLoading: _isBusy,
|
||||
hasReachedMax: _totalCount != null &&
|
||||
_notifications.length >= _totalCount!,
|
||||
hasReachedMax: _totalCount != null && _notifications.length >= _totalCount!,
|
||||
itemBuilder: (context, idx) {
|
||||
final nty = _notifications[idx];
|
||||
return Row(
|
||||
@ -221,29 +218,36 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
||||
isAutoWarp: true,
|
||||
),
|
||||
),
|
||||
if ([
|
||||
'interactive.feedback',
|
||||
'interactive.subscription'
|
||||
].contains(nty.topic) &&
|
||||
if (['interactive.reply', 'interactive.feedback', 'interactive.subscription']
|
||||
.contains(nty.topic) &&
|
||||
nty.metadata['related_post'] != null)
|
||||
StyledWidget(Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(8)),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1,
|
||||
GestureDetector(
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: PostItem(
|
||||
data: SnPost.fromJson(
|
||||
nty.metadata['related_post']!,
|
||||
),
|
||||
showComments: false,
|
||||
showReactions: false,
|
||||
showMenu: false,
|
||||
),
|
||||
),
|
||||
child: PostItem(
|
||||
data: SnPost.fromJson(
|
||||
nty.metadata['related_post']!,
|
||||
),
|
||||
showComments: false,
|
||||
showReactions: false,
|
||||
showMenu: false,
|
||||
),
|
||||
)).padding(top: 8),
|
||||
onTap: () {
|
||||
GoRouter.of(context).pushNamed(
|
||||
'postDetail',
|
||||
pathParameters: {
|
||||
'slug': nty.metadata['related_post']!['id'].toString(),
|
||||
},
|
||||
);
|
||||
},
|
||||
).padding(top: 8),
|
||||
const Gap(8),
|
||||
Row(
|
||||
children: [
|
||||
@ -268,10 +272,8 @@ class _NotificationScreenState extends State<NotificationScreen> {
|
||||
IconButton(
|
||||
icon: const Icon(Symbols.check),
|
||||
padding: EdgeInsets.all(0),
|
||||
visualDensity:
|
||||
const VisualDensity(horizontal: -4, vertical: -4),
|
||||
onPressed:
|
||||
_isSubmitting ? null : () => _markOneAsRead(nty),
|
||||
visualDensity: const VisualDensity(horizontal: -4, vertical: -4),
|
||||
onPressed: _isSubmitting ? null : () => _markOneAsRead(nty),
|
||||
),
|
||||
],
|
||||
).padding(horizontal: 16);
|
||||
|
@ -37,6 +37,8 @@ class SnPost with _$SnPost {
|
||||
required DateTime? publishedUntil,
|
||||
required int totalUpvote,
|
||||
required int totalDownvote,
|
||||
@Default(0) int totalViews,
|
||||
@Default(0) int totalAggregatedViews,
|
||||
required int publisherId,
|
||||
required int? pollId,
|
||||
required SnPublisher publisher,
|
||||
|
@ -47,6 +47,8 @@ mixin _$SnPost {
|
||||
DateTime? get publishedUntil => throw _privateConstructorUsedError;
|
||||
int get totalUpvote => throw _privateConstructorUsedError;
|
||||
int get totalDownvote => throw _privateConstructorUsedError;
|
||||
int get totalViews => throw _privateConstructorUsedError;
|
||||
int get totalAggregatedViews => throw _privateConstructorUsedError;
|
||||
int get publisherId => throw _privateConstructorUsedError;
|
||||
int? get pollId => throw _privateConstructorUsedError;
|
||||
SnPublisher get publisher => throw _privateConstructorUsedError;
|
||||
@ -95,6 +97,8 @@ abstract class $SnPostCopyWith<$Res> {
|
||||
DateTime? publishedUntil,
|
||||
int totalUpvote,
|
||||
int totalDownvote,
|
||||
int totalViews,
|
||||
int totalAggregatedViews,
|
||||
int publisherId,
|
||||
int? pollId,
|
||||
SnPublisher publisher,
|
||||
@ -150,6 +154,8 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
||||
Object? publishedUntil = freezed,
|
||||
Object? totalUpvote = null,
|
||||
Object? totalDownvote = null,
|
||||
Object? totalViews = null,
|
||||
Object? totalAggregatedViews = null,
|
||||
Object? publisherId = null,
|
||||
Object? pollId = freezed,
|
||||
Object? publisher = null,
|
||||
@ -265,6 +271,14 @@ class _$SnPostCopyWithImpl<$Res, $Val extends SnPost>
|
||||
? _value.totalDownvote
|
||||
: totalDownvote // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
totalViews: null == totalViews
|
||||
? _value.totalViews
|
||||
: totalViews // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
totalAggregatedViews: null == totalAggregatedViews
|
||||
? _value.totalAggregatedViews
|
||||
: totalAggregatedViews // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
publisherId: null == publisherId
|
||||
? _value.publisherId
|
||||
: publisherId // ignore: cast_nullable_to_non_nullable
|
||||
@ -386,6 +400,8 @@ abstract class _$$SnPostImplCopyWith<$Res> implements $SnPostCopyWith<$Res> {
|
||||
DateTime? publishedUntil,
|
||||
int totalUpvote,
|
||||
int totalDownvote,
|
||||
int totalViews,
|
||||
int totalAggregatedViews,
|
||||
int publisherId,
|
||||
int? pollId,
|
||||
SnPublisher publisher,
|
||||
@ -444,6 +460,8 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
||||
Object? publishedUntil = freezed,
|
||||
Object? totalUpvote = null,
|
||||
Object? totalDownvote = null,
|
||||
Object? totalViews = null,
|
||||
Object? totalAggregatedViews = null,
|
||||
Object? publisherId = null,
|
||||
Object? pollId = freezed,
|
||||
Object? publisher = null,
|
||||
@ -559,6 +577,14 @@ class __$$SnPostImplCopyWithImpl<$Res>
|
||||
? _value.totalDownvote
|
||||
: totalDownvote // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
totalViews: null == totalViews
|
||||
? _value.totalViews
|
||||
: totalViews // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
totalAggregatedViews: null == totalAggregatedViews
|
||||
? _value.totalAggregatedViews
|
||||
: totalAggregatedViews // ignore: cast_nullable_to_non_nullable
|
||||
as int,
|
||||
publisherId: null == publisherId
|
||||
? _value.publisherId
|
||||
: publisherId // ignore: cast_nullable_to_non_nullable
|
||||
@ -614,6 +640,8 @@ class _$SnPostImpl extends _SnPost {
|
||||
required this.publishedUntil,
|
||||
required this.totalUpvote,
|
||||
required this.totalDownvote,
|
||||
this.totalViews = 0,
|
||||
this.totalAggregatedViews = 0,
|
||||
required this.publisherId,
|
||||
required this.pollId,
|
||||
required this.publisher,
|
||||
@ -731,6 +759,12 @@ class _$SnPostImpl extends _SnPost {
|
||||
@override
|
||||
final int totalDownvote;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int totalViews;
|
||||
@override
|
||||
@JsonKey()
|
||||
final int totalAggregatedViews;
|
||||
@override
|
||||
final int publisherId;
|
||||
@override
|
||||
final int? pollId;
|
||||
@ -743,7 +777,7 @@ class _$SnPostImpl extends _SnPost {
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnPost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, body: $body, language: $language, alias: $alias, aliasPrefix: $aliasPrefix, tags: $tags, categories: $categories, replies: $replies, replyId: $replyId, repostId: $repostId, replyTo: $replyTo, repostTo: $repostTo, visibleUsersList: $visibleUsersList, invisibleUsersList: $invisibleUsersList, visibility: $visibility, editedAt: $editedAt, pinnedAt: $pinnedAt, lockedAt: $lockedAt, isDraft: $isDraft, publishedAt: $publishedAt, publishedUntil: $publishedUntil, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, publisherId: $publisherId, pollId: $pollId, publisher: $publisher, metric: $metric, preload: $preload)';
|
||||
return 'SnPost(id: $id, createdAt: $createdAt, updatedAt: $updatedAt, deletedAt: $deletedAt, type: $type, body: $body, language: $language, alias: $alias, aliasPrefix: $aliasPrefix, tags: $tags, categories: $categories, replies: $replies, replyId: $replyId, repostId: $repostId, replyTo: $replyTo, repostTo: $repostTo, visibleUsersList: $visibleUsersList, invisibleUsersList: $invisibleUsersList, visibility: $visibility, editedAt: $editedAt, pinnedAt: $pinnedAt, lockedAt: $lockedAt, isDraft: $isDraft, publishedAt: $publishedAt, publishedUntil: $publishedUntil, totalUpvote: $totalUpvote, totalDownvote: $totalDownvote, totalViews: $totalViews, totalAggregatedViews: $totalAggregatedViews, publisherId: $publisherId, pollId: $pollId, publisher: $publisher, metric: $metric, preload: $preload)';
|
||||
}
|
||||
|
||||
@override
|
||||
@ -796,6 +830,10 @@ class _$SnPostImpl extends _SnPost {
|
||||
other.totalUpvote == totalUpvote) &&
|
||||
(identical(other.totalDownvote, totalDownvote) ||
|
||||
other.totalDownvote == totalDownvote) &&
|
||||
(identical(other.totalViews, totalViews) ||
|
||||
other.totalViews == totalViews) &&
|
||||
(identical(other.totalAggregatedViews, totalAggregatedViews) ||
|
||||
other.totalAggregatedViews == totalAggregatedViews) &&
|
||||
(identical(other.publisherId, publisherId) ||
|
||||
other.publisherId == publisherId) &&
|
||||
(identical(other.pollId, pollId) || other.pollId == pollId) &&
|
||||
@ -836,6 +874,8 @@ class _$SnPostImpl extends _SnPost {
|
||||
publishedUntil,
|
||||
totalUpvote,
|
||||
totalDownvote,
|
||||
totalViews,
|
||||
totalAggregatedViews,
|
||||
publisherId,
|
||||
pollId,
|
||||
publisher,
|
||||
@ -888,6 +928,8 @@ abstract class _SnPost extends SnPost {
|
||||
required final DateTime? publishedUntil,
|
||||
required final int totalUpvote,
|
||||
required final int totalDownvote,
|
||||
final int totalViews,
|
||||
final int totalAggregatedViews,
|
||||
required final int publisherId,
|
||||
required final int? pollId,
|
||||
required final SnPublisher publisher,
|
||||
@ -952,6 +994,10 @@ abstract class _SnPost extends SnPost {
|
||||
@override
|
||||
int get totalDownvote;
|
||||
@override
|
||||
int get totalViews;
|
||||
@override
|
||||
int get totalAggregatedViews;
|
||||
@override
|
||||
int get publisherId;
|
||||
@override
|
||||
int? get pollId;
|
||||
|
@ -62,6 +62,9 @@ _$SnPostImpl _$$SnPostImplFromJson(Map<String, dynamic> json) => _$SnPostImpl(
|
||||
: DateTime.parse(json['published_until'] as String),
|
||||
totalUpvote: (json['total_upvote'] as num).toInt(),
|
||||
totalDownvote: (json['total_downvote'] as num).toInt(),
|
||||
totalViews: (json['total_views'] as num?)?.toInt() ?? 0,
|
||||
totalAggregatedViews:
|
||||
(json['total_aggregated_views'] as num?)?.toInt() ?? 0,
|
||||
publisherId: (json['publisher_id'] as num).toInt(),
|
||||
pollId: (json['poll_id'] as num?)?.toInt(),
|
||||
publisher:
|
||||
@ -101,6 +104,8 @@ Map<String, dynamic> _$$SnPostImplToJson(_$SnPostImpl instance) =>
|
||||
'published_until': instance.publishedUntil?.toIso8601String(),
|
||||
'total_upvote': instance.totalUpvote,
|
||||
'total_downvote': instance.totalDownvote,
|
||||
'total_views': instance.totalViews,
|
||||
'total_aggregated_views': instance.totalAggregatedViews,
|
||||
'publisher_id': instance.publisherId,
|
||||
'poll_id': instance.pollId,
|
||||
'publisher': instance.publisher.toJson(),
|
||||
|
@ -684,6 +684,15 @@ class _PostBottomAction extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
InkWell(
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Symbols.play_circle, size: 20, color: iconColor),
|
||||
const Gap(8),
|
||||
Text('postViews').plural(data.totalViews),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
InkWell(
|
||||
@ -829,7 +838,6 @@ class _PostContentHeader extends StatelessWidget {
|
||||
await sn.client.delete('/cgi/co/posts/${data.id}', queryParameters: {
|
||||
'publisherId': data.publisherId,
|
||||
});
|
||||
|
||||
if (!context.mounted) return;
|
||||
context.showSnackbar('postDeleted'.tr(args: ['#${data.id}']));
|
||||
} catch (err) {
|
||||
@ -838,6 +846,25 @@ class _PostContentHeader extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _flagPost(BuildContext context) async {
|
||||
final confirm = await context.showConfirmDialog(
|
||||
'flagPost'.tr(),
|
||||
'flagPostDescription'.tr(),
|
||||
);
|
||||
if (!confirm) return;
|
||||
if (!context.mounted) return;
|
||||
|
||||
try {
|
||||
final sn = context.read<SnNetworkProvider>();
|
||||
await sn.client.post('/cgi/co/posts/${data.id}/flag');
|
||||
if (!context.mounted) return;
|
||||
context.showSnackbar('postFlagged'.tr());
|
||||
} catch (err) {
|
||||
if (!context.mounted) return;
|
||||
context.showErrorDialog(err);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
@ -1029,6 +1056,18 @@ class _PostContentHeader extends StatelessWidget {
|
||||
children: [
|
||||
const Icon(Symbols.flag),
|
||||
const Gap(16),
|
||||
Text('flagPostAction').tr(),
|
||||
],
|
||||
),
|
||||
onTap: () {
|
||||
_flagPost(context);
|
||||
},
|
||||
),
|
||||
PopupMenuItem(
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Symbols.report),
|
||||
const Gap(16),
|
||||
Text('report').tr(),
|
||||
],
|
||||
),
|
||||
|
Loading…
x
Reference in New Issue
Block a user