✨ Edit, delete and reply messages!
This commit is contained in:
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 {
|
||||
final String channel;
|
||||
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
|
||||
State<ChatMessageEditor> createState() => _ChatMessageEditorState();
|
||||
@ -37,6 +39,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
||||
req.headers['Content-Type'] = 'application/json';
|
||||
req.body = jsonEncode(<String, dynamic>{
|
||||
'content': _textController.value.text,
|
||||
'reply_to': widget.replying?.id,
|
||||
});
|
||||
|
||||
setState(() => _isSubmitting = true);
|
||||
@ -54,41 +57,96 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
|
||||
|
||||
void reset() {
|
||||
_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
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: 56,
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: const BoxDecoration(
|
||||
border: Border(
|
||||
top: BorderSide(width: 0.3, color: Color(0xffdedede)),
|
||||
final editingBanner = MaterialBanner(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 20),
|
||||
leading: const Icon(Icons.edit_note),
|
||||
backgroundColor: const Color(0xFFE0E0E0),
|
||||
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
||||
content: Text(AppLocalizations.of(context)!.chatMessageEditNotify),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
onPressed: () => reset(),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
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),
|
||||
],
|
||||
);
|
||||
|
||||
final replyingBanner = MaterialBanner(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 20),
|
||||
leading: const Icon(Icons.reply),
|
||||
backgroundColor: const Color(0xFFE0E0E0),
|
||||
dividerColor: const Color.fromARGB(1, 0, 0, 0),
|
||||
content: Text(AppLocalizations.of(context)!.chatMessageReplyNotify),
|
||||
actions: [
|
||||
TextButton(
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
onPressed: () => reset(),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
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(
|
||||
style: TextButton.styleFrom(shape: const CircleBorder(), padding: const EdgeInsets.all(4)),
|
||||
onPressed: !_isSubmitting ? () => sendMessage(context) : null,
|
||||
child: const Icon(Icons.send),
|
||||
)
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
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),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user