💄 Better user send message experience

This commit is contained in:
LittleSheep 2024-05-07 21:49:24 +08:00
parent b3e266d564
commit dffa0077de
7 changed files with 56 additions and 19 deletions

View File

@ -123,6 +123,7 @@
"chatCallDisconnectConfirm": "Are you sure you want to disconnect? You can reconnect after this if you want.",
"chatCallChangeSpeaker": "Change Speaker",
"chatMessagePlaceholder": "Write a message...",
"chatMessageSending": "Now delivering your messages...",
"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!"

View File

@ -123,6 +123,7 @@
"chatCallDisconnect": "断开连接",
"chatCallDisconnectConfirm": "你确定你要断开连接吗?你可以之后在任何时候重新连接。",
"chatMessagePlaceholder": "发条消息……",
"chatMessageSending": "正在送出你的信息……",
"chatMessageEditNotify": "你正在编辑信息中……",
"chatMessageReplyNotify": "你正在回复消息中……",
"chatMessageDeleteConfirm": "你确定要删除这条消息吗?这条消息将永远的从所有人的视图中消失,并且不会有本地消息记录保存!"

View File

@ -8,8 +8,6 @@ class Channel {
String alias;
String name;
String description;
dynamic members;
dynamic calls;
int type;
Account account;
int accountId;
@ -25,8 +23,6 @@ class Channel {
required this.alias,
required this.name,
required this.description,
this.members,
this.calls,
required this.type,
required this.account,
required this.accountId,
@ -41,8 +37,6 @@ class Channel {
alias: json['alias'],
name: json['name'],
description: json['description'],
members: json['members'],
calls: json['calls'],
type: json['type'],
account: Account.fromJson(json['account']),
accountId: json['account_id'],
@ -57,8 +51,6 @@ class Channel {
'alias': alias,
'name': name,
'description': description,
'members': members,
'calls': calls,
'type': type,
'account': account,
'account_id': accountId,

View File

@ -8,7 +8,7 @@ class Message {
DateTime updatedAt;
DateTime? deletedAt;
String content;
dynamic metadata;
Map<String, dynamic>? metadata;
int type;
List<Attachment>? attachments;
Channel? channel;
@ -18,6 +18,8 @@ class Message {
int channelId;
int senderId;
bool isSending = false;
Message({
required this.id,
required this.createdAt,

View File

@ -1,6 +1,8 @@
import 'dart:convert';
import 'package:easy_debounce/easy_debounce.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:http/http.dart';
import 'package:provider/provider.dart';
@ -36,7 +38,8 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
final _textController = TextEditingController();
final _focusNode = FocusNode();
bool _isSubmitting = false;
List<int> _pendingMessages = List.empty(growable: true);
int? _prevEditingId;
List<Attachment> _attachments = List.empty(growable: true);
@ -53,8 +56,6 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
}
Future<void> sendMessage(BuildContext context) async {
if (_isSubmitting) return;
_focusNode.requestFocus();
final auth = context.read<AuthProvider>();
@ -72,15 +73,25 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
'reply_to': widget.replying?.id,
});
setState(() => _isSubmitting = true);
reset();
final messageMarkId = DateTime.now().microsecondsSinceEpoch >> 10;
final messageDebounceId = 'm-pending$messageMarkId';
EasyDebounce.debounce(messageDebounceId, 350.ms, () {
setState(() => _pendingMessages.add(messageMarkId));
});
var res = await Response.fromStream(await auth.client!.send(req));
if (res.statusCode != 200) {
var message = utf8.decode(res.bodyBytes);
context.showErrorDialog(message);
} else {
reset();
}
setState(() => _isSubmitting = false);
EasyDebounce.cancel(messageDebounceId);
if (_pendingMessages.isNotEmpty) {
setState(() => _pendingMessages.remove(messageMarkId));
}
}
void reset() {
@ -94,8 +105,8 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
if (widget.editing != null && _prevEditingId != widget.editing!.id) {
setState(() {
_prevEditingId = widget.editing!.id;
_textController.text = widget.editing!.content;
_attachments = widget.editing!.attachments ?? List.empty(growable: true);
_textController.text = widget.editing!.content;
});
}
}
@ -113,6 +124,15 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
@override
Widget build(BuildContext context) {
final sendingBanner = MaterialBanner(
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20),
leading: const Icon(Icons.schedule_send),
backgroundColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.9),
dividerColor: const Color.fromARGB(1, 0, 0, 0),
content: Text('${AppLocalizations.of(context)!.chatMessageSending} (${_pendingMessages.length})'),
actions: const [SizedBox()],
);
final editingBanner = MaterialBanner(
padding: const EdgeInsets.only(top: 4, bottom: 4, left: 20),
leading: const Icon(Icons.edit_note),
@ -143,6 +163,18 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
return Column(
children: [
_pendingMessages.isNotEmpty
? sendingBanner
.animate()
.scaleY(
begin: 0,
curve: Curves.fastEaseInToSlowEaseOut,
)
.slideY(
begin: 1,
curve: Curves.fastEaseInToSlowEaseOut,
)
: Container(),
widget.editing != null ? editingBanner : Container(),
widget.replying != null ? replyingBanner : Container(),
Container(
@ -162,7 +194,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
position: badge.BadgePosition.custom(top: -2, end: 8),
child: TextButton(
style: TextButton.styleFrom(shape: const CircleBorder(), padding: const EdgeInsets.all(4)),
onPressed: !_isSubmitting ? () => viewAttachments(context) : null,
onPressed: () => viewAttachments(context),
child: const Icon(Icons.attach_file),
),
),
@ -182,7 +214,7 @@ class _ChatMessageEditorState extends State<ChatMessageEditor> {
),
TextButton(
style: TextButton.styleFrom(shape: const CircleBorder(), padding: const EdgeInsets.all(4)),
onPressed: !_isSubmitting ? () => sendMessage(context) : null,
onPressed: () => sendMessage(context),
child: const Icon(Icons.send),
)
],

View File

@ -209,6 +209,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.0"
easy_debounce:
dependency: "direct main"
description:
name: easy_debounce
sha256: f082609cfb8f37defb9e37fc28bc978c6712dedf08d4c5a26f820fa10165a236
url: "https://pub.dev"
source: hosted
version: "2.0.3"
fake_async:
dependency: transitive
description:

View File

@ -71,6 +71,7 @@ dependencies:
package_info_plus: ^7.0.0
cached_network_image: ^3.3.1
desktop_drop: ^0.4.4
easy_debounce: ^2.0.3
dev_dependencies:
flutter_test: