Post detail

This commit is contained in:
LittleSheep 2024-05-25 13:19:16 +08:00
parent f376603482
commit daee3e8074
8 changed files with 147 additions and 14 deletions

View File

@ -97,6 +97,7 @@ class AuthProvider extends GetConnect {
); );
Get.find<AccountProvider>().connect(); Get.find<AccountProvider>().connect();
Get.find<AccountProvider>().notifyPrefetch();
return credentials!; return credentials!;
} }
@ -105,6 +106,8 @@ class AuthProvider extends GetConnect {
_cacheUserProfileResponse = null; _cacheUserProfileResponse = null;
Get.find<AccountProvider>().disconnect(); Get.find<AccountProvider>().disconnect();
Get.find<AccountProvider>().notifications.clear();
Get.find<AccountProvider>().notificationUnread.value = 0;
storage.deleteAll(); storage.deleteAll();
} }

View File

@ -1,11 +1,27 @@
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:solian/services.dart'; import 'package:solian/services.dart';
class PostExploreProvider extends GetConnect { class PostProvider extends GetConnect {
@override @override
void onInit() { void onInit() {
httpClient.baseUrl = ServiceFinder.services['interactive']; httpClient.baseUrl = ServiceFinder.services['interactive'];
} }
Future<Response> listPost(int page) => get('/api/feed?take=${10}&offset=$page'); Future<Response> listPost(int page) async {
final resp = await get('/api/feed?take=${10}&offset=$page');
if (resp.statusCode != 200) {
throw Exception(resp.body);
}
return resp;
}
Future<Response> getPost(String alias) async {
final resp = await get('/api/posts/$alias');
if (resp.statusCode != 200) {
throw Exception(resp.body);
}
return resp;
}
} }

View File

@ -3,6 +3,7 @@ import 'package:solian/screens/account.dart';
import 'package:solian/screens/account/friend.dart'; import 'package:solian/screens/account/friend.dart';
import 'package:solian/screens/account/personalize.dart'; import 'package:solian/screens/account/personalize.dart';
import 'package:solian/screens/contact.dart'; import 'package:solian/screens/contact.dart';
import 'package:solian/screens/posts/post_detail.dart';
import 'package:solian/screens/social.dart'; import 'package:solian/screens/social.dart';
import 'package:solian/screens/posts/publish.dart'; import 'package:solian/screens/posts/publish.dart';
import 'package:solian/shells/basic_shell.dart'; import 'package:solian/shells/basic_shell.dart';
@ -32,6 +33,19 @@ abstract class AppRouter {
), ),
], ],
), ),
ShellRoute(
builder: (context, state, child) =>
BasicShell(state: state, child: child),
routes: [
GoRoute(
path: '/posts/:alias',
name: 'postDetail',
builder: (context, state) => PostDetailScreen(
alias: state.pathParameters['alias']!,
),
),
],
),
ShellRoute( ShellRoute(
builder: (context, state, child) => builder: (context, state, child) =>
BasicShell(state: state, child: child), BasicShell(state: state, child: child),

View File

@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:solian/exts.dart';
import 'package:solian/models/post.dart';
import 'package:solian/providers/content/post_explore.dart';
import 'package:solian/widgets/posts/post_item.dart';
class PostDetailScreen extends StatefulWidget {
final String alias;
const PostDetailScreen({super.key, required this.alias});
@override
State<PostDetailScreen> createState() => _PostDetailScreenState();
}
class _PostDetailScreenState extends State<PostDetailScreen> {
Post? item;
Future<Post?> getDetail() async {
final PostProvider provider = Get.find();
try {
final resp = await provider.getPost(widget.alias);
item = Post.fromJson(resp.body);
} catch (e) {
context.showErrorDialog(e).then((_) => Navigator.pop(context));
}
return item;
}
@override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context).colorScheme.surface,
child: FutureBuilder(
future: getDetail(),
builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == null) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Column(
children: [
PostItem(
item: item!,
isClickable: true,
isShowReply: false,
),
],
);
},
),
);
}
}

View File

@ -23,10 +23,13 @@ class _SocialScreenState extends State<SocialScreen> {
PagingController(firstPageKey: 0); PagingController(firstPageKey: 0);
getPosts(int pageKey) async { getPosts(int pageKey) async {
final PostExploreProvider provider = Get.find(); final PostProvider provider = Get.find();
final resp = await provider.listPost(pageKey);
if (resp.statusCode != 200) { Response resp;
_pagingController.error = resp.bodyString; try {
resp = await provider.listPost(pageKey);
} catch (e) {
_pagingController.error = e;
return; return;
} }
@ -41,7 +44,7 @@ class _SocialScreenState extends State<SocialScreen> {
@override @override
void initState() { void initState() {
Get.lazyPut(() => PostExploreProvider()); Get.lazyPut(() => PostProvider());
super.initState(); super.initState();
_pagingController.addPageRequestListener(getPosts); _pagingController.addPageRequestListener(getPosts);
@ -104,11 +107,17 @@ class _SocialScreenState extends State<SocialScreen> {
child: PostItem( child: PostItem(
key: Key('p${item.alias}'), key: Key('p${item.alias}'),
item: item, item: item,
isClickable: true,
).paddingSymmetric( ).paddingSymmetric(
vertical: vertical:
(item.attachments?.isEmpty ?? false) ? 8 : 0, (item.attachments?.isEmpty ?? false) ? 8 : 0,
), ),
onTap: () {}, onTap: () {
AppRouter.instance.pushNamed(
'postDetail',
pathParameters: {'alias': item.alias},
);
},
onLongPress: () { onLongPress: () {
showModalBottomSheet( showModalBottomSheet(
useRootNavigator: true, useRootNavigator: true,

View File

@ -62,6 +62,7 @@ class SolianMessages extends Translations {
'notifyEmpty': 'All notifications read', 'notifyEmpty': 'All notifications read',
'notifyEmptyCaption': 'It seems like nothing happened recently', 'notifyEmptyCaption': 'It seems like nothing happened recently',
'postAction': 'Post', 'postAction': 'Post',
'postDetail': 'Post',
'postPublishing': 'Post a post', 'postPublishing': 'Post a post',
'postIdentityNotify': 'You will post this post as', 'postIdentityNotify': 'You will post this post as',
'postContentPlaceholder': 'What\'s happened?!', 'postContentPlaceholder': 'What\'s happened?!',
@ -140,6 +141,7 @@ class SolianMessages extends Translations {
'notifyEmpty': '通知箱为空', 'notifyEmpty': '通知箱为空',
'notifyEmptyCaption': '看起来最近没发生什么呢', 'notifyEmptyCaption': '看起来最近没发生什么呢',
'postAction': '发表', 'postAction': '发表',
'postDetail': '帖子详情',
'postPublishing': '发表帖子', 'postPublishing': '发表帖子',
'postIdentityNotify': '你将会以本身份发表帖子', 'postIdentityNotify': '你将会以本身份发表帖子',
'postContentPlaceholder': '发生什么事了?!', 'postContentPlaceholder': '发生什么事了?!',

View File

@ -4,6 +4,7 @@ import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get_utils/get_utils.dart'; import 'package:get/get_utils/get_utils.dart';
import 'package:solian/models/post.dart'; import 'package:solian/models/post.dart';
import 'package:solian/router.dart';
import 'package:solian/widgets/account/account_avatar.dart'; import 'package:solian/widgets/account/account_avatar.dart';
import 'package:solian/widgets/attachments/attachment_list.dart'; import 'package:solian/widgets/attachments/attachment_list.dart';
import 'package:solian/widgets/posts/post_quick_action.dart'; import 'package:solian/widgets/posts/post_quick_action.dart';
@ -11,14 +12,18 @@ import 'package:timeago/timeago.dart' show format;
class PostItem extends StatefulWidget { class PostItem extends StatefulWidget {
final Post item; final Post item;
final bool isClickable;
final bool isCompact; final bool isCompact;
final bool isReactable; final bool isReactable;
final bool isShowReply;
const PostItem({ const PostItem({
super.key, super.key,
required this.item, required this.item,
this.isClickable = false,
this.isCompact = false, this.isCompact = false,
this.isReactable = true, this.isReactable = true,
this.isShowReply = true,
}); });
@override @override
@ -158,9 +163,31 @@ class _PostItemState extends State<PostItem> {
padding: const EdgeInsets.all(0), padding: const EdgeInsets.all(0),
).paddingOnly(left: 12, right: 8), ).paddingOnly(left: 12, right: 8),
if (widget.item.replyTo != null) if (widget.item.replyTo != null)
buildReply(context).paddingOnly(top: 4), GestureDetector(
child: buildReply(context).paddingOnly(top: 4),
onTap: () {
if (!widget.isClickable) return;
AppRouter.instance.pushNamed(
'postDetail',
pathParameters: {
'alias': widget.item.replyTo!.alias,
},
);
},
),
if (widget.item.repostTo != null) if (widget.item.repostTo != null)
buildRepost(context).paddingOnly(top: 4), GestureDetector(
child: buildRepost(context).paddingOnly(top: 4),
onTap: () {
if (!widget.isClickable) return;
AppRouter.instance.pushNamed(
'postDetail',
pathParameters: {
'alias': widget.item.repostTo!.alias,
},
);
},
),
], ],
), ),
) )
@ -173,6 +200,7 @@ class _PostItemState extends State<PostItem> {
), ),
AttachmentList(attachmentsId: item.attachments ?? List.empty()), AttachmentList(attachmentsId: item.attachments ?? List.empty()),
PostQuickAction( PostQuickAction(
isShowReply: widget.isShowReply,
isReactable: widget.isReactable, isReactable: widget.isReactable,
item: widget.item, item: widget.item,
onReact: (symbol, changes) { onReact: (symbol, changes) {

View File

@ -10,11 +10,13 @@ import 'package:solian/widgets/posts/post_reaction.dart';
class PostQuickAction extends StatefulWidget { class PostQuickAction extends StatefulWidget {
final Post item; final Post item;
final bool isReactable; final bool isReactable;
final bool isShowReply;
final void Function(String symbol, int num) onReact; final void Function(String symbol, int num) onReact;
const PostQuickAction({ const PostQuickAction({
super.key, super.key,
required this.item, required this.item,
this.isShowReply = true,
this.isReactable = true, this.isReactable = true,
required this.onReact, required this.onReact,
}); });
@ -93,17 +95,17 @@ class _PostQuickActionState extends State<PostQuickAction> {
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
if (widget.isReactable) if (widget.isReactable && widget.isShowReply)
ActionChip( ActionChip(
avatar: const Icon(Icons.comment), avatar: const Icon(Icons.comment),
label: Text(widget.item.replyCount.toString()), label: Text(widget.item.replyCount.toString()),
visualDensity: density, visualDensity: density,
onPressed: () {}, onPressed: () {},
), ),
if (widget.isReactable) if (widget.isReactable && widget.isShowReply)
const VerticalDivider( const VerticalDivider(
thickness: 0.3, width: 0.3, indent: 8, endIndent: 8) thickness: 0.3, width: 0.3, indent: 8, endIndent: 8)
.paddingOnly(left: 8), .paddingSymmetric(horizontal: 8),
Expanded( Expanded(
child: ListView( child: ListView(
shrinkWrap: true, shrinkWrap: true,
@ -132,7 +134,7 @@ class _PostQuickActionState extends State<PostQuickAction> {
onPressed: () => showReactMenu(), onPressed: () => showReactMenu(),
), ),
], ],
).paddingOnly(left: 8), ),
) )
], ],
), ),