diff --git a/assets/translations/en.json b/assets/translations/en.json index aae381b..40bea63 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -211,6 +211,9 @@ "other": "{} results" }, "postSearchTook": "Took {}", + "postDelete": "Delete post {}", + "postDeleteDescription": "Are you sure you want to delete this post? This operation is irreversible.", + "postDeleted": "Post {} has been deleted.", "call" : "Call", "callOngoingNotice": "A call is ongoing", "callJoin": "Join", diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 1c91e49..59b9d20 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -211,6 +211,9 @@ "other": "搜索到 {} 个结果" }, "postSearchTook": "耗时 {}", + "postDelete": "删除帖子 {}", + "postDeleteDescription": "你确定要删除这个帖子吗?该操作不可撤销。", + "postDeleted": "帖子 {} 已被删除。", "call": "通话", "callOngoingNotice": "一则通话进行中", "callJoin": "加入", diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index 95b41b5..9b0de23 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -164,6 +164,10 @@ class _ExploreScreenState extends State { onChanged: (data) { setState(() => _posts[idx] = data); }, + onDeleted: () { + _posts.clear(); + _fetchPosts(); + }, ), onTap: () { GoRouter.of(context).pushNamed( diff --git a/lib/screens/post/post_detail.dart b/lib/screens/post/post_detail.dart index 5cf2729..d890b8d 100644 --- a/lib/screens/post/post_detail.dart +++ b/lib/screens/post/post_detail.dart @@ -113,6 +113,9 @@ class _PostDetailScreenState extends State { onChanged: (data) { setState(() => _data = data); }, + onDeleted: () { + Navigator.pop(context); + }, ), ), const SliverToBoxAdapter(child: Divider(height: 1)), diff --git a/lib/screens/post/post_search.dart b/lib/screens/post/post_search.dart index 32686d7..afffde0 100644 --- a/lib/screens/post/post_search.dart +++ b/lib/screens/post/post_search.dart @@ -101,6 +101,10 @@ class _PostSearchScreenState extends State { onChanged: (data) { setState(() => _posts[idx] = data); }, + onDeleted: () { + _posts.clear(); + _fetchPosts(); + }, ), onTap: () { GoRouter.of(context).pushNamed( diff --git a/lib/widgets/post/post_comment_list.dart b/lib/widgets/post/post_comment_list.dart index da4a148..355be56 100644 --- a/lib/widgets/post/post_comment_list.dart +++ b/lib/widgets/post/post_comment_list.dart @@ -74,6 +74,10 @@ class PostCommentSliverListState extends State { onChanged: (data) { setState(() => _posts[idx] = data); }, + onDeleted: () { + _posts.clear(); + _fetchPosts(); + }, ), onTap: () { GoRouter.of(context).pushNamed( diff --git a/lib/widgets/post/post_item.dart b/lib/widgets/post/post_item.dart index 370aed4..a575e67 100644 --- a/lib/widgets/post/post_item.dart +++ b/lib/widgets/post/post_item.dart @@ -5,10 +5,12 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import 'package:relative_time/relative_time.dart'; import 'package:styled_widget/styled_widget.dart'; +import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/userinfo.dart'; import 'package:surface/types/post.dart'; import 'package:surface/widgets/account/account_image.dart'; import 'package:surface/widgets/attachment/attachment_list.dart'; +import 'package:surface/widgets/dialog.dart'; import 'package:surface/widgets/markdown_content.dart'; import 'package:gap/gap.dart'; import 'package:surface/widgets/post/post_comment_list.dart'; @@ -21,6 +23,7 @@ class PostItem extends StatelessWidget { final bool showMenu; final double? maxWidth; final Function(SnPost data)? onChanged; + final Function()? onDeleted; const PostItem({ super.key, required this.data, @@ -29,6 +32,7 @@ class PostItem extends StatelessWidget { this.showMenu = true, this.maxWidth, this.onChanged, + this.onDeleted, }); void _onChanged(SnPost data) { @@ -45,8 +49,13 @@ class PostItem extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _PostContentHeader(data: data, showMenu: showMenu) - .padding(horizontal: 12, vertical: 8), + _PostContentHeader( + data: data, + showMenu: showMenu, + onDeleted: () { + if (onDeleted != null) onDeleted!(); + }, + ).padding(horizontal: 12, vertical: 8), if (data.body['title'] != null || data.body['description'] != null) _PostHeadline(data: data).padding(horizontal: 16, bottom: 8), @@ -217,12 +226,37 @@ class _PostContentHeader extends StatelessWidget { final SnPost data; final bool isCompact; final bool showMenu; + final Function onDeleted; const _PostContentHeader({ required this.data, this.isCompact = false, this.showMenu = true, + required this.onDeleted, }); + Future _deletePost(BuildContext context) async { + final confirm = await context.showConfirmDialog( + 'postDelete'.tr(args: ['#${data.id}']), + 'postDeleteDescription'.tr(), + ); + + if (!confirm) return; + if (!context.mounted) return; + + try { + final sn = context.read(); + 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) { + if (!context.mounted) return; + context.showErrorDialog(err); + } + } + @override Widget build(BuildContext context) { final ua = context.read(); @@ -302,6 +336,7 @@ class _PostContentHeader extends StatelessWidget { Text('delete').tr(), ], ), + onTap: () => _deletePost(context), ), if (isAuthor) const PopupMenuDivider(), PopupMenuItem( @@ -381,8 +416,12 @@ class _PostQuoteContent extends StatelessWidget { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: Column( children: [ - _PostContentHeader(data: child, isCompact: true, showMenu: false) - .padding(bottom: 4), + _PostContentHeader( + data: child, + isCompact: true, + showMenu: false, + onDeleted: () {}, + ).padding(bottom: 4), _PostContentBody(data: child.body), ], ),