Further Microblog/Entry Support (#4)
* Added posts sub feeds for magazines and users. * Added ability to edit posts. * Added ability to edit post comments. * Added ability to delete posts and post comments. * Added ability to edit/delete entries and entry comments. * Edit/Delete buttons are now greyed out on feed page. Editing populates the text field with the current body of the entry/post. * Edit/Delete buttons are greyed out if logged in user is not op.
This commit is contained in:
parent
ee657d55c4
commit
ffe80277db
|
@ -175,3 +175,39 @@ Future<Comment> postComment(
|
|||
|
||||
return Comment.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<Comment> editComment(
|
||||
http.Client client,
|
||||
String instanceHost,
|
||||
int commentId,
|
||||
String body,
|
||||
String lang,
|
||||
bool? isAdult
|
||||
) async {
|
||||
final response = await client.put(Uri.https(
|
||||
instanceHost,
|
||||
'/api/comments/$commentId'
|
||||
),
|
||||
body: jsonEncode({
|
||||
'body': body,
|
||||
'lang': lang,
|
||||
'isAdult': isAdult ?? false
|
||||
}));
|
||||
|
||||
httpErrorHandler(response, message: "Failed to edit comment");
|
||||
|
||||
return Comment.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<void> deleteComment(
|
||||
http.Client client,
|
||||
String instanceHost,
|
||||
int commentId,
|
||||
) async {
|
||||
final response = await client.delete(Uri.https(
|
||||
instanceHost,
|
||||
'/api/comments/$commentId'
|
||||
));
|
||||
|
||||
httpErrorHandler(response, message: "Failed to delete comment");
|
||||
}
|
|
@ -154,3 +154,44 @@ Future<EntryItem> putFavorite(
|
|||
|
||||
return EntryItem.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<EntryItem> editEntry(
|
||||
http.Client client,
|
||||
String instanceHost,
|
||||
int entryID,
|
||||
String title,
|
||||
bool isOc,
|
||||
String body,
|
||||
String lang,
|
||||
bool isAdult
|
||||
) async {
|
||||
final response = await client.put(Uri.https(
|
||||
instanceHost,
|
||||
'/api/entry/$entryID'
|
||||
),
|
||||
body: jsonEncode({
|
||||
'title': title,
|
||||
'tags': [],
|
||||
'isOc': isOc,
|
||||
'body': body,
|
||||
'lang': lang,
|
||||
'isAdult': isAdult
|
||||
}));
|
||||
|
||||
httpErrorHandler(response, message: "Failed to edit entry");
|
||||
|
||||
return EntryItem.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<void> deletePost(
|
||||
http.Client client,
|
||||
String instanceHost,
|
||||
int postID,
|
||||
) async {
|
||||
final response = await client.delete(Uri.https(
|
||||
instanceHost,
|
||||
'/api/entry/$postID'
|
||||
));
|
||||
|
||||
httpErrorHandler(response, message: "Failed to delete entry");
|
||||
}
|
|
@ -175,3 +175,39 @@ Future<Comment> postComment(
|
|||
|
||||
return Comment.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<Comment> editComment(
|
||||
http.Client client,
|
||||
String instanceHost,
|
||||
int commentId,
|
||||
String body,
|
||||
String lang,
|
||||
bool? isAdult
|
||||
) async {
|
||||
final response = await client.put(Uri.https(
|
||||
instanceHost,
|
||||
'/api/post-comments/$commentId'
|
||||
),
|
||||
body: jsonEncode({
|
||||
'body': body,
|
||||
'lang': lang,
|
||||
'isAdult': isAdult ?? false
|
||||
}));
|
||||
|
||||
httpErrorHandler(response, message: "Failed to edit comment");
|
||||
|
||||
return Comment.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<void> deleteComment(
|
||||
http.Client client,
|
||||
String instanceHost,
|
||||
int commentId,
|
||||
) async {
|
||||
final response = await client.delete(Uri.https(
|
||||
instanceHost,
|
||||
'/api/post-comments/$commentId'
|
||||
));
|
||||
|
||||
httpErrorHandler(response, message: "Failed to delete comment");
|
||||
}
|
|
@ -144,3 +144,39 @@ Future<PostItem> putFavorite(
|
|||
|
||||
return PostItem.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<PostItem> editPost(
|
||||
http.Client client,
|
||||
String instanceHost,
|
||||
int postID,
|
||||
String body,
|
||||
String lang,
|
||||
bool isAdult
|
||||
) async {
|
||||
final response = await client.put(Uri.https(
|
||||
instanceHost,
|
||||
'/api/post/$postID'
|
||||
),
|
||||
body: jsonEncode({
|
||||
'body': body,
|
||||
'lang': lang,
|
||||
'isAdult': isAdult
|
||||
}));
|
||||
|
||||
httpErrorHandler(response, message: "Failed to edit post");
|
||||
|
||||
return PostItem.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
|
||||
}
|
||||
|
||||
Future<void> deletePost(
|
||||
http.Client client,
|
||||
String instanceHost,
|
||||
int postID,
|
||||
) async {
|
||||
final response = await client.delete(Uri.https(
|
||||
instanceHost,
|
||||
'/api/post/$postID'
|
||||
));
|
||||
|
||||
httpErrorHandler(response, message: "Failed to delete post");
|
||||
}
|
|
@ -117,6 +117,29 @@ class _EntryCommentState extends State<EntryComment> {
|
|||
newComment.children!.insert(0, newSubComment);
|
||||
widget.onUpdate(newComment);
|
||||
},
|
||||
onEdit: whenLoggedIn(context, (body) async {
|
||||
var newComment = await api_comments.editComment(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
body,
|
||||
widget.comment.lang,
|
||||
widget.comment.isAdult
|
||||
);
|
||||
setState(() {
|
||||
widget.comment.body = newComment.body;
|
||||
});
|
||||
}),
|
||||
onDelete: whenLoggedIn(context, () async {
|
||||
await api_comments.deleteComment(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
);
|
||||
setState(() {
|
||||
widget.comment.body = "deleted";
|
||||
});
|
||||
}),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
|
|
|
@ -19,11 +19,15 @@ class EntryItem extends StatelessWidget {
|
|||
super.key,
|
||||
this.isPreview = false,
|
||||
this.onReply,
|
||||
this.onEdit,
|
||||
this.onDelete,
|
||||
});
|
||||
|
||||
final api_entries.EntryItem item;
|
||||
final void Function(api_entries.EntryItem) onUpdate;
|
||||
final Future<void> Function(String)? onReply;
|
||||
final Future<void> Function(String)? onEdit;
|
||||
final Future<void> Function()? onDelete;
|
||||
final bool isPreview;
|
||||
|
||||
_onImageClick(BuildContext context) {
|
||||
|
@ -208,6 +212,11 @@ class EntryItem extends StatelessWidget {
|
|||
));
|
||||
}),
|
||||
onReply: onReply,
|
||||
onEdit: isLoggedInUser(context, item.user.username, onEdit),
|
||||
onDelete: isLoggedInUser(context, item.user.username, onDelete),
|
||||
initEdit: () {
|
||||
return item.body;
|
||||
},
|
||||
leadingWidgets: [
|
||||
const Icon(Icons.comment),
|
||||
const SizedBox(width: 4),
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:interstellar/src/api/entries.dart' as api_entries;
|
|||
import 'package:interstellar/src/screens/entries/entry_comment.dart';
|
||||
import 'package:interstellar/src/screens/entries/entry_item.dart';
|
||||
import 'package:interstellar/src/screens/settings/settings_controller.dart';
|
||||
import 'package:interstellar/src/utils/utils.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class EntryPage extends StatefulWidget {
|
||||
|
@ -89,6 +90,31 @@ class _EntryPageState extends State<EntryPage> {
|
|||
_pagingController.itemList = newList;
|
||||
});
|
||||
},
|
||||
onEdit: whenLoggedIn(context, (body) async {
|
||||
final newEntry = await api_entries.editEntry(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.item.entryId,
|
||||
widget.item.title,
|
||||
widget.item.isOc,
|
||||
body,
|
||||
widget.item.lang,
|
||||
widget.item.isAdult
|
||||
);
|
||||
setState(() {
|
||||
widget.item.body = newEntry.body;
|
||||
});
|
||||
}),
|
||||
onDelete: whenLoggedIn(context, () async {
|
||||
await api_entries.deletePost(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.item.entryId,
|
||||
);
|
||||
setState(() {
|
||||
widget.item.body = "deleted";
|
||||
});
|
||||
}),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
|
|
|
@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:interstellar/src/api/content_sources.dart';
|
||||
import 'package:interstellar/src/api/magazines.dart' as api_magazines;
|
||||
import 'package:interstellar/src/screens/entries/entries_list.dart';
|
||||
import 'package:interstellar/src/screens/feed_screen.dart';
|
||||
import 'package:interstellar/src/screens/posts/posts_list.dart';
|
||||
import 'package:interstellar/src/screens/settings/settings_controller.dart';
|
||||
import 'package:interstellar/src/utils/utils.dart';
|
||||
import 'package:interstellar/src/widgets/avatar.dart';
|
||||
|
@ -21,6 +23,7 @@ class MagazineScreen extends StatefulWidget {
|
|||
|
||||
class _MagazineScreenState extends State<MagazineScreen> {
|
||||
api_magazines.DetailedMagazine? _data;
|
||||
FeedMode _feedMode = FeedMode.entries;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -41,71 +44,109 @@ class _MagazineScreenState extends State<MagazineScreen> {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(_data?.name ?? '')),
|
||||
body: EntriesListView(
|
||||
contentSource: ContentMagazine(widget.magazineId),
|
||||
details: _data != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
if (_data!.icon?.storageUrl != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 12),
|
||||
child:
|
||||
Avatar(_data!.icon!.storageUrl, radius: 32)),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_data!.title,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
softWrap: true,
|
||||
),
|
||||
),
|
||||
OutlinedButton(
|
||||
style: ButtonStyle(
|
||||
foregroundColor: _data!.isUserSubscribed == true
|
||||
? MaterialStatePropertyAll(
|
||||
Colors.purple.shade400)
|
||||
: null),
|
||||
onPressed: whenLoggedIn(context, () async {
|
||||
var newValue = await api_magazines.putSubscribe(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
_data!.magazineId,
|
||||
!_data!.isUserSubscribed!);
|
||||
Widget _magazineDetails() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
if (_data!.icon?.storageUrl != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 12),
|
||||
child:
|
||||
Avatar(_data!.icon!.storageUrl, radius: 32)),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_data!.title,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
softWrap: true,
|
||||
),
|
||||
),
|
||||
OutlinedButton(
|
||||
style: ButtonStyle(
|
||||
foregroundColor: _data!.isUserSubscribed == true
|
||||
? MaterialStatePropertyAll(
|
||||
Colors.purple.shade400)
|
||||
: null),
|
||||
onPressed: whenLoggedIn(context, () async {
|
||||
var newValue = await api_magazines.putSubscribe(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
_data!.magazineId,
|
||||
!_data!.isUserSubscribed!);
|
||||
|
||||
if (widget.onUpdate != null) {
|
||||
widget.onUpdate!(newValue);
|
||||
}
|
||||
setState(() {
|
||||
_data = newValue;
|
||||
});
|
||||
}),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.group),
|
||||
Text(' ${intFormat(_data!.subscriptionsCount)}'),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (_data!.description != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Markdown(_data!.description!),
|
||||
)
|
||||
if (widget.onUpdate != null) {
|
||||
widget.onUpdate!(newValue);
|
||||
}
|
||||
setState(() {
|
||||
_data = newValue;
|
||||
});
|
||||
}),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.group),
|
||||
Text(' ${intFormat(_data!.subscriptionsCount)}'),
|
||||
],
|
||||
),
|
||||
)
|
||||
: null,
|
||||
],
|
||||
),
|
||||
if (_data!.description != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Markdown(_data!.description!),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(_data?.name ?? ''),
|
||||
actions: [
|
||||
SegmentedButton(
|
||||
segments: const [
|
||||
ButtonSegment(
|
||||
value: FeedMode.entries,
|
||||
label: Text("Threads"),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: FeedMode.posts,
|
||||
label: Text("Posts"),
|
||||
),
|
||||
],
|
||||
style: const ButtonStyle(
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
visualDensity: VisualDensity(horizontal: -3, vertical: -3),
|
||||
),
|
||||
selected: <FeedMode>{_feedMode},
|
||||
onSelectionChanged: (Set<FeedMode> newSelection) {
|
||||
setState(() {
|
||||
_feedMode = newSelection.first;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: switch (_feedMode) {
|
||||
FeedMode.entries => EntriesListView(
|
||||
contentSource: ContentMagazine(widget.magazineId),
|
||||
details: _data != null
|
||||
? _magazineDetails()
|
||||
: null,
|
||||
),
|
||||
FeedMode.posts => PostsListView(
|
||||
contentSource: ContentMagazine(widget.magazineId),
|
||||
details: _data != null
|
||||
? _magazineDetails()
|
||||
: null,
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:interstellar/src/api/content_sources.dart';
|
||||
import 'package:interstellar/src/api/users.dart' as api_users;
|
||||
import 'package:interstellar/src/screens/entries/entries_list.dart';
|
||||
import 'package:interstellar/src/screens/feed_screen.dart';
|
||||
import 'package:interstellar/src/screens/posts/posts_list.dart';
|
||||
import 'package:interstellar/src/screens/settings/settings_controller.dart';
|
||||
import 'package:interstellar/src/utils/utils.dart';
|
||||
import 'package:interstellar/src/widgets/avatar.dart';
|
||||
|
@ -21,6 +23,7 @@ class UserScreen extends StatefulWidget {
|
|||
|
||||
class _UserScreenState extends State<UserScreen> {
|
||||
api_users.DetailedUser? _data;
|
||||
FeedMode _feedMode = FeedMode.entries;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -41,71 +44,109 @@ class _UserScreenState extends State<UserScreen> {
|
|||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(_data?.username ?? '')),
|
||||
body: EntriesListView(
|
||||
contentSource: ContentUser(widget.userId),
|
||||
details: _data != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
if (_data!.avatar?.storageUrl != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 12),
|
||||
child: Avatar(_data!.avatar!.storageUrl,
|
||||
radius: 32)),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_data!.username,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
softWrap: true,
|
||||
),
|
||||
),
|
||||
OutlinedButton(
|
||||
style: ButtonStyle(
|
||||
foregroundColor: _data!.isFollowedByUser == true
|
||||
? MaterialStatePropertyAll(
|
||||
Colors.purple.shade400)
|
||||
: null),
|
||||
onPressed: whenLoggedIn(context, () async {
|
||||
var newValue = await api_users.putFollow(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
_data!.userId,
|
||||
!_data!.isFollowedByUser!);
|
||||
Widget _userDetails() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
if (_data!.avatar?.storageUrl != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 12),
|
||||
child: Avatar(_data!.avatar!.storageUrl,
|
||||
radius: 32)),
|
||||
Expanded(
|
||||
child: Text(
|
||||
_data!.username,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
softWrap: true,
|
||||
),
|
||||
),
|
||||
OutlinedButton(
|
||||
style: ButtonStyle(
|
||||
foregroundColor: _data!.isFollowedByUser == true
|
||||
? MaterialStatePropertyAll(
|
||||
Colors.purple.shade400)
|
||||
: null),
|
||||
onPressed: whenLoggedIn(context, () async {
|
||||
var newValue = await api_users.putFollow(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
_data!.userId,
|
||||
!_data!.isFollowedByUser!);
|
||||
|
||||
if (widget.onUpdate != null) {
|
||||
widget.onUpdate!(newValue);
|
||||
}
|
||||
setState(() {
|
||||
_data = newValue;
|
||||
});
|
||||
}),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.group),
|
||||
Text(' ${intFormat(_data!.followersCount)}'),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (_data!.about != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Markdown(_data!.about!),
|
||||
)
|
||||
if (widget.onUpdate != null) {
|
||||
widget.onUpdate!(newValue);
|
||||
}
|
||||
setState(() {
|
||||
_data = newValue;
|
||||
});
|
||||
}),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.group),
|
||||
Text(' ${intFormat(_data!.followersCount)}'),
|
||||
],
|
||||
),
|
||||
)
|
||||
: null,
|
||||
],
|
||||
),
|
||||
if (_data!.about != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Markdown(_data!.about!),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(_data?.username ?? ''),
|
||||
actions: [
|
||||
SegmentedButton(
|
||||
segments: const [
|
||||
ButtonSegment(
|
||||
value: FeedMode.entries,
|
||||
label: Text("Threads"),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: FeedMode.posts,
|
||||
label: Text("Posts"),
|
||||
),
|
||||
],
|
||||
style: const ButtonStyle(
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
visualDensity: VisualDensity(horizontal: -3, vertical: -3),
|
||||
),
|
||||
selected: <FeedMode>{_feedMode},
|
||||
onSelectionChanged: (Set<FeedMode> newSelection) {
|
||||
setState(() {
|
||||
_feedMode = newSelection.first;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: switch (_feedMode) {
|
||||
FeedMode.entries => EntriesListView(
|
||||
contentSource: ContentUser(widget.userId),
|
||||
details: _data != null
|
||||
? _userDetails()
|
||||
: null,
|
||||
),
|
||||
FeedMode.posts => PostsListView(
|
||||
contentSource: ContentUser(widget.userId),
|
||||
details: _data != null
|
||||
? _userDetails()
|
||||
: null,
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -117,6 +117,29 @@ class _EntryCommentState extends State<PostComment> {
|
|||
newComment.children!.insert(0, newSubComment);
|
||||
widget.onUpdate(newComment);
|
||||
},
|
||||
onEdit: whenLoggedIn(context, (body) async {
|
||||
var newComment = await api_comments.editComment(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
body,
|
||||
widget.comment.lang,
|
||||
widget.comment.isAdult
|
||||
);
|
||||
setState(() {
|
||||
widget.comment.body = newComment.body;
|
||||
});
|
||||
}),
|
||||
onDelete: whenLoggedIn(context, () async {
|
||||
await api_comments.deleteComment(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
);
|
||||
setState(() {
|
||||
widget.comment.body = "deleted";
|
||||
});
|
||||
}),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
|
|
|
@ -16,11 +16,15 @@ class PostItem extends StatelessWidget {
|
|||
super.key,
|
||||
this.isPreview = false,
|
||||
this.onReply,
|
||||
this.onEdit,
|
||||
this.onDelete,
|
||||
});
|
||||
|
||||
final api_posts.PostItem item;
|
||||
final void Function(api_posts.PostItem) onUpdate;
|
||||
final Future<void> Function(String)? onReply;
|
||||
final Future<void> Function(String)? onEdit;
|
||||
final Future<void> Function()? onDelete;
|
||||
final bool isPreview;
|
||||
|
||||
_onImageClick(BuildContext context) {
|
||||
|
@ -159,6 +163,11 @@ class PostItem extends StatelessWidget {
|
|||
));
|
||||
}),
|
||||
onReply: onReply,
|
||||
onEdit: isLoggedInUser(context, item.user.username, onEdit),
|
||||
onDelete: isLoggedInUser(context, item.user.username, onDelete),
|
||||
initEdit: () {
|
||||
return item.body;
|
||||
},
|
||||
leadingWidgets: [
|
||||
const Icon(Icons.comment),
|
||||
const SizedBox(width: 4),
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:interstellar/src/api/posts.dart' as api_posts;
|
|||
import 'package:interstellar/src/screens/posts/post_comment.dart';
|
||||
import 'package:interstellar/src/screens/posts/post_item.dart';
|
||||
import 'package:interstellar/src/screens/settings/settings_controller.dart';
|
||||
import 'package:interstellar/src/utils/utils.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class PostPage extends StatefulWidget {
|
||||
|
@ -89,6 +90,29 @@ class _PostPageState extends State<PostPage> {
|
|||
_pagingController.itemList = newList;
|
||||
});
|
||||
},
|
||||
onEdit: whenLoggedIn(context, (body) async {
|
||||
final newPost = await api_posts.editPost(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.item.postId,
|
||||
body,
|
||||
widget.item.lang,
|
||||
widget.item.isAdult
|
||||
);
|
||||
setState(() {
|
||||
widget.item.body = newPost.body;
|
||||
});
|
||||
}),
|
||||
onDelete: whenLoggedIn(context, () async {
|
||||
await api_posts.deletePost(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.item.postId,
|
||||
);
|
||||
setState(() {
|
||||
widget.item.body = "deleted";
|
||||
});
|
||||
}),
|
||||
),
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:interstellar/src/api/posts.dart' as api_posts;
|
|||
import 'package:interstellar/src/screens/posts/post_item.dart';
|
||||
import 'package:interstellar/src/screens/posts/post_page.dart';
|
||||
import 'package:interstellar/src/screens/settings/settings_controller.dart';
|
||||
import 'package:interstellar/src/utils/utils.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class PostsListView extends StatefulWidget {
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
|||
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:interstellar/src/api/users.dart';
|
||||
import 'package:interstellar/src/screens/settings/settings_controller.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -74,3 +75,6 @@ Map<String, dynamic> removeNulls(Map<String, dynamic> map) {
|
|||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
T? isLoggedInUser<T>(BuildContext context, String username, T? value, {T? otherwise}) =>
|
||||
context.read<SettingsController>().selectedAccount.split("@").first == username ? value : otherwise;
|
||||
|
|
|
@ -17,6 +17,9 @@ class ActionBar extends StatefulWidget {
|
|||
final void Function()? onDownVote;
|
||||
final void Function()? onCollapse;
|
||||
final Future<void> Function(String)? onReply;
|
||||
final Future<void> Function(String)? onEdit;
|
||||
final void Function()? onDelete;
|
||||
final String? Function()? initEdit;
|
||||
|
||||
final List<Widget>? leadingWidgets;
|
||||
|
||||
|
@ -34,6 +37,9 @@ class ActionBar extends StatefulWidget {
|
|||
this.onDownVote,
|
||||
this.onReply,
|
||||
this.onCollapse,
|
||||
this.onEdit,
|
||||
this.onDelete,
|
||||
this.initEdit,
|
||||
this.leadingWidgets,
|
||||
});
|
||||
|
||||
|
@ -43,6 +49,8 @@ class ActionBar extends StatefulWidget {
|
|||
|
||||
class _ActionBarState extends State<ActionBar> {
|
||||
TextEditingController? _replyTextController;
|
||||
TextEditingController? _editTextController;
|
||||
final MenuController _menuController = MenuController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -71,9 +79,38 @@ class _ActionBarState extends State<ActionBar> {
|
|||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 12),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onPressed: () {},
|
||||
child: MenuAnchor(
|
||||
builder: (BuildContext context, MenuController controller, Widget? child) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onPressed: () {
|
||||
if (_menuController.isOpen) {
|
||||
_menuController.close();
|
||||
} else {
|
||||
_menuController.open();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
controller: _menuController,
|
||||
menuChildren: [
|
||||
MenuItemButton(
|
||||
onPressed: widget.onEdit != null ? () => setState(() {
|
||||
_editTextController = TextEditingController();
|
||||
}) : null,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Text("Edit")
|
||||
),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: widget.onDelete,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Text("Delete")
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
if (widget.boosts != null)
|
||||
|
@ -146,6 +183,39 @@ class _ActionBarState extends State<ActionBar> {
|
|||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.onEdit != null && _editTextController != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
MarkdownEditor(_editTextController!..text = (widget.initEdit != null ? widget.initEdit!() : "")!),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () => setState(() {
|
||||
_editTextController!.dispose();
|
||||
_editTextController = null;
|
||||
}),
|
||||
child: const Text('Cancel')),
|
||||
const SizedBox(width: 8),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
// Wait in case of errors before closing
|
||||
await widget.onEdit!(_editTextController!.text);
|
||||
|
||||
setState(() {
|
||||
_editTextController!.dispose();
|
||||
_editTextController = null;
|
||||
});
|
||||
},
|
||||
child: const Text('Submit'))
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue