diff --git a/assets/translations/en.json b/assets/translations/en.json index ebc2ae5..664ddfa 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -55,6 +55,13 @@ "untitled": "Untitled", "postDetail": "Post detail", "postNoun": "Post", + "postReadMore": "Read more", + "postReadEstimate": "Est read time {}", + "postTotalLength": { + "zero": "No character", + "one": "{} character", + "other": "{} characters" + }, "fieldUsername": "Username", "fieldNickname": "Nickname", "fieldEmail": "Email address", diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 8f6083b..8665731 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -55,6 +55,13 @@ "untitled": "无题", "postDetail": "帖子详情", "postNoun": "帖子", + "postReadMore": "阅读更多", + "postReadEstimate": "预计花费 {} 阅读", + "postTotalLength": { + "zero": "没有内容", + "one": "总计 {} 字", + "other": "总计 {} 字" + }, "fieldUsername": "用户名", "fieldNickname": "显示名", "fieldEmail": "电子邮箱地址", diff --git a/lib/widgets/attachment/attachment_item.dart b/lib/widgets/attachment/attachment_item.dart index fb3037d..e412220 100644 --- a/lib/widgets/attachment/attachment_item.dart +++ b/lib/widgets/attachment/attachment_item.dart @@ -109,35 +109,38 @@ class _AttachmentItemSensitiveBlurState child: Container( color: Colors.black.withOpacity(0.5), alignment: Alignment.center, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Icon( - Symbols.visibility_off, - color: Colors.white, - size: 32, - ), - const Gap(8), - Text('sensitiveContent') - .tr() - .fontSize(20) - .textColor(Colors.white) - .bold(), - Text('sensitiveContentDescription') - .tr() - .fontSize(14) - .textColor(Colors.white.withOpacity(0.8)), - const Gap(16), - InkWell( - child: Text('sensitiveContentReveal') + child: Container( + constraints: const BoxConstraints(maxWidth: 280), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Symbols.visibility_off, + color: Colors.white, + size: 32, + ), + const Gap(8), + Text('sensitiveContent') .tr() - .textColor(Colors.white), - onTap: () { - setState(() => _doesShow = !_doesShow); - }, - ), - ], - ), + .fontSize(20) + .textColor(Colors.white) + .bold(), + Text('sensitiveContentDescription') + .tr() + .fontSize(14) + .textColor(Colors.white.withOpacity(0.8)), + const Gap(16), + InkWell( + child: Text('sensitiveContentReveal') + .tr() + .textColor(Colors.white), + onTap: () { + setState(() => _doesShow = !_doesShow); + }, + ), + ], + ), + ).center(), ), ), ) diff --git a/lib/widgets/post/post_item.dart b/lib/widgets/post/post_item.dart index b41212f..19f45db 100644 --- a/lib/widgets/post/post_item.dart +++ b/lib/widgets/post/post_item.dart @@ -41,15 +41,24 @@ class PostItem extends StatelessWidget { Container( constraints: BoxConstraints(maxWidth: maxWidth ?? double.infinity), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ _PostContentHeader(data: data) .padding(horizontal: 12, vertical: 8), + if (data.body['title'] != null || + data.body['description'] != null) + _PostHeadline(data: data).padding(horizontal: 16, bottom: 8), _PostContentBody(data: data.body) .padding(horizontal: 16, bottom: 6), if (data.repostTo != null) _PostQuoteContent(child: data.repostTo!).padding( horizontal: 12, ), + if (data.body['content_truncated'] == true) + _PostTruncatedHint(data: data).padding( + horizontal: 16, + vertical: 4, + ), ], ), ), @@ -69,7 +78,7 @@ class PostItem extends StatelessWidget { showComments: showComments, showReactions: showReactions, onChanged: _onChanged, - ).padding(left: 12, right: 18), + ).padding(left: 8, right: 14), ], ), ), @@ -175,6 +184,30 @@ class _PostBottomAction extends StatelessWidget { } } +class _PostHeadline extends StatelessWidget { + final SnPost data; + const _PostHeadline({super.key, required this.data}); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (data.body['title'] != null) + Text( + data.body['title'], + style: Theme.of(context).textTheme.titleMedium, + ), + if (data.body['description'] != null) + Text( + data.body['description'], + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ); + } +} + class _PostContentHeader extends StatelessWidget { final SnPost data; final bool isCompact; @@ -351,3 +384,49 @@ class _PostQuoteContent extends StatelessWidget { ); } } + +class _PostTruncatedHint extends StatelessWidget { + final SnPost data; + const _PostTruncatedHint({super.key, required this.data}); + + static const int kHumanReadSpeed = 238; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + if (data.body['content_length'] != null) + Row( + children: [ + const Icon(Symbols.timer, size: 20), + const Gap(4), + Text('postReadEstimate').tr(args: [ + '${Duration( + seconds: ((data.body['content_length'] as num).toDouble() / + kHumanReadSpeed) + .round(), + ).inSeconds}s', + ]), + ], + ).padding(right: 12), + if (data.body['content_length'] != null) + Row( + children: [ + const Icon(Symbols.height, size: 20), + const Gap(4), + Text( + 'postTotalLength'.plural(data.body['content_length']), + ).padding(right: 12) + ], + ), + Row( + children: [ + const Icon(Symbols.unfold_more, size: 20), + const Gap(4), + Text('postReadMore').tr(), + ], + ) + ], + ).opacity(0.75); + } +}