diff --git a/assets/translations/en-US.json b/assets/translations/en-US.json index 83a1e40..c907edd 100644 --- a/assets/translations/en-US.json +++ b/assets/translations/en-US.json @@ -37,6 +37,9 @@ "report": "Report", "repost": "Repost", "reply": "Reply", + "untitled": "Untitled", + "postDetail": "Post detail", + "postNoun": "Post", "fieldUsername": "Username", "fieldNickname": "Nickname", "fieldEmail": "Email address", @@ -84,6 +87,7 @@ "postRepostingNotice": "You're about to repost a post that posted {}.", "postReact": "React", "postComments": { + "zero": "Comment", "one": "{} comment", "other": "{} comments" }, diff --git a/assets/translations/zh-CN.json b/assets/translations/zh-CN.json index 8d016f4..73741b4 100644 --- a/assets/translations/zh-CN.json +++ b/assets/translations/zh-CN.json @@ -37,6 +37,9 @@ "report": "检举", "repost": "转帖", "reply": "回贴", + "untitled": "无题", + "postDetail": "帖子详情", + "postNoun": "帖子", "fieldUsername": "用户名", "fieldNickname": "显示名", "fieldEmail": "电子邮箱地址", @@ -84,6 +87,7 @@ "postRepostingNotice": "你正在转发由 {} 发布的帖子。", "postReact": "反应", "postComments": { + "zero": "评论", "one": "{} 条评论", "other": "{} 条评论" }, diff --git a/lib/router.dart b/lib/router.dart index 5335c67..5b2b6d7 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -8,8 +8,10 @@ import 'package:surface/screens/auth/login.dart'; import 'package:surface/screens/auth/register.dart'; import 'package:surface/screens/explore.dart'; import 'package:surface/screens/home.dart'; +import 'package:surface/screens/post/post_detail.dart'; import 'package:surface/screens/post/post_editor.dart'; import 'package:surface/screens/settings.dart'; +import 'package:surface/types/post.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart'; final appRouter = GoRouter( @@ -58,6 +60,14 @@ final appRouter = GoRouter( ), ), ), + GoRoute( + path: '/post/:slug', + name: 'postDetail', + builder: (context, state) => PostDetailScreen( + slug: state.pathParameters['slug']!, + preload: state.extra as SnPost?, + ), + ) ], ), ShellRoute( diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index fda1525..38a7f14 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -171,7 +171,16 @@ class _ExploreScreenState extends State { hasReachedMax: _postCount != null && _posts.length >= _postCount!, onFetchData: _fetchPosts, itemBuilder: (context, idx) { - return PostItem(data: _posts[idx]); + return GestureDetector( + child: PostItem(data: _posts[idx]), + onTap: () { + GoRouter.of(context).pushNamed( + 'postDetail', + pathParameters: {'slug': _posts[idx].id.toString()}, + extra: _posts[idx], + ); + }, + ); }, separatorBuilder: (context, index) => const Divider(), ) diff --git a/lib/screens/post/post_detail.dart b/lib/screens/post/post_detail.dart new file mode 100644 index 0000000..32c450a --- /dev/null +++ b/lib/screens/post/post_detail.dart @@ -0,0 +1,102 @@ +import 'dart:math' as math; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; +import 'package:styled_widget/styled_widget.dart'; +import 'package:surface/providers/sn_attachment.dart'; +import 'package:surface/providers/sn_network.dart'; +import 'package:surface/types/post.dart'; +import 'package:surface/widgets/dialog.dart'; +import 'package:surface/widgets/loading_indicator.dart'; +import 'package:surface/widgets/navigation/app_scaffold.dart'; +import 'package:surface/widgets/post/post_item.dart'; + +class PostDetailScreen extends StatefulWidget { + final String slug; + final SnPost? preload; + const PostDetailScreen({ + super.key, + required this.slug, + this.preload, + }); + + @override + State createState() => _PostDetailScreenState(); +} + +class _PostDetailScreenState extends State { + bool _isBusy = false; + + SnPost? _data; + + void _fetchPost() async { + setState(() => _isBusy = true); + + try { + final sn = context.read(); + final attach = context.read(); + final resp = await sn.client.get('/cgi/co/posts/${widget.slug}'); + if (!mounted) return; + final attachments = await attach.getMultiple( + resp.data['body']['attachments']?.cast() ?? [], + ); + if (!mounted) return; + setState(() { + _data = SnPost.fromJson(resp.data).copyWith( + preload: SnPostPreload( + attachments: attachments, + ), + ); + }); + } catch (err) { + context.showErrorDialog(err); + } finally { + setState(() => _isBusy = false); + } + } + + @override + void initState() { + super.initState(); + if (widget.preload != null) { + _data = widget.preload; + } + _fetchPost(); + } + + @override + Widget build(BuildContext context) { + return AppScaffold( + appBar: AppBar( + leading: BackButton( + onPressed: () { + if (GoRouter.of(context).canPop()) { + Navigator.pop(context); + } + GoRouter.of(context).replaceNamed('explore'); + }, + ), + flexibleSpace: Column( + children: [ + Text(_data?.body['title'] ?? 'postNoun'.tr()) + .textStyle(Theme.of(context).textTheme.titleLarge!) + .textColor(Colors.white), + Text('postDetail') + .tr() + .textColor(Colors.white.withAlpha((255 * 0.9).round())), + ], + ).padding(top: math.max(MediaQuery.of(context).padding.top, 8)), + ), + body: SingleChildScrollView( + child: Column( + children: [ + LoadingIndicator(isActive: _isBusy), + if (_data != null) PostItem(data: _data!), + ], + ), + ), + ); + } +} diff --git a/lib/screens/post/post_editor.dart b/lib/screens/post/post_editor.dart index 4a77f50..42cd753 100644 --- a/lib/screens/post/post_editor.dart +++ b/lib/screens/post/post_editor.dart @@ -1,3 +1,5 @@ +import 'dart:math' as math; + import 'package:collection/collection.dart'; import 'package:dio/dio.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; @@ -276,14 +278,14 @@ class _PostEditorScreenState extends State { ), flexibleSpace: Column( children: [ - Text(_title ?? 'Untitled') + Text(_title ?? 'untitled'.tr()) .textStyle(Theme.of(context).textTheme.titleLarge!) .textColor(Colors.white), Text(_kTitleMap[widget.mode]!) .tr() .textColor(Colors.white.withAlpha((255 * 0.9).round())), ], - ).padding(top: MediaQuery.of(context).padding.top), + ).padding(top: math.max(MediaQuery.of(context).padding.top, 8)), actions: [ IconButton( icon: const Icon(Symbols.tune), diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index a5bbc7f..93d568f 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -70,7 +70,7 @@ class _SettingsScreenState extends State { .bold() .fontSize(17) .tr() - .padding(horizontal: 20), + .padding(horizontal: 20, bottom: 4), if (!kIsWeb) ListTile( title: Text('settingsBackgroundImage').tr(), @@ -141,7 +141,7 @@ class _SettingsScreenState extends State { .bold() .fontSize(17) .tr() - .padding(horizontal: 20), + .padding(horizontal: 20, bottom: 4), TextField( controller: _serverUrlController, decoration: InputDecoration( diff --git a/lib/theme.dart b/lib/theme.dart index cf9447f..10fdec7 100644 --- a/lib/theme.dart +++ b/lib/theme.dart @@ -23,15 +23,22 @@ Future createAppTheme( }) async { final prefs = await SharedPreferences.getInstance(); + final colorScheme = ColorScheme.fromSeed( + seedColor: Colors.indigo, + brightness: brightness, + ); + return ThemeData( useMaterial3: useMaterial3 ?? (prefs.getBool(kMaterialYouToggleStoreKey) ?? false), - colorScheme: ColorScheme.fromSeed( - seedColor: Colors.indigo, - brightness: brightness, - ), + colorScheme: colorScheme, brightness: brightness, - iconTheme: const IconThemeData(fill: 0, weight: 400, opticalSize: 20), + iconTheme: IconThemeData( + fill: 0, + weight: 400, + opticalSize: 20, + color: colorScheme.onSurface, + ), scaffoldBackgroundColor: Colors.transparent, ); } diff --git a/pubspec.lock b/pubspec.lock index 02018a6..ea5ef1d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -199,7 +199,7 @@ packages: source: hosted version: "4.10.1" collection: - dependency: transitive + dependency: "direct main" description: name: collection sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf diff --git a/pubspec.yaml b/pubspec.yaml index c907290..83944fb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,8 +30,6 @@ environment: dependencies: flutter: sdk: flutter - collection: - sdk: flutter # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. @@ -74,6 +72,7 @@ dependencies: photo_view: ^0.15.0 shared_preferences: ^2.3.3 path_provider: ^2.1.5 + collection: ^1.19.0 dev_dependencies: flutter_test: