From d654c162e35bd5fec216575bb92077691d71ebf7 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 9 Mar 2025 00:49:13 +0800 Subject: [PATCH] :sparkles: Shuffle post --- assets/translations/en-US.json | 3 +- assets/translations/zh-CN.json | 3 +- assets/translations/zh-HK.json | 3 +- assets/translations/zh-TW.json | 3 +- lib/providers/post.dart | 5 +- lib/router.dart | 6 ++ lib/screens/explore.dart | 4 +- lib/screens/post/post_shuffle.dart | 132 +++++++++++++++++++++++++++++ 8 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 lib/screens/post/post_shuffle.dart diff --git a/assets/translations/en-US.json b/assets/translations/en-US.json index fe34bb0..6660269 100644 --- a/assets/translations/en-US.json +++ b/assets/translations/en-US.json @@ -771,5 +771,6 @@ "messageUnablePreviewEncrypted": "Unable preview encrypted message", "postViewInGlobalDescription": "Do not view the post in the specific realm.", "postDraftSaved": "The draft has been saved.", - "postDraftBox": "Draft Box" + "postDraftBox": "Draft Box", + "postShuffle": "Read Randomly" } diff --git a/assets/translations/zh-CN.json b/assets/translations/zh-CN.json index 3580333..afc0b6f 100644 --- a/assets/translations/zh-CN.json +++ b/assets/translations/zh-CN.json @@ -769,5 +769,6 @@ "messageUnablePreviewEncrypted": "无法预览加密消息", "postViewInGlobalDescription": "不查看特定领域的帖子。", "postDraftSaved": "已保存为草稿。", - "postDraftBox": "草稿箱" + "postDraftBox": "草稿箱", + "postShuffle": "随便看看" } diff --git a/assets/translations/zh-HK.json b/assets/translations/zh-HK.json index 7a81952..cb55561 100644 --- a/assets/translations/zh-HK.json +++ b/assets/translations/zh-HK.json @@ -769,5 +769,6 @@ "messageUnablePreviewEncrypted": "無法預覽加密消息", "postViewInGlobalDescription": "不查看特定領域的帖子。", "postDraftSaved": "已保存為草稿。", - "postDraftBox": "草稿箱" + "postDraftBox": "草稿箱", + "postShuffle": "隨便看看" } diff --git a/assets/translations/zh-TW.json b/assets/translations/zh-TW.json index 5ff276d..4ae6a8f 100644 --- a/assets/translations/zh-TW.json +++ b/assets/translations/zh-TW.json @@ -769,5 +769,6 @@ "messageUnablePreviewEncrypted": "無法預覽加密消息", "postViewInGlobalDescription": "不查看特定領域的帖子。", "postDraftSaved": "已保存為草稿。", - "postDraftBox": "草稿箱" + "postDraftBox": "草稿箱", + "postShuffle": "隨便看看" } diff --git a/lib/providers/post.dart b/lib/providers/post.dart index c66b04c..95a16c7 100644 --- a/lib/providers/post.dart +++ b/lib/providers/post.dart @@ -155,9 +155,12 @@ class SnPostContentProvider { String? realm, String? channel, bool isDraft = false, + bool isShuffle = false, }) async { final resp = await _sn.client.get( - '/cgi/co/posts${isDraft ? '/drafts' : ''}', + isShuffle + ? '/cgi/co/recommendations/shuffle' + : '/cgi/co/posts${isDraft ? '/drafts' : ''}', queryParameters: { 'take': take, 'offset': offset, diff --git a/lib/router.dart b/lib/router.dart index 3a13d1a..f041d1a 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -30,6 +30,7 @@ import 'package:surface/screens/notification.dart'; import 'package:surface/screens/post/post_detail.dart'; import 'package:surface/screens/post/post_draft.dart'; import 'package:surface/screens/post/post_editor.dart'; +import 'package:surface/screens/post/post_shuffle.dart'; import 'package:surface/screens/post/publisher_page.dart'; import 'package:surface/screens/post/post_search.dart'; import 'package:surface/screens/realm.dart'; @@ -88,6 +89,11 @@ final _appRoutes = [ extraProps: state.extra as PostEditorExtra?, ), ), + GoRoute( + path: '/shuffle', + name: 'postShuffle', + builder: (context, state) => const PostShuffleScreen(), + ), GoRoute( path: '/search', name: 'postSearch', diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index b5af07c..be773ab 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -225,7 +225,9 @@ class _ExploreScreenState extends State children: [ IconButton( icon: const Icon(Symbols.shuffle), - onPressed: () {}, + onPressed: () { + GoRouter.of(context).pushNamed('postShuffle'); + }, ), Expanded( child: Center( diff --git a/lib/screens/post/post_shuffle.dart b/lib/screens/post/post_shuffle.dart new file mode 100644 index 0000000..2c65072 --- /dev/null +++ b/lib/screens/post/post_shuffle.dart @@ -0,0 +1,132 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_card_swiper/flutter_card_swiper.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:provider/provider.dart'; +import 'package:styled_widget/styled_widget.dart'; +import 'package:surface/providers/post.dart'; +import 'package:surface/types/post.dart'; +import 'package:surface/widgets/dialog.dart'; +import 'package:surface/widgets/navigation/app_scaffold.dart'; +import 'package:surface/widgets/post/post_item.dart'; + +class PostShuffleScreen extends StatefulWidget { + const PostShuffleScreen({super.key}); + + @override + State createState() => _PostShuffleScreenState(); +} + +class _PostShuffleScreenState extends State { + late final CardSwiperController _cardController = CardSwiperController(); + + bool _isBusy = false; + final List _posts = List.empty(growable: true); + + Future _fetchPosts() async { + _posts.clear(); + setState(() => _isBusy = true); + try { + final pt = context.read(); + final result = await pt.listPosts( + take: 10, + offset: _posts.length, + isShuffle: true, + ); + _posts.addAll(result.$1); + } catch (err) { + if (!mounted) return; + context.showErrorDialog(err); + } finally { + setState(() => _isBusy = false); + } + } + + @override + void initState() { + super.initState(); + _fetchPosts(); + } + + @override + void dispose() { + super.dispose(); + _cardController.dispose(); + } + + @override + Widget build(BuildContext context) { + return AppScaffold( + appBar: AppBar( + title: Text('postShuffle').tr(), + ), + body: Stack( + children: [ + Column( + children: [ + if (_isBusy || _posts.isEmpty) + const Expanded( + child: Center( + child: CircularProgressIndicator(), + ), + ) + else + Expanded( + child: CardSwiper( + controller: _cardController, + isLoop: false, + padding: EdgeInsets.zero, + cardsCount: _posts.length, + cardBuilder: (context, idx, _, __) { + final ele = _posts[idx]; + return SingleChildScrollView( + child: Center( + child: OpenablePostItem( + key: ValueKey(ele), + data: ele, + maxWidth: 640, + onChanged: (ele) { + _posts[idx] = ele; + setState(() {}); + }, + onDeleted: () { + _fetchPosts(); + }, + ).padding( + all: 24, + bottom: + MediaQuery.of(context).padding.bottom + 16 + 50, + ), + ), + ); + }, + onEnd: () { + _fetchPosts(); + }, + ), + ), + ], + ), + if (!_isBusy && _posts.isNotEmpty) + Positioned( + bottom: MediaQuery.of(context).padding.bottom + 16, + left: 16, + right: 16, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton.filled( + icon: const Icon(Symbols.next_plan), + color: Theme.of(context).colorScheme.onPrimary, + onPressed: () { + _cardController.swipe(CardSwiperDirection.right); + }, + ), + ], + ), + ), + ], + ), + ); + } +}