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:
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/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(
|
||||||
|
@ -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
|
||||||
|
@ -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(
|
||||||
|
@ -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);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user