1
0
mirror of https://gitlab.com/comunic/comunicmobile synced 2024-11-25 06:19:22 +00:00
comunicmobile/lib/ui/screens/conversation_screen.dart

756 lines
24 KiB
Dart
Raw Normal View History

2019-04-27 07:52:16 +00:00
import 'dart:async';
2019-04-25 06:56:16 +00:00
import 'package:comunic/helpers/conversations_helper.dart';
2020-04-19 11:58:24 +00:00
import 'package:comunic/helpers/events_helper.dart';
2021-03-12 15:37:21 +00:00
import 'package:comunic/helpers/server_config_helper.dart';
2019-04-25 06:56:16 +00:00
import 'package:comunic/helpers/users_helper.dart';
import 'package:comunic/lists/conversation_messages_list.dart';
import 'package:comunic/lists/users_list.dart';
2021-03-12 18:36:42 +00:00
import 'package:comunic/models/api_request.dart';
2021-04-25 14:35:50 +00:00
import 'package:comunic/models/config.dart';
2021-03-10 23:23:11 +00:00
import 'package:comunic/models/conversation.dart';
2019-05-04 06:58:14 +00:00
import 'package:comunic/models/conversation_message.dart';
2019-04-25 07:48:52 +00:00
import 'package:comunic/models/new_conversation_message.dart';
import 'package:comunic/ui/dialogs/pick_file_dialog.dart';
2021-03-11 16:27:20 +00:00
import 'package:comunic/ui/routes/main_route/main_route.dart';
2019-04-25 06:56:16 +00:00
import 'package:comunic/ui/tiles/conversation_message_tile.dart';
import 'package:comunic/ui/tiles/server_conversation_message_tile.dart';
2021-03-13 07:26:56 +00:00
import 'package:comunic/ui/widgets/account_image_widget.dart';
2020-04-19 11:42:47 +00:00
import 'package:comunic/ui/widgets/safe_state.dart';
2019-04-27 16:29:30 +00:00
import 'package:comunic/ui/widgets/scroll_watcher.dart';
2021-03-13 08:29:54 +00:00
import 'package:comunic/ui/widgets/user_writing_in_conv_notifier.dart';
import 'package:comunic/utils/account_utils.dart';
2021-03-13 07:17:54 +00:00
import 'package:comunic/utils/date_utils.dart';
2021-03-12 18:36:42 +00:00
import 'package:comunic/utils/files_utils.dart';
2019-04-25 06:56:16 +00:00
import 'package:comunic/utils/intl_utils.dart';
import 'package:comunic/utils/list_utils.dart';
2021-03-12 16:47:09 +00:00
import 'package:comunic/utils/log_utils.dart';
2019-04-25 06:56:16 +00:00
import 'package:comunic/utils/ui_utils.dart';
2021-03-12 18:36:42 +00:00
import 'package:comunic/utils/video_utils.dart';
2021-03-13 08:00:00 +00:00
import 'package:dio/dio.dart';
2022-03-10 17:31:29 +00:00
import 'package:emoji_picker_flutter/emoji_picker_flutter.dart' as emoji;
2019-04-25 06:56:16 +00:00
import 'package:flutter/material.dart';
2021-03-12 18:36:42 +00:00
import 'package:mime/mime.dart';
2019-04-25 06:56:16 +00:00
/// Conversation screen
///
/// @author Pierre HUBERT
enum ErrorLevel { NONE, MINOR, MAJOR }
2019-04-27 16:29:30 +00:00
enum _OlderMessagesLevel { NONE, LOADING, NO_MORE_AVAILABLE }
2019-04-25 06:56:16 +00:00
class ConversationScreen extends StatefulWidget {
final int conversationID;
const ConversationScreen({Key? key, required this.conversationID})
2019-04-25 06:56:16 +00:00
: assert(conversationID != null),
super(key: key);
@override
State<StatefulWidget> createState() => _ConversationScreenState();
}
2020-04-19 11:42:47 +00:00
class _ConversationScreenState extends SafeState<ConversationScreen> {
2019-04-25 07:48:52 +00:00
//Helpers
2019-04-25 06:56:16 +00:00
final ConversationsHelper _conversationsHelper = ConversationsHelper();
final UsersHelper _usersHelper = UsersHelper();
2019-04-25 07:48:52 +00:00
// Class members
late Conversation _conversation;
ConversationMessagesList? _messages;
2019-04-25 06:56:16 +00:00
UsersList _usersInfo = UsersList();
ErrorLevel _error = ErrorLevel.NONE;
2021-03-12 21:18:32 +00:00
final _textFieldFocus = FocusNode();
bool _showEmojiPicker = false;
2021-03-12 16:47:09 +00:00
2019-04-25 07:48:52 +00:00
bool _isSendingMessage = false;
2021-03-12 21:18:32 +00:00
TextEditingController _textController = TextEditingController();
ScrollWatcher? _scrollController;
2019-04-27 16:29:30 +00:00
_OlderMessagesLevel _loadingOlderMessages = _OlderMessagesLevel.NONE;
2021-03-13 08:09:26 +00:00
int _lastWritingEventSent = 0;
CancelToken? _sendCancel;
double? _sendProgress;
2021-03-13 08:00:00 +00:00
2021-03-12 21:18:32 +00:00
String get textMessage => _textController.text;
2021-03-12 16:47:09 +00:00
bool get _isMessageValid =>
textMessage.length >=
ServerConfigurationHelper.config!.conversationsPolicy.minMessageLen &&
2021-03-12 16:47:09 +00:00
textMessage.length <
ServerConfigurationHelper.config!.conversationsPolicy.maxMessageLen;
2021-03-12 16:47:09 +00:00
2021-03-12 21:18:32 +00:00
showKeyboard() => _textFieldFocus.requestFocus();
hideKeyboard() => _textFieldFocus.unfocus();
hideEmojiContainer() => setState(() => _showEmojiPicker = false);
showEmojiContainer() => setState(() => _showEmojiPicker = true);
2021-03-12 21:40:33 +00:00
// Colors definition
2021-03-13 07:17:54 +00:00
Color get _senderColor =>
_conversation.color ??
2021-04-25 14:35:50 +00:00
config().defaultConversationColor ??
2021-03-13 07:41:54 +00:00
/*(darkTheme() ? Color(0xff2b343b) :*/ Colors.blue.shade700; //);
2021-03-13 07:17:54 +00:00
2021-03-12 21:40:33 +00:00
Color get _receiverColor =>
darkTheme() ? Color(0xff3a3d40) : Colors.grey.shade600;
Color get _greyColor => Color(0xff8f8f8f);
Color get _gradientColorStart =>
2021-04-25 14:35:50 +00:00
(_conversation.color ?? config().defaultConversationColor)
?.withOpacity(0.7) ??
2021-03-12 22:09:40 +00:00
(darkTheme() ? Color(0xff00b6f3) : Colors.blue.shade300);
2021-03-12 21:40:33 +00:00
Color get _gradientColorEnd =>
2021-04-25 14:35:50 +00:00
_conversation.color ??
config().defaultConversationColor ??
2021-03-12 22:09:40 +00:00
(darkTheme() ? Color(0xff0184dc) : Colors.blueAccent.shade700);
2021-03-12 21:40:33 +00:00
Color get _separatorColor =>
darkTheme() ? Color(0xff272c35) : Color(0xffBEBEBE);
LinearGradient get _fabGradient => LinearGradient(
colors: [_gradientColorStart, _gradientColorEnd],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
LinearGradient get _disabledGradient => LinearGradient(
colors: [_greyColor, _receiverColor],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
);
2019-04-27 16:29:30 +00:00
@override
void initState() {
super.initState();
2020-04-19 11:42:47 +00:00
_init();
2019-04-27 16:29:30 +00:00
}
2019-04-25 06:56:16 +00:00
@override
2020-04-19 11:42:47 +00:00
void dispose() {
super.dispose();
_deallocate();
2019-04-27 07:52:16 +00:00
}
2019-04-25 06:56:16 +00:00
void _setError(ErrorLevel err) => setState(() => _error = err);
2019-04-25 07:48:52 +00:00
void _setSending(bool sending) => setState(() => _isSendingMessage = sending);
2019-04-27 16:29:30 +00:00
void _setLoadingOlderMessagesState(_OlderMessagesLevel state) => setState(() {
_loadingOlderMessages = state;
});
2019-04-25 06:56:16 +00:00
/// Method called when an error occurred while loading messages
2021-03-10 16:54:41 +00:00
void _errorLoading() =>
_setError(_messages == null ? ErrorLevel.MAJOR : ErrorLevel.MINOR);
2019-04-25 06:56:16 +00:00
/// Load the first conversations
2020-04-19 11:42:47 +00:00
Future<void> _init() async {
_scrollController = ScrollWatcher(onReachBottom: _loadOlderMessages);
2021-03-10 23:23:11 +00:00
_conversation =
await ConversationsHelper().getSingle(widget.conversationID);
2020-04-19 11:42:47 +00:00
// Fetch latest messages
await _loadMessages(false);
await _loadMessages(true);
2019-04-27 07:52:16 +00:00
2020-04-19 11:42:47 +00:00
await _conversationsHelper
.registerConversationEvents(widget.conversationID);
2020-04-19 11:58:24 +00:00
this.listen<NewConversationMessageEvent>((ev) async {
2021-03-10 16:54:41 +00:00
if (ev.msg.convID == widget.conversationID) {
try {
await _conversationsHelper.saveMessage(ev.msg);
await _applyNewMessages(ConversationMessagesList()..add(ev.msg));
} catch (e, s) {
print("Failed to show new message! $e => $s");
_errorLoading();
}
2020-04-19 12:16:35 +00:00
}
});
this.listen<UpdatedConversationMessageEvent>((ev) async {
2021-03-10 16:54:41 +00:00
if (ev.msg.convID == widget.conversationID) {
2020-04-19 12:16:35 +00:00
await _conversationsHelper.saveMessage(ev.msg);
setState(() => _messages!.replace(ev.msg));
2020-04-19 12:16:35 +00:00
}
2020-04-19 11:58:24 +00:00
});
2020-04-19 12:29:01 +00:00
this.listen<DeletedConversationMessageEvent>((ev) async {
2021-03-10 16:54:41 +00:00
if (ev.msg.convID == widget.conversationID) {
await _conversationsHelper.removeMessage(ev.msg);
setState(() => _messages!.removeMsg(ev.msg.id));
2020-04-19 12:29:01 +00:00
}
});
2021-03-13 12:52:18 +00:00
this.listen<RemovedUserFromConversationEvent>((ev) {
if (ev.userID == userID() && ev.convID == widget.conversationID) {
setState(() => _error = ErrorLevel.MAJOR);
}
});
2021-03-13 12:52:18 +00:00
this.listen<DeletedConversationEvent>((ev) {
if (ev.convID == widget.conversationID) {
setState(() => _error = ErrorLevel.MAJOR);
}
});
2020-04-19 11:42:47 +00:00
}
/// Free resources when this conversation widget is no longer required
void _deallocate() {
_conversationsHelper.unregisterConversationEvents(widget.conversationID);
}
2019-04-25 06:56:16 +00:00
/// Load a list of messages
Future<void> _loadMessages(bool online) async {
if (!mounted) return;
2021-03-10 16:54:41 +00:00
try {
//First, get the messages
final messages = await _conversationsHelper.getNewMessages(
2019-04-27 06:51:58 +00:00
conversationID: widget.conversationID,
lastMessageID: _messages == null ? 0 : _messages!.lastMessageID,
2021-03-10 16:54:41 +00:00
online: online,
);
2019-04-25 06:56:16 +00:00
2021-03-10 16:54:41 +00:00
// In case we are offline and we did not get any message we do not do
// anything (we wait for the online request)
if (messages.length == 0 && !online) return;
2021-03-10 16:54:41 +00:00
await _applyNewMessages(messages);
} catch (e, s) {
debugPrint("Failed to load messages! $e => $s", wrapWidth: 4096);
2021-03-10 16:54:41 +00:00
_errorLoading();
}
2019-04-27 16:29:30 +00:00
}
/// Get older messages
Future<void> _loadOlderMessages() async {
if (_loadingOlderMessages != _OlderMessagesLevel.NONE ||
_messages == null ||
_messages!.length == 0) return;
2021-03-10 16:54:41 +00:00
try {
// Let's start to load older messages
_setLoadingOlderMessagesState(_OlderMessagesLevel.LOADING);
2019-04-27 16:29:30 +00:00
2021-03-10 16:54:41 +00:00
final messages = await _conversationsHelper.getOlderMessages(
conversationID: widget.conversationID,
oldestMessagesID: _messages!.firstMessageID);
2019-04-27 16:29:30 +00:00
2021-03-10 16:54:41 +00:00
// Mark as not loading anymore
_setLoadingOlderMessagesState(_OlderMessagesLevel.NONE);
2019-04-27 16:29:30 +00:00
2021-03-10 16:54:41 +00:00
// Check if there is no more unread messages
if (messages.length == 0) {
_setLoadingOlderMessagesState(_OlderMessagesLevel.NO_MORE_AVAILABLE);
return;
}
2019-04-27 16:29:30 +00:00
2021-03-10 16:54:41 +00:00
// Apply the messages
_applyNewMessages(messages);
} catch (e, s) {
print("Failed to load older messages! $e => $s");
2019-04-27 16:29:30 +00:00
_errorLoading();
}
}
/// Apply new messages [messages] must not be null
2021-03-10 16:54:41 +00:00
///
/// Throws in case of failure
2019-04-27 16:29:30 +00:00
Future<void> _applyNewMessages(ConversationMessagesList messages) async {
// We ignore new messages once the area is no longer visible
if (!this.mounted) return;
2019-04-27 16:29:30 +00:00
2019-04-25 06:56:16 +00:00
//Then get information about users
final usersToGet =
findMissingFromSet(_usersInfo.usersID.toSet(), messages.getUsersID());
2019-04-25 06:56:16 +00:00
2021-03-10 16:54:41 +00:00
final users = await _usersHelper.getList(usersToGet);
2019-04-25 06:56:16 +00:00
// Save the new list of messages
setState(() {
_usersInfo.addAll(users);
if (_messages == null)
_messages = messages;
else
_messages!.addAll(messages);
2019-04-25 07:48:52 +00:00
2019-04-27 07:52:16 +00:00
//Reverse the order of the messages (if required)
if (messages.length > 0) {
_messages!.sort();
final Iterable<ConversationMessage> reverse = _messages!.reversed;
2019-04-27 07:52:16 +00:00
_messages = ConversationMessagesList();
_messages!.addAll(reverse);
2019-04-27 07:52:16 +00:00
}
2019-04-25 07:48:52 +00:00
});
2019-04-27 07:52:16 +00:00
// Remove previous errors
_setError(ErrorLevel.NONE);
2019-04-25 07:48:52 +00:00
}
/// Send a file message
Future<void> _sendFileMessage() async {
2021-03-12 16:47:09 +00:00
try {
final file = await showPickFileDialog(
context: context,
maxFileSize: srvConfig!.conversationsPolicy.filesMaxSize,
allowedMimeTypes: srvConfig!.conversationsPolicy.allowedFilesType,
imageMaxWidth: srvConfig!.conversationsPolicy.maxMessageImageWidth,
imageMaxHeight: srvConfig!.conversationsPolicy.maxMessageImageHeight,
);
2019-04-25 18:14:19 +00:00
if (file == null) return;
2021-03-12 16:47:09 +00:00
BytesFile? thumbnail;
2021-03-12 18:36:42 +00:00
if (isVideo(lookupMimeType(file.filename)!))
2021-03-12 18:37:13 +00:00
thumbnail = await generateVideoThumbnail(
videoFile: file,
maxWidth: srvConfig!.conversationsPolicy.maxThumbnailWidth,
2021-03-12 18:37:13 +00:00
);
2021-03-12 18:36:42 +00:00
2021-03-13 08:00:00 +00:00
_sendCancel = CancelToken();
final progressCb =
(count, total) => setState(() => _sendProgress = count / total);
final res = await ConversationsHelper().sendMessage(
NewConversationMessage(
2021-03-12 18:36:42 +00:00
conversationID: widget.conversationID,
message: null,
file: file,
thumbnail: thumbnail),
2021-03-13 08:00:00 +00:00
sendProgress: progressCb,
cancelToken: _sendCancel,
);
2021-03-13 08:00:00 +00:00
assert(res == SendMessageResult.SUCCESS);
2021-03-12 16:47:09 +00:00
} catch (e, s) {
logError(e, s);
showSimpleSnack(context, tr("Failed to send a file!")!);
2021-03-12 16:47:09 +00:00
}
2021-03-13 08:00:00 +00:00
setState(() {
_sendCancel = null;
_sendProgress = null;
});
2021-03-12 16:47:09 +00:00
}
2019-04-25 18:14:19 +00:00
/// Send a new text message
Future<void> _submitTextMessage() async {
if (await _submitMessage(NewConversationMessage(
conversationID: widget.conversationID,
message: textMessage,
)) ==
2019-04-25 18:22:53 +00:00
SendMessageResult.SUCCESS) _clearSendMessageForm();
2019-04-25 09:13:02 +00:00
}
2019-04-25 18:14:19 +00:00
/// Submit a new message
2019-04-25 18:22:53 +00:00
Future<SendMessageResult> _submitMessage(
NewConversationMessage message) async {
2019-04-25 07:48:52 +00:00
//Send the message
_setSending(true);
2019-04-25 18:14:19 +00:00
final result = await _conversationsHelper.sendMessage(message);
2019-04-25 07:48:52 +00:00
_setSending(false);
//Check the result of the operation
2019-04-25 18:22:53 +00:00
if (result != SendMessageResult.SUCCESS)
2021-03-13 14:28:34 +00:00
ScaffoldMessenger.of(context).showSnackBar(
2019-04-25 07:48:52 +00:00
SnackBar(
content: Text(
result == SendMessageResult.MESSAGE_REJECTED
? tr("Message rejected by the server!")!
: tr("Could not send message!")!,
2019-04-25 07:48:52 +00:00
),
duration: Duration(milliseconds: 500),
),
);
2019-04-25 18:22:53 +00:00
return result;
}
2019-04-25 07:48:52 +00:00
/// Clear send message form
void _clearSendMessageForm() {
2021-03-12 21:18:32 +00:00
setState(() => _textController = TextEditingController());
2019-04-25 06:56:16 +00:00
}
/// Error handling
Widget _buildError() {
return buildErrorCard(tr("Could not load the list of messages!"));
}
2019-04-27 16:29:30 +00:00
/// Widget shown when loading older messages
Widget _buildLoadingOlderMessage() {
return Container(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
);
}
/// Notice shown when there is no messages to show
Widget _buildNoMessagesNotice() {
return Expanded(
child: Center(
child: Text(tr("There is no message yet in this conversation.")!),
),
);
}
2019-04-25 07:48:52 +00:00
/// Messages list
Widget _buildMessagesList() {
return Expanded(
2021-03-13 07:17:54 +00:00
child: ListView.builder(
controller: _scrollController,
reverse: true,
itemCount: _messages!.length,
2021-03-13 07:17:54 +00:00
itemBuilder: (c, i) => _buildMessageItem(i),
));
}
Widget _buildMessageItem(int msgIndex) {
final msg = _messages![msgIndex];
2021-03-13 07:17:54 +00:00
final nextMessage =
msgIndex + 1 < _messages!.length ? _messages![msgIndex + 1] : null;
2021-03-13 07:17:54 +00:00
return Column(
children: <Widget>[
Container(
child: !isSameDate(msg.date, nextMessage?.date)
? _buildDateWidget(msg.date)
: null,
),
msg.isServerMessage
? Container(
alignment: Alignment.center,
child: ServerConversationMessageTile(
message: msg.serverMessage!, users: _usersInfo),
2021-03-13 07:17:54 +00:00
)
: Container(
margin: EdgeInsets.symmetric(vertical: 5),
alignment:
msg.isOwner ? Alignment.centerRight : Alignment.centerLeft,
child: msg.isOwner
? _buildSenderLayout(msg, nextMessage)
: _buildReceiverLayout(msg, nextMessage),
),
],
);
}
Widget _buildSenderLayout(
ConversationMessage message, ConversationMessage? previousMessage) {
2021-03-13 07:17:54 +00:00
final messageRadius = Radius.circular(10);
return Container(
2021-03-13 07:26:56 +00:00
margin: EdgeInsets.only(
top: previousMessage?.isOwner == true ? 0 : 12, right: 5),
2021-03-13 07:17:54 +00:00
constraints:
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.65),
decoration: BoxDecoration(
color: _senderColor,
borderRadius: BorderRadius.only(
topLeft: messageRadius,
topRight: messageRadius,
bottomLeft: messageRadius,
),
),
child: Padding(
padding: EdgeInsets.all(10),
child: _buildMessage(message),
),
2019-04-25 07:48:52 +00:00
);
}
2021-03-13 07:17:54 +00:00
Widget _buildReceiverLayout(
ConversationMessage message, ConversationMessage? previousMessage) {
2021-03-13 07:17:54 +00:00
final messageRadius = Radius.circular(10);
2021-03-13 07:26:56 +00:00
return Row(children: [
SizedBox(width: 5),
AccountImageWidget(
user: _usersInfo.getUser(message.userID),
2021-03-13 07:17:54 +00:00
),
2021-03-13 07:26:56 +00:00
SizedBox(width: 5),
Container(
margin: EdgeInsets.only(
top: previousMessage == null ||
message.userID != previousMessage.userID
? 12
: 0),
constraints:
BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.65),
decoration: BoxDecoration(
color: _receiverColor,
borderRadius: BorderRadius.only(
bottomRight: messageRadius,
topRight: messageRadius,
bottomLeft: messageRadius,
),
),
child: Padding(
padding: EdgeInsets.all(10),
child: _buildMessage(message),
),
2021-03-13 07:17:54 +00:00
),
2021-03-13 07:26:56 +00:00
]);
2021-03-13 07:17:54 +00:00
}
Widget _buildMessage(ConversationMessage msg) => ConversationMessageTile(
message: msg,
user: _usersInfo.getUser(msg.userID),
onRequestMessageStats: _requestMessageStats,
onRequestMessageUpdate: _updateMessage,
onRequestMessageDelete: _deleteMessage,
);
Widget _buildDateWidget(DateTime dt) => Center(
child: Container(
child: Text(
formatDisplayDate(dt, time: false),
style: TextStyle(fontWeight: FontWeight.bold),
),
padding: EdgeInsets.only(top: 50, bottom: 5),
));
2021-03-12 21:18:32 +00:00
/// Send new message form
Widget _buildSendMessageForm() => Container(
2021-03-12 21:40:33 +00:00
padding: EdgeInsets.fromLTRB(10, 5, 10, 5),
2021-03-12 21:18:32 +00:00
child: Row(
children: <Widget>[
GestureDetector(
onTap: !_isSendingMessage ? _sendFileMessage : null,
child: Container(
2021-03-12 21:40:33 +00:00
padding: EdgeInsets.all(6),
2021-03-12 21:18:32 +00:00
decoration: BoxDecoration(
gradient:
_isSendingMessage ? _disabledGradient : _fabGradient,
shape: BoxShape.circle,
),
child: Icon(
Icons.add,
2021-03-13 08:10:55 +00:00
color: Colors.white,
2021-03-12 21:18:32 +00:00
),
2019-04-25 18:22:53 +00:00
),
),
2021-03-12 21:18:32 +00:00
SizedBox(width: 5),
Expanded(
child: Stack(
alignment: Alignment.centerRight,
children: [
TextField(
enabled: !_isSendingMessage,
maxLines: 10,
minLines: 1,
controller: _textController,
focusNode: _textFieldFocus,
onTap: () => hideEmojiContainer(),
textInputAction: TextInputAction.send,
onSubmitted: (s) => _submitTextMessage(),
style: TextStyle(
color: darkTheme() ? Colors.white : Colors.black,
),
2021-03-13 08:09:26 +00:00
onChanged: (s) {
_sendWritingEvent();
setState(() {});
},
2021-03-12 21:18:32 +00:00
decoration: InputDecoration(
hintText: tr("New message..."),
hintStyle: TextStyle(
color: _greyColor,
),
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
const Radius.circular(50.0),
),
borderSide: BorderSide.none),
2021-03-12 21:40:33 +00:00
contentPadding: EdgeInsets.fromLTRB(20, 8, 32, 8),
2021-03-12 21:18:32 +00:00
filled: true,
fillColor: _separatorColor,
),
),
IconButton(
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
onPressed: () {
if (!_showEmojiPicker) {
// keyboard is visible
hideKeyboard();
Future.delayed(Duration(milliseconds: 100),
() => showEmojiContainer());
} else {
//keyboard is hidden
showKeyboard();
hideEmojiContainer();
}
},
icon: Icon(
Icons.face,
2021-05-04 05:52:44 +00:00
color: _showEmojiPicker ? _senderColor : null,
2021-03-12 21:18:32 +00:00
),
),
],
2019-04-27 08:13:29 +00:00
),
2019-04-25 07:48:52 +00:00
),
2021-03-12 21:40:33 +00:00
SizedBox(width: 5),
GestureDetector(
onTap: _isMessageValid ? _submitTextMessage : null,
child: Container(
padding: EdgeInsets.all(8),
2021-03-12 21:18:32 +00:00
decoration: BoxDecoration(
2021-03-12 21:40:33 +00:00
gradient: !_isMessageValid ? _disabledGradient : _fabGradient,
2021-03-12 21:18:32 +00:00
shape: BoxShape.circle,
),
2021-03-12 21:40:33 +00:00
child: Icon(
Icons.send,
2021-03-13 08:10:55 +00:00
color: Colors.white,
2021-03-12 21:40:33 +00:00
),
),
),
2021-03-12 21:18:32 +00:00
],
),
);
2019-04-25 07:48:52 +00:00
2022-03-10 17:31:29 +00:00
Widget _buildEmojiContainer() => Container(
height: 200,
child: emoji.EmojiPicker(
config: emoji.Config(
bgColor: _senderColor,
indicatorColor: _senderColor,
columns: 8,
showRecentsTab: false,
),
onEmojiSelected: (category, emoji) {
_textController.text = _textController.text + emoji.emoji;
},
),
2021-03-12 21:18:32 +00:00
);
2019-04-25 07:48:52 +00:00
2021-03-13 08:00:00 +00:00
Widget _buildSendingWidget() => Container(
height: 68,
color: _senderColor,
child: Row(
children: <Widget>[
Spacer(flex: 1),
Flexible(
child: LinearProgressIndicator(value: _sendProgress),
flex: 5,
),
Spacer(flex: 1),
Text("${(_sendProgress! * 100).toInt()}%"),
2021-03-13 08:00:00 +00:00
Spacer(flex: 1),
2021-03-13 14:28:34 +00:00
OutlinedButton(
onPressed: () => _sendCancel!.cancel(),
child: Text(tr("Cancel")!.toUpperCase()),
2021-03-13 08:00:00 +00:00
),
Spacer(flex: 1),
],
),
);
2019-04-25 06:56:16 +00:00
@override
Widget build(BuildContext context) {
if (_error == ErrorLevel.MAJOR) return _buildError();
if (_messages == null) return buildCenteredProgressBar();
2019-04-25 07:48:52 +00:00
return Column(
children: <Widget>[
Container(
child: _error == ErrorLevel.MINOR ? _buildError() : null,
),
2019-04-27 16:29:30 +00:00
Container(
child: _loadingOlderMessages == _OlderMessagesLevel.LOADING
? _buildLoadingOlderMessage()
: null,
),
_messages!.length == 0 ? _buildNoMessagesNotice() : _buildMessagesList(),
UserWritingInConvNotifier(convID: _conversation.id!),
2021-03-13 08:00:00 +00:00
_sendCancel != null ? _buildSendingWidget() : _buildSendMessageForm(),
2021-03-12 21:18:32 +00:00
_showEmojiPicker ? _buildEmojiContainer() : Container(),
2019-04-25 07:48:52 +00:00
],
);
2019-04-25 06:56:16 +00:00
}
2019-05-04 06:58:14 +00:00
2021-03-13 08:09:26 +00:00
void _sendWritingEvent() async {
try {
if (textMessage.isEmpty) return;
final t = time();
if (t - _lastWritingEventSent <
srvConfig!.conversationsPolicy.writingEventInterval) return;
2021-03-13 08:09:26 +00:00
_lastWritingEventSent = t;
await ConversationsHelper.sendWritingEvent(_conversation.id);
} catch (e, s) {
logError(e, s);
}
}
2021-03-11 16:27:20 +00:00
/// Request message statistics
void _requestMessageStats(ConversationMessage message) async {
MainController.of(context)!
2021-03-11 16:27:20 +00:00
.openConversationMessageStats(_conversation, message);
}
2019-05-04 08:24:38 +00:00
/// Request message content update
Future<void> _updateMessage(ConversationMessage message) async {
final newContent = await askUserString(
2021-03-12 15:37:21 +00:00
context: context,
title: tr("Update message")!,
message: tr("Please enter new message content:")!,
defaultValue: message.message.content!,
hint: tr("New message")!,
2021-03-12 15:37:21 +00:00
minLength:
ServerConfigurationHelper.config!.conversationsPolicy.minMessageLen,
2021-03-12 15:37:21 +00:00
maxLength:
ServerConfigurationHelper.config!.conversationsPolicy.maxMessageLen,
2021-03-12 15:37:21 +00:00
);
2019-05-04 08:24:38 +00:00
if (newContent == null) return;
if (!await _conversationsHelper.updateMessage(message.id, newContent)) {
showSimpleSnack(context, tr("Could not update message content!")!);
2019-05-04 08:24:38 +00:00
return;
}
}
2019-05-04 06:58:14 +00:00
/// Request message deletion
Future<void> _deleteMessage(ConversationMessage message) async {
final choice = await showDialog<bool>(
context: context,
builder: (c) => AlertDialog(
title: Text(tr("Confirm deletion")!),
content: Text(
tr("Do you really want to delete this message ? The operation can not be cancelled !")!,
textAlign: TextAlign.justify,
),
actions: <Widget>[
2021-03-13 14:28:34 +00:00
TextButton(
child: Text(
tr("Cancel")!.toUpperCase(),
2019-05-04 06:58:14 +00:00
),
onPressed: () => Navigator.pop(c, false),
2019-05-04 06:58:14 +00:00
),
2021-03-13 14:28:34 +00:00
TextButton(
child: Text(
tr("Confirm")!.toUpperCase(),
style: TextStyle(color: Colors.red),
),
onPressed: () => Navigator.pop(c, true),
),
],
),
2019-05-04 06:58:14 +00:00
);
2019-05-04 08:24:38 +00:00
if (choice == null || !choice) return;
2019-05-04 06:58:14 +00:00
// Execute the request
2019-05-04 08:24:38 +00:00
if (!await _conversationsHelper.deleteMessage(message.id))
showSimpleSnack(context, tr("Could not delete conversation message!")!);
2019-05-04 06:58:14 +00:00
}
2019-04-25 06:56:16 +00:00
}