Better(?) comment nesting

This commit is contained in:
LittleSheep 2025-03-16 21:41:38 +08:00
parent fc5a79b29b
commit 7b96013406
7 changed files with 132 additions and 8 deletions

View File

@ -207,6 +207,7 @@
"one": "{} comment", "one": "{} comment",
"other": "{} comments" "other": "{} comments"
}, },
"postCommentExpand": "Show comments",
"settingsAppearance": "Appearance", "settingsAppearance": "Appearance",
"settingsCustomFonts": "Custom Fonts", "settingsCustomFonts": "Custom Fonts",
"settingsCustomFontsDescription": "Set custom fonts for the application.", "settingsCustomFontsDescription": "Set custom fonts for the application.",

View File

@ -205,6 +205,7 @@
"one": "{} 条评论", "one": "{} 条评论",
"other": "{} 条评论" "other": "{} 条评论"
}, },
"postCommentExpand": "展开评论",
"settingsAppearance": "外观", "settingsAppearance": "外观",
"settingsCustomFonts": "自定义字体", "settingsCustomFonts": "自定义字体",
"settingsCustomFontsDescription": "设置应用程序使用的字体。", "settingsCustomFontsDescription": "设置应用程序使用的字体。",

View File

@ -205,6 +205,7 @@
"one": "{} 條評論", "one": "{} 條評論",
"other": "{} 條評論" "other": "{} 條評論"
}, },
"postCommentExpand": "展開評論",
"settingsAppearance": "外觀", "settingsAppearance": "外觀",
"settingsCustomFonts": "自定義字體", "settingsCustomFonts": "自定義字體",
"settingsCustomFontsDescription": "設置應用程序使用的字體。", "settingsCustomFontsDescription": "設置應用程序使用的字體。",

View File

@ -205,6 +205,7 @@
"one": "{} 條評論", "one": "{} 條評論",
"other": "{} 條評論" "other": "{} 條評論"
}, },
"postCommentExpand": "展開評論",
"settingsAppearance": "外觀", "settingsAppearance": "外觀",
"settingsCustomFonts": "自定義字體", "settingsCustomFonts": "自定義字體",
"settingsCustomFontsDescription": "設置應用程序使用的字體。", "settingsCustomFontsDescription": "設置應用程序使用的字體。",

View File

@ -14,6 +14,7 @@ class AccountImage extends StatelessWidget {
final Widget? fallbackWidget; final Widget? fallbackWidget;
final Widget? badge; final Widget? badge;
final Offset? badgeOffset; final Offset? badgeOffset;
final FilterQuality? filterQuality;
const AccountImage({ const AccountImage({
super.key, super.key,
@ -25,6 +26,7 @@ class AccountImage extends StatelessWidget {
this.fallbackWidget, this.fallbackWidget,
this.badge, this.badge,
this.badgeOffset, this.badgeOffset,
this.filterQuality,
}); });
@override @override
@ -54,6 +56,7 @@ class AccountImage extends StatelessWidget {
) )
: AutoResizeUniversalImage( : AutoResizeUniversalImage(
sn.getAttachmentUrl(url), sn.getAttachmentUrl(url),
filterQuality: filterQuality,
key: Key('attachment-${content.hashCode}'), key: Key('attachment-${content.hashCode}'),
fit: BoxFit.cover, fit: BoxFit.cover,
), ),

View File

@ -138,6 +138,7 @@ class PostCommentSliverListState extends State<PostCommentSliverList> {
child: PostItem( child: PostItem(
data: _posts[idx], data: _posts[idx],
maxWidth: widget.maxWidth, maxWidth: widget.maxWidth,
showExpandableComments: true,
onSelectAnswer: widget.parentPost.type == 'question' onSelectAnswer: widget.parentPost.type == 'question'
? () => _selectAnswer(_posts[idx]) ? () => _selectAnswer(_posts[idx])
: null, : null,
@ -209,6 +210,7 @@ class _PostCommentListPopupState extends State<PostCommentListPopup> {
if (ua.isAuthorized) if (ua.isAuthorized)
SliverToBoxAdapter( SliverToBoxAdapter(
child: Container( child: Container(
margin: const EdgeInsets.only(bottom: 8),
height: 240, height: 240,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.symmetric( border: Border.symmetric(

View File

@ -50,6 +50,7 @@ class OpenablePostItem extends StatelessWidget {
final bool showComments; final bool showComments;
final bool showMenu; final bool showMenu;
final bool showFullPost; final bool showFullPost;
final bool showExpandableComments;
final double? maxWidth; final double? maxWidth;
final Function(SnPost data)? onChanged; final Function(SnPost data)? onChanged;
final Function()? onDeleted; final Function()? onDeleted;
@ -62,6 +63,7 @@ class OpenablePostItem extends StatelessWidget {
this.showComments = true, this.showComments = true,
this.showMenu = true, this.showMenu = true,
this.showFullPost = false, this.showFullPost = false,
this.showExpandableComments = false,
this.maxWidth, this.maxWidth,
this.onChanged, this.onChanged,
this.onDeleted, this.onDeleted,
@ -83,6 +85,7 @@ class OpenablePostItem extends StatelessWidget {
maxWidth: maxWidth, maxWidth: maxWidth,
showComments: showComments, showComments: showComments,
showFullPost: showFullPost, showFullPost: showFullPost,
showExpandableComments: showExpandableComments,
onChanged: onChanged, onChanged: onChanged,
onDeleted: onDeleted, onDeleted: onDeleted,
onSelectAnswer: onSelectAnswer, onSelectAnswer: onSelectAnswer,
@ -115,6 +118,7 @@ class PostItem extends StatelessWidget {
final bool showComments; final bool showComments;
final bool showMenu; final bool showMenu;
final bool showFullPost; final bool showFullPost;
final bool showExpandableComments;
final double? maxWidth; final double? maxWidth;
final Function(SnPost data)? onChanged; final Function(SnPost data)? onChanged;
final Function()? onDeleted; final Function()? onDeleted;
@ -127,6 +131,7 @@ class PostItem extends StatelessWidget {
this.showComments = true, this.showComments = true,
this.showMenu = true, this.showMenu = true,
this.showFullPost = false, this.showFullPost = false,
this.showExpandableComments = false,
this.maxWidth, this.maxWidth,
this.onChanged, this.onChanged,
this.onDeleted, this.onDeleted,
@ -354,14 +359,17 @@ class PostItem extends StatelessWidget {
), ),
if (data.preload?.poll != null) if (data.preload?.poll != null)
PostPoll(poll: data.preload!.poll!) PostPoll(poll: data.preload!.poll!)
.padding(horizontal: 12, vertical: 4), .padding(left: 60, right: 12, top: 12, bottom: 4),
if (data.body['content'] != null && if (data.body['content'] != null &&
(cfg.prefs.getBool(kAppExpandPostLink) ?? true)) (cfg.prefs.getBool(kAppExpandPostLink) ?? true))
LinkPreviewWidget( LinkPreviewWidget(
text: data.body['content'], text: data.body['content'],
).padding(left: 60, right: 4), ).padding(left: 60, right: 4),
_PostFeaturedComment(data: data, maxWidth: maxWidth) if (showExpandableComments)
.padding(left: 60, right: 12), _PostCommentIntent(data: data).padding(left: 60, right: 12)
else
_PostFeaturedComment(data: data, maxWidth: maxWidth)
.padding(left: 60, right: 12),
Padding( Padding(
padding: const EdgeInsets.only(top: 4), padding: const EdgeInsets.only(top: 4),
child: _PostReactionList( child: _PostReactionList(
@ -404,13 +412,24 @@ class PostShareImageWidget extends StatelessWidget {
child: AutoResizeUniversalImage( child: AutoResizeUniversalImage(
sn.getAttachmentUrl(data.preload!.thumbnail!.rid), sn.getAttachmentUrl(data.preload!.thumbnail!.rid),
fit: BoxFit.cover, fit: BoxFit.cover,
filterQuality: FilterQuality.high,
), ),
), ),
).padding(bottom: 8), ).padding(bottom: 8),
_PostContentHeader( Row(
data: data, children: [
isRelativeDate: false, _PostAvatar(
).padding(horizontal: 16, bottom: 8), data: data,
isCompact: false,
filterQuality: FilterQuality.high,
),
const Gap(12),
_PostContentHeader(
data: data,
isRelativeDate: false,
).padding(horizontal: 16, bottom: 8),
],
),
if (data.type == 'question') if (data.type == 'question')
_PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8), _PostQuestionHint(data: data).padding(horizontal: 16, bottom: 8),
_PostHeadline( _PostHeadline(
@ -803,7 +822,12 @@ class _PostHeadline extends StatelessWidget {
class _PostAvatar extends StatelessWidget { class _PostAvatar extends StatelessWidget {
final SnPost data; final SnPost data;
final bool isCompact; final bool isCompact;
const _PostAvatar({required this.data, required this.isCompact}); final FilterQuality? filterQuality;
const _PostAvatar({
required this.data,
required this.isCompact,
this.filterQuality,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -815,6 +839,7 @@ class _PostAvatar extends StatelessWidget {
return GestureDetector( return GestureDetector(
child: data.preload?.realm == null child: data.preload?.realm == null
? AccountImage( ? AccountImage(
filterQuality: filterQuality,
content: data.publisher.avatar, content: data.publisher.avatar,
radius: isCompact ? 12 : 20, radius: isCompact ? 12 : 20,
borderRadius: data.publisher.type == 1 ? (isCompact ? 4 : 8) : 20, borderRadius: data.publisher.type == 1 ? (isCompact ? 4 : 8) : 20,
@ -827,6 +852,7 @@ class _PostAvatar extends StatelessWidget {
: null, : null,
) )
: AccountImage( : AccountImage(
filterQuality: filterQuality,
content: data.preload!.realm!.avatar, content: data.preload!.realm!.avatar,
radius: isCompact ? 12 : 20, radius: isCompact ? 12 : 20,
borderRadius: isCompact ? 4 : 8, borderRadius: isCompact ? 4 : 8,
@ -1395,6 +1421,95 @@ class _PostTruncatedHint extends StatelessWidget {
} }
} }
class _PostCommentIntent extends StatefulWidget {
final SnPost data;
const _PostCommentIntent({required this.data});
@override
State<_PostCommentIntent> createState() => _PostCommentIntentState();
}
class _PostCommentIntentState extends State<_PostCommentIntent> {
bool _isBusy = false;
int? _totalCount;
final List<SnPost> _comments = List.empty(growable: true);
bool get _isAllLoaded =>
_totalCount != null && _comments.length >= _totalCount!;
Future<void> _fetchComments() async {
setState(() => _isBusy = true);
try {
final sn = context.read<SnNetworkProvider>();
final resp = await sn.client.get(
'/cgi/co/posts/${widget.data.id}/replies',
queryParameters: {
'take': 10,
'offset': _comments.length,
},
);
_totalCount = resp.data['count'];
_comments.addAll(
resp.data['data'].map((ele) => SnPost.fromJson(ele)).cast<SnPost>(),
);
} catch (err) {
if (!mounted) return;
context.showErrorDialog(err);
} finally {
setState(() => _isBusy = false);
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
if (_comments.isNotEmpty)
Card(
margin: EdgeInsets.zero,
child: Column(
spacing: 8,
children: [
for (final ele in _comments)
PostItem(
data: ele,
showExpandableComments: true,
maxWidth: double.infinity,
).padding(vertical: 8),
],
),
).padding(bottom: 8),
Row(
children: [
Transform.flip(
flipX: true,
child: const Icon(Symbols.comment, size: 20),
),
const Gap(4),
Text('postCommentsDetailed'.plural(widget.data.metric.replyCount)),
if (widget.data.metric.replyCount > 0 && !_isAllLoaded)
SizedBox(
width: 20,
height: 20,
child: IconButton(
visualDensity: VisualDensity(horizontal: -4, vertical: -4),
constraints: const BoxConstraints(),
padding: EdgeInsets.zero,
icon: const Icon(Symbols.expand_more, size: 18),
onPressed: _isBusy
? null
: () {
_fetchComments();
},
),
).padding(left: 8),
],
).opacity(0.75).padding(horizontal: 4),
],
);
}
}
class _PostFeaturedComment extends StatefulWidget { class _PostFeaturedComment extends StatefulWidget {
final SnPost data; final SnPost data;
final double? maxWidth; final double? maxWidth;