From 56fb7d6054e6c35c1f5a95936761ba774eca0f19 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Sun, 14 Apr 2024 18:38:44 +0800 Subject: [PATCH] :sparkles: Update posts --- lib/i18n/app_en.arb | 3 + lib/i18n/app_zh.arb | 3 + lib/providers/auth.dart | 3 +- lib/router.dart | 10 +- lib/screens/explore.dart | 7 +- .../{new_moment.dart => moment_editor.dart} | 52 +++++-- lib/widgets/posts/attachment_editor.dart | 4 +- lib/widgets/posts/item.dart | 147 ++++++++++-------- lib/widgets/posts/item_action.dart | 74 +++++++++ 9 files changed, 215 insertions(+), 88 deletions(-) rename lib/screens/posts/{new_moment.dart => moment_editor.dart} (74%) create mode 100644 lib/widgets/posts/item_action.dart diff --git a/lib/i18n/app_en.arb b/lib/i18n/app_en.arb index 7fb89e9..26c23ef 100644 --- a/lib/i18n/app_en.arb +++ b/lib/i18n/app_en.arb @@ -6,6 +6,9 @@ "signInCaption": "Sign in to create post, start a realm, message your friend and more!", "signUp": "Sign Up", "signUpCaption": "Create an account on Solarpass and then get the access of entire Solar Networks!", + "edit": "Edit", + "action": "Action", + "report": "Report", "post": "Post", "postVerb": "Post", "comment": "Comment", diff --git a/lib/i18n/app_zh.arb b/lib/i18n/app_zh.arb index 348e1f4..dff4136 100644 --- a/lib/i18n/app_zh.arb +++ b/lib/i18n/app_zh.arb @@ -6,6 +6,9 @@ "signInCaption": "登陆以发表帖子、文章、创建领域、和你的朋友聊天,以及获取更多功能!", "signUp": "注册", "signUpCaption": "在 Solarpass 注册一个账号以获得整个 Solar Networks 的存取权!", + "edit": "编辑", + "action": "操作", + "report": "举报", "post": "帖子", "postVerb": "发表", "comment": "评论", diff --git a/lib/providers/auth.dart b/lib/providers/auth.dart index 39e86a2..66582da 100755 --- a/lib/providers/auth.dart +++ b/lib/providers/auth.dart @@ -115,8 +115,9 @@ class AuthProvider { if (lastRefreshedAt == null || lastRefreshedAt! .add(const Duration(minutes: 3)) - .isAfter(DateTime.now())) { + .isBefore(DateTime.now())) { await refreshToken(); + await pickClient(); lastRefreshedAt = DateTime.now(); } } diff --git a/lib/router.dart b/lib/router.dart index 3ee6358..ae013fd 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -1,7 +1,8 @@ import 'package:go_router/go_router.dart'; +import 'package:solian/models/post.dart'; import 'package:solian/screens/account.dart'; import 'package:solian/screens/explore.dart'; -import 'package:solian/screens/posts/new_moment.dart'; +import 'package:solian/screens/posts/moment_editor.dart'; import 'package:solian/screens/posts/screen.dart'; final router = GoRouter( @@ -17,9 +18,10 @@ final router = GoRouter( builder: (context, state) => const AccountScreen(), ), GoRoute( - path: '/posts/moments/new', - name: 'posts.moments.new', - builder: (context, state) => const NewMomentScreen(), + path: '/posts/moments/do/editor', + name: 'posts.moments.editor', + builder: (context, state) => + MomentEditorScreen(editing: state.extra as Post?), ), GoRoute( path: '/posts/:dataset/:alias', diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index d4a7baf..83c3bb7 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -63,7 +63,7 @@ class _ExploreScreenState extends State { floatingActionButton: FloatingActionButton( child: const Icon(Icons.edit), onPressed: () async { - final did = await router.pushNamed("posts.moments.new"); + final did = await router.pushNamed("posts.moments.editor"); if (did == true) _pagingController.refresh(); }, ), @@ -81,7 +81,10 @@ class _ExploreScreenState extends State { const Divider(thickness: 0.3), builderDelegate: PagedChildBuilderDelegate( itemBuilder: (context, item, index) => GestureDetector( - child: PostItem(item: item), + child: PostItem( + item: item, + onUpdate: () => _pagingController.refresh(), + ), onTap: () { router.pushNamed( 'posts.screen', diff --git a/lib/screens/posts/new_moment.dart b/lib/screens/posts/moment_editor.dart similarity index 74% rename from lib/screens/posts/new_moment.dart rename to lib/screens/posts/moment_editor.dart index 87b6fe6..7179a67 100644 --- a/lib/screens/posts/new_moment.dart +++ b/lib/screens/posts/moment_editor.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:http/http.dart'; import 'package:provider/provider.dart'; import 'package:solian/models/post.dart'; import 'package:solian/providers/auth.dart'; @@ -10,16 +11,19 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:solian/widgets/indent_wrapper.dart'; import 'package:solian/widgets/posts/attachment_editor.dart'; -class NewMomentScreen extends StatefulWidget { - const NewMomentScreen({super.key}); +class MomentEditorScreen extends StatefulWidget { + final Post? editing; + + const MomentEditorScreen({super.key, this.editing}); @override - State createState() => _NewMomentScreenState(); + State createState() => _MomentEditorScreenState(); } -class _NewMomentScreenState extends State { +class _MomentEditorScreenState extends State { final _textController = TextEditingController(); + String? _alias; bool _isSubmitting = false; List _attachments = List.empty(growable: true); @@ -34,21 +38,24 @@ class _NewMomentScreenState extends State { ); } - Future createPost(BuildContext context) async { + Future applyPost(BuildContext context) async { final auth = context.read(); if (!await auth.isAuthorized()) return; + final uri = widget.editing == null + ? getRequestUri('interactive', '/api/p/moments') + : getRequestUri('interactive', '/api/p/moments/${widget.editing!.id}'); + + final req = Request(widget.editing == null ? "POST" : "PUT", uri); + req.headers['Content-Type'] = 'application/json'; + req.body = jsonEncode({ + 'alias': _alias, + 'content': _textController.value.text, + 'attachments': _attachments, + }); + setState(() => _isSubmitting = true); - var res = await auth.client!.post( - getRequestUri('interactive', '/api/p/moments'), - headers: { - 'Content-Type': 'application/json', - }, - body: jsonEncode({ - 'content': _textController.value.text, - 'attachments': _attachments, - }), - ); + var res = await Response.fromStream(await auth.client!.send(req)); if (res.statusCode != 200) { var message = utf8.decode(res.bodyBytes); ScaffoldMessenger.of(context).showSnackBar( @@ -62,6 +69,17 @@ class _NewMomentScreenState extends State { setState(() => _isSubmitting = false); } + @override + void initState() { + if (widget.editing != null) { + _alias = widget.editing!.alias; + _textController.text = widget.editing!.content; + _attachments = widget.editing!.attachments ?? List.empty(growable: true); + } + + super.initState(); + } + @override Widget build(BuildContext context) { final auth = context.read(); @@ -71,8 +89,8 @@ class _NewMomentScreenState extends State { title: AppLocalizations.of(context)!.newMoment, appBarActions: [ TextButton( - onPressed: !_isSubmitting ? () => createPost(context) : null, - child: Text(AppLocalizations.of(context)!.postVerb), + onPressed: !_isSubmitting ? () => applyPost(context) : null, + child: Text(AppLocalizations.of(context)!.postVerb.toUpperCase()), ), ], child: Center( diff --git a/lib/widgets/posts/attachment_editor.dart b/lib/widgets/posts/attachment_editor.dart index 7d9ca1e..c871e31 100755 --- a/lib/widgets/posts/attachment_editor.dart +++ b/lib/widgets/posts/attachment_editor.dart @@ -173,8 +173,8 @@ class _AttachmentEditorState extends State { children: [ Padding( padding: const EdgeInsets.symmetric( - horizontal: 8.0, - vertical: 12.0, + horizontal: 8, + vertical: 12, ), child: Text( AppLocalizations.of(context)!.attachment, diff --git a/lib/widgets/posts/item.dart b/lib/widgets/posts/item.dart index 8b9c0a7..cedb2cc 100644 --- a/lib/widgets/posts/item.dart +++ b/lib/widgets/posts/item.dart @@ -3,13 +3,15 @@ import 'package:solian/models/post.dart'; import 'package:solian/widgets/posts/content/article.dart'; import 'package:solian/widgets/posts/content/attachment.dart'; import 'package:solian/widgets/posts/content/moment.dart'; +import 'package:solian/widgets/posts/item_action.dart'; import 'package:timeago/timeago.dart' as timeago; class PostItem extends StatefulWidget { final Post item; final bool? brief; + final Function? onUpdate; - const PostItem({super.key, required this.item, this.brief}); + const PostItem({super.key, required this.item, this.brief, this.onUpdate}); @override State createState() => _PostItemState(); @@ -18,6 +20,16 @@ class PostItem extends StatefulWidget { class _PostItemState extends State { Map? reactionList; + void viewActions(BuildContext context) { + showModalBottomSheet( + context: context, + builder: (context) => PostItemAction( + item: widget.item, + onUpdate: widget.onUpdate, + ), + ); + } + Widget renderContent() { switch (widget.item.modelType) { case 'article': @@ -64,73 +76,84 @@ class _PostItemState extends State { ]; if (widget.brief ?? true) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - child: Column( - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CircleAvatar( - backgroundImage: NetworkImage(widget.item.author.avatar), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...headingParts, - Padding( - padding: const EdgeInsets.only(left: 12, right: 12, top: 4), - child: renderContent(), - ), - renderAttachments(), - ], + return GestureDetector( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CircleAvatar( + backgroundImage: NetworkImage(widget.item.author.avatar), ), - ), - ], - ), - ], + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...headingParts, + Padding( + padding: const EdgeInsets.only( + left: 12, right: 12, top: 4), + child: renderContent(), + ), + renderAttachments(), + ], + ), + ), + ], + ), + ], + ), ), + onLongPress: () { + viewActions(context); + }, ); } else { - return Column( - children: [ - Padding( - padding: const EdgeInsets.only(left: 12, right: 12, top: 16), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CircleAvatar( - backgroundImage: NetworkImage(widget.item.author.avatar), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...headingParts, - Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: Text( - getAuthorDescribe(), - maxLines: 1, - ), - ), - ], + return GestureDetector( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(left: 12, right: 12, top: 16), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CircleAvatar( + backgroundImage: NetworkImage(widget.item.author.avatar), ), - ), - ], + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...headingParts, + Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Text( + getAuthorDescribe(), + maxLines: 1, + ), + ), + ], + ), + ), + ], + ), ), - ), - const Padding( - padding: EdgeInsets.only(top: 6), - child: Divider(thickness: 0.3), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: renderContent(), - ), - renderAttachments() - ], + const Padding( + padding: EdgeInsets.only(top: 6), + child: Divider(thickness: 0.3), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: renderContent(), + ), + renderAttachments() + ], + ), + onLongPress: () { + viewActions(context); + }, ); } } diff --git a/lib/widgets/posts/item_action.dart b/lib/widgets/posts/item_action.dart new file mode 100644 index 0000000..ffade9d --- /dev/null +++ b/lib/widgets/posts/item_action.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:solian/models/post.dart'; +import 'package:solian/providers/auth.dart'; +import 'package:solian/router.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +class PostItemAction extends StatelessWidget { + final Post item; + final Function? onUpdate; + + const PostItemAction({super.key, required this.item, this.onUpdate}); + + @override + Widget build(BuildContext context) { + final auth = context.read(); + + return SizedBox( + height: 280, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: const EdgeInsets.only(left: 20, top: 20, bottom: 12), + child: Text( + AppLocalizations.of(context)!.action, + style: Theme.of(context).textTheme.headlineSmall, + ), + ), + Expanded( + child: FutureBuilder( + future: auth.getProfiles(), + builder: (context, snapshot) { + if (snapshot.hasData) { + final authorizedItems = [ + ListTile( + leading: const Icon(Icons.edit), + title: Text(AppLocalizations.of(context)!.edit), + onTap: () { + router + .pushNamed('posts.moments.editor', extra: item) + .then((did) { + if(did == true && onUpdate != null) { + onUpdate!(); + } + }); + }, + ) + ]; + + return ListView( + children: [ + ...(snapshot.data['id'] == item.authorId + ? authorizedItems + : List.empty()), + ListTile( + leading: const Icon(Icons.report), + title: Text(AppLocalizations.of(context)!.report), + onTap: () {}, + ) + ], + ); + } else { + return const Center( + child: CircularProgressIndicator(), + ); + } + }), + ), + ], + ), + ); + } +}