✨ Edit, delete and reply messages!
This commit is contained in:
parent
1a1a3a5155
commit
b257378a82
@ -15,6 +15,7 @@
|
|||||||
"action": "Action",
|
"action": "Action",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"report": "Report",
|
"report": "Report",
|
||||||
|
"reply": "Reply",
|
||||||
"reaction": "Reaction",
|
"reaction": "Reaction",
|
||||||
"reactVerb": "React",
|
"reactVerb": "React",
|
||||||
"post": "Post",
|
"post": "Post",
|
||||||
@ -35,5 +36,8 @@
|
|||||||
"postEditNotify": "You are about editing a post that already published.",
|
"postEditNotify": "You are about editing a post that already published.",
|
||||||
"reactionAdded": "Your reaction has been added.",
|
"reactionAdded": "Your reaction has been added.",
|
||||||
"reactionRemoved": "Your reaction has been removed.",
|
"reactionRemoved": "Your reaction has been removed.",
|
||||||
"chatMessagePlaceholder": "Write a message..."
|
"chatMessagePlaceholder": "Write a message...",
|
||||||
|
"chatMessageEditNotify": "You are about editing a message.",
|
||||||
|
"chatMessageReplyNotify": "You are about replying a message.",
|
||||||
|
"chatMessageDeleteConfirm": "Are you sure you want to delete this message? This operation cannot be revert and no local history is saved!"
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
"action": "操作",
|
"action": "操作",
|
||||||
"cancel": "取消",
|
"cancel": "取消",
|
||||||
"report": "举报",
|
"report": "举报",
|
||||||
|
"reply": "回复",
|
||||||
"reaction": "反应",
|
"reaction": "反应",
|
||||||
"reactVerb": "作出反应",
|
"reactVerb": "作出反应",
|
||||||
"post": "帖子",
|
"post": "帖子",
|
||||||
@ -35,5 +36,8 @@
|
|||||||
"postEditNotify": "你正在修改一个已经发布了的帖子。",
|
"postEditNotify": "你正在修改一个已经发布了的帖子。",
|
||||||
"reactionAdded": "你的反应已被添加。",
|
"reactionAdded": "你的反应已被添加。",
|
||||||
"reactionRemoved": "你的反应已被移除。",
|
"reactionRemoved": "你的反应已被移除。",
|
||||||
"chatMessagePlaceholder": "发条消息……"
|
"chatMessagePlaceholder": "发条消息……",
|
||||||
|
"chatMessageEditNotify": "你正在编辑信息中……",
|
||||||
|
"chatMessageReplyNotify": "你正在回复消息中……",
|
||||||
|
"chatMessageDeleteConfirm": "你确定要删除这条消息吗?这条消息将永远的从所有人的视图中消失,并且不会有本地消息记录保存!"
|
||||||
}
|
}
|
@ -10,6 +10,7 @@ import 'package:solian/providers/auth.dart';
|
|||||||
import 'package:solian/utils/service_url.dart';
|
import 'package:solian/utils/service_url.dart';
|
||||||
import 'package:solian/widgets/chat/maintainer.dart';
|
import 'package:solian/widgets/chat/maintainer.dart';
|
||||||
import 'package:solian/widgets/chat/message.dart';
|
import 'package:solian/widgets/chat/message.dart';
|
||||||
|
import 'package:solian/widgets/chat/message_action.dart';
|
||||||
import 'package:solian/widgets/chat/message_editor.dart';
|
import 'package:solian/widgets/chat/message_editor.dart';
|
||||||
import 'package:solian/widgets/indent_wrapper.dart';
|
import 'package:solian/widgets/indent_wrapper.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
@ -49,7 +50,7 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
if (!await auth.isAuthorized()) return;
|
if (!await auth.isAuthorized()) return;
|
||||||
|
|
||||||
final offset = pageKey;
|
final offset = pageKey;
|
||||||
const take = 5;
|
const take = 10;
|
||||||
|
|
||||||
var uri = getRequestUri(
|
var uri = getRequestUri(
|
||||||
'messaging',
|
'messaging',
|
||||||
@ -96,6 +97,25 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Message? _editingItem;
|
||||||
|
Message? _replyingItem;
|
||||||
|
|
||||||
|
void viewActions(Message item) {
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ChatMessageAction(
|
||||||
|
channel: widget.alias,
|
||||||
|
item: item,
|
||||||
|
onEdit: () => setState(() {
|
||||||
|
_editingItem = item;
|
||||||
|
}),
|
||||||
|
onReply: () => setState(() {
|
||||||
|
_replyingItem = item;
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
@ -128,24 +148,35 @@ class _ChatScreenState extends State<ChatScreen> {
|
|||||||
if (index + 1 < (_pagingController.itemList?.length ?? 0)) {
|
if (index + 1 < (_pagingController.itemList?.length ?? 0)) {
|
||||||
isMerged = getMessageMergeable(item, _pagingController.itemList?[index + 1]);
|
isMerged = getMessageMergeable(item, _pagingController.itemList?[index + 1]);
|
||||||
}
|
}
|
||||||
return Container(
|
return InkWell(
|
||||||
padding: EdgeInsets.only(
|
child: Container(
|
||||||
top: !isMerged ? 8 : 0,
|
padding: EdgeInsets.only(
|
||||||
bottom: !hasMerged ? 8 : 0,
|
top: !isMerged ? 8 : 0,
|
||||||
left: 12,
|
bottom: !hasMerged ? 8 : 0,
|
||||||
right: 12,
|
left: 12,
|
||||||
),
|
right: 12,
|
||||||
child: ChatMessage(
|
),
|
||||||
key: Key('m${item.id}'),
|
child: ChatMessage(
|
||||||
item: item,
|
key: Key('m${item.id}'),
|
||||||
underMerged: isMerged,
|
item: item,
|
||||||
|
underMerged: isMerged,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
onLongPress: () => viewActions(item),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ChatMessageEditor(channel: widget.alias),
|
ChatMessageEditor(
|
||||||
|
channel: widget.alias,
|
||||||
|
editing: _editingItem,
|
||||||
|
replying: _replyingItem,
|
||||||
|
onReset: () => setState(() {
|
||||||
|
_editingItem = null;
|
||||||
|
_replyingItem = null;
|
||||||
|
}),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
onInsertMessage: (message) => addMessage(message),
|
onInsertMessage: (message) => addMessage(message),
|
||||||
|
@ -75,11 +75,9 @@ class _ExploreScreenState extends State<ExploreScreen> {
|
|||||||
pagingController: _pagingController,
|
pagingController: _pagingController,
|
||||||
separatorBuilder: (context, index) => const Divider(thickness: 0.3),
|
separatorBuilder: (context, index) => const Divider(thickness: 0.3),
|
||||||
builderDelegate: PagedChildBuilderDelegate<Post>(
|
builderDelegate: PagedChildBuilderDelegate<Post>(
|
||||||
itemBuilder: (context, item, index) => GestureDetector(
|
itemBuilder: (context, item, index) => PostItem(
|
||||||
child: PostItem(
|
item: item,
|
||||||
item: item,
|
onUpdate: () => _pagingController.refresh(),
|
||||||
onUpdate: () => _pagingController.refresh(),
|
|
||||||
),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
router.pushNamed(
|
router.pushNamed(
|
||||||
'posts.screen',
|
'posts.screen',
|
||||||
|
@ -60,6 +60,7 @@ class _PostScreenState extends State<PostScreen> {
|
|||||||
child: PostItem(
|
child: PostItem(
|
||||||
item: snapshot.data!,
|
item: snapshot.data!,
|
||||||
brief: false,
|
brief: false,
|
||||||
|
ripple: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
|
109
lib/widgets/chat/message_action.dart
Normal file
109
lib/widgets/chat/message_action.dart
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:solian/models/message.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';
|
||||||
|
import 'package:solian/screens/posts/comment_editor.dart';
|
||||||
|
import 'package:solian/widgets/chat/message_deletion.dart';
|
||||||
|
import 'package:solian/widgets/posts/item_deletion.dart';
|
||||||
|
|
||||||
|
class ChatMessageAction extends StatelessWidget {
|
||||||
|
final String channel;
|
||||||
|
final Message item;
|
||||||
|
final Function? onEdit;
|
||||||
|
final Function? onReply;
|
||||||
|
|
||||||
|
const ChatMessageAction({
|
||||||
|
super.key,
|
||||||
|
required this.channel,
|
||||||
|
required this.item,
|
||||||
|
this.onEdit,
|
||||||
|
this.onReply,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final auth = context.read<AuthProvider>();
|
||||||
|
|
||||||
|
return SizedBox(
|
||||||
|
height: 320,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.only(left: 20, top: 20, bottom: 8),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)!.action,
|
||||||
|
style: Theme.of(context).textTheme.headlineSmall,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Message ID #${item.id.toString().padLeft(8, '0')}',
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
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: () {
|
||||||
|
if (onEdit != null) onEdit!();
|
||||||
|
if (Navigator.canPop(context)) Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.delete),
|
||||||
|
title: Text(AppLocalizations.of(context)!.delete),
|
||||||
|
onTap: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => ChatMessageDeletionDialog(
|
||||||
|
item: item,
|
||||||
|
channel: channel,
|
||||||
|
),
|
||||||
|
).then((did) {
|
||||||
|
if (did == true && Navigator.canPop(context)) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
return ListView(
|
||||||
|
children: [
|
||||||
|
...(snapshot.data['id'] == item.sender.accountId ? authorizedItems : List.empty()),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.reply),
|
||||||
|
title: Text(AppLocalizations.of(context)!.reply),
|
||||||
|
onTap: () {
|
||||||
|
if (onReply != null) onReply!();
|
||||||
|
if (Navigator.canPop(context)) Navigator.pop(context);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
64
lib/widgets/chat/message_deletion.dart
Normal file
64
lib/widgets/chat/message_deletion.dart
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:solian/models/message.dart';
|
||||||
|
import 'package:solian/models/post.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:solian/providers/auth.dart';
|
||||||
|
import 'package:solian/utils/service_url.dart';
|
||||||
|
|
||||||
|
class ChatMessageDeletionDialog extends StatefulWidget {
|
||||||
|
final String channel;
|
||||||
|
final Message item;
|
||||||
|
|
||||||
|
const ChatMessageDeletionDialog({
|
||||||
|
super.key,
|
||||||
|
required this.item,
|
||||||
|
required this.channel,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ChatMessageDeletionDialog> createState() => _ChatMessageDeletionDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ChatMessageDeletionDialogState extends State<ChatMessageDeletionDialog> {
|
||||||
|
bool _isSubmitting = false;
|
||||||
|
|
||||||
|
void doDeletion(BuildContext context) async {
|
||||||
|
final auth = context.read<AuthProvider>();
|
||||||
|
if (!await auth.isAuthorized()) return;
|
||||||
|
|
||||||
|
final uri = getRequestUri('messaging', '/api/channels/${widget.channel}/messages/${widget.item.id}');
|
||||||
|
|
||||||
|
setState(() => _isSubmitting = true);
|
||||||
|
final res = await auth.client!.delete(uri);
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
var message = utf8.decode(res.bodyBytes);
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(content: Text("Something went wrong... $message")),
|
||||||
|
);
|
||||||
|
setState(() => _isSubmitting = false);
|
||||||
|
} else {
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(AppLocalizations.of(context)!.confirmation),
|
||||||
|
content: Text(AppLocalizations.of(context)!.chatMessageDeleteConfirm),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isSubmitting ? null : () => Navigator.pop(context, false),
|
||||||
|
child: Text(AppLocalizations.of(context)!.confirmCancel),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isSubmitting ? null : () => doDeletion(context),
|
||||||
|
child: Text(AppLocalizations.of(context)!.confirmOkay),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -11,8 +11,10 @@ import 'package:solian/utils/service_url.dart';
|
|||||||
class ChatMessageEditor extends StatefulWidget {
|
class ChatMessageEditor extends StatefulWidget {
|
||||||
final String channel;
|
final String channel;
|
||||||
final Message? editing;
|
final Message? editing;
|
||||||
|
final Message? replying;
|
||||||
|
final Function? onReset;
|
||||||
|
|
||||||
const ChatMessageEditor({super.key, required this.channel, this.editing});
|
const ChatMessageEditor({super.key, required this.channel, this.editing, this.replying, this.onReset});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChatMessageEditor> createState() => _ChatMessageEditorState();
|
State<ChatMessageEditor> createState() => _ChatMessageEditorState();
|
||||||
@ -37,6 +39,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
|||||||
req.headers['Content-Type'] = 'application/json';
|
req.headers['Content-Type'] = 'application/json';
|
||||||
req.body = jsonEncode(<String, dynamic>{
|
req.body = jsonEncode(<String, dynamic>{
|
||||||
'content': _textController.value.text,
|
'content': _textController.value.text,
|
||||||
|
'reply_to': widget.replying?.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
setState(() => _isSubmitting = true);
|
setState(() => _isSubmitting = true);
|
||||||
@ -54,41 +57,96 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
|||||||
|
|
||||||
void reset() {
|
void reset() {
|
||||||
_textController.clear();
|
_textController.clear();
|
||||||
|
|
||||||
|
if (widget.onReset != null) widget.onReset!();
|
||||||
|
}
|
||||||
|
|
||||||
|
void syncWidget() {
|
||||||
|
if (widget.editing != null) {
|
||||||
|
setState(() {
|
||||||
|
_textController.text = widget.editing!.content;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(oldWidget) {
|
||||||
|
syncWidget();
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container(
|
final editingBanner = MaterialBanner(
|
||||||
height: 56,
|
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 20),
|
||||||
padding: const EdgeInsets.all(12),
|
leading: const Icon(Icons.edit_note),
|
||||||
decoration: const BoxDecoration(
|
backgroundColor: const Color(0xFFE0E0E0),
|
||||||
border: Border(
|
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
||||||
top: BorderSide(width: 0.3, color: Color(0xffdedede)),
|
content: Text(AppLocalizations.of(context)!.chatMessageEditNotify),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
|
onPressed: () => reset(),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
child: Row(
|
);
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
final replyingBanner = MaterialBanner(
|
||||||
Expanded(
|
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 20),
|
||||||
child: TextField(
|
leading: const Icon(Icons.reply),
|
||||||
controller: _textController,
|
backgroundColor: const Color(0xFFE0E0E0),
|
||||||
maxLines: null,
|
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
||||||
autofocus: true,
|
content: Text(AppLocalizations.of(context)!.chatMessageReplyNotify),
|
||||||
autocorrect: true,
|
actions: [
|
||||||
keyboardType: TextInputType.text,
|
TextButton(
|
||||||
decoration: InputDecoration.collapsed(
|
child: Text(AppLocalizations.of(context)!.cancel),
|
||||||
hintText: AppLocalizations.of(context)!.chatMessagePlaceholder,
|
onPressed: () => reset(),
|
||||||
),
|
),
|
||||||
onSubmitted: (_) => sendMessage(context),
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
widget.editing != null ? editingBanner : Container(),
|
||||||
|
widget.replying != null ? replyingBanner : Container(),
|
||||||
|
Container(
|
||||||
|
height: 56,
|
||||||
|
padding: const EdgeInsets.only(top: 4, left: 16, right: 8),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
top: BorderSide(width: 0.3, color: Color(0xffdedede)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
TextButton(
|
child: Row(
|
||||||
style: TextButton.styleFrom(shape: const CircleBorder(), padding: const EdgeInsets.all(4)),
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
onPressed: !_isSubmitting ? () => sendMessage(context) : null,
|
children: [
|
||||||
child: const Icon(Icons.send),
|
Expanded(
|
||||||
)
|
child: TextField(
|
||||||
],
|
controller: _textController,
|
||||||
),
|
maxLines: null,
|
||||||
|
autofocus: true,
|
||||||
|
autocorrect: true,
|
||||||
|
keyboardType: TextInputType.text,
|
||||||
|
decoration: InputDecoration.collapsed(
|
||||||
|
hintText: AppLocalizations.of(context)!.chatMessagePlaceholder,
|
||||||
|
),
|
||||||
|
onSubmitted: (_) => sendMessage(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(shape: const CircleBorder(), padding: const EdgeInsets.all(4)),
|
||||||
|
onPressed: !_isSubmitting ? () => sendMessage(context) : null,
|
||||||
|
child: const Icon(Icons.send),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,15 +13,19 @@ 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 bool? ripple;
|
||||||
final Function? onUpdate;
|
final Function? onUpdate;
|
||||||
final Function? onDelete;
|
final Function? onDelete;
|
||||||
|
final Function? onTap;
|
||||||
|
|
||||||
const PostItem({
|
const PostItem({
|
||||||
super.key,
|
super.key,
|
||||||
required this.item,
|
required this.item,
|
||||||
this.brief,
|
this.brief,
|
||||||
|
this.ripple,
|
||||||
this.onUpdate,
|
this.onUpdate,
|
||||||
this.onDelete,
|
this.onDelete,
|
||||||
|
this.onTap,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -31,7 +35,7 @@ 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) {
|
void viewActions() {
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => PostItemAction(
|
builder: (context) => PostItemAction(
|
||||||
@ -41,9 +45,8 @@ class _PostItemState extends State<PostItem> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void viewComments(BuildContext context) {
|
void viewComments() {
|
||||||
final PagingController<int, Post> commentPaging =
|
final PagingController<int, Post> commentPaging = PagingController(firstPageKey: 0);
|
||||||
PagingController(firstPageKey: 0);
|
|
||||||
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
context: context,
|
context: context,
|
||||||
@ -83,8 +86,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
Widget renderAttachments() {
|
Widget renderAttachments() {
|
||||||
if (widget.item.modelType == 'article') return Container();
|
if (widget.item.modelType == 'article') return Container();
|
||||||
|
|
||||||
if (widget.item.attachments != null &&
|
if (widget.item.attachments != null && widget.item.attachments!.isNotEmpty) {
|
||||||
widget.item.attachments!.isNotEmpty) {
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: AttachmentList(items: widget.item.attachments!, provider: 'interactive'),
|
child: AttachmentList(items: widget.item.attachments!, provider: 'interactive'),
|
||||||
@ -105,7 +107,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
avatar: const Icon(Icons.comment),
|
avatar: const Icon(Icons.comment),
|
||||||
label: Text(widget.item.commentCount.toString()),
|
label: Text(widget.item.commentCount.toString()),
|
||||||
tooltip: AppLocalizations.of(context)!.comment,
|
tooltip: AppLocalizations.of(context)!.comment,
|
||||||
onPressed: () => viewComments(context),
|
onPressed: () => viewComments(),
|
||||||
),
|
),
|
||||||
const VerticalDivider(thickness: 0.3, indent: 8, endIndent: 8),
|
const VerticalDivider(thickness: 0.3, indent: 8, endIndent: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -127,9 +129,8 @@ class _PostItemState extends State<PostItem> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String getAuthorDescribe() => widget.item.author.description.isNotEmpty
|
String getAuthorDescribe() =>
|
||||||
? widget.item.author.description
|
widget.item.author.description.isNotEmpty ? widget.item.author.description : 'No description yet.';
|
||||||
: 'No description yet.';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@ -174,8 +175,7 @@ class _PostItemState extends State<PostItem> {
|
|||||||
children: [
|
children: [
|
||||||
...headingParts,
|
...headingParts,
|
||||||
Padding(
|
Padding(
|
||||||
padding:
|
padding: const EdgeInsets.only(left: 12, right: 12, top: 4),
|
||||||
const EdgeInsets.only(left: 12, right: 12, top: 4),
|
|
||||||
child: renderContent(),
|
child: renderContent(),
|
||||||
),
|
),
|
||||||
renderAttachments(),
|
renderAttachments(),
|
||||||
@ -240,11 +240,24 @@ class _PostItemState extends State<PostItem> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return GestureDetector(
|
final ripple = widget.ripple ?? true;
|
||||||
child: content,
|
|
||||||
onLongPress: () {
|
if (ripple) {
|
||||||
viewActions(context);
|
return InkWell(
|
||||||
},
|
child: content,
|
||||||
);
|
onTap: () {
|
||||||
|
if (widget.onTap != null) widget.onTap!();
|
||||||
|
},
|
||||||
|
onLongPress: () => viewActions(),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return GestureDetector(
|
||||||
|
child: content,
|
||||||
|
onTap: () {
|
||||||
|
if (widget.onTap != null) widget.onTap!();
|
||||||
|
},
|
||||||
|
onLongPress: () => viewActions(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -81,11 +81,10 @@ class PostItemAction extends StatelessWidget {
|
|||||||
builder: (context) => ItemDeletionDialog(
|
builder: (context) => ItemDeletionDialog(
|
||||||
item: item,
|
item: item,
|
||||||
dataset: dataset,
|
dataset: dataset,
|
||||||
onDelete: (did) {
|
|
||||||
if (did == true && onDelete != null) onDelete!();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
).then((did) {
|
||||||
|
if (did == true && onDelete != null) onDelete!();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
@ -10,13 +10,11 @@ import 'package:solian/utils/service_url.dart';
|
|||||||
class ItemDeletionDialog extends StatefulWidget {
|
class ItemDeletionDialog extends StatefulWidget {
|
||||||
final Post item;
|
final Post item;
|
||||||
final String dataset;
|
final String dataset;
|
||||||
final Function? onDelete;
|
|
||||||
|
|
||||||
const ItemDeletionDialog({
|
const ItemDeletionDialog({
|
||||||
super.key,
|
super.key,
|
||||||
required this.item,
|
required this.item,
|
||||||
required this.dataset,
|
required this.dataset,
|
||||||
this.onDelete,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
Loading…
Reference in New Issue
Block a user