Select & Featured Answer

This commit is contained in:
2025-02-08 13:27:53 +08:00
parent 1aa70827dc
commit a97c3bce3a
8 changed files with 109 additions and 25 deletions

View File

@ -8,17 +8,23 @@ import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/post.dart';
import 'package:surface/providers/userinfo.dart';
import 'package:surface/types/post.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/post/post_item.dart';
import 'package:surface/widgets/post/post_mini_editor.dart';
import 'package:very_good_infinite_list/very_good_infinite_list.dart';
import '../../providers/sn_network.dart';
class PostCommentSliverList extends StatefulWidget {
final int parentPostId;
final SnPost parentPost;
final double? maxWidth;
final Function(SnPost)? onSelectAnswer;
const PostCommentSliverList({
super.key,
required this.parentPostId,
required this.parentPost,
this.maxWidth,
this.onSelectAnswer,
});
@override
@ -37,7 +43,7 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
setState(() => _isBusy = true);
final pt = context.read<SnPostContentProvider>();
final result = await pt.listPostReplies(widget.parentPostId);
final result = await pt.listPostReplies(widget.parentPost.id);
final List<SnPost> out = result.$1;
if (!mounted) return;
@ -48,6 +54,21 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
if (mounted) setState(() => _isBusy = false);
}
Future<void> _selectAnswer(SnPost answer) async {
try {
final sn = context.read<SnNetworkProvider>();
await sn.client.put('/cgi/co/questions/${widget.parentPost.id}/answer', data: {
'publisher': answer.publisherId,
'answer_id': answer.id,
});
if (!mounted) return;
await refresh();
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
}
}
Future<void> refresh() async {
_posts.clear();
_fetchPosts();
@ -71,6 +92,7 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
child: PostItem(
data: _posts[idx],
maxWidth: widget.maxWidth,
onSelectAnswer: widget.parentPost.type == 'question' ? () => _selectAnswer(_posts[idx]) : null,
onChanged: (data) {
setState(() => _posts[idx] = data);
},
@ -94,11 +116,12 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
}
class PostCommentListPopup extends StatefulWidget {
final int postId;
final SnPost post;
final int commentCount;
const PostCommentListPopup({
super.key,
required this.postId,
required this.post,
this.commentCount = 0,
});
@ -122,9 +145,7 @@ class _PostCommentListPopupState extends State<PostCommentListPopup> {
children: [
const Icon(Symbols.comment, size: 24),
const Gap(16),
Text('postCommentsDetailed')
.plural(widget.commentCount)
.textStyle(Theme.of(context).textTheme.titleLarge!),
Text('postCommentsDetailed').plural(widget.commentCount).textStyle(Theme.of(context).textTheme.titleLarge!),
],
).padding(horizontal: 20, top: 16, bottom: 12),
Expanded(
@ -143,7 +164,7 @@ class _PostCommentListPopupState extends State<PostCommentListPopup> {
),
),
child: PostMiniEditor(
postReplyId: widget.postId,
postReplyId: widget.post.id,
onPost: () {
_childListKey.currentState!.refresh();
},
@ -151,8 +172,8 @@ class _PostCommentListPopupState extends State<PostCommentListPopup> {
),
),
PostCommentSliverList(
parentPost: widget.post,
key: _childListKey,
parentPostId: widget.postId,
),
],
),

View File

@ -47,6 +47,7 @@ class PostItem extends StatelessWidget {
final double? maxWidth;
final Function(SnPost data)? onChanged;
final Function()? onDeleted;
final Function()? onSelectAnswer;
const PostItem({
super.key,
@ -58,6 +59,7 @@ class PostItem extends StatelessWidget {
this.maxWidth,
this.onChanged,
this.onDeleted,
this.onSelectAnswer,
});
void _onChanged(SnPost data) {
@ -142,6 +144,7 @@ class PostItem extends StatelessWidget {
isRelativeDate: !showFullPost,
onShare: () => _doShare(context),
onShareImage: () => _doShareViaPicture(context),
onSelectAnswer: onSelectAnswer,
onDeleted: () {
if (onDeleted != null) {}
},
@ -224,6 +227,7 @@ class PostItem extends StatelessWidget {
showMenu: showMenu,
onShare: () => _doShare(context),
onShareImage: () => _doShareViaPicture(context),
onSelectAnswer: onSelectAnswer,
onDeleted: () {
if (onDeleted != null) onDeleted!();
},
@ -456,9 +460,9 @@ class _PostQuestionHint extends StatelessWidget {
'${data.body['reward']}',
])).opacity(0.75)
else if (data.body['answer'] == null)
Text('postQuestionUnanswered').opacity(0.75)
Text('postQuestionUnanswered'.tr()).opacity(0.75)
else
Text('postQuestionAnswered').opacity(0.75),
Text('postQuestionAnswered'.tr()).opacity(0.75),
],
).opacity(0.75);
}
@ -555,7 +559,7 @@ class _PostBottomAction extends StatelessWidget {
context: context,
useRootNavigator: true,
builder: (context) => PostCommentListPopup(
postId: data.id,
post: data,
commentCount: data.metric.replyCount,
),
);
@ -678,6 +682,7 @@ class _PostContentHeader extends StatelessWidget {
final bool showMenu;
final Function onDeleted;
final Function() onShare, onShareImage;
final Function()? onSelectAnswer;
const _PostContentHeader({
required this.data,
@ -688,6 +693,7 @@ class _PostContentHeader extends StatelessWidget {
required this.onDeleted,
required this.onShare,
required this.onShareImage,
this.onSelectAnswer,
});
Future<void> _deletePost(BuildContext context) async {
@ -786,6 +792,20 @@ class _PostContentHeader extends StatelessWidget {
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
if (isAuthor && onSelectAnswer != null)
PopupMenuItem(
child: Row(
children: [
const Icon(Symbols.check_circle),
const Gap(16),
Text('postQuestionAnswerSelect').tr(),
],
),
onTap: () {
onSelectAnswer?.call();
},
),
if (isAuthor && onSelectAnswer != null) PopupMenuDivider(),
if (isAuthor)
PopupMenuItem(
child: Row(
@ -1165,8 +1185,18 @@ class _PostFeaturedComment extends StatefulWidget {
class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
SnPost? _featuredComment;
bool _isAnswer = false;
Future<void> _fetchComments() async {
// If this is a answered question, fetch the answer instead
if (widget.data.type == 'question' && widget.data.body['answer'] != null) {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/co/posts/${widget.data.body['answer']}');
_isAnswer = true;
setState(() => _featuredComment = SnPost.fromJson(resp.data));
return;
}
try {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get('/cgi/co/posts/${widget.data.id}/replies/featured', queryParameters: {
@ -1192,13 +1222,15 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
if (widget.data.metric.replyCount == 0) return const SizedBox.shrink();
if (_featuredComment == null) return const SizedBox.shrink();
final sn = context.read<SnNetworkProvider>();
return AnimateWidgetExtensions(Container(
constraints: BoxConstraints(maxWidth: widget.maxWidth ?? double.infinity),
margin: const EdgeInsets.only(top: 8),
width: double.infinity,
child: Material(
borderRadius: const BorderRadius.all(Radius.circular(8)),
color: Theme.of(context).colorScheme.surfaceContainerHigh,
color: _isAnswer ? Colors.green.withOpacity(0.5) : Theme.of(context).colorScheme.surfaceContainerHigh,
child: InkWell(
borderRadius: const BorderRadius.all(Radius.circular(8)),
onTap: () {
@ -1206,7 +1238,7 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
context: context,
useRootNavigator: true,
builder: (context) => PostCommentListPopup(
postId: widget.data.id,
post: widget.data,
commentCount: widget.data.metric.replyCount,
),
);
@ -1214,7 +1246,18 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('postFeaturedComment', style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 16)).tr(),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Gap(2),
Icon(_isAnswer ? Symbols.task_alt : Symbols.prompt_suggestion, size: 20),
const Gap(10),
Text(
_isAnswer ? 'postQuestionAnswerTitle' : 'postFeaturedComment',
style: Theme.of(context).textTheme.titleMedium!.copyWith(fontSize: 15),
).tr(),
],
),
const Gap(4),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
@ -1222,7 +1265,7 @@ class _PostFeaturedCommentState extends State<_PostFeaturedComment> {
CircleAvatar(
radius: 12,
backgroundImage: UniversalImage.provider(
_featuredComment!.publisher.avatar,
sn.getAttachmentUrl(_featuredComment!.publisher.avatar),
),
),
const Gap(8),