1
0
mirror of https://gitlab.com/comunic/comunicmobile synced 2024-11-25 06:19: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/widgets/safe_state.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/intl_utils.dart';
import 'package:comunic/utils/list_utils.dart';
@ -80,6 +81,10 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
showEmojiContainer() => setState(() => _showEmojiPicker = true);
// Colors definition
Color get _senderColor =>
_conversation.color ??
(darkTheme() ? Color(0xff2b343b) : Colors.blue.shade900);
Color get _receiverColor =>
darkTheme() ? Color(0xff3a3d40) : Colors.grey.shade600;
@ -337,19 +342,6 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
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
Widget _buildError() {
return buildErrorCard(tr("Could not load the list of messages!"));
@ -375,30 +367,111 @@ class _ConversationScreenState extends SafeState<ConversationScreen> {
/// Messages list
Widget _buildMessagesList() {
return Expanded(
child: ListView.builder(
controller: _scrollController,
reverse: true,
itemCount: _messages.length,
itemBuilder: (c, i) {
return _messages[i].isServerMessage
? ServerConversationMessageTile(
message: _messages[i].serverMessage,
users: _usersInfo,
)
: ConversationMessageTile(
conversation: _conversation,
message: _messages.elementAt(i),
userInfo: _usersInfo.getUser(_messages[i].userID),
isLastMessage: _isLastMessage(i),
isFirstMessage: _isFirstMessage(i),
onRequestMessageStats: _requestMessageStats,
onRequestMessageUpdate: _updateMessage,
onRequestMessageDelete: _deleteMessage,
);
}),
child: ListView.builder(
controller: _scrollController,
reverse: true,
itemCount: _messages.length,
itemBuilder: (c, i) => _buildMessageItem(i),
));
}
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),
)
: 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) {
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,
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),
));
/// Send new message form
Widget _buildSendMessageForm() => Container(
padding: EdgeInsets.fromLTRB(10, 5, 10, 5),

View File

@ -1,8 +1,6 @@
import 'package:clipboard/clipboard.dart';
import 'package:comunic/models/conversation.dart';
import 'package:comunic/models/conversation_message.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/text_widget.dart';
import 'package:comunic/utils/date_utils.dart';
@ -27,253 +25,95 @@ typedef OnRequestMessageUpdate = void Function(ConversationMessage);
typedef OnRequestMessageDelete = void Function(ConversationMessage);
class ConversationMessageTile extends StatelessWidget {
final Conversation conversation;
final ConversationMessage message;
final User userInfo;
final bool isLastMessage;
final bool isFirstMessage;
final User user;
final OnRequestMessageStats onRequestMessageStats;
final OnRequestMessageUpdate onRequestMessageUpdate;
final OnRequestMessageDelete onRequestMessageDelete;
const ConversationMessageTile({
Key key,
@required this.conversation,
@required this.message,
@required this.userInfo,
@required this.isLastMessage,
@required this.isFirstMessage,
@required this.user,
@required this.onRequestMessageStats,
@required this.onRequestMessageUpdate,
@required this.onRequestMessageDelete,
}) : assert(message != null),
assert(userInfo != null),
assert(isLastMessage != null),
assert(isFirstMessage != null),
assert(user != null),
assert(onRequestMessageStats != null),
assert(onRequestMessageUpdate != null),
assert(onRequestMessageDelete != null),
super(key: key);
Color get backgroundColor => message.isOwner
? conversation.color ?? Colors.blueAccent
: darkTheme()
? Colors.white12
: Colors.black12;
/// Build account image
Widget _buildAccountImage(BuildContext context) {
return Container(
margin: EdgeInsets.all(10.0),
child: PopupMenuButton<_MenuChoices>(
child: AccountImageWidget(
user: userInfo,
width: 35.0,
),
itemBuilder: (c) => <PopupMenuItem<_MenuChoices>>[
PopupMenuItem(
enabled: (message.message?.content ?? "") != "",
value: _MenuChoices.COPY_MESSAGE,
child: Text(tr("Copy message")),
),
PopupMenuItem(
enabled: message.file != null,
value: _MenuChoices.COPY_URL,
child: Text(tr("Copy URL")),
),
PopupMenuItem(
value: _MenuChoices.GET_STATS,
child: Text(tr("Statistics")),
),
// Update message content
PopupMenuItem(
enabled: message.isOwner &&
message.message != null &&
message.message.content.isNotEmpty,
value: _MenuChoices.REQUEST_UPDATE_CONTENT,
child: Text(tr("Update")),
),
// Delete the message
PopupMenuItem(
enabled: message.isOwner,
value: _MenuChoices.DELETE,
child: Text(tr("Delete")),
),
]..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,
),
// Text message
Container(
child: message.hasMessage
? Container(
width: 200.0,
alignment: Alignment.centerRight,
child: Container(
child: TextWidget(
content: message.message,
textAlign: TextAlign.justify,
style: TextStyle(color: Colors.white),
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
Widget _buildLeftMessage(BuildContext context) {
return Container(
margin: EdgeInsets.only(bottom: isLastMessage ? 20.0 : 5.0),
child: Column(
@override
Widget build(BuildContext context) => 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),
RichText(
text: TextSpan(
text:
"${user.fullName} - ${formatDisplayDate(message.date, date: false)}",
style: TextStyle(color: Colors.white, fontSize: 11),
children: [
WidgetSpan(
child: PopupMenuButton<_MenuChoices>(
child: Icon(
Icons.more_vert,
color: Colors.white,
size: 12,
),
onSelected: _menuOptionSelected,
itemBuilder: (c) => <PopupMenuItem<_MenuChoices>>[
PopupMenuItem(
enabled: (message.message?.content ?? "") != "",
value: _MenuChoices.COPY_MESSAGE,
child: Text(tr("Copy message")),
),
PopupMenuItem(
enabled: message.file != null,
value: _MenuChoices.COPY_URL,
child: Text(tr("Copy URL")),
),
PopupMenuItem(
value: _MenuChoices.GET_STATS,
child: Text(tr("Statistics")),
),
// Update message content
PopupMenuItem(
enabled: message.isOwner &&
message.message != null &&
message.message.content.isNotEmpty,
value: _MenuChoices.REQUEST_UPDATE_CONTENT,
child: Text(tr("Update")),
),
// Delete the message
PopupMenuItem(
enabled: message.isOwner,
value: _MenuChoices.DELETE,
child: Text(tr("Delete")),
),
]..removeWhere((element) => !element.enabled),
),
)
: 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(),
),
)
_buildMessageContent(),
],
),
);
}
);
@override
Widget build(BuildContext context) {
return message.isOwner
? _buildRightMessage(context)
: _buildLeftMessage(context);
Widget _buildMessageContent() {
if (!message.hasFile)
return TextWidget(
content: message.message,
textAlign: TextAlign.justify,
style: TextStyle(color: darkTheme() ? Colors.white : Colors.black),
);
return ConversationFileWidget(messageID: message.id, file: message.file);
}
/// Process menu choice

View File

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

View File

@ -1,4 +1,5 @@
import 'package:comunic/utils/intl_utils.dart';
import 'package:intl/intl.dart';
/// Date utilities
///
@ -56,3 +57,22 @@ String dateTimeToString(DateTime time) {
/// Format a [Duration] in the form "MM:SS"
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()}";
/// 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);
}