Update posts

This commit is contained in:
LittleSheep 2024-04-14 18:38:44 +08:00
parent c693b0dcd7
commit 56fb7d6054
9 changed files with 215 additions and 88 deletions

View File

@ -6,6 +6,9 @@
"signInCaption": "Sign in to create post, start a realm, message your friend and more!", "signInCaption": "Sign in to create post, start a realm, message your friend and more!",
"signUp": "Sign Up", "signUp": "Sign Up",
"signUpCaption": "Create an account on Solarpass and then get the access of entire Solar Networks!", "signUpCaption": "Create an account on Solarpass and then get the access of entire Solar Networks!",
"edit": "Edit",
"action": "Action",
"report": "Report",
"post": "Post", "post": "Post",
"postVerb": "Post", "postVerb": "Post",
"comment": "Comment", "comment": "Comment",

View File

@ -6,6 +6,9 @@
"signInCaption": "登陆以发表帖子、文章、创建领域、和你的朋友聊天,以及获取更多功能!", "signInCaption": "登陆以发表帖子、文章、创建领域、和你的朋友聊天,以及获取更多功能!",
"signUp": "注册", "signUp": "注册",
"signUpCaption": "在 Solarpass 注册一个账号以获得整个 Solar Networks 的存取权!", "signUpCaption": "在 Solarpass 注册一个账号以获得整个 Solar Networks 的存取权!",
"edit": "编辑",
"action": "操作",
"report": "举报",
"post": "帖子", "post": "帖子",
"postVerb": "发表", "postVerb": "发表",
"comment": "评论", "comment": "评论",

View File

@ -115,8 +115,9 @@ class AuthProvider {
if (lastRefreshedAt == null || if (lastRefreshedAt == null ||
lastRefreshedAt! lastRefreshedAt!
.add(const Duration(minutes: 3)) .add(const Duration(minutes: 3))
.isAfter(DateTime.now())) { .isBefore(DateTime.now())) {
await refreshToken(); await refreshToken();
await pickClient();
lastRefreshedAt = DateTime.now(); lastRefreshedAt = DateTime.now();
} }
} }

View File

@ -1,7 +1,8 @@
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:solian/models/post.dart';
import 'package:solian/screens/account.dart'; import 'package:solian/screens/account.dart';
import 'package:solian/screens/explore.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'; import 'package:solian/screens/posts/screen.dart';
final router = GoRouter( final router = GoRouter(
@ -17,9 +18,10 @@ final router = GoRouter(
builder: (context, state) => const AccountScreen(), builder: (context, state) => const AccountScreen(),
), ),
GoRoute( GoRoute(
path: '/posts/moments/new', path: '/posts/moments/do/editor',
name: 'posts.moments.new', name: 'posts.moments.editor',
builder: (context, state) => const NewMomentScreen(), builder: (context, state) =>
MomentEditorScreen(editing: state.extra as Post?),
), ),
GoRoute( GoRoute(
path: '/posts/:dataset/:alias', path: '/posts/:dataset/:alias',

View File

@ -63,7 +63,7 @@ class _ExploreScreenState extends State<ExploreScreen> {
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
child: const Icon(Icons.edit), child: const Icon(Icons.edit),
onPressed: () async { onPressed: () async {
final did = await router.pushNamed("posts.moments.new"); final did = await router.pushNamed("posts.moments.editor");
if (did == true) _pagingController.refresh(); if (did == true) _pagingController.refresh();
}, },
), ),
@ -81,7 +81,10 @@ class _ExploreScreenState extends State<ExploreScreen> {
const Divider(thickness: 0.3), const Divider(thickness: 0.3),
builderDelegate: PagedChildBuilderDelegate<Post>( builderDelegate: PagedChildBuilderDelegate<Post>(
itemBuilder: (context, item, index) => GestureDetector( itemBuilder: (context, item, index) => GestureDetector(
child: PostItem(item: item), child: PostItem(
item: item,
onUpdate: () => _pagingController.refresh(),
),
onTap: () { onTap: () {
router.pushNamed( router.pushNamed(
'posts.screen', 'posts.screen',

View File

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:solian/models/post.dart'; import 'package:solian/models/post.dart';
import 'package:solian/providers/auth.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/indent_wrapper.dart';
import 'package:solian/widgets/posts/attachment_editor.dart'; import 'package:solian/widgets/posts/attachment_editor.dart';
class NewMomentScreen extends StatefulWidget { class MomentEditorScreen extends StatefulWidget {
const NewMomentScreen({super.key}); final Post? editing;
const MomentEditorScreen({super.key, this.editing});
@override @override
State<NewMomentScreen> createState() => _NewMomentScreenState(); State<MomentEditorScreen> createState() => _MomentEditorScreenState();
} }
class _NewMomentScreenState extends State<NewMomentScreen> { class _MomentEditorScreenState extends State<MomentEditorScreen> {
final _textController = TextEditingController(); final _textController = TextEditingController();
String? _alias;
bool _isSubmitting = false; bool _isSubmitting = false;
List<Attachment> _attachments = List.empty(growable: true); List<Attachment> _attachments = List.empty(growable: true);
@ -34,21 +38,24 @@ class _NewMomentScreenState extends State<NewMomentScreen> {
); );
} }
Future<void> createPost(BuildContext context) async { Future<void> applyPost(BuildContext context) async {
final auth = context.read<AuthProvider>(); final auth = context.read<AuthProvider>();
if (!await auth.isAuthorized()) return; if (!await auth.isAuthorized()) return;
setState(() => _isSubmitting = true); final uri = widget.editing == null
var res = await auth.client!.post( ? getRequestUri('interactive', '/api/p/moments')
getRequestUri('interactive', '/api/p/moments'), : getRequestUri('interactive', '/api/p/moments/${widget.editing!.id}');
headers: <String, String>{
'Content-Type': 'application/json', final req = Request(widget.editing == null ? "POST" : "PUT", uri);
}, req.headers['Content-Type'] = 'application/json';
body: jsonEncode(<String, dynamic>{ req.body = jsonEncode(<String, dynamic>{
'alias': _alias,
'content': _textController.value.text, 'content': _textController.value.text,
'attachments': _attachments, 'attachments': _attachments,
}), });
);
setState(() => _isSubmitting = true);
var res = await Response.fromStream(await auth.client!.send(req));
if (res.statusCode != 200) { if (res.statusCode != 200) {
var message = utf8.decode(res.bodyBytes); var message = utf8.decode(res.bodyBytes);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@ -62,6 +69,17 @@ class _NewMomentScreenState extends State<NewMomentScreen> {
setState(() => _isSubmitting = false); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final auth = context.read<AuthProvider>(); final auth = context.read<AuthProvider>();
@ -71,8 +89,8 @@ class _NewMomentScreenState extends State<NewMomentScreen> {
title: AppLocalizations.of(context)!.newMoment, title: AppLocalizations.of(context)!.newMoment,
appBarActions: <Widget>[ appBarActions: <Widget>[
TextButton( TextButton(
onPressed: !_isSubmitting ? () => createPost(context) : null, onPressed: !_isSubmitting ? () => applyPost(context) : null,
child: Text(AppLocalizations.of(context)!.postVerb), child: Text(AppLocalizations.of(context)!.postVerb.toUpperCase()),
), ),
], ],
child: Center( child: Center(

View File

@ -173,8 +173,8 @@ class _AttachmentEditorState extends State<AttachmentEditor> {
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 8.0, horizontal: 8,
vertical: 12.0, vertical: 12,
), ),
child: Text( child: Text(
AppLocalizations.of(context)!.attachment, AppLocalizations.of(context)!.attachment,

View File

@ -3,13 +3,15 @@ import 'package:solian/models/post.dart';
import 'package:solian/widgets/posts/content/article.dart'; import 'package:solian/widgets/posts/content/article.dart';
import 'package:solian/widgets/posts/content/attachment.dart'; import 'package:solian/widgets/posts/content/attachment.dart';
import 'package:solian/widgets/posts/content/moment.dart'; import 'package:solian/widgets/posts/content/moment.dart';
import 'package:solian/widgets/posts/item_action.dart';
import 'package:timeago/timeago.dart' as timeago; import 'package:timeago/timeago.dart' as timeago;
class PostItem extends StatefulWidget { class PostItem extends StatefulWidget {
final Post item; final Post item;
final bool? brief; 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 @override
State<PostItem> createState() => _PostItemState(); State<PostItem> createState() => _PostItemState();
@ -18,6 +20,16 @@ class PostItem extends StatefulWidget {
class _PostItemState extends State<PostItem> { class _PostItemState extends State<PostItem> {
Map<String, dynamic>? reactionList; Map<String, dynamic>? reactionList;
void viewActions(BuildContext context) {
showModalBottomSheet(
context: context,
builder: (context) => PostItemAction(
item: widget.item,
onUpdate: widget.onUpdate,
),
);
}
Widget renderContent() { Widget renderContent() {
switch (widget.item.modelType) { switch (widget.item.modelType) {
case 'article': case 'article':
@ -64,7 +76,8 @@ class _PostItemState extends State<PostItem> {
]; ];
if (widget.brief ?? true) { if (widget.brief ?? true) {
return Padding( return GestureDetector(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
child: Column( child: Column(
children: [ children: [
@ -80,7 +93,8 @@ class _PostItemState extends State<PostItem> {
children: [ children: [
...headingParts, ...headingParts,
Padding( Padding(
padding: const EdgeInsets.only(left: 12, right: 12, top: 4), padding: const EdgeInsets.only(
left: 12, right: 12, top: 4),
child: renderContent(), child: renderContent(),
), ),
renderAttachments(), renderAttachments(),
@ -91,9 +105,14 @@ class _PostItemState extends State<PostItem> {
), ),
], ],
), ),
),
onLongPress: () {
viewActions(context);
},
); );
} else { } else {
return Column( return GestureDetector(
child: Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.only(left: 12, right: 12, top: 16), padding: const EdgeInsets.only(left: 12, right: 12, top: 16),
@ -131,6 +150,10 @@ class _PostItemState extends State<PostItem> {
), ),
renderAttachments() renderAttachments()
], ],
),
onLongPress: () {
viewActions(context);
},
); );
} }
} }

View File

@ -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<AuthProvider>();
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(),
);
}
}),
),
],
),
);
}
}