💄 Better user send message experience
This commit is contained in:
parent
b3e266d564
commit
dffa0077de
@ -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!"
|
||||
|
@ -123,6 +123,7 @@
|
||||
"chatCallDisconnect": "断开连接",
|
||||
"chatCallDisconnectConfirm": "你确定你要断开连接吗?你可以之后在任何时候重新连接。",
|
||||
"chatMessagePlaceholder": "发条消息……",
|
||||
"chatMessageSending": "正在送出你的信息……",
|
||||
"chatMessageEditNotify": "你正在编辑信息中……",
|
||||
"chatMessageReplyNotify": "你正在回复消息中……",
|
||||
"chatMessageDeleteConfirm": "你确定要删除这条消息吗?这条消息将永远的从所有人的视图中消失,并且不会有本地消息记录保存!"
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
)
|
||||
],
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user