✨ Update posts
This commit is contained in:
parent
c693b0dcd7
commit
56fb7d6054
@ -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",
|
||||||
|
@ -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": "评论",
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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',
|
||||||
|
@ -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',
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
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(<String, dynamic>{
|
||||||
|
'alias': _alias,
|
||||||
|
'content': _textController.value.text,
|
||||||
|
'attachments': _attachments,
|
||||||
|
});
|
||||||
|
|
||||||
setState(() => _isSubmitting = true);
|
setState(() => _isSubmitting = true);
|
||||||
var res = await auth.client!.post(
|
var res = await Response.fromStream(await auth.client!.send(req));
|
||||||
getRequestUri('interactive', '/api/p/moments'),
|
|
||||||
headers: <String, String>{
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: jsonEncode(<String, dynamic>{
|
|
||||||
'content': _textController.value.text,
|
|
||||||
'attachments': _attachments,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
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(
|
@ -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,
|
||||||
|
@ -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,73 +76,84 @@ class _PostItemState extends State<PostItem> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
if (widget.brief ?? true) {
|
if (widget.brief ?? true) {
|
||||||
return Padding(
|
return GestureDetector(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
child: Padding(
|
||||||
child: Column(
|
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||||
children: [
|
child: Column(
|
||||||
Row(
|
children: [
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
Row(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
CircleAvatar(
|
children: [
|
||||||
backgroundImage: NetworkImage(widget.item.author.avatar),
|
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(),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
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 {
|
} else {
|
||||||
return Column(
|
return GestureDetector(
|
||||||
children: [
|
child: Column(
|
||||||
Padding(
|
children: [
|
||||||
padding: const EdgeInsets.only(left: 12, right: 12, top: 16),
|
Padding(
|
||||||
child: Row(
|
padding: const EdgeInsets.only(left: 12, right: 12, top: 16),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Row(
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
CircleAvatar(
|
children: [
|
||||||
backgroundImage: NetworkImage(widget.item.author.avatar),
|
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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
Expanded(
|
||||||
],
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
...headingParts,
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||||
|
child: Text(
|
||||||
|
getAuthorDescribe(),
|
||||||
|
maxLines: 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
const Padding(
|
||||||
const Padding(
|
padding: EdgeInsets.only(top: 6),
|
||||||
padding: EdgeInsets.only(top: 6),
|
child: Divider(thickness: 0.3),
|
||||||
child: Divider(thickness: 0.3),
|
),
|
||||||
),
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
child: renderContent(),
|
||||||
child: renderContent(),
|
),
|
||||||
),
|
renderAttachments()
|
||||||
renderAttachments()
|
],
|
||||||
],
|
),
|
||||||
|
onLongPress: () {
|
||||||
|
viewActions(context);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
74
lib/widgets/posts/item_action.dart
Normal file
74
lib/widgets/posts/item_action.dart
Normal 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(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user