Featured replies on post

This commit is contained in:
LittleSheep 2024-09-16 23:35:44 +08:00
parent 47eb6cbc66
commit ce16de9c71
8 changed files with 116 additions and 5 deletions

View File

@ -1,6 +1,7 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/exceptions/request.dart'; import 'package:solian/exceptions/request.dart';
import 'package:solian/exceptions/unauthorized.dart'; import 'package:solian/exceptions/unauthorized.dart';
import 'package:solian/models/post.dart';
import 'package:solian/providers/auth.dart'; import 'package:solian/providers/auth.dart';
import 'package:solian/services.dart'; import 'package:solian/services.dart';
@ -96,6 +97,15 @@ class PostProvider extends GetConnect {
return resp; return resp;
} }
Future<List<Post>> listPostFeaturedReply(String alias) async {
final resp = await get('/posts/$alias/replies/featured');
if (resp.statusCode != 200) {
throw RequestException(resp);
}
return List<Post>.from(resp.body.map((x) => Post.fromJson(x)));
}
Future<Response> getPost(String alias) async { Future<Response> getPost(String alias) async {
final resp = await get('/posts/$alias'); final resp = await get('/posts/$alias');
if (resp.statusCode != 200) { if (resp.statusCode != 200) {

View File

@ -302,6 +302,7 @@ class _AccountProfilePageState extends State<AccountProfilePage> {
isClickable: true, isClickable: true,
isNestedClickable: true, isNestedClickable: true,
isShowEmbed: true, isShowEmbed: true,
showFeaturedReply: true,
onUpdate: () { onUpdate: () {
_postController.reloadAllOver(); _postController.reloadAllOver();
}, },

View File

@ -379,6 +379,7 @@ class _DashboardScreenState extends State<DashboardScreen> {
isClickable: true, isClickable: true,
isShowEmbed: true, isShowEmbed: true,
isNestedClickable: true, isNestedClickable: true,
showFeaturedReply: true,
onUpdate: (_) { onUpdate: (_) {
_pullPosts(); _pullPosts();
}, },

View File

@ -3,9 +3,10 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:get/get_utils/get_utils.dart'; import 'package:get/get.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:solian/models/post.dart'; import 'package:solian/models/post.dart';
import 'package:solian/providers/content/posts.dart';
import 'package:solian/screens/posts/post_detail.dart'; import 'package:solian/screens/posts/post_detail.dart';
import 'package:solian/shells/title_shell.dart'; import 'package:solian/shells/title_shell.dart';
import 'package:solian/theme.dart'; import 'package:solian/theme.dart';
@ -30,6 +31,7 @@ class PostItem extends StatefulWidget {
final bool isFullDate; final bool isFullDate;
final bool isFullContent; final bool isFullContent;
final bool isContentSelectable; final bool isContentSelectable;
final bool showFeaturedReply;
final String? attachmentParent; final String? attachmentParent;
final Color? backgroundColor; final Color? backgroundColor;
@ -45,6 +47,7 @@ class PostItem extends StatefulWidget {
this.isFullDate = false, this.isFullDate = false,
this.isFullContent = false, this.isFullContent = false,
this.isContentSelectable = false, this.isContentSelectable = false,
this.showFeaturedReply = false,
this.attachmentParent, this.attachmentParent,
this.backgroundColor, this.backgroundColor,
}); });
@ -103,7 +106,7 @@ class _PostItemState extends State<PostItem> {
children: [ children: [
if (widget.isCompact) if (widget.isCompact)
AccountAvatar( AccountAvatar(
content: item.author.avatar.toString(), content: item.author.avatar,
radius: 10, radius: 10,
).paddingOnly(left: 2, top: 1), ).paddingOnly(left: 2, top: 1),
Expanded( Expanded(
@ -320,6 +323,95 @@ class _PostItemState extends State<PostItem> {
} }
} }
Widget _buildFeaturedReply() {
if ((widget.item.metric?.replyCount ?? 0) == 0) {
return const SizedBox.shrink();
}
final List<String> attachments = item.body['attachments'] is List
? List.from(item.body['attachments']?.whereType<String>())
: List.empty();
final unFocusColor =
Theme.of(context).colorScheme.onSurface.withOpacity(0.75);
return FutureBuilder(
future: Get.find<PostProvider>().listPostFeaturedReply(
widget.item.id.toString(),
),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const SizedBox.shrink();
}
return Card(
margin: EdgeInsets.zero,
child: Column(
children: snapshot.data!
.map(
(x) => Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AccountAvatar(content: x.author.avatar, radius: 10),
const Gap(6),
Text(
x.author.nick,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const Gap(6),
Text(
format(
x.publishedAt?.toLocal() ?? DateTime.now(),
locale: 'en_short',
),
).paddingOnly(top: 0.5),
const Gap(16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MarkdownTextContent(
content: x.body['content'],
parentId: 'p${item.id}-featured-reply${x.id}',
),
if (x.body['attachments'] is List &&
x.body['attachments'].length > 0)
Row(
children: [
Icon(
Icons.file_copy,
size: 15,
color: unFocusColor,
).paddingOnly(right: 5),
Text(
'attachmentHint'.trParams(
{
'count': x.body['attachments'].length
.toString()
},
),
style: TextStyle(color: unFocusColor),
)
],
),
],
),
),
],
).paddingSymmetric(horizontal: 12, vertical: 8),
)
.toList(),
),
).paddingOnly(
top: (attachments.length == 1 && !AppTheme.isLargeScreen(context))
? 10
: 6,
left: (attachments.length == 1 && !AppTheme.isLargeScreen(context))
? 24
: 60,
right: 16,
);
},
);
}
double _contentHeight = 0; double _contentHeight = 0;
@override @override
@ -417,7 +509,7 @@ class _PostItemState extends State<PostItem> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
GestureDetector( GestureDetector(
child: AccountAvatar(content: item.author.avatar.toString()), child: AccountAvatar(content: item.author.avatar),
onTap: () { onTap: () {
showModalBottomSheet( showModalBottomSheet(
useRootNavigator: true, useRootNavigator: true,
@ -506,6 +598,7 @@ class _PostItemState extends State<PostItem> {
left: 16, left: 16,
), ),
_buildAttachments(), _buildAttachments(),
if (widget.showFeaturedReply) _buildFeaturedReply(),
if (widget.isShowReply || widget.isReactable) if (widget.isShowReply || widget.isReactable)
PostQuickAction( PostQuickAction(
isShowReply: widget.isShowReply, isShowReply: widget.isShowReply,

View File

@ -33,6 +33,7 @@ class PostListWidget extends StatelessWidget {
isShowEmbed: isShowEmbed, isShowEmbed: isShowEmbed,
isNestedClickable: isNestedClickable, isNestedClickable: isNestedClickable,
isClickable: isClickable, isClickable: isClickable,
showFeaturedReply: true,
item: item, item: item,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
onUpdate: () { onUpdate: () {
@ -51,6 +52,7 @@ class PostListEntryWidget extends StatelessWidget {
final bool isShowEmbed; final bool isShowEmbed;
final bool isNestedClickable; final bool isNestedClickable;
final bool isClickable; final bool isClickable;
final bool showFeaturedReply;
final Post item; final Post item;
final Function onUpdate; final Function onUpdate;
final Color? backgroundColor; final Color? backgroundColor;
@ -61,6 +63,7 @@ class PostListEntryWidget extends StatelessWidget {
required this.isShowEmbed, required this.isShowEmbed,
required this.isNestedClickable, required this.isNestedClickable,
required this.isClickable, required this.isClickable,
required this.showFeaturedReply,
required this.item, required this.item,
required this.onUpdate, required this.onUpdate,
this.backgroundColor, this.backgroundColor,
@ -74,6 +77,7 @@ class PostListEntryWidget extends StatelessWidget {
item: item, item: item,
isShowEmbed: isShowEmbed, isShowEmbed: isShowEmbed,
isClickable: isNestedClickable, isClickable: isNestedClickable,
showFeaturedReply: showFeaturedReply,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
).paddingSymmetric(vertical: 8), ).paddingSymmetric(vertical: 8),
onLongPress: () { onLongPress: () {

View File

@ -23,6 +23,7 @@ class PostSingleDisplay extends StatelessWidget {
isClickable: true, isClickable: true,
isShowEmbed: true, isShowEmbed: true,
isNestedClickable: true, isNestedClickable: true,
showFeaturedReply: true,
onUpdate: onUpdate, onUpdate: onUpdate,
backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow, backgroundColor: Theme.of(context).colorScheme.surfaceContainerLow,
), ),

View File

@ -36,6 +36,7 @@ class PostWarpedListWidget extends StatelessWidget {
isShowEmbed: isShowEmbed, isShowEmbed: isShowEmbed,
isNestedClickable: isNestedClickable, isNestedClickable: isNestedClickable,
isClickable: isClickable, isClickable: isClickable,
showFeaturedReply: true,
item: item, item: item,
onUpdate: onUpdate ?? () {}, onUpdate: onUpdate ?? () {},
); );

View File

@ -1894,10 +1894,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: sqflite_common name: sqflite_common
sha256: "7b41b6c3507854a159e24ae90a8e3e9cc01eb26a477c118d6dca065b5f55453e" sha256: "4058172e418eb7e7f2058dcb7657d451a8fc264afa0dea4dbd0f304a57131611"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.4+2" version: "2.5.4+3"
sqlite3: sqlite3:
dependency: transitive dependency: transitive
description: description: