mirror of
https://gitlab.com/comunic/comunicmobile
synced 2024-12-26 12:58:51 +00:00
Start to improve messages appearance
This commit is contained in:
parent
05c806b358
commit
5a25769b71
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user