1
0
mirror of https://gitlab.com/comunic/comunicmobile synced 2024-11-25 14:29:22 +00:00

Start to improve messages appearance

This commit is contained in:
Pierre HUBERT 2021-03-13 08:17:54 +01:00
parent 05c806b358
commit 5a25769b71
4 changed files with 193 additions and 263 deletions

View File

@ -16,6 +16,7 @@ import 'package:comunic/ui/tiles/conversation_message_tile.dart';
import 'package:comunic/ui/tiles/server_conversation_message_tile.dart'; import 'package:comunic/ui/tiles/server_conversation_message_tile.dart';
import 'package:comunic/ui/widgets/safe_state.dart'; import 'package:comunic/ui/widgets/safe_state.dart';
import 'package:comunic/ui/widgets/scroll_watcher.dart'; import 'package:comunic/ui/widgets/scroll_watcher.dart';
import 'package:comunic/utils/date_utils.dart';
import 'package:comunic/utils/files_utils.dart'; import 'package:comunic/utils/files_utils.dart';
import 'package:comunic/utils/intl_utils.dart'; import 'package:comunic/utils/intl_utils.dart';
import 'package:comunic/utils/list_utils.dart'; import 'package:comunic/utils/list_utils.dart';
@ -80,6 +81,10 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
showEmojiContainer() => setState(() => _showEmojiPicker = true); showEmojiContainer() => setState(() => _showEmojiPicker = true);
// Colors definition // Colors definition
Color get _senderColor =>
_conversation.color ??
(darkTheme() ? Color(0xff2b343b) : Colors.blue.shade900);
Color get _receiverColor => Color get _receiverColor =>
darkTheme() ? Color(0xff3a3d40) : Colors.grey.shade600; darkTheme() ? Color(0xff3a3d40) : Colors.grey.shade600;
@ -337,19 +342,6 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
setState(() => _textController = TextEditingController()); setState(() => _textController = TextEditingController());
} }
/// Check if a message is the last message of a user or not
bool _isLastMessage(int index) {
return index == 0 ||
(index > 0 && _messages[index - 1].userID != _messages[index].userID);
}
/// Check if a message is the first message of a user or not
bool _isFirstMessage(int index) {
return index == _messages.length - 1 ||
(index < _messages.length - 1 &&
_messages[index + 1].userID != _messages[index].userID);
}
/// Error handling /// Error handling
Widget _buildError() { Widget _buildError() {
return buildErrorCard(tr("Could not load the list of messages!")); return buildErrorCard(tr("Could not load the list of messages!"));
@ -379,25 +371,106 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
controller: _scrollController, controller: _scrollController,
reverse: true, reverse: true,
itemCount: _messages.length, itemCount: _messages.length,
itemBuilder: (c, i) { itemBuilder: (c, i) => _buildMessageItem(i),
return _messages[i].isServerMessage ));
? ServerConversationMessageTile( }
message: _messages[i].serverMessage,
users: _usersInfo, Widget _buildMessageItem(int msgIndex) {
final msg = _messages[msgIndex];
final nextMessage =
msgIndex + 1 < _messages.length ? _messages[msgIndex + 1] : null;
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),
) )
: ConversationMessageTile( : Container(
conversation: _conversation, margin: EdgeInsets.symmetric(vertical: 5),
message: _messages.elementAt(i), alignment:
userInfo: _usersInfo.getUser(_messages[i].userID), msg.isOwner ? Alignment.centerRight : Alignment.centerLeft,
isLastMessage: _isLastMessage(i), child: msg.isOwner
isFirstMessage: _isFirstMessage(i), ? _buildSenderLayout(msg, nextMessage)
: _buildReceiverLayout(msg, nextMessage),
),
],
);
}
Widget _buildSenderLayout(
ConversationMessage message, ConversationMessage previousMessage) {
final messageRadius = Radius.circular(10);
return Container(
margin: EdgeInsets.only(top: previousMessage?.isOwner == true ? 0 : 12),
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),
),
);
}
Widget _buildReceiverLayout(
ConversationMessage message, ConversationMessage previousMessage) {
final messageRadius = Radius.circular(10);
return 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),
),
);
}
Widget _buildMessage(ConversationMessage msg) => ConversationMessageTile(
message: msg,
user: _usersInfo.getUser(msg.userID),
onRequestMessageStats: _requestMessageStats, onRequestMessageStats: _requestMessageStats,
onRequestMessageUpdate: _updateMessage, onRequestMessageUpdate: _updateMessage,
onRequestMessageDelete: _deleteMessage, 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),
));
/// Send new message form /// Send new message form
Widget _buildSendMessageForm() => Container( Widget _buildSendMessageForm() => Container(

View File

@ -1,8 +1,6 @@
import 'package:clipboard/clipboard.dart'; import 'package:clipboard/clipboard.dart';
import 'package:comunic/models/conversation.dart';
import 'package:comunic/models/conversation_message.dart'; import 'package:comunic/models/conversation_message.dart';
import 'package:comunic/models/user.dart'; import 'package:comunic/models/user.dart';
import 'package:comunic/ui/widgets/account_image_widget.dart';
import 'package:comunic/ui/widgets/conversation_file_tile.dart'; import 'package:comunic/ui/widgets/conversation_file_tile.dart';
import 'package:comunic/ui/widgets/text_widget.dart'; import 'package:comunic/ui/widgets/text_widget.dart';
import 'package:comunic/utils/date_utils.dart'; import 'package:comunic/utils/date_utils.dart';
@ -27,49 +25,44 @@ typedef OnRequestMessageUpdate = void Function(ConversationMessage);
typedef OnRequestMessageDelete = void Function(ConversationMessage); typedef OnRequestMessageDelete = void Function(ConversationMessage);
class ConversationMessageTile extends StatelessWidget { class ConversationMessageTile extends StatelessWidget {
final Conversation conversation;
final ConversationMessage message; final ConversationMessage message;
final User userInfo; final User user;
final bool isLastMessage;
final bool isFirstMessage;
final OnRequestMessageStats onRequestMessageStats; final OnRequestMessageStats onRequestMessageStats;
final OnRequestMessageUpdate onRequestMessageUpdate; final OnRequestMessageUpdate onRequestMessageUpdate;
final OnRequestMessageDelete onRequestMessageDelete; final OnRequestMessageDelete onRequestMessageDelete;
const ConversationMessageTile({ const ConversationMessageTile({
Key key, Key key,
@required this.conversation,
@required this.message, @required this.message,
@required this.userInfo, @required this.user,
@required this.isLastMessage,
@required this.isFirstMessage,
@required this.onRequestMessageStats, @required this.onRequestMessageStats,
@required this.onRequestMessageUpdate, @required this.onRequestMessageUpdate,
@required this.onRequestMessageDelete, @required this.onRequestMessageDelete,
}) : assert(message != null), }) : assert(message != null),
assert(userInfo != null), assert(user != null),
assert(isLastMessage != null),
assert(isFirstMessage != null),
assert(onRequestMessageStats != null), assert(onRequestMessageStats != null),
assert(onRequestMessageUpdate != null), assert(onRequestMessageUpdate != null),
assert(onRequestMessageDelete != null), assert(onRequestMessageDelete != null),
super(key: key); super(key: key);
Color get backgroundColor => message.isOwner @override
? conversation.color ?? Colors.blueAccent Widget build(BuildContext context) => Column(
: darkTheme() crossAxisAlignment: CrossAxisAlignment.start,
? Colors.white12 children: <Widget>[
: Colors.black12; RichText(
text: TextSpan(
/// Build account image text:
Widget _buildAccountImage(BuildContext context) { "${user.fullName} - ${formatDisplayDate(message.date, date: false)}",
return Container( style: TextStyle(color: Colors.white, fontSize: 11),
margin: EdgeInsets.all(10.0), children: [
WidgetSpan(
child: PopupMenuButton<_MenuChoices>( child: PopupMenuButton<_MenuChoices>(
child: AccountImageWidget( child: Icon(
user: userInfo, Icons.more_vert,
width: 35.0, color: Colors.white,
size: 12,
), ),
onSelected: _menuOptionSelected,
itemBuilder: (c) => <PopupMenuItem<_MenuChoices>>[ itemBuilder: (c) => <PopupMenuItem<_MenuChoices>>[
PopupMenuItem( PopupMenuItem(
enabled: (message.message?.content ?? "") != "", enabled: (message.message?.content ?? "") != "",
@ -104,176 +97,23 @@ class ConversationMessageTile extends StatelessWidget {
child: Text(tr("Delete")), child: Text(tr("Delete")),
), ),
]..removeWhere((element) => !element.enabled), ]..removeWhere((element) => !element.enabled),
onSelected: _menuOptionSelected,
),
);
}
/// Build widget image
Widget _buildMessageFile(BuildContext context) => ConversationFileWidget(
messageID: message.id,
file: message.file,
defaultBackgroundColor: backgroundColor,
);
/// Build message date
Widget _buildMessageDate() {
return isLastMessage
? Container(
margin: EdgeInsets.only(top: 5.0),
child: Text(
dateTimeToString(message.date),
style: TextStyle(
color: darkTheme() ? Colors.white : Colors.black54,
fontSize: 12.0),
textAlign: TextAlign.center,
), ),
) )
: Container(); ]),
}
/// Build a message of the current user
Widget _buildRightMessage(BuildContext context) {
return Container(
margin: EdgeInsets.only(bottom: isLastMessage ? 20.0 : 2.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Column(
children: <Widget>[
Row(
children: <Widget>[
Column(
children: <Widget>[
// Text image
Container(
child:
message.hasFile ? _buildMessageFile(context) : null,
), ),
_buildMessageContent(),
],
);
// Text message Widget _buildMessageContent() {
Container( if (!message.hasFile)
child: message.hasMessage return TextWidget(
? Container(
width: 200.0,
alignment: Alignment.centerRight,
child: Container(
child: TextWidget(
content: message.message, content: message.message,
textAlign: TextAlign.justify, textAlign: TextAlign.justify,
style: TextStyle(color: Colors.white), style: TextStyle(color: darkTheme() ? Colors.white : Colors.black),
linksColor: Colors.indigo,
),
padding: EdgeInsets.fromLTRB(
15.0, 10.0, 15.0, 10.0),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(8.0),
),
),
)
: null,
),
],
),
// Account image
_buildAccountImage(context)
],
),
// Date
Container(
child: _buildMessageDate(),
margin: EdgeInsets.only(right: 45.0),
)
],
),
],
),
); );
// Text
}
/// Build a message of a peer user return ConversationFileWidget(messageID: message.id, file: message.file);
Widget _buildLeftMessage(BuildContext context) {
return Container(
margin: EdgeInsets.only(bottom: isLastMessage ? 20.0 : 5.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
//User name
Container(
margin: EdgeInsets.only(left: 55.0),
child: isFirstMessage
? Text(
userInfo.fullName,
textAlign: TextAlign.left,
style: TextStyle(fontSize: 12.0),
)
: null,
),
Row(
children: <Widget>[
// Account image
_buildAccountImage(context),
Column(
children: <Widget>[
// Text image
Container(
child: message.hasFile ? _buildMessageFile(context) : null,
),
// Text message
Container(
child: message.hasMessage
? Container(
width: 200.0,
alignment: Alignment.centerLeft,
child: Container(
child: TextWidget(
content: message.message,
textAlign: TextAlign.justify,
style: TextStyle(
color: darkTheme()
? Colors.white
: Colors.black),
),
padding:
EdgeInsets.fromLTRB(15.0, 10.0, 15.0, 10.0),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(8.0),
),
),
)
: null,
),
],
),
],
),
// Date
Container(
margin: EdgeInsets.only(left: 50.0),
child: Align(
alignment: Alignment.topLeft,
child: _buildMessageDate(),
),
)
],
),
);
}
@override
Widget build(BuildContext context) {
return message.isOwner
? _buildRightMessage(context)
: _buildLeftMessage(context);
} }
/// Process menu choice /// Process menu choice

View File

@ -13,11 +13,9 @@ import 'package:url_launcher/url_launcher.dart';
class ConversationFileWidget extends StatefulWidget { class ConversationFileWidget extends StatefulWidget {
final int messageID; final int messageID;
final ConversationMessageFile file; final ConversationMessageFile file;
final Color defaultBackgroundColor;
const ConversationFileWidget({ const ConversationFileWidget({
Key key, Key key,
@required this.defaultBackgroundColor,
@required this.messageID, @required this.messageID,
@required this.file, @required this.file,
}) : assert(messageID != null), }) : assert(messageID != null),
@ -53,7 +51,6 @@ class _ConversationFileWidgetState extends State<ConversationFileWidget> {
// We open it in the browser // We open it in the browser
default: default:
return Container( return Container(
color: widget.defaultBackgroundColor,
child: Center( child: Center(
child: MaterialButton( child: MaterialButton(
child: Column( child: Column(

View File

@ -1,4 +1,5 @@
import 'package:comunic/utils/intl_utils.dart'; import 'package:comunic/utils/intl_utils.dart';
import 'package:intl/intl.dart';
/// Date utilities /// Date utilities
/// ///
@ -56,3 +57,22 @@ String dateTimeToString(DateTime time) {
/// Format a [Duration] in the form "MM:SS" /// Format a [Duration] in the form "MM:SS"
String formatDuration(Duration d) => String formatDuration(Duration d) =>
"${d.inMinutes < 10 ? "0" + d.inMinutes.toString() : d.inMinutes.toString()}:${d.inSeconds % 60 < 10 ? "0" + (d.inSeconds % 60).toString() : (d.inSeconds % 60).toString()}"; "${d.inMinutes < 10 ? "0" + d.inMinutes.toString() : d.inMinutes.toString()}:${d.inSeconds % 60 < 10 ? "0" + (d.inSeconds % 60).toString() : (d.inSeconds % 60).toString()}";
/// Compare two [DateTime] on their date
bool isSameDate(DateTime one, DateTime other) {
if (other == null) return false;
return one.year == other.year &&
one.month == other.month &&
one.day == other.day;
}
/// Format a date to make it easily shown
String formatDisplayDate(DateTime dt, {bool date = true, bool time = true}) {
final format = Intl(Intl.systemLocale).date();
if (date) {
format.add_EEEE();
format.add_yMMMMd();
}
if (time) format.add_jm();
return format.format(dt);
}