From 4552dfd3f3a8f6a62ee5e56076d101d8d7cdc9f8 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Fri, 26 Jul 2024 18:23:51 +0800 Subject: [PATCH] :sparkles: Pinned post & Total vote counts --- lib/models/post.dart | 6 ++ lib/screens/account/profile_page.dart | 97 ++++++++++++++++++++++++--- lib/translations/en_us.dart | 5 ++ lib/translations/zh_cn.dart | 5 ++ lib/widgets/feed/feed_list.dart | 5 ++ lib/widgets/posts/post_action.dart | 18 ++++- lib/widgets/posts/post_item.dart | 6 ++ 7 files changed, 133 insertions(+), 9 deletions(-) diff --git a/lib/models/post.dart b/lib/models/post.dart index 77dcca2..9d321ce 100755 --- a/lib/models/post.dart +++ b/lib/models/post.dart @@ -18,6 +18,7 @@ class Post { Post? repostTo; Realm? realm; DateTime? publishedAt; + DateTime? pinnedAt; bool? isDraft; int authorId; Account author; @@ -39,6 +40,7 @@ class Post { required this.repostTo, required this.realm, required this.publishedAt, + required this.pinnedAt, required this.isDraft, required this.authorId, required this.author, @@ -70,6 +72,9 @@ class Post { publishedAt: json['published_at'] != null ? DateTime.parse(json['published_at']) : null, + pinnedAt: json['pinned_at'] != null + ? DateTime.parse(json['pinned_at']) + : null, isDraft: json['is_draft'], authorId: json['author_id'], author: Account.fromJson(json['author']), @@ -93,6 +98,7 @@ class Post { 'repost_to': repostTo?.toJson(), 'realm': realm?.toJson(), 'published_at': publishedAt?.toIso8601String(), + 'pinned_at': pinnedAt?.toIso8601String(), 'is_draft': isDraft, 'author_id': authorId, 'author': author.toJson(), diff --git a/lib/screens/account/profile_page.dart b/lib/screens/account/profile_page.dart index a0a904a..57775a3 100644 --- a/lib/screens/account/profile_page.dart +++ b/lib/screens/account/profile_page.dart @@ -6,17 +6,18 @@ import 'package:solian/exts.dart'; import 'package:solian/models/account.dart'; import 'package:solian/models/attachment.dart'; import 'package:solian/models/pagination.dart'; +import 'package:solian/models/post.dart'; import 'package:solian/screens/account/notification.dart'; import 'package:solian/services.dart'; import 'package:solian/theme.dart'; import 'package:solian/widgets/account/account_avatar.dart'; import 'package:solian/widgets/app_bar_leading.dart'; +import 'package:solian/widgets/attachments/attachment_list.dart'; import 'package:solian/widgets/current_state_action.dart'; import 'package:solian/widgets/feed/feed_list.dart'; +import 'package:solian/widgets/posts/post_list.dart'; import 'package:solian/widgets/sized_container.dart'; -import '../../widgets/attachments/attachment_list.dart'; - class AccountProfilePage extends StatefulWidget { final String name; @@ -35,20 +36,44 @@ class _AccountProfilePageState extends State { bool _showMature = false; Account? _userinfo; + List _pinnedPosts = List.empty(); + int _totalUpvote = 0, _totalDownvote = 0; Future getUserinfo() async { setState(() => _isBusy = true); - final client = ServiceFinder.configureClient('auth'); - final resp = await client.get('/users/${widget.name}'); - if (resp.statusCode == 200) { - _userinfo = Account.fromJson(resp.body); - setState(() => _isBusy = false); - } else { + var client = ServiceFinder.configureClient('auth'); + var resp = await client.get('/users/${widget.name}'); + if (resp.statusCode != 200) { context.showErrorDialog(resp.bodyString).then((_) { Navigator.pop(context); }); + } else { + _userinfo = Account.fromJson(resp.body); } + + client = ServiceFinder.configureClient('interactive'); + resp = await client.get('/users/${widget.name}'); + if (resp.statusCode != 200) { + context.showErrorDialog(resp.bodyString).then((_) { + Navigator.pop(context); + }); + } else { + _totalUpvote = resp.body['total_upvote']; + _totalDownvote = resp.body['total_downvote']; + } + + resp = await client.get('/users/${widget.name}/pin'); + if (resp.statusCode != 200) { + context.showErrorDialog(resp.bodyString).then((_) { + Navigator.pop(context); + }); + } else { + _pinnedPosts = + resp.body.map((x) => Post.fromJson(x)).toList().cast(); + } + + setState(() => _isBusy = false); } @override @@ -144,12 +169,68 @@ class _AccountProfilePageState extends State { RefreshIndicator( onRefresh: () => _postController.reloadAllOver(), child: CustomScrollView(slivers: [ + SliverToBoxAdapter( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Column( + children: [ + Text( + 'totalUpvote'.tr, + style: Theme.of(context).textTheme.bodySmall, + ), + Text( + _totalUpvote.toString(), + style: Theme.of(context).textTheme.bodyLarge, + ), + ], + ), + Column( + children: [ + Text( + 'totalDownvote'.tr, + style: Theme.of(context).textTheme.bodySmall, + ), + Text( + _totalDownvote.toString(), + style: Theme.of(context).textTheme.bodyLarge, + ), + ], + ), + ], + ).paddingOnly(top: 16, bottom: 12), + ), + const SliverToBoxAdapter( + child: Divider(thickness: 0.3, height: 0.3), + ), + SliverList.separated( + itemCount: _pinnedPosts.length, + itemBuilder: (context, idx) { + final element = _pinnedPosts[idx]; + return Material( + color: + Theme.of(context).colorScheme.surfaceContainerLow, + child: PostListEntryWidget( + item: element, + isClickable: true, + isNestedClickable: true, + isShowEmbed: true, + onUpdate: () { + _postController.reloadAllOver(); + }, + ), + ); + }, + separatorBuilder: (context, idx) => + const Divider(thickness: 0.3, height: 0.3), + ), if (_userinfo == null) const SliverFillRemaining( child: Center(child: CircularProgressIndicator()), ), if (_userinfo != null) FeedListWidget( + isPinned: false, controller: _postController.pagingController, ), ]), diff --git a/lib/translations/en_us.dart b/lib/translations/en_us.dart index f0abf89..878acab 100644 --- a/lib/translations/en_us.dart +++ b/lib/translations/en_us.dart @@ -86,6 +86,11 @@ const messagesEnglish = { 'notifyAllRead': 'Mark all as read', 'notifyEmpty': 'All notifications read', 'notifyEmptyCaption': 'It seems like nothing happened recently', + 'totalUpvote': 'Upvote', + 'totalDownvote': 'Downvote', + 'pinPost': 'Pin this post', + 'unpinPost': 'Unpin this post', + 'postPinned': 'Pinned', 'postListNews': 'News', 'postListShuffle': 'Random', 'postEditor': 'Create new post', diff --git a/lib/translations/zh_cn.dart b/lib/translations/zh_cn.dart index cfbcc59..c976c74 100644 --- a/lib/translations/zh_cn.dart +++ b/lib/translations/zh_cn.dart @@ -80,6 +80,11 @@ const simplifiedChineseMessages = { 'notifyAllRead': '已读所有通知', 'notifyEmpty': '通知箱为空', 'notifyEmptyCaption': '看起来最近没发生什么呢', + 'totalUpvote': '获顶数', + 'totalDownvote': '获踩数', + 'pinPost': '置顶本帖', + 'unpinPost': '取消置顶本帖', + 'postPinned': '已置顶', 'postEditor': '发个帖子', 'articleEditor': '撰写文章', 'articleDetail': '文章详情', diff --git a/lib/widgets/feed/feed_list.dart b/lib/widgets/feed/feed_list.dart index dd4643a..0af8357 100644 --- a/lib/widgets/feed/feed_list.dart +++ b/lib/widgets/feed/feed_list.dart @@ -7,6 +7,7 @@ class FeedListWidget extends StatelessWidget { final bool isShowEmbed; final bool isClickable; final bool isNestedClickable; + final bool isPinned; final PagingController controller; const FeedListWidget({ @@ -15,6 +16,7 @@ class FeedListWidget extends StatelessWidget { this.isShowEmbed = true, this.isClickable = true, this.isNestedClickable = true, + this.isPinned = true, }); @override @@ -24,6 +26,9 @@ class FeedListWidget extends StatelessWidget { pagingController: controller, builderDelegate: PagedChildBuilderDelegate( itemBuilder: (context, item, index) { + if (item.pinnedAt != null && !isPinned) { + return const SizedBox(); + } return PostListEntryWidget( isShowEmbed: isShowEmbed, isNestedClickable: isNestedClickable, diff --git a/lib/widgets/posts/post_action.dart b/lib/widgets/posts/post_action.dart index ec5b531..270ec19 100644 --- a/lib/widgets/posts/post_action.dart +++ b/lib/widgets/posts/post_action.dart @@ -31,7 +31,8 @@ class _PostActionState extends State { setState(() => _isBusy = true); setState(() { - _canModifyContent = auth.userProfile.value!['id'] == widget.item.author.externalId; + _canModifyContent = + auth.userProfile.value!['id'] == widget.item.author.externalId; _isBusy = false; }); } @@ -98,6 +99,21 @@ class _PostActionState extends State { if (_canModifyContent && !widget.noReact) const Divider(thickness: 0.3, height: 0.3) .paddingSymmetric(vertical: 16), + if (_canModifyContent) + ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + leading: const Icon(Icons.push_pin), + title: Text( + widget.item.pinnedAt == null + ? 'pinPost'.tr + : 'unpinPost'.tr, + ), + onTap: () async { + final client = Get.find().configureClient('interactive'); + await client.post('/posts/${widget.item.id}/pin', {}); + Navigator.pop(context, true); + }, + ), if (_canModifyContent) ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 24), diff --git a/lib/widgets/posts/post_item.dart b/lib/widgets/posts/post_item.dart index e1c8f06..4aa4eb0 100644 --- a/lib/widgets/posts/post_item.dart +++ b/lib/widgets/posts/post_item.dart @@ -112,6 +112,12 @@ class _PostItemState extends State { ), )); } + if (widget.item.pinnedAt != null) { + widgets.add(Text( + 'postPinned'.tr, + style: TextStyle(fontSize: 12, color: _unFocusColor), + )); + } if (widgets.isEmpty) { return const SizedBox();