diff --git a/assets/translations/en.json b/assets/translations/en.json index a98d131..8e7329c 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -430,5 +430,6 @@ "unauthorizedDescription": "Login to explore the entire Solar Network.", "serviceStatus": "Service Status", "termRelated": "Related Terms", - "appDetails": "App Details" + "appDetails": "App Details", + "postRecommendation": "Highlight Posts" } diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 54b2bf0..745cbb2 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -35,7 +35,7 @@ "errorRequestForbidden": "被禁止的请求,您没有足够的权限去做那件事。", "errorRequestNotFound": "您正查找的资源无法被找到。", "errorRequestConnection": "网络连接错误,请检查您的网络状态或者检查我们的服务状态。", - "errorRequestUnknown": "位置请求错误,您可能想将此对话框截图并发送给我们。", + "errorRequestUnknown": "未知请求错误,您可能想将此对话框截图并发送给我们。", "unknown": "未知", "loading": "加载中…", "prev": "上一步", @@ -428,5 +428,6 @@ "unauthorizedDescription": "登陆以探索整个 Solar Network。", "serviceStatus": "服务状态", "termRelated": "相关条款", - "appDetails": "应用程序详情" + "appDetails": "应用程序详情", + "postRecommendation": "推荐帖子" } diff --git a/lib/providers/post.dart b/lib/providers/post.dart index efffe71..46d30ae 100644 --- a/lib/providers/post.dart +++ b/lib/providers/post.dart @@ -60,6 +60,14 @@ class SnPostContentProvider { return out; } + Future> listRecommendations() async { + final resp = await _sn.client.get('/cgi/co/recommendations'); + final out = _preloadRelatedDataInBatch( + List.from(resp.data.map((ele) => SnPost.fromJson(ele))), + ); + return out; + } + Future<(List, int)> listPosts({ int take = 10, int offset = 0, diff --git a/lib/screens/home.dart b/lib/screens/home.dart index 9911ec2..05b4d90 100644 --- a/lib/screens/home.dart +++ b/lib/screens/home.dart @@ -9,11 +9,14 @@ import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; import 'package:styled_widget/styled_widget.dart'; import 'package:flutter/material.dart'; +import 'package:surface/providers/post.dart'; import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/userinfo.dart'; import 'package:surface/types/check_in.dart'; +import 'package:surface/types/post.dart'; import 'package:surface/widgets/app_bar_leading.dart'; import 'package:surface/widgets/dialog.dart'; +import 'package:surface/widgets/post/post_item.dart'; class HomeScreenDashEntry { final String name; @@ -37,6 +40,12 @@ class HomeScreen extends StatefulWidget { class _HomeScreenState extends State { static const List kCards = [ + HomeScreenDashEntry( + name: 'dashEntryRecommendation', + cols: 2, + rows: 2, + child: _HomeDashRecommendationPostWidget(), + ), HomeScreenDashEntry( name: 'dashEntryCheckIn', child: _HomeDashCheckInWidget(), @@ -390,3 +399,75 @@ class _HomeDashNotificationWidgetState extends State<_HomeDashNotificationWidget ); } } + +class _HomeDashRecommendationPostWidget extends StatefulWidget { + const _HomeDashRecommendationPostWidget({super.key}); + + @override + State<_HomeDashRecommendationPostWidget> createState() => _HomeDashRecommendationPostWidgetState(); +} + +class _HomeDashRecommendationPostWidgetState extends State<_HomeDashRecommendationPostWidget> { + bool _isBusy = false; + List? _posts; + + Future _fetchRecommendationPosts() async { + setState(() => _isBusy = true); + try { + final pt = context.read(); + _posts = await pt.listRecommendations(); + } catch (err) { + if (!mounted) return; + context.showErrorDialog(err); + } finally { + setState(() => _isBusy = false); + } + } + + @override + void initState() { + super.initState(); + _fetchRecommendationPosts(); + } + + @override + Widget build(BuildContext context) { + if (_isBusy) { + return Card( + child: CircularProgressIndicator().center(), + ); + } + + return Card( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'postRecommendation', + style: Theme.of(context).textTheme.titleLarge, + ).tr().padding(horizontal: 20, top: 16, bottom: 8), + Expanded( + child: PageView.builder( + itemCount: _posts?.length ?? 0, + itemBuilder: (context, index) { + return SingleChildScrollView( + child: GestureDetector( + child: PostItem( + data: _posts![index], + showMenu: false, + ).padding(left: 8, right: 8, bottom: 8), + onTap: () { + GoRouter.of(context).pushNamed('postDetail', pathParameters: { + 'slug': _posts![index].id.toString(), + }); + }, + ), + ); + }, + ), + ), + ], + ), + ); + } +}