From bc1761867d73a7fd73c1f326b94e4465ea4e4e1c Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 12 Jan 2021 12:27:42 +0530 Subject: [PATCH 01/12] [MessageListView] Make thread type messages all corners rounded Signed-off-by: Sahil Kumar --- lib/src/message_list_view.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index cda0d64c..2e45dff7 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -857,14 +857,21 @@ class _MessageListViewState extends State { attachmentBorderRadiusGeometry: BorderRadius.only( topLeft: Radius.circular(attachmentBorderRadius), bottomLeft: Radius.circular( - timeDiff >= 1 || !isNextUserSame ? 0 : attachmentBorderRadius), + (timeDiff >= 1 || !isNextUserSame) && !hasReplies && !isThreadMessage + ? 0 + : attachmentBorderRadius, + ), topRight: Radius.circular(attachmentBorderRadius), bottomRight: Radius.circular(attachmentBorderRadius), ), attachmentPadding: const EdgeInsets.all(2), borderRadiusGeometry: BorderRadius.only( topLeft: Radius.circular(16), - bottomLeft: Radius.circular(timeDiff >= 1 || !isNextUserSame ? 0 : 16), + bottomLeft: Radius.circular( + (timeDiff >= 1 || !isNextUserSame) && !hasReplies && !isThreadMessage + ? 0 + : 16, + ), topRight: Radius.circular(16), bottomRight: Radius.circular(16), ), From 4a7350530ebb2ba4a0aae634f484ef34db37172e Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 12 Jan 2021 12:52:52 +0530 Subject: [PATCH 02/12] [MessageListView] Remove topMessage extra top padding in thread conversation. Signed-off-by: Sahil Kumar --- lib/src/message_list_view.dart | 107 ++++++++++++++++----------------- 1 file changed, 52 insertions(+), 55 deletions(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 2e45dff7..aa557c6a 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -338,9 +338,28 @@ class _MessageListViewState extends State { messages.length + 2 + (_isThreadConversation ? 1 : 0), separatorBuilder: (context, i) { if (i == messages.length) return Offstage(); - if (i == messages.length + 2) return Offstage(); - if (i == messages.length + 1) return Offstage(); if (i == 0) return SizedBox(height: 30); + if (i == messages.length + 1) { + final replyCount = widget.parentMessage.replyCount; + return Container( + decoration: BoxDecoration( + gradient: + StreamChatTheme.of(context).colorTheme.bgGradient, + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', + textAlign: TextAlign.center, + style: StreamChatTheme.of(context) + .channelTheme + .channelHeaderTheme + .lastMessageAt, + ), + ), + ); + } + final message = messages[i]; final nextMessage = messages[i - 1]; if (!Jiffy(message.createdAt.toLocal()).isSame( @@ -385,30 +404,7 @@ class _MessageListViewState extends State { widget.parentMessage, ); } else { - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - buildParentMessage(widget.parentMessage), - Container( - decoration: BoxDecoration( - gradient: StreamChatTheme.of(context) - .colorTheme - .bgGradient, - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - '${widget.parentMessage.replyCount} ${widget.parentMessage.replyCount == 1 ? 'Reply' : 'Replies'}', - textAlign: TextAlign.center, - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .lastMessageAt, - ), - ), - ), - ], - ); + return buildParentMessage(widget.parentMessage); } } if (i == messages.length + 1) { @@ -602,37 +598,38 @@ class _MessageListViewState extends State { ? streamChannel.queryTopMessages : streamChannel.queryBottomMessages; return StreamBuilder( - key: Key('LOADING-INDICATOR'), - stream: stream, - initialData: false, - builder: (context, snapshot) { - if (snapshot.hasError) { - return Container( - color: StreamChatTheme.of(context) - .colorTheme - .accentRed - .withOpacity(.2), - child: Center( - child: Text('Error loading messages'), - ), - ); - } - if (!snapshot.data) { - if (direction == QueryDirection.top) { - return Container( - height: 52, - width: double.infinity, - ); - } - return Offstage(); - } - return Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: const CircularProgressIndicator(), + key: Key('LOADING-INDICATOR'), + stream: stream, + initialData: false, + builder: (context, snapshot) { + if (snapshot.hasError) { + return Container( + color: StreamChatTheme.of(context) + .colorTheme + .accentRed + .withOpacity(.2), + child: Center( + child: Text('Error loading messages'), ), ); - }); + } + if (!snapshot.data) { + if (!_isThreadConversation && direction == QueryDirection.top) { + return Container( + height: 52, + width: double.infinity, + ); + } + return Offstage(); + } + return Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: const CircularProgressIndicator(), + ), + ); + }, + ); } Widget _buildTopMessage( From 9379c4672701689e31a2c36c1a091f740a37a921 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 12 Jan 2021 14:32:02 +0530 Subject: [PATCH 03/12] [QuotedMessageWidget] Fix attachment getting cropped by border side Signed-off-by: Sahil Kumar --- lib/src/quoted_message_widget.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/src/quoted_message_widget.dart b/lib/src/quoted_message_widget.dart index 7621174f..d012e15a 100644 --- a/lib/src/quoted_message_widget.dart +++ b/lib/src/quoted_message_widget.dart @@ -235,9 +235,7 @@ class QuotedMessageWidget extends StatelessWidget { ShapeBorder _getDefaultShape(BuildContext context) { return RoundedRectangleBorder( - side: BorderSide( - color: StreamChatTheme.of(context).colorTheme.greyWhisper, - ), + side: BorderSide(width: 0.0, color: Colors.transparent), borderRadius: BorderRadius.circular(8), ); } From f9ac3fe6faf6d4d2c58b9e293b3684b58399cd04 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 12 Jan 2021 14:58:43 +0530 Subject: [PATCH 04/12] [MessageListView] minor fixes Signed-off-by: Sahil Kumar --- lib/src/message_list_view.dart | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index aa557c6a..ba9c499e 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -708,6 +708,7 @@ class _MessageListViewState extends State { Message message, ) { final isMyMessage = message.user.id == StreamChat.of(context).user.id; + final isOnlyEmoji = textIsOnlyEmoji(message.text); return MessageWidget( showThreadReplyIndicator: false, @@ -721,12 +722,7 @@ class _MessageListViewState extends State { message: message, reverse: isMyMessage, showUsername: !isMyMessage, - padding: EdgeInsets.only( - top: 8.0, - left: 8.0, - right: 8.0, - bottom: 16.0, - ), + padding: const EdgeInsets.all(8.0), showSendingIndicator: false, onThreadTap: _onThreadTap, borderRadiusGeometry: BorderRadius.only( @@ -735,7 +731,7 @@ class _MessageListViewState extends State { topRight: Radius.circular(16), bottomRight: Radius.circular(16), ), - borderSide: isMyMessage ? BorderSide.none : null, + borderSide: isMyMessage && !isOnlyEmoji ? BorderSide.none : null, showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show, messageTheme: isMyMessage ? StreamChatTheme.of(context).ownMessageTheme @@ -810,8 +806,9 @@ class _MessageListViewState extends State { final showSendingIndicator = isMyMessage && (index == 0 || timeDiff >= 1 || !isNextUserSame); - bool showInChannelIndicator = !_isThreadConversation && isThreadMessage; - bool showThreadReplyIndicator = !_isThreadConversation && hasReplies; + final showInChannelIndicator = !_isThreadConversation && isThreadMessage; + final showThreadReplyIndicator = !_isThreadConversation && hasReplies; + final isOnlyEmoji = textIsOnlyEmoji(message.text); Widget child = MessageWidget( key: ValueKey('MESSAGE-${message.id}'), @@ -848,7 +845,7 @@ class _MessageListViewState extends State { showEditMessage: isMyMessage, showDeleteMessage: isMyMessage, showThreadReplyMessage: !isThreadMessage, - borderSide: isMyMessage ? BorderSide.none : null, + borderSide: isMyMessage && !isOnlyEmoji ? BorderSide.none : null, onThreadTap: _onThreadTap, onReplyTap: widget.onReplyTap, attachmentBorderRadiusGeometry: BorderRadius.only( From 85aaf9eab89bc1dd1c1191166d370fc18fdaa3d9 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 12 Jan 2021 15:05:50 +0530 Subject: [PATCH 05/12] [MessageListView] simplify some conditions Signed-off-by: Sahil Kumar --- lib/src/message_list_view.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index ba9c499e..6f61e261 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -851,7 +851,7 @@ class _MessageListViewState extends State { attachmentBorderRadiusGeometry: BorderRadius.only( topLeft: Radius.circular(attachmentBorderRadius), bottomLeft: Radius.circular( - (timeDiff >= 1 || !isNextUserSame) && !hasReplies && !isThreadMessage + (timeDiff >= 1 || !isNextUserSame) && !(hasReplies || isThreadMessage) ? 0 : attachmentBorderRadius, ), @@ -862,7 +862,7 @@ class _MessageListViewState extends State { borderRadiusGeometry: BorderRadius.only( topLeft: Radius.circular(16), bottomLeft: Radius.circular( - (timeDiff >= 1 || !isNextUserSame) && !hasReplies && !isThreadMessage + (timeDiff >= 1 || !isNextUserSame) && !(hasReplies || isThreadMessage) ? 0 : 16, ), From 0605cbc3b4ffcfd68464c8be3afb812922a1431f Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 12 Jan 2021 15:30:45 +0530 Subject: [PATCH 06/12] Move `isOnlyEmoji` utility function into String extension Signed-off-by: Sahil Kumar --- lib/src/extension.dart | 21 +++++++++++++++++++++ lib/src/message_list_view.dart | 5 +++-- lib/src/message_widget.dart | 2 +- lib/src/quoted_message_widget.dart | 2 +- lib/src/utils.dart | 14 -------------- 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/lib/src/extension.dart b/lib/src/extension.dart index 71edefe6..3ffd0e24 100644 --- a/lib/src/extension.dart +++ b/lib/src/extension.dart @@ -1,7 +1,28 @@ +import 'package:emojis/emoji.dart'; +import 'package:characters/characters.dart'; + +final _emojis = Emoji.all(); + extension StringExtension on String { String capitalize() { return "${this[0].toUpperCase()}${this.substring(1)}"; } + + // Emojis guidelines + // 1 to 3 emojis: big size with no text bubble. + // 4+ emojis or emojis+text: standard size with text bubble. + bool get isOnlyEmoji { + final characters = this.trim().characters; + if (characters.isEmpty) return false; + if (characters.length > 3) return false; + return characters.every((c) { + return _emojis.firstWhere( + (Emoji emoji) => emoji.char.contains(c), + orElse: () => null, + ) != + null; + }); + } } /// List extension diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 6f61e261..095b6b73 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -17,6 +17,7 @@ import '../stream_chat_flutter.dart'; import 'date_divider.dart'; import 'stream_channel.dart'; import 'swipeable.dart'; +import 'extension.dart'; typedef MessageBuilder = Widget Function( BuildContext, @@ -708,7 +709,7 @@ class _MessageListViewState extends State { Message message, ) { final isMyMessage = message.user.id == StreamChat.of(context).user.id; - final isOnlyEmoji = textIsOnlyEmoji(message.text); + final isOnlyEmoji = message.text.isOnlyEmoji; return MessageWidget( showThreadReplyIndicator: false, @@ -808,7 +809,7 @@ class _MessageListViewState extends State { final showInChannelIndicator = !_isThreadConversation && isThreadMessage; final showThreadReplyIndicator = !_isThreadConversation && hasReplies; - final isOnlyEmoji = textIsOnlyEmoji(message.text); + final isOnlyEmoji = message.text.isOnlyEmoji; Widget child = MessageWidget( key: ValueKey('MESSAGE-${message.id}'), diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index e9ea4873..cd14ac40 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -938,7 +938,7 @@ class _MessageWidgetState extends State { ); } - bool get isOnlyEmoji => textIsOnlyEmoji(widget.message.text); + bool get isOnlyEmoji => widget.message.text.isOnlyEmoji; Color _getBackgroundColor() { if (hasQuotedMessage) { diff --git a/lib/src/quoted_message_widget.dart b/lib/src/quoted_message_widget.dart index d012e15a..8a102635 100644 --- a/lib/src/quoted_message_widget.dart +++ b/lib/src/quoted_message_widget.dart @@ -125,7 +125,7 @@ class QuotedMessageWidget extends StatelessWidget { } Widget _buildMessage(BuildContext context) { - final isOnlyEmoji = textIsOnlyEmoji(message.text); + final isOnlyEmoji = message.text.isOnlyEmoji; var msg = _hasAttachments && !_containsText ? message.copyWith(text: message.attachments.last?.title ?? '') : message; diff --git a/lib/src/utils.dart b/lib/src/utils.dart index be10263e..333b96f1 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,4 +1,3 @@ -import 'package:emojis/emoji.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -215,16 +214,3 @@ StreamSvgIcon getFileTypeImage(String type) { break; } } - -final _emojis = Emoji.all(); - -bool textIsOnlyEmoji(String text) { - return text.trim().characters.isNotEmpty && - text.trim().characters.every((c) => - _emojis.firstWhere( - (Emoji emoji) => emoji.char.contains(c), - orElse: () => null, - ) != - null) && - text.characters.length < 4; -} From fbc8fd9a5937f5b0826cbb625183c6fa95091bc5 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 12 Jan 2021 15:38:54 +0530 Subject: [PATCH 07/12] [QuotedMessageWidget, MessageWidget] Fix emoji and regular text size Signed-off-by: Sahil Kumar --- lib/src/message_widget.dart | 2 +- lib/src/quoted_message_widget.dart | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index cd14ac40..a40556a2 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -923,7 +923,7 @@ class _MessageWidgetState extends State { ? widget.messageTheme.copyWith( messageText: widget.messageTheme.messageText.copyWith( - fontSize: 40, + fontSize: 42, )) : widget.messageTheme, ), diff --git a/lib/src/quoted_message_widget.dart b/lib/src/quoted_message_widget.dart index 8a102635..c718cdb5 100644 --- a/lib/src/quoted_message_widget.dart +++ b/lib/src/quoted_message_widget.dart @@ -145,9 +145,12 @@ class QuotedMessageWidget extends StatelessWidget { messageTheme: isOnlyEmoji && _containsText ? messageTheme.copyWith( messageText: messageTheme.messageText.copyWith( - fontSize: 24, + fontSize: 32, )) - : messageTheme, + : messageTheme.copyWith( + messageText: messageTheme.messageText.copyWith( + fontSize: 12, + )), ), ), ), From 47a3a94c93b38112c19612017f876d8383b34346 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 12 Jan 2021 16:57:04 +0530 Subject: [PATCH 08/12] [MessageInput] Fix iconButtons and textField paddings and text sizes Signed-off-by: Sahil Kumar --- lib/src/message_input.dart | 559 ++++++++++++++++++------------------- 1 file changed, 271 insertions(+), 288 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index ee8763ba..8bb20302 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -358,31 +358,31 @@ class MessageInputState extends State { ); } - AnimatedCrossFade _animateSendButton(BuildContext context) { - return AnimatedCrossFade( - crossFadeState: ((_messageIsPresent || _attachments.isNotEmpty) && - _attachments.every((a) => a.uploaded == true)) - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: _buildSendButton(context), - secondChild: _buildIdleSendButton(context), - duration: Duration(milliseconds: 300), - alignment: Alignment.center, + Widget _animateSendButton(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: AnimatedCrossFade( + crossFadeState: ((_messageIsPresent || _attachments.isNotEmpty) && + _attachments.every((a) => a.uploaded == true)) + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: _buildSendButton(context), + secondChild: _buildIdleSendButton(context), + duration: Duration(milliseconds: 300), + alignment: Alignment.center, + ), ); } Widget _buildExpandActionsButton() { - return AnimatedCrossFade( - crossFadeState: - _actionsShrunk ? CrossFadeState.showFirst : CrossFadeState.showSecond, - firstChild: Padding( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), - child: IconButton( - onPressed: () { - setState(() { - _actionsShrunk = false; - }); - }, + return Padding( + padding: const EdgeInsets.all(8.0), + child: AnimatedCrossFade( + crossFadeState: _actionsShrunk + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: IconButton( + onPressed: () => setState(() => _actionsShrunk = false), icon: StreamSvgIcon.emptyCircleLeft( color: StreamChatTheme.of(context).colorTheme.accentBlue, ), @@ -393,34 +393,36 @@ class MessageInputState extends State { ), splashRadius: 24, ), + secondChild: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + if (!widget.disableAttachments) _buildAttachmentButton(), + if (widget.editMessage == null && + StreamChannel.of(context) + .channel + ?.config + ?.commands + ?.isNotEmpty == + true) + _buildCommandButton(), + ].insertBetween(const SizedBox(width: 8)), + ), + duration: Duration(milliseconds: 300), + alignment: Alignment.center, ), - secondChild: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - if (!widget.disableAttachments) _buildAttachmentButton(), - if (widget.editMessage == null && - StreamChannel.of(context).channel?.config?.commands?.isNotEmpty == - true) - _buildCommandButton(), - ], - ), - duration: Duration(milliseconds: 300), - alignment: Alignment.center, ); } Expanded _buildTextInput(BuildContext context) { + final theme = StreamChatTheme.of(context); return Expanded( child: Center( child: Container( clipBehavior: Clip.antiAlias, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(24.0), - border: Border.all( - color: StreamChatTheme.of(context).colorTheme.greyGainsboro, - ), + borderRadius: BorderRadius.circular(20.0), + border: Border.all(color: theme.colorTheme.greyGainsboro), ), - padding: _attachments.isEmpty ? null : EdgeInsets.all(6.0), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, @@ -429,52 +431,48 @@ class MessageInputState extends State { _buildAttachments(), LimitedBox( maxHeight: widget.maxHeight, - child: TextField( - key: Key('messageInputText'), - enabled: _inputEnabled, - minLines: null, - maxLines: null, - onSubmitted: (_) { - sendMessage(); - }, - keyboardType: widget.keyboardType, - controller: textEditingController, - focusNode: _focusNode, - style: Theme.of(context).textTheme.bodyText2, - autofocus: false, - textAlignVertical: TextAlignVertical.center, - decoration: InputDecoration( - isDense: true, - hintText: _getHint(), - prefixText: _commandEnabled ? null : ' ', - border: OutlineInputBorder( - borderSide: BorderSide(color: Colors.transparent)), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.transparent)), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.transparent)), - errorBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.transparent)), - disabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.transparent)), - contentPadding: EdgeInsets.symmetric( - horizontal: 16, - vertical: 13, - ), - prefixIcon: _commandEnabled - ? Padding( - padding: - const EdgeInsets.symmetric(horizontal: 8.0), - child: Chip( - backgroundColor: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - padding: EdgeInsets.zero, - labelPadding: - EdgeInsets.symmetric(horizontal: 8.0), - label: Row( + child: SizedBox( + height: 40, + child: TextField( + key: Key('messageInputText'), + enabled: _inputEnabled, + minLines: null, + maxLines: null, + onSubmitted: (_) => sendMessage(), + keyboardType: widget.keyboardType, + controller: textEditingController, + focusNode: _focusNode, + style: theme.textTheme.body, + autofocus: false, + textAlignVertical: TextAlignVertical.center, + decoration: InputDecoration( + isDense: true, + hintText: _getHint(), + hintStyle: theme.textTheme.body.copyWith( + color: theme.colorTheme.grey, + ), + border: OutlineInputBorder( + borderSide: BorderSide(color: Colors.transparent)), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.transparent)), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.transparent)), + errorBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.transparent)), + disabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.transparent)), + contentPadding: const EdgeInsets.fromLTRB(16, 12, 13, 11), + prefixIcon: _commandEnabled + ? Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: theme.colorTheme.accentBlue, + ), + height: 24, + margin: const EdgeInsets.all(8.0), + padding: const EdgeInsets.only(right: 8, left: 4), + child: Row( mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, children: [ StreamSvgIcon.lightning( color: Colors.white, @@ -484,27 +482,32 @@ class MessageInputState extends State { _chosenCommand?.name?.toUpperCase() ?? '', style: StreamChatTheme.of(context) .textTheme - .footnote + .footnoteBold .copyWith( color: Colors.white, ), ), ], ), - ), - ) - : null, - suffixIcon: _commandEnabled - ? IconButton( - icon: StreamSvgIcon.close_small(), - splashRadius: 24, - onPressed: () { - setState(() => _commandEnabled = false); - }, - ) - : null, + ) + : null, + suffixIcon: _commandEnabled + ? IconButton( + icon: StreamSvgIcon.close_small(), + splashRadius: 24, + padding: const EdgeInsets.all(0), + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + onPressed: () { + setState(() => _commandEnabled = false); + }, + ) + : null, + ), + textCapitalization: TextCapitalization.sentences, ), - textCapitalization: TextCapitalization.sentences, ), ) ], @@ -1515,9 +1518,7 @@ class MessageInputState extends State { } Widget _buildReplyToMessage() { - if (!_hasQuotedMessage) { - return Offstage(); - } + if (!_hasQuotedMessage) return Offstage(); final containsUrl = widget.quotedMessage.attachments ?.any((element) => element.ogScrapeUrl != null) == true; @@ -1534,109 +1535,104 @@ class MessageInputState extends State { } Widget _buildAttachments() { - return _attachments.isEmpty - ? Container() - : Column( - children: [ - if (_attachments.any((e) => e.attachment?.type == 'file')) - LimitedBox( - maxHeight: 136.0, - child: ListView( - reverse: true, - shrinkWrap: true, - children: _attachments.reversed - .where((e) => e.attachment?.type == 'file') - .map( - (e) => Padding( - padding: - const EdgeInsets.symmetric(horizontal: 8.0), - child: ClipRRect( - borderRadius: BorderRadius.circular(10), - clipBehavior: Clip.antiAlias, - child: FileAttachment( - attachment: e.attachment, - attachmentType: FileAttachmentType.local, - file: e.file, - size: Size( - MediaQuery.of(context).size.width * 0.65, - 56.0, + if (_attachments.isEmpty) return Offstage(); + return Column( + children: [ + if (_attachments.any((e) => e.attachment?.type == 'file')) + LimitedBox( + maxHeight: 136.0, + child: ListView( + reverse: true, + shrinkWrap: true, + children: _attachments.reversed + .where((e) => e.attachment?.type == 'file') + .map( + (e) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + clipBehavior: Clip.antiAlias, + child: FileAttachment( + attachment: e.attachment, + attachmentType: FileAttachmentType.local, + file: e.file, + size: Size( + MediaQuery.of(context).size.width * 0.65, + 56.0, + ), + trailing: Padding( + padding: const EdgeInsets.all(8.0), + child: InkWell( + child: CircleAvatar( + backgroundColor: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.6), + maxRadius: 12.0, + child: StreamSvgIcon.close( + color: StreamChatTheme.of(context) + .colorTheme + .white, ), - trailing: Padding( - padding: const EdgeInsets.all(8.0), - child: InkWell( - child: CircleAvatar( - backgroundColor: - StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.6), - maxRadius: 12.0, - child: StreamSvgIcon.close( - color: StreamChatTheme.of(context) - .colorTheme - .white, + ), + onTap: () { + setState(() { + _attachments.remove(e); + }); + }, + ), + ), + ), + ), + ), + ) + .toList(), + ), + ), + if (_attachments.any((e) => e.attachment?.type != 'file')) + LimitedBox( + maxHeight: 104.0, + child: ListView( + scrollDirection: Axis.horizontal, + children: _attachments + .where((e) => e.attachment?.type != 'file') + .map( + (attachment) => Padding( + padding: const EdgeInsets.all(8.0), + child: ClipRRect( + borderRadius: BorderRadius.circular(10), + clipBehavior: Clip.antiAlias, + child: Stack( + children: [ + AspectRatio( + aspectRatio: 1.0, + child: Container( + height: 104, + width: 104, + child: _buildAttachment(attachment), + ), + ), + _buildRemoveButton(attachment), + attachment.uploaded + ? SizedBox() + : Positioned.fill( + child: Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: CircularProgressIndicator(), ), ), - onTap: () { - setState(() { - _attachments.remove(e); - }); - }, ), - ), - ), - ), - ), - ) - .toList(), - ), - ), - if (_attachments.any((e) => e.attachment?.type != 'file')) - LimitedBox( - maxHeight: 104.0, - child: ListView( - scrollDirection: Axis.horizontal, - children: _attachments - .where((e) => e.attachment?.type != 'file') - .map( - (attachment) => Padding( - padding: const EdgeInsets.all(8.0), - child: ClipRRect( - borderRadius: BorderRadius.circular(10), - clipBehavior: Clip.antiAlias, - child: Stack( - children: [ - AspectRatio( - aspectRatio: 1.0, - child: Container( - height: 104, - width: 104, - child: _buildAttachment(attachment), - ), - ), - _buildRemoveButton(attachment), - attachment.uploaded - ? SizedBox() - : Positioned.fill( - child: Center( - child: Padding( - padding: - const EdgeInsets.all(16.0), - child: - CircularProgressIndicator(), - ), - ), - ), - ], - ), - ), - ), - ) - .toList(), - ), - ), - ], - ); + ], + ), + ), + ), + ) + .toList(), + ), + ), + ], + ); } Positioned _buildRemoveButton(_SendingAttachment attachment) { @@ -1746,80 +1742,74 @@ class MessageInputState extends State { } Widget _buildCommandButton() { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), - child: IconButton( - icon: StreamSvgIcon.lightning( - color: _commandsOverlay != null - ? StreamChatTheme.of(context).colorTheme.accentBlue - : StreamChatTheme.of(context).colorTheme.grey, - ), - padding: const EdgeInsets.all(0), - constraints: BoxConstraints.tightFor( - height: 24, - width: 24, - ), - splashRadius: 24, - onPressed: () async { - if (_openFilePickerSection) { - setState(() { - _animateContainer = false; - _openFilePickerSection = false; - _filePickerSize = _kMinMediaPickerSize; - }); - await Future.delayed(Duration(milliseconds: 300)); - } - - if (_commandsOverlay == null) { - setState(() { - _commandsOverlay = _buildCommandsOverlayEntry(); - Overlay.of(context).insert(_commandsOverlay); - }); - } else { - setState(() { - _commandsOverlay?.remove(); - _commandsOverlay = null; - }); - } - }, + return IconButton( + icon: StreamSvgIcon.lightning( + color: _commandsOverlay != null + ? StreamChatTheme.of(context).colorTheme.accentBlue + : StreamChatTheme.of(context).colorTheme.grey, ), + padding: const EdgeInsets.all(0), + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + splashRadius: 24, + onPressed: () async { + if (_openFilePickerSection) { + setState(() { + _animateContainer = false; + _openFilePickerSection = false; + _filePickerSize = _kMinMediaPickerSize; + }); + await Future.delayed(Duration(milliseconds: 300)); + } + + if (_commandsOverlay == null) { + setState(() { + _commandsOverlay = _buildCommandsOverlayEntry(); + Overlay.of(context).insert(_commandsOverlay); + }); + } else { + setState(() { + _commandsOverlay?.remove(); + _commandsOverlay = null; + }); + } + }, ); } Widget _buildAttachmentButton() { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), - child: IconButton( - icon: StreamSvgIcon.attach( - color: _openFilePickerSection - ? StreamChatTheme.of(context).colorTheme.accentBlue - : StreamChatTheme.of(context).colorTheme.grey, - ), - padding: const EdgeInsets.all(0), - constraints: BoxConstraints.tightFor( - height: 24, - width: 24, - ), - splashRadius: 24, - onPressed: () async { - _emojiOverlay?.remove(); - _emojiOverlay = null; - _commandsOverlay?.remove(); - _commandsOverlay = null; - _mentionsOverlay?.remove(); - _mentionsOverlay = null; - - if (_openFilePickerSection) { - setState(() { - _animateContainer = true; - _openFilePickerSection = false; - _filePickerSize = _kMinMediaPickerSize; - }); - } else { - showAttachmentModal(); - } - }, + return IconButton( + icon: StreamSvgIcon.attach( + color: _openFilePickerSection + ? StreamChatTheme.of(context).colorTheme.accentBlue + : StreamChatTheme.of(context).colorTheme.grey, ), + padding: const EdgeInsets.all(0), + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + splashRadius: 24, + onPressed: () async { + _emojiOverlay?.remove(); + _emojiOverlay = null; + _commandsOverlay?.remove(); + _commandsOverlay = null; + _mentionsOverlay?.remove(); + _mentionsOverlay = null; + + if (_openFilePickerSection) { + setState(() { + _animateContainer = true; + _openFilePickerSection = false; + _filePickerSize = _kMinMediaPickerSize; + }); + } else { + showAttachmentModal(); + } + }, ); } @@ -2118,31 +2108,24 @@ class MessageInputState extends State { } Widget _buildIdleSendButton(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), - child: StreamSvgIcon( - assetName: _getIdleSendIcon(), - color: StreamChatTheme.of(context).colorTheme.greyGainsboro, - ), + return StreamSvgIcon( + assetName: _getIdleSendIcon(), + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, ); } Widget _buildSendButton(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), - child: IconButton( - onPressed: sendMessage, - visualDensity: VisualDensity.compact, - padding: const EdgeInsets.all(0), - splashRadius: 24, - constraints: BoxConstraints.tightFor( - height: 24, - width: 24, - ), - icon: StreamSvgIcon( - assetName: _getSendIcon(), - color: StreamChatTheme.of(context).colorTheme.accentBlue, - ), + return IconButton( + onPressed: sendMessage, + padding: const EdgeInsets.all(0), + splashRadius: 24, + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + icon: StreamSvgIcon( + assetName: _getSendIcon(), + color: StreamChatTheme.of(context).colorTheme.accentBlue, ), ); } From c5f245808378e685af2daec504cc8989233706c2 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 12 Jan 2021 18:03:24 +0530 Subject: [PATCH 09/12] [MessageWidget] Move messageBorderSide handling one level up Signed-off-by: Sahil Kumar --- lib/src/message_list_view.dart | 10 +++++++++- lib/src/message_widget.dart | 7 ++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 095b6b73..dab8eda1 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -811,6 +811,14 @@ class _MessageListViewState extends State { final showThreadReplyIndicator = !_isThreadConversation && hasReplies; final isOnlyEmoji = message.text.isOnlyEmoji; + final borderSide = isMyMessage + ? !(showThreadReplyIndicator || showInChannelIndicator) + ? BorderSide.none + : null + : isOnlyEmoji + ? BorderSide.none + : null; + Widget child = MessageWidget( key: ValueKey('MESSAGE-${message.id}'), message: message, @@ -846,7 +854,7 @@ class _MessageListViewState extends State { showEditMessage: isMyMessage, showDeleteMessage: isMyMessage, showThreadReplyMessage: !isThreadMessage, - borderSide: isMyMessage && !isOnlyEmoji ? BorderSide.none : null, + borderSide: borderSide, onThreadTap: _onThreadTap, onReplyTap: widget.onReplyTap, attachmentBorderRadiusGeometry: BorderRadius.only( diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index a40556a2..ba458a7e 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -383,11 +383,8 @@ class _MessageWidgetState extends State { ), shape: widget.shape ?? RoundedRectangleBorder( - side: isOnlyEmoji && - !(showThreadReplyIndicator || - showInChannel) - ? BorderSide.none - : widget.borderSide ?? + side: + widget.borderSide ?? BorderSide( color: widget .messageTheme From 7e8def27072acc5231bb826a5036848b533402a6 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 12 Jan 2021 18:08:50 +0530 Subject: [PATCH 10/12] [MessageListView] Minor border visibility condition fixes Signed-off-by: Sahil Kumar --- lib/src/message_list_view.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index dab8eda1..6242bd8a 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -732,7 +732,7 @@ class _MessageListViewState extends State { topRight: Radius.circular(16), bottomRight: Radius.circular(16), ), - borderSide: isMyMessage && !isOnlyEmoji ? BorderSide.none : null, + borderSide: isMyMessage || isOnlyEmoji ? BorderSide.none : null, showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show, messageTheme: isMyMessage ? StreamChatTheme.of(context).ownMessageTheme @@ -811,11 +811,13 @@ class _MessageListViewState extends State { final showThreadReplyIndicator = !_isThreadConversation && hasReplies; final isOnlyEmoji = message.text.isOnlyEmoji; + final showMessageBorder = + showThreadReplyIndicator || showInChannelIndicator; final borderSide = isMyMessage - ? !(showThreadReplyIndicator || showInChannelIndicator) + ? !showMessageBorder ? BorderSide.none : null - : isOnlyEmoji + : isOnlyEmoji && !showMessageBorder ? BorderSide.none : null; From 16bedf87287d845a2a765f114b016c62419ef9d6 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 12 Jan 2021 19:36:12 +0530 Subject: [PATCH 11/12] [QuotedMessageWidget, MessageWidget] Fix quoted message padding in case of attachments Signed-off-by: Sahil Kumar --- lib/src/message_widget.dart | 33 +++++++++++++++++++++-------- lib/src/quoted_message_widget.dart | 34 ++++++++++++------------------ 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index c4b03ce2..8f4b0158 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -272,6 +272,12 @@ class _MessageWidgetState extends State { widget.message.attachments?.any((element) => element.type == 'giphy') == true; + bool get hasNonUrlAttachments => + widget.message.attachments + ?.where((it) => it.ogScrapeUrl == null) + ?.isNotEmpty == + true; + bool get showBottomRow => showThreadReplyIndicator || showUsername || @@ -409,8 +415,9 @@ class _MessageWidgetState extends State { children: [ if (hasQuotedMessage) _buildQuotedMessage(), - ..._parseAttachments( - context), + if (hasNonUrlAttachments) + ..._parseAttachments( + context), if (widget.message.text .trim() .isNotEmpty && @@ -487,13 +494,21 @@ class _MessageWidgetState extends State { widget.onQuotedMessageTap != null ? () => widget.onQuotedMessageTap(widget.message.quotedMessageId) : null; - return QuotedMessageWidget( - onTap: onTap, - message: widget.message.quotedMessage, - messageTheme: isMyMessage - ? StreamChatTheme.of(context).otherMessageTheme - : StreamChatTheme.of(context).ownMessageTheme, - reverse: widget.reverse, + return Padding( + padding: EdgeInsets.only( + right: 8, + left: 8, + top: 8, + bottom: hasNonUrlAttachments ? 8 : 0, + ), + child: QuotedMessageWidget( + onTap: onTap, + message: widget.message.quotedMessage, + messageTheme: isMyMessage + ? StreamChatTheme.of(context).otherMessageTheme + : StreamChatTheme.of(context).ownMessageTheme, + reverse: widget.reverse, + ), ); } diff --git a/lib/src/quoted_message_widget.dart b/lib/src/quoted_message_widget.dart index c718cdb5..309df703 100644 --- a/lib/src/quoted_message_widget.dart +++ b/lib/src/quoted_message_widget.dart @@ -109,17 +109,14 @@ class QuotedMessageWidget extends StatelessWidget { Widget build(BuildContext context) { return InkWell( onTap: onTap, - child: Padding( - padding: const EdgeInsets.only(top: 8, right: 4, left: 8), - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - Flexible(child: _buildMessage(context)), - SizedBox(width: 4), - _buildUserAvatar(), - ], - ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Flexible(child: _buildMessage(context)), + SizedBox(width: 8), + _buildUserAvatar(), + ], ), ); } @@ -247,16 +244,13 @@ class QuotedMessageWidget extends StatelessWidget { return Transform( transform: Matrix4.rotationY(reverse ? pi : 0), alignment: Alignment.center, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: UserAvatar( - user: message.user, - constraints: BoxConstraints.tightFor( - height: 24, - width: 24, - ), - showOnlineStatus: false, + child: UserAvatar( + user: message.user, + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, ), + showOnlineStatus: false, ), ); } From 80d8c48826a7f1da7b9ab8f6bd6813bdc28e0ea2 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 12 Jan 2021 19:46:19 +0530 Subject: [PATCH 12/12] Minor ui fixes Signed-off-by: Sahil Kumar --- lib/src/message_actions_modal.dart | 6 ++---- lib/src/message_input.dart | 13 ++++++++----- lib/src/utils.dart | 22 ++++++++-------------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/lib/src/message_actions_modal.dart b/lib/src/message_actions_modal.dart index 7f9c5e85..2d125bab 100644 --- a/lib/src/message_actions_modal.dart +++ b/lib/src/message_actions_modal.dart @@ -164,7 +164,7 @@ class _MessageActionsModalState extends State { ), Padding( padding: EdgeInsets.only( - right: widget.reverse ? 16 : 0, + right: widget.reverse ? 8 : 0, left: widget.reverse ? 0 : 48, ), child: SizedBox( @@ -448,9 +448,7 @@ class _MessageActionsModalState extends State { ), ], ), - onTap: () { - _showFlagDialog(); - }, + onTap: () => _showFlagDialog(), ); } diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 8bb20302..4561372e 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -1525,11 +1525,14 @@ class MessageInputState extends State { return Transform( transform: Matrix4.rotationY(pi), alignment: Alignment.center, - child: QuotedMessageWidget( - reverse: true, - showBorder: !containsUrl, - message: widget.quotedMessage, - messageTheme: StreamChatTheme.of(context).otherMessageTheme, + child: Padding( + padding: const EdgeInsets.fromLTRB(8, 8, 8, 0), + child: QuotedMessageWidget( + reverse: true, + showBorder: !containsUrl, + message: widget.quotedMessage, + messageTheme: StreamChatTheme.of(context).otherMessageTheme, + ), ), ); } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 6548b987..d1dec9c8 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -37,27 +37,21 @@ Future showConfirmationDialog( return Column( mainAxisSize: MainAxisSize.min, children: [ - SizedBox( - height: 26.0, - ), + SizedBox(height: 26.0), if (icon != null) icon, - SizedBox( - height: 26.0, - ), + SizedBox(height: 26.0), Text( title, style: StreamChatTheme.of(context).textTheme.headlineBold, ), - SizedBox( - height: 7.0, - ), - Text(question), - SizedBox( - height: 36.0, + SizedBox(height: 7.0), + Text( + question, + textAlign: TextAlign.center, ), + SizedBox(height: 36.0), Container( - color: - StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, height: 1.0, ), Row(