Merge pull request #209 from GetStream/qa4-fixes

QA4-Fixes
This commit is contained in:
Salvatore Giordano
2021-01-13 17:00:21 +01:00
committed by GitHub
13 changed files with 600 additions and 494 deletions
+3 -1
View File
@@ -74,7 +74,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
), ),
borderRadius: BorderRadius.circular(32.0), borderRadius: BorderRadius.circular(32.0),
onlineIndicatorConstraints: onlineIndicatorConstraints:
BoxConstraints.tight(Size(16.0, 16.0)), BoxConstraints.tight(Size(12.0, 12.0)),
), ),
SizedBox( SizedBox(
height: 6.0, height: 6.0,
@@ -110,6 +110,8 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
maxWidth: 64.0, maxWidth: 64.0,
), ),
borderRadius: BorderRadius.circular(32.0), borderRadius: BorderRadius.circular(32.0),
onlineIndicatorConstraints:
BoxConstraints.tight(Size(12.0, 12.0)),
), ),
SizedBox( SizedBox(
height: 6.0, height: 6.0,
+14 -19
View File
@@ -62,16 +62,9 @@ class _FileAttachmentState extends State<FileAttachment> {
child: Container( child: Container(
width: widget.size?.width ?? 100, width: widget.size?.width ?? 100,
height: 56.0, height: 56.0,
decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.white, color: StreamChatTheme.of(context).colorTheme.white,
borderRadius:
widget.trailing != null ? BorderRadius.circular(16.0) : null,
border: widget.trailing != null
? Border.fromBorderSide(BorderSide(
color: StreamChatTheme.of(context).colorTheme.greyWhisper))
: null,
),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
child: _getFileTypeImage(), child: _getFileTypeImage(),
@@ -79,9 +72,7 @@ class _FileAttachmentState extends State<FileAttachment> {
width: 33.33, width: 33.33,
margin: EdgeInsets.all(8.0), margin: EdgeInsets.all(8.0),
), ),
SizedBox( SizedBox(width: 8.0),
width: 6.0,
),
Expanded( Expanded(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -93,12 +84,13 @@ class _FileAttachmentState extends State<FileAttachment> {
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
SizedBox( SizedBox(height: 3.0),
height: 3.0,
),
Text( Text(
'${getSizeText(widget.attachment.extraData['file_size'])}', '${getSizeText(widget.attachment.extraData['file_size'])}',
style: StreamChatTheme.of(context).textTheme.body.copyWith( style: StreamChatTheme.of(context)
.textTheme
.footnote
.copyWith(
color: StreamChatTheme.of(context) color: StreamChatTheme.of(context)
.colorTheme .colorTheme
.black .black
@@ -107,18 +99,21 @@ class _FileAttachmentState extends State<FileAttachment> {
], ],
), ),
), ),
Column( SizedBox(width: 8.0),
children: [ Material(
widget.trailing ?? type: MaterialType.transparency,
child: widget.trailing ??
IconButton( IconButton(
icon: StreamSvgIcon.cloud_download( icon: StreamSvgIcon.cloud_download(
color: StreamChatTheme.of(context).colorTheme.black, color: StreamChatTheme.of(context).colorTheme.black,
), ),
padding: const EdgeInsets.all(8),
visualDensity: VisualDensity.compact,
splashRadius: 16,
onPressed: () { onPressed: () {
launchURL(context, widget.attachment.assetUrl); launchURL(context, widget.attachment.assetUrl);
}, },
), ),
],
), ),
], ],
), ),
+58 -60
View File
@@ -8,6 +8,7 @@ import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import '../stream_chat_flutter.dart'; import '../stream_chat_flutter.dart';
import 'extension.dart';
class ImageActionsModal extends StatelessWidget { class ImageActionsModal extends StatelessWidget {
final Message message; final Message message;
@@ -35,61 +36,49 @@ class ImageActionsModal extends StatelessWidget {
} }
Widget _buildPage(context) { Widget _buildPage(context) {
return Material( return Column(
color: Colors.transparent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
SizedBox( SizedBox(height: kToolbarHeight),
height: kToolbarHeight, Padding(
child: IconButton( padding: const EdgeInsets.only(right: 8.0),
icon: StreamSvgIcon.close(),
onPressed: () => Navigator.maybePop(context),
),
),
Align(
alignment: Alignment.centerRight,
child: Container( child: Container(
width: MediaQuery.of(context).size.width / 1.8, width: MediaQuery.of(context).size.width * 0.5,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Material(
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16.0),
), ),
child: Container(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.end,
children: ListTile.divideTiles( mainAxisSize: MainAxisSize.min,
color: StreamChatTheme.of(context).colorTheme.greyWhisper, children: [
context: context,
tiles: [
_buildButton( _buildButton(
context, context,
'Reply', 'Reply',
StreamSvgIcon.Icon_curve_line_left_up( StreamSvgIcon.Icon_curve_line_left_up(
size: 24.0, size: 24.0,
color: color: StreamChatTheme.of(context).colorTheme.grey,
StreamChatTheme.of(context).colorTheme.grey, ),
() {},
), ),
() {}),
_buildButton( _buildButton(
context, context,
'Show in Chat', 'Show in Chat',
StreamSvgIcon.eye( StreamSvgIcon.eye(
size: 24.0, size: 24.0,
color: color: StreamChatTheme.of(context).colorTheme.black,
StreamChatTheme.of(context).colorTheme.black, ),
onShowMessage,
), ),
onShowMessage),
_buildButton( _buildButton(
context, context,
'Save ${urls[currentIndex].type == 'video' ? 'Video' : 'Image'}', 'Save ${urls[currentIndex].type == 'video' ? 'Video' : 'Image'}',
StreamSvgIcon.Icon_save( StreamSvgIcon.Icon_save(
size: 24.0, size: 24.0,
color: color: StreamChatTheme.of(context).colorTheme.grey,
StreamChatTheme.of(context).colorTheme.grey, ),
), () async { () async {
var url = urls[currentIndex].imageUrl ?? var url = urls[currentIndex].imageUrl ??
urls[currentIndex].assetUrl ?? urls[currentIndex].assetUrl ??
urls[currentIndex].thumbUrl; urls[currentIndex].thumbUrl;
@@ -101,16 +90,15 @@ class ImageActionsModal extends StatelessWidget {
await _saveImage(url); await _saveImage(url);
Navigator.pop(context); Navigator.pop(context);
} }
}), },
),
if (StreamChat.of(context).user.id == message.user.id) if (StreamChat.of(context).user.id == message.user.id)
_buildButton( _buildButton(
context, context,
'Delete', 'Delete',
StreamSvgIcon.delete( StreamSvgIcon.delete(
size: 24.0, size: 24.0,
color: StreamChatTheme.of(context) color: StreamChatTheme.of(context).colorTheme.accentRed,
.colorTheme
.accentRed,
), ),
() { () {
Navigator.pop(context); Navigator.pop(context);
@@ -120,42 +108,52 @@ class ImageActionsModal extends StatelessWidget {
StreamChannel.of(context).channel.cid, StreamChannel.of(context).channel.cid,
); );
}, },
color: StreamChatTheme.of(context) color: StreamChatTheme.of(context).colorTheme.accentRed,
.colorTheme
.accentRed,
), ),
]
.map<Widget>((e) =>
Align(alignment: Alignment.centerRight, child: e))
.insertBetween(
Container(
height: 1,
color:
StreamChatTheme.of(context).colorTheme.greyWhisper,
),
),
),
),
),
)
], ],
).toList(),
),
),
),
),
),
],
),
); );
} }
Widget _buildButton( Widget _buildButton(
context, String title, StreamSvgIcon icon, VoidCallback onTap, context,
{Color color}) { String title,
var titleStyle = TextStyle( StreamSvgIcon icon,
fontSize: 14.5, VoidCallback onTap, {
color: StreamChatTheme.of(context).colorTheme.black, Color color,
); }) {
return Material( return Material(
color: StreamChatTheme.of(context).colorTheme.white, color: StreamChatTheme.of(context).colorTheme.white,
child: InkWell( child: InkWell(
onTap: onTap, onTap: onTap,
child: ListTile( child: Padding(
dense: true, padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
title: Text( child: Row(
children: [
icon,
SizedBox(width: 16),
Text(
title, title,
style: style: StreamChatTheme.of(context)
color == null ? titleStyle : titleStyle.copyWith(color: color), .textTheme
.body
.copyWith(color: color),
),
],
), ),
leading: icon,
), ),
), ),
); );
+99 -73
View File
@@ -14,6 +14,7 @@ import 'message_input.dart';
import 'message_widget.dart'; import 'message_widget.dart';
import 'stream_chat.dart'; import 'stream_chat.dart';
import 'stream_chat_theme.dart'; import 'stream_chat_theme.dart';
import 'extension.dart';
class MessageActionsModal extends StatefulWidget { class MessageActionsModal extends StatefulWidget {
final Widget Function(BuildContext, Message) editMessageInputBuilder; final Widget Function(BuildContext, Message) editMessageInputBuilder;
@@ -89,6 +90,8 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
duration: Duration(milliseconds: 300), duration: Duration(milliseconds: 300),
curve: Curves.easeInOutBack, curve: Curves.easeInOutBack,
builder: (context, val, snapshot) { builder: (context, val, snapshot) {
final hasFileAttachment =
widget.message.attachments?.any((it) => it.type == 'file') == true;
return GestureDetector( return GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: () => Navigator.maybePop(context), onTap: () => Navigator.maybePop(context),
@@ -110,7 +113,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
child: Center( child: Center(
child: SingleChildScrollView( child: SingleChildScrollView(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.all(8.0),
child: Column( child: Column(
crossAxisAlignment: widget.reverse crossAxisAlignment: widget.reverse
? CrossAxisAlignment.end ? CrossAxisAlignment.end
@@ -135,6 +138,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
messageTheme: widget.messageTheme, messageTheme: widget.messageTheme,
), ),
), ),
SizedBox(height: 8),
IgnorePointer( IgnorePointer(
child: MessageWidget( child: MessageWidget(
key: Key('MessageWidget'), key: Key('MessageWidget'),
@@ -150,8 +154,17 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
showThreadReplyIndicator: false, showThreadReplyIndicator: false,
showReplyMessage: false, showReplyMessage: false,
showUserAvatar: widget.showUserAvatar, showUserAvatar: widget.showUserAvatar,
attachmentPadding: EdgeInsets.all(
hasFileAttachment ? 4 : 2,
),
showTimestamp: false, showTimestamp: false,
translateUserAvatar: false, translateUserAvatar: false,
padding: const EdgeInsets.all(0),
textPadding: EdgeInsets.symmetric(
vertical: 8.0,
horizontal:
widget.message.text.isOnlyEmoji ? 0 : 16.0,
),
showReactionPickerIndicator: showReactionPickerIndicator:
widget.showReactions && widget.showReactions &&
(widget.message.status == (widget.message.status ==
@@ -162,10 +175,10 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
shape: widget.messageShape, shape: widget.messageShape,
), ),
), ),
SizedBox(height: 8),
Padding( Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
right: widget.reverse ? 8 : 0, left: widget.reverse ? 0 : 40,
left: widget.reverse ? 0 : 48,
), ),
child: SizedBox( child: SizedBox(
width: MediaQuery.of(context).size.width * 0.75, width: MediaQuery.of(context).size.width * 0.75,
@@ -180,12 +193,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
child: Column( child: Column(
crossAxisAlignment: crossAxisAlignment:
CrossAxisAlignment.stretch, CrossAxisAlignment.stretch,
children: ListTile.divideTiles( children: [
color: StreamChatTheme.of(context)
.colorTheme
.greyWhisper,
context: context,
tiles: [
if (widget.showReplyMessage && if (widget.showReplyMessage &&
(widget.message.status == (widget.message.status ==
MessageSendingStatus.SENT || MessageSendingStatus.SENT ||
@@ -208,8 +216,14 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
_buildFlagButton(context), _buildFlagButton(context),
if (widget.showDeleteMessage) if (widget.showDeleteMessage)
_buildDeleteButton(context), _buildDeleteButton(context),
], ].insertBetween(
).toList(), Container(
height: 1,
color: StreamChatTheme.of(context)
.colorTheme
.greyWhisper,
),
),
), ),
), ),
), ),
@@ -410,9 +424,16 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
} }
Widget _buildReplyButton(BuildContext context) { Widget _buildReplyButton(BuildContext context) {
return ListTile( return InkWell(
dense: true, onTap: () {
title: Row( Navigator.pop(context);
if (widget.onReplyTap != null) {
widget.onReplyTap(widget.message);
}
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: Row(
children: [ children: [
StreamSvgIcon.reply( StreamSvgIcon.reply(
color: StreamChatTheme.of(context).primaryIconTheme.color, color: StreamChatTheme.of(context).primaryIconTheme.color,
@@ -420,23 +441,20 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
'Reply', 'Reply',
style: StreamChatTheme.of(context).textTheme.headline, style: StreamChatTheme.of(context).textTheme.body,
), ),
], ],
), ),
onTap: () { ),
Navigator.pop(context);
if (widget.onReplyTap != null) {
widget.onReplyTap(widget.message);
}
},
); );
} }
Widget _buildFlagButton(BuildContext context) { Widget _buildFlagButton(BuildContext context) {
return ListTile( return InkWell(
dense: true, onTap: () => _showFlagDialog(),
title: Row( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: Row(
children: [ children: [
StreamSvgIcon.icon_flag( StreamSvgIcon.icon_flag(
color: StreamChatTheme.of(context).primaryIconTheme.color, color: StreamChatTheme.of(context).primaryIconTheme.color,
@@ -444,20 +462,22 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
'Flag', 'Flag',
style: StreamChatTheme.of(context).textTheme.headline, style: StreamChatTheme.of(context).textTheme.body,
), ),
], ],
), ),
onTap: () => _showFlagDialog(), ),
); );
} }
Widget _buildDeleteButton(BuildContext context) { Widget _buildDeleteButton(BuildContext context) {
final isDeleteFailed = final isDeleteFailed =
widget.message.status == MessageSendingStatus.FAILED_DELETE; widget.message.status == MessageSendingStatus.FAILED_DELETE;
return ListTile( return InkWell(
dense: true, onTap: () => _showDeleteDialog(),
title: Row( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: Row(
children: [ children: [
StreamSvgIcon.delete( StreamSvgIcon.delete(
color: Colors.red, color: Colors.red,
@@ -467,21 +487,24 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
isDeleteFailed ? 'Retry Deleting Message' : 'Delete Message', isDeleteFailed ? 'Retry Deleting Message' : 'Delete Message',
style: StreamChatTheme.of(context) style: StreamChatTheme.of(context)
.textTheme .textTheme
.headline .body
.copyWith(color: Colors.red), .copyWith(color: Colors.red),
), ),
], ],
), ),
onTap: () { ),
_showDeleteDialog();
},
); );
} }
Widget _buildCopyButton(BuildContext context) { Widget _buildCopyButton(BuildContext context) {
return ListTile( return InkWell(
dense: true, onTap: () async {
title: Row( await Clipboard.setData(ClipboardData(text: widget.message.text));
Navigator.pop(context);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: Row(
children: [ children: [
StreamSvgIcon.copy( StreamSvgIcon.copy(
size: 24, size: 24,
@@ -490,21 +513,23 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
'Copy Message', 'Copy Message',
style: StreamChatTheme.of(context).textTheme.headline, style: StreamChatTheme.of(context).textTheme.body,
), ),
], ],
), ),
onTap: () async { ),
await Clipboard.setData(ClipboardData(text: widget.message.text));
Navigator.pop(context);
},
); );
} }
Widget _buildEditMessage(BuildContext context) { Widget _buildEditMessage(BuildContext context) {
return ListTile( return InkWell(
dense: true, onTap: () async {
title: Row( Navigator.pop(context);
_showEditBottomSheet(context);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: Row(
children: [ children: [
StreamSvgIcon.edit( StreamSvgIcon.edit(
color: StreamChatTheme.of(context).primaryIconTheme.color, color: StreamChatTheme.of(context).primaryIconTheme.color,
@@ -512,34 +537,18 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
'Edit Message', 'Edit Message',
style: StreamChatTheme.of(context).textTheme.headline, style: StreamChatTheme.of(context).textTheme.body,
), ),
], ],
), ),
onTap: () async { ),
Navigator.pop(context);
_showEditBottomSheet(context);
},
); );
} }
Widget _buildResendMessage(BuildContext context) { Widget _buildResendMessage(BuildContext context) {
final isUpdateFailed = final isUpdateFailed =
widget.message.status == MessageSendingStatus.FAILED_UPDATE; widget.message.status == MessageSendingStatus.FAILED_UPDATE;
return ListTile( return InkWell(
dense: true,
title: Row(
children: [
StreamSvgIcon.circle_up(
color: StreamChatTheme.of(context).colorTheme.accentBlue,
),
const SizedBox(width: 16),
Text(
isUpdateFailed ? 'Resend Edited Message' : 'Resend',
style: StreamChatTheme.of(context).textTheme.headline,
),
],
),
onTap: () { onTap: () {
Navigator.pop(context); Navigator.pop(context);
final client = StreamChat.of(context).client; final client = StreamChat.of(context).client;
@@ -550,6 +559,21 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
channel.sendMessage(widget.message); channel.sendMessage(widget.message);
} }
}, },
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: Row(
children: [
StreamSvgIcon.circle_up(
color: StreamChatTheme.of(context).colorTheme.accentBlue,
),
const SizedBox(width: 16),
Text(
isUpdateFailed ? 'Resend Edited Message' : 'Resend',
style: StreamChatTheme.of(context).textTheme.body,
),
],
),
),
); );
} }
@@ -623,9 +647,16 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
} }
Widget _buildThreadReplyButton(BuildContext context) { Widget _buildThreadReplyButton(BuildContext context) {
return ListTile( return InkWell(
dense: true, onTap: () {
title: Row( Navigator.pop(context);
if (widget.onThreadReplyTap != null) {
widget.onThreadReplyTap(widget.message);
}
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: Row(
children: [ children: [
StreamSvgIcon.thread( StreamSvgIcon.thread(
color: StreamChatTheme.of(context).primaryIconTheme.color, color: StreamChatTheme.of(context).primaryIconTheme.color,
@@ -633,16 +664,11 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
'Thread Reply', 'Thread Reply',
style: StreamChatTheme.of(context).textTheme.headline, style: StreamChatTheme.of(context).textTheme.body,
), ),
], ],
), ),
onTap: () { ),
Navigator.pop(context);
if (widget.onThreadReplyTap != null) {
widget.onThreadReplyTap(widget.message);
}
},
); );
} }
} }
+17 -22
View File
@@ -1527,14 +1527,12 @@ class MessageInputState extends State<MessageInput> {
return Transform( return Transform(
transform: Matrix4.rotationY(pi), transform: Matrix4.rotationY(pi),
alignment: Alignment.center, alignment: Alignment.center,
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
child: QuotedMessageWidget( child: QuotedMessageWidget(
reverse: true, reverse: true,
showBorder: !containsUrl, showBorder: !containsUrl,
message: widget.quotedMessage, message: widget.quotedMessage,
messageTheme: StreamChatTheme.of(context).otherMessageTheme, messageTheme: StreamChatTheme.of(context).otherMessageTheme,
), padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
), ),
); );
} }
@@ -1544,17 +1542,17 @@ class MessageInputState extends State<MessageInput> {
return Column( return Column(
children: [ children: [
if (_attachments.any((e) => e.attachment?.type == 'file')) if (_attachments.any((e) => e.attachment?.type == 'file'))
LimitedBox( Padding(
padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
child: LimitedBox(
maxHeight: 136.0, maxHeight: 136.0,
child: ListView( child: ListView(
reverse: true, reverse: true,
shrinkWrap: true, shrinkWrap: true,
children: _attachments.reversed children: _attachments.reversed
.where((e) => e.attachment?.type == 'file') .where((e) => e.attachment?.type == 'file')
.map( .map<Widget>(
(e) => Padding( (e) => ClipRRect(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: FileAttachment( child: FileAttachment(
@@ -1580,31 +1578,28 @@ class MessageInputState extends State<MessageInput> {
.white, .white,
), ),
), ),
onTap: () { onTap: () =>
setState(() { setState(() => _attachments.remove(e)),
_attachments.remove(e);
});
},
),
), ),
), ),
), ),
), ),
) )
.toList(), .insertBetween(const SizedBox(width: 8)),
),
), ),
), ),
if (_attachments.any((e) => e.attachment?.type != 'file')) if (_attachments.any((e) => e.attachment?.type != 'file'))
LimitedBox( Padding(
padding: const EdgeInsets.fromLTRB(8, 8, 8, 0),
child: LimitedBox(
maxHeight: 104.0, maxHeight: 104.0,
child: ListView( child: ListView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
children: _attachments children: _attachments
.where((e) => e.attachment?.type != 'file') .where((e) => e.attachment?.type != 'file')
.map( .map<Widget>(
(attachment) => Padding( (attachment) => ClipRRect(
padding: const EdgeInsets.all(8.0),
child: ClipRRect(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: Stack( child: Stack(
@@ -1631,9 +1626,9 @@ class MessageInputState extends State<MessageInput> {
], ],
), ),
), ),
),
) )
.toList(), .insertBetween(const SizedBox(width: 8)),
),
), ),
), ),
], ],
+17 -10
View File
@@ -770,6 +770,10 @@ class _MessageListViewState extends State<MessageListView> {
topRight: Radius.circular(16), topRight: Radius.circular(16),
bottomRight: Radius.circular(16), bottomRight: Radius.circular(16),
), ),
textPadding: EdgeInsets.symmetric(
vertical: 8.0,
horizontal: isOnlyEmoji ? 0 : 16.0,
),
borderSide: isMyMessage || isOnlyEmoji ? BorderSide.none : null, borderSide: isMyMessage || isOnlyEmoji ? BorderSide.none : null,
showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show, showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show,
messageTheme: isMyMessage messageTheme: isMyMessage
@@ -817,7 +821,7 @@ class _MessageListViewState extends State<MessageListView> {
final allRead = readList.length >= (channel.memberCount ?? 0) - 1; final allRead = readList.length >= (channel.memberCount ?? 0) - 1;
final hasFileAttachment = final hasFileAttachment =
message.attachments.any((it) => it.type == 'file'); message.attachments?.any((it) => it.type == 'file') == true;
final isThreadMessage = final isThreadMessage =
message?.parentId != null && message?.showInChannel == true; message?.parentId != null && message?.showInChannel == true;
@@ -849,13 +853,11 @@ class _MessageListViewState extends State<MessageListView> {
final showThreadReplyIndicator = !_isThreadConversation && hasReplies; final showThreadReplyIndicator = !_isThreadConversation && hasReplies;
final isOnlyEmoji = message.text.isOnlyEmoji; final isOnlyEmoji = message.text.isOnlyEmoji;
final showMessageBorder = final hasUrlAttachment =
showThreadReplyIndicator || showInChannelIndicator; message.attachments?.any((it) => it.ogScrapeUrl != null) == true;
final borderSide = isMyMessage
? !showMessageBorder final borderSide =
? BorderSide.none isOnlyEmoji || hasUrlAttachment || (isMyMessage && !hasFileAttachment)
: null
: isOnlyEmoji && !showMessageBorder
? BorderSide.none ? BorderSide.none
: null; : null;
@@ -901,14 +903,15 @@ class _MessageListViewState extends State<MessageListView> {
attachmentBorderRadiusGeometry: BorderRadius.only( attachmentBorderRadiusGeometry: BorderRadius.only(
topLeft: Radius.circular(attachmentBorderRadius), topLeft: Radius.circular(attachmentBorderRadius),
bottomLeft: Radius.circular( bottomLeft: Radius.circular(
(timeDiff >= 1 || !isNextUserSame) && !(hasReplies || isThreadMessage) (timeDiff >= 1 || !isNextUserSame) &&
!(hasReplies || isThreadMessage || hasFileAttachment)
? 0 ? 0
: attachmentBorderRadius, : attachmentBorderRadius,
), ),
topRight: Radius.circular(attachmentBorderRadius), topRight: Radius.circular(attachmentBorderRadius),
bottomRight: Radius.circular(attachmentBorderRadius), bottomRight: Radius.circular(attachmentBorderRadius),
), ),
attachmentPadding: const EdgeInsets.all(2), attachmentPadding: EdgeInsets.all(hasFileAttachment ? 4 : 2),
borderRadiusGeometry: BorderRadius.only( borderRadiusGeometry: BorderRadius.only(
topLeft: Radius.circular(16), topLeft: Radius.circular(16),
bottomLeft: Radius.circular( bottomLeft: Radius.circular(
@@ -919,6 +922,10 @@ class _MessageListViewState extends State<MessageListView> {
topRight: Radius.circular(16), topRight: Radius.circular(16),
bottomRight: Radius.circular(16), bottomRight: Radius.circular(16),
), ),
textPadding: EdgeInsets.symmetric(
vertical: 8.0,
horizontal: isOnlyEmoji ? 0 : 16.0,
),
messageTheme: isMyMessage messageTheme: isMyMessage
? StreamChatTheme.of(context).ownMessageTheme ? StreamChatTheme.of(context).ownMessageTheme
: StreamChatTheme.of(context).otherMessageTheme, : StreamChatTheme.of(context).otherMessageTheme,
+27 -11
View File
@@ -9,6 +9,7 @@ import 'package:stream_chat_flutter/src/user_avatar.dart';
import 'message_widget.dart'; import 'message_widget.dart';
import 'stream_chat_theme.dart'; import 'stream_chat_theme.dart';
import 'extension.dart';
class MessageReactionsModal extends StatelessWidget { class MessageReactionsModal extends StatelessWidget {
final Widget Function(BuildContext, Message) editMessageInputBuilder; final Widget Function(BuildContext, Message) editMessageInputBuilder;
@@ -61,6 +62,8 @@ class MessageReactionsModal extends StatelessWidget {
duration: Duration(milliseconds: 300), duration: Duration(milliseconds: 300),
curve: Curves.easeInOutBack, curve: Curves.easeInOutBack,
builder: (context, val, snapshot) { builder: (context, val, snapshot) {
final hasFileAttachment =
message.attachments?.any((it) => it.type == 'file') == true;
return GestureDetector( return GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: () => Navigator.maybePop(context), onTap: () => Navigator.maybePop(context),
@@ -82,7 +85,7 @@ class MessageReactionsModal extends StatelessWidget {
child: Center( child: Center(
child: SingleChildScrollView( child: SingleChildScrollView(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0), padding: const EdgeInsets.all(8.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@@ -105,6 +108,7 @@ class MessageReactionsModal extends StatelessWidget {
messageTheme: messageTheme, messageTheme: messageTheme,
), ),
), ),
const SizedBox(height: 8),
IgnorePointer( IgnorePointer(
child: MessageWidget( child: MessageWidget(
key: Key('MessageWidget'), key: Key('MessageWidget'),
@@ -123,15 +127,25 @@ class MessageReactionsModal extends StatelessWidget {
translateUserAvatar: false, translateUserAvatar: false,
showSendingIndicator: false, showSendingIndicator: false,
shape: messageShape, shape: messageShape,
padding: const EdgeInsets.all(0),
attachmentPadding: EdgeInsets.all(
hasFileAttachment ? 4 : 2,
),
showInChannelIndicator: false, showInChannelIndicator: false,
textPadding: EdgeInsets.symmetric(
vertical: 8.0,
horizontal: message.text.isOnlyEmoji ? 0 : 16.0,
),
showReactionPickerIndicator: showReactions && showReactionPickerIndicator: showReactions &&
(message.status == (message.status ==
MessageSendingStatus.SENT || MessageSendingStatus.SENT ||
message.status == null), message.status == null),
), ),
), ),
if (message.latestReactions?.isNotEmpty == true) if (message.latestReactions?.isNotEmpty == true) ...[
const SizedBox(height: 8),
_buildReactionCard(context), _buildReactionCard(context),
]
], ],
), ),
), ),
@@ -145,13 +159,9 @@ class MessageReactionsModal extends StatelessWidget {
); );
} }
Padding _buildReactionCard(BuildContext context) { Widget _buildReactionCard(BuildContext context) {
final currentUser = StreamChat.of(context).user; final currentUser = StreamChat.of(context).user;
return Padding( return Card(
padding: const EdgeInsets.symmetric(
horizontal: 8.0,
),
child: Card(
color: StreamChatTheme.of(context).colorTheme.white, color: StreamChatTheme.of(context).colorTheme.white,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
@@ -187,7 +197,6 @@ class MessageReactionsModal extends StatelessWidget {
], ],
), ),
), ),
),
); );
} }
@@ -208,6 +217,7 @@ class MessageReactionsModal extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Stack( Stack(
overflow: Overflow.visible,
children: [ children: [
UserAvatar( UserAvatar(
onTap: onUserAvatarTap, onTap: onUserAvatarTap,
@@ -216,6 +226,10 @@ class MessageReactionsModal extends StatelessWidget {
height: 64, height: 64,
width: 64, width: 64,
), ),
onlineIndicatorConstraints: BoxConstraints.tightFor(
height: 12,
width: 12,
),
borderRadius: BorderRadius.circular(32), borderRadius: BorderRadius.circular(32),
), ),
Positioned( Positioned(
@@ -227,12 +241,14 @@ class MessageReactionsModal extends StatelessWidget {
flipTail: !reverse, flipTail: !reverse,
borderColor: messageTheme.reactionsBorderColor, borderColor: messageTheme.reactionsBorderColor,
backgroundColor: messageTheme.reactionsBackgroundColor, backgroundColor: messageTheme.reactionsBackgroundColor,
maskColor: StreamChatTheme.of(context).colorTheme.white,
tailCirclesSpacing: 1,
highlightOwnReactions: false, highlightOwnReactions: false,
), ),
), ),
bottom: 6, bottom: 6,
left: isCurrentUser ? 0 : null, left: isCurrentUser ? -3 : null,
right: isCurrentUser ? 0 : null, right: isCurrentUser ? -3 : null,
), ),
], ],
), ),
+47 -53
View File
@@ -278,6 +278,9 @@ class _MessageWidgetState extends State<MessageWidget> {
?.isNotEmpty == ?.isNotEmpty ==
true; true;
bool get hasUrlAttachments =>
widget.message.attachments?.any((it) => it.ogScrapeUrl != null) == true;
bool get showBottomRow => bool get showBottomRow =>
showThreadReplyIndicator || showThreadReplyIndicator ||
showUsername || showUsername ||
@@ -416,7 +419,7 @@ class _MessageWidgetState extends State<MessageWidget> {
if (hasQuotedMessage) if (hasQuotedMessage)
_buildQuotedMessage(), _buildQuotedMessage(),
if (hasNonUrlAttachments) if (hasNonUrlAttachments)
..._parseAttachments( _parseAttachments(
context), context),
if (widget.message.text if (widget.message.text
.trim() .trim()
@@ -441,9 +444,9 @@ class _MessageWidgetState extends State<MessageWidget> {
StreamChatTheme.of(context) StreamChatTheme.of(context)
.colorTheme .colorTheme
.white, .white,
StreamChatTheme.of(context) Colors.transparent,
.colorTheme Colors.transparent,
.white, tailCirclesSpace: 1,
), ),
), ),
), ),
@@ -494,21 +497,15 @@ class _MessageWidgetState extends State<MessageWidget> {
widget.onQuotedMessageTap != null widget.onQuotedMessageTap != null
? () => widget.onQuotedMessageTap(widget.message.quotedMessageId) ? () => widget.onQuotedMessageTap(widget.message.quotedMessageId)
: null; : null;
return Padding( return QuotedMessageWidget(
padding: EdgeInsets.only(
right: 8,
left: 8,
top: 8,
bottom: hasNonUrlAttachments ? 8 : 0,
),
child: QuotedMessageWidget(
onTap: onTap, onTap: onTap,
message: widget.message.quotedMessage, message: widget.message.quotedMessage,
messageTheme: isMyMessage messageTheme: isMyMessage
? StreamChatTheme.of(context).otherMessageTheme ? StreamChatTheme.of(context).otherMessageTheme
: StreamChatTheme.of(context).ownMessageTheme, : StreamChatTheme.of(context).ownMessageTheme,
reverse: widget.reverse, reverse: widget.reverse,
), padding: EdgeInsets.only(
right: 8, left: 8, top: 8, bottom: hasNonUrlAttachments ? 8 : 0),
); );
} }
@@ -564,11 +561,10 @@ class _MessageWidgetState extends State<MessageWidget> {
}; };
children.addAll([ children.addAll([
if (showSendingIndicator) _buildSendingIndicator(),
if (showInChannel || showThreadReplyIndicator) ...[ if (showInChannel || showThreadReplyIndicator) ...[
if (showThreadParticipants) if (showThreadParticipants)
SizedBox.fromSize( SizedBox.fromSize(
size: Size((threadParticipants.length * 8.0) + 10, 16), size: Size((threadParticipants.length * 8.0) + 8, 16),
child: _buildThreadParticipantsIndicator(), child: _buildThreadParticipantsIndicator(),
), ),
InkWell( InkWell(
@@ -588,16 +584,18 @@ class _MessageWidgetState extends State<MessageWidget> {
Jiffy(widget.message.createdAt.toLocal()).jm, Jiffy(widget.message.createdAt.toLocal()).jm,
style: widget.messageTheme.createdAt, style: widget.messageTheme.createdAt,
), ),
if (showSendingIndicator) _buildSendingIndicator(),
]); ]);
if (widget.reverse) children = children.reversed.toList(); final showThreadTail = !(hasUrlAttachments || isGiphy || isOnlyEmoji) &&
(showThreadReplyIndicator || showInChannel);
return Flex( return Flex(
direction: Axis.horizontal, direction: Axis.horizontal,
clipBehavior: Clip.none, clipBehavior: Clip.none,
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
if (showThreadReplyIndicator || showInChannel) if (showThreadTail)
Container( Container(
margin: EdgeInsets.only( margin: EdgeInsets.only(
bottom: widget.messageTheme.replies.fontSize / 2, bottom: widget.messageTheme.replies.fontSize / 2,
@@ -648,18 +646,20 @@ class _MessageWidgetState extends State<MessageWidget> {
var padding = 0.0; var padding = 0.0;
return Stack( return Stack(
children: widget.message.threadParticipants.map((user) { children: widget.message.threadParticipants.map((user) {
padding += 10.0; padding += 8.0;
return Positioned( return Positioned(
left: padding - 10, right: padding - 8,
bottom: 0, bottom: 0,
top: 0, top: 0,
child: Material( child: Container(
color: Colors.white, decoration: BoxDecoration(
clipBehavior: Clip.antiAlias, shape: BoxShape.circle,
shape: CircleBorder(), color: StreamChatTheme.of(context).colorTheme.white,
),
padding: const EdgeInsets.all(1),
child: UserAvatar( child: UserAvatar(
user: user, user: user,
constraints: BoxConstraints.loose(Size.fromRadius(8)), constraints: BoxConstraints.loose(Size.fromRadius(7)),
showOnlineStatus: false, showOnlineStatus: false,
), ),
), ),
@@ -694,6 +694,7 @@ class _MessageWidgetState extends State<MessageWidget> {
flipTail: widget.reverse, flipTail: widget.reverse,
backgroundColor: widget.messageTheme.reactionsBackgroundColor, backgroundColor: widget.messageTheme.reactionsBackgroundColor,
borderColor: widget.messageTheme.reactionsBorderColor, borderColor: widget.messageTheme.reactionsBorderColor,
maskColor: widget.messageTheme.reactionsMaskColor,
reactions: reactionsList, reactions: reactionsList,
), ),
) )
@@ -777,7 +778,7 @@ class _MessageWidgetState extends State<MessageWidget> {
side: widget.attachmentBorderSide ?? side: widget.attachmentBorderSide ??
widget.borderSide ?? widget.borderSide ??
BorderSide( BorderSide(
color: StreamChatTheme.of(context).colorTheme.greyGainsboro, color: StreamChatTheme.of(context).colorTheme.greyWhisper,
), ),
borderRadius: widget.attachmentBorderRadiusGeometry ?? borderRadius: widget.attachmentBorderRadiusGeometry ??
widget.borderRadiusGeometry ?? widget.borderRadiusGeometry ??
@@ -785,7 +786,7 @@ class _MessageWidgetState extends State<MessageWidget> {
); );
} }
List<Widget> _parseAttachments(BuildContext context) { Widget _parseAttachments(BuildContext context) {
final images = widget.message.attachments final images = widget.message.attachments
?.where((element) => ?.where((element) =>
element.type == 'image' && element.ogScrapeUrl == null) element.type == 'image' && element.ogScrapeUrl == null)
@@ -793,8 +794,9 @@ class _MessageWidgetState extends State<MessageWidget> {
[]; [];
if (images.length > 1) { if (images.length > 1) {
return [ return Padding(
wrapAttachmentWidget( padding: widget.attachmentPadding,
child: wrapAttachmentWidget(
context, context,
Material( Material(
color: widget.messageTheme.messageBackgroundColor, color: widget.messageTheme.messageBackgroundColor,
@@ -809,18 +811,20 @@ class _MessageWidgetState extends State<MessageWidget> {
), ),
), ),
), ),
]; );
} }
return widget.message.attachments return Padding(
padding: widget.attachmentPadding,
child: Column(
mainAxisSize: MainAxisSize.min,
children: widget.message.attachments
?.where((element) => element.ogScrapeUrl == null) ?.where((element) => element.ogScrapeUrl == null)
?.map((attachment) { ?.map((attachment) {
final attachmentBuilder = widget.attachmentBuilders[attachment.type]; final attachmentBuilder =
widget.attachmentBuilders[attachment.type];
if (attachmentBuilder == null) {
return SizedBox();
}
if (attachmentBuilder == null) return SizedBox();
final attachmentWidget = attachmentBuilder( final attachmentWidget = attachmentBuilder(
context, context,
widget.message, widget.message,
@@ -831,8 +835,12 @@ class _MessageWidgetState extends State<MessageWidget> {
attachmentWidget, attachmentWidget,
attachment: attachment, attachment: attachment,
); );
})?.toList() ?? })?.insertBetween(SizedBox(
[]; height: widget.attachmentPadding.vertical / 2,
)) ??
[],
),
);
} }
Widget wrapAttachmentWidget( Widget wrapAttachmentWidget(
@@ -843,22 +851,14 @@ class _MessageWidgetState extends State<MessageWidget> {
final attachmentShape = final attachmentShape =
widget.attachmentShape ?? widget.shape ?? _getDefaultShape(context); widget.attachmentShape ?? widget.shape ?? _getDefaultShape(context);
return Material( return Material(
color: _getBackgroundColor(),
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
shape: attachmentShape, shape: attachmentShape,
child: Padding(
padding: widget.attachmentPadding,
child: Material(
clipBehavior: Clip.hardEdge,
shape: attachmentShape,
type: MaterialType.transparency, type: MaterialType.transparency,
child: Transform( child: Transform(
transform: Matrix4.rotationY(widget.reverse ? pi : 0), transform: Matrix4.rotationY(widget.reverse ? pi : 0),
alignment: Alignment.center, alignment: Alignment.center,
child: attachmentWidget, child: attachmentWidget,
), ),
),
),
); );
} }
@@ -944,11 +944,7 @@ class _MessageWidgetState extends State<MessageWidget> {
: widget.messageTheme, : widget.messageTheme,
), ),
), ),
if (widget.message.attachments if (hasUrlAttachments && !hasQuotedMessage) _buildUrlAttachment(),
?.any((element) => element.ogScrapeUrl != null) ==
true &&
!hasQuotedMessage)
_buildUrlAttachment(),
], ],
), ),
); );
@@ -961,9 +957,7 @@ class _MessageWidgetState extends State<MessageWidget> {
return widget.messageTheme.messageBackgroundColor; return widget.messageTheme.messageBackgroundColor;
} }
if (widget.message.attachments if (hasUrlAttachments) {
?.any((element) => element.ogScrapeUrl != null) ==
true) {
return StreamChatTheme.of(context).colorTheme.blueAlice; return StreamChatTheme.of(context).colorTheme.blueAlice;
} }
+7 -1
View File
@@ -83,6 +83,8 @@ class QuotedMessageWidget extends StatelessWidget {
final Map<String, QuotedMessageAttachmentThumbnailBuilder> final Map<String, QuotedMessageAttachmentThumbnailBuilder>
attachmentThumbnailBuilders; attachmentThumbnailBuilders;
final EdgeInsetsGeometry padding;
final GestureTapCallback onTap; final GestureTapCallback onTap;
/// ///
@@ -94,6 +96,7 @@ class QuotedMessageWidget extends StatelessWidget {
this.showBorder = false, this.showBorder = false,
this.textLimit = 170, this.textLimit = 170,
this.attachmentThumbnailBuilders, this.attachmentThumbnailBuilders,
this.padding = const EdgeInsets.all(8),
this.onTap, this.onTap,
}) : super(key: key); }) : super(key: key);
@@ -107,7 +110,9 @@ class QuotedMessageWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InkWell( return Padding(
padding: padding,
child: InkWell(
onTap: onTap, onTap: onTap,
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
@@ -118,6 +123,7 @@ class QuotedMessageWidget extends StatelessWidget {
_buildUserAvatar(), _buildUserAvatar(),
], ],
), ),
),
); );
} }
+63 -6
View File
@@ -12,17 +12,21 @@ class ReactionBubble extends StatelessWidget {
@required this.reactions, @required this.reactions,
@required this.borderColor, @required this.borderColor,
@required this.backgroundColor, @required this.backgroundColor,
@required this.maskColor,
this.reverse = false, this.reverse = false,
this.flipTail = false, this.flipTail = false,
this.highlightOwnReactions = true, this.highlightOwnReactions = true,
this.tailCirclesSpacing = 0,
}) : super(key: key); }) : super(key: key);
final List<Reaction> reactions; final List<Reaction> reactions;
final Color borderColor; final Color borderColor;
final Color backgroundColor; final Color backgroundColor;
final Color maskColor;
final bool reverse; final bool reverse;
final bool flipTail; final bool flipTail;
final bool highlightOwnReactions; final bool highlightOwnReactions;
final double tailCirclesSpacing;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -37,6 +41,12 @@ class ReactionBubble extends StatelessWidget {
children: [ children: [
Transform.translate( Transform.translate(
offset: Offset(reverse ? offset : -offset, 0), offset: Offset(reverse ? offset : -offset, 0),
child: Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: maskColor,
borderRadius: BorderRadius.all(Radius.circular(16)),
),
child: Container( child: Container(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
vertical: 4, vertical: 4,
@@ -79,10 +89,11 @@ class ReactionBubble extends StatelessWidget {
), ),
), ),
), ),
),
Positioned( Positioned(
bottom: 0, bottom: 2,
left: reverse ? null : 11, left: reverse ? null : 13,
right: !reverse ? null : 11, right: !reverse ? null : 13,
child: _buildReactionsTail(context), child: _buildReactionsTail(context),
), ),
], ],
@@ -136,6 +147,8 @@ class ReactionBubble extends StatelessWidget {
painter: ReactionBubblePainter( painter: ReactionBubblePainter(
backgroundColor, backgroundColor,
borderColor, borderColor,
maskColor,
tailCirclesSpace: tailCirclesSpacing,
), ),
); );
return Transform( return Transform(
@@ -149,14 +162,22 @@ class ReactionBubble extends StatelessWidget {
class ReactionBubblePainter extends CustomPainter { class ReactionBubblePainter extends CustomPainter {
final Color color; final Color color;
final Color borderColor; final Color borderColor;
final Color maskColor;
final double tailCirclesSpace;
ReactionBubblePainter( ReactionBubblePainter(
this.color, this.color,
this.borderColor, this.borderColor,
); this.maskColor, {
this.tailCirclesSpace = 0,
});
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
_drawOvalMask(size, canvas);
_drawMask(size, canvas);
_drawOval(size, canvas); _drawOval(size, canvas);
_drawOvalBorder(size, canvas); _drawOvalBorder(size, canvas);
@@ -166,6 +187,21 @@ class ReactionBubblePainter extends CustomPainter {
_drawBorder(size, canvas); _drawBorder(size, canvas);
} }
void _drawOvalMask(Size size, Canvas canvas) {
final paint = Paint()
..color = maskColor
..style = PaintingStyle.fill;
final path = Path();
path.addOval(
Rect.fromCircle(
center: Offset(4, 3) + Offset(tailCirclesSpace, tailCirclesSpace),
radius: 4,
),
);
canvas.drawPath(path, paint);
}
void _drawOvalBorder(Size size, Canvas canvas) { void _drawOvalBorder(Size size, Canvas canvas) {
final paint = Paint() final paint = Paint()
..color = borderColor ..color = borderColor
@@ -175,7 +211,7 @@ class ReactionBubblePainter extends CustomPainter {
final path = Path(); final path = Path();
path.addOval( path.addOval(
Rect.fromCircle( Rect.fromCircle(
center: Offset(4, 3), center: Offset(4, 3) + Offset(tailCirclesSpace, tailCirclesSpace),
radius: 2, radius: 2,
), ),
); );
@@ -189,7 +225,7 @@ class ReactionBubblePainter extends CustomPainter {
final path = Path(); final path = Path();
path.addOval(Rect.fromCircle( path.addOval(Rect.fromCircle(
center: Offset(4, 3), center: Offset(4, 3) + Offset(tailCirclesSpace, tailCirclesSpace),
radius: 2, radius: 2,
)); ));
canvas.drawPath(path, paint); canvas.drawPath(path, paint);
@@ -236,6 +272,27 @@ class ReactionBubblePainter extends CustomPainter {
canvas.drawPath(path, paint); canvas.drawPath(path, paint);
} }
void _drawMask(Size size, Canvas canvas) {
final paint = Paint()
..color = maskColor
..strokeWidth = 1
..style = PaintingStyle.fill;
final dy = -2.2;
final startAngle = 1.1;
final sweepAngle = 1.2;
final path = Path();
path.addArc(
Rect.fromCircle(
center: Offset(1, dy),
radius: 6,
),
-pi * startAngle,
-pi / sweepAngle,
);
canvas.drawPath(path, paint);
}
@override @override
bool shouldRepaint(CustomPainter oldDelegate) { bool shouldRepaint(CustomPainter oldDelegate) {
return true; return true;
+7
View File
@@ -234,6 +234,7 @@ class StreamChatThemeData {
messageBackgroundColor: colorTheme.greyGainsboro, messageBackgroundColor: colorTheme.greyGainsboro,
reactionsBackgroundColor: colorTheme.white, reactionsBackgroundColor: colorTheme.white,
reactionsBorderColor: colorTheme.greyWhisper, reactionsBorderColor: colorTheme.greyWhisper,
reactionsMaskColor: colorTheme.whiteSnow,
messageBorderColor: colorTheme.greyGainsboro, messageBorderColor: colorTheme.greyGainsboro,
avatarTheme: AvatarTheme( avatarTheme: AvatarTheme(
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
@@ -249,6 +250,7 @@ class StreamChatThemeData {
otherMessageTheme: MessageTheme( otherMessageTheme: MessageTheme(
reactionsBackgroundColor: colorTheme.greyGainsboro, reactionsBackgroundColor: colorTheme.greyGainsboro,
reactionsBorderColor: colorTheme.white, reactionsBorderColor: colorTheme.white,
reactionsMaskColor: colorTheme.whiteSnow,
messageText: textTheme.body, messageText: textTheme.body,
createdAt: textTheme.footnote.copyWith(color: colorTheme.grey), createdAt: textTheme.footnote.copyWith(color: colorTheme.grey),
replies: textTheme.footnoteBold.copyWith(color: accentColor), replies: textTheme.footnoteBold.copyWith(color: accentColor),
@@ -710,6 +712,7 @@ class MessageTheme {
final Color messageBorderColor; final Color messageBorderColor;
final Color reactionsBackgroundColor; final Color reactionsBackgroundColor;
final Color reactionsBorderColor; final Color reactionsBorderColor;
final Color reactionsMaskColor;
final AvatarTheme avatarTheme; final AvatarTheme avatarTheme;
const MessageTheme({ const MessageTheme({
@@ -721,6 +724,7 @@ class MessageTheme {
this.messageBorderColor, this.messageBorderColor,
this.reactionsBackgroundColor, this.reactionsBackgroundColor,
this.reactionsBorderColor, this.reactionsBorderColor,
this.reactionsMaskColor,
this.avatarTheme, this.avatarTheme,
this.createdAt, this.createdAt,
}); });
@@ -736,6 +740,7 @@ class MessageTheme {
AvatarTheme avatarTheme, AvatarTheme avatarTheme,
Color reactionsBackgroundColor, Color reactionsBackgroundColor,
Color reactionsBorderColor, Color reactionsBorderColor,
Color reactionsMaskColor,
}) => }) =>
MessageTheme( MessageTheme(
messageText: messageText ?? this.messageText, messageText: messageText ?? this.messageText,
@@ -750,6 +755,7 @@ class MessageTheme {
reactionsBackgroundColor: reactionsBackgroundColor:
reactionsBackgroundColor ?? this.reactionsBackgroundColor, reactionsBackgroundColor ?? this.reactionsBackgroundColor,
reactionsBorderColor: reactionsBorderColor ?? this.reactionsBorderColor, reactionsBorderColor: reactionsBorderColor ?? this.reactionsBorderColor,
reactionsMaskColor: reactionsMaskColor ?? this.reactionsMaskColor,
); );
MessageTheme merge(MessageTheme other) { MessageTheme merge(MessageTheme other) {
@@ -767,6 +773,7 @@ class MessageTheme {
avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme, avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme,
reactionsBackgroundColor: other.reactionsBackgroundColor, reactionsBackgroundColor: other.reactionsBackgroundColor,
reactionsBorderColor: other.reactionsBorderColor, reactionsBorderColor: other.reactionsBorderColor,
reactionsMaskColor: other.reactionsMaskColor,
); );
} }
} }
+4 -4
View File
@@ -92,18 +92,18 @@ class UserAvatar extends StatelessWidget {
child: Material( child: Material(
type: MaterialType.circle, type: MaterialType.circle,
child: Container( child: Container(
padding: const EdgeInsets.all(2.0), margin: const EdgeInsets.all(2.0),
constraints: onlineIndicatorConstraints ?? constraints: onlineIndicatorConstraints ??
BoxConstraints.tightFor( BoxConstraints.tightFor(
width: 12, width: 8,
height: 12, height: 8,
), ),
child: Material( child: Material(
shape: CircleBorder(), shape: CircleBorder(),
color: streamChatTheme.colorTheme.accentGreen, color: streamChatTheme.colorTheme.accentGreen,
), ),
), ),
color: StreamChatTheme.of(context).colorTheme.white, color: streamChatTheme.colorTheme.white,
), ),
), ),
), ),
+3
View File
@@ -19,6 +19,7 @@ void main() {
reactions: [], reactions: [],
borderColor: Colors.black, borderColor: Colors.black,
backgroundColor: Colors.white, backgroundColor: Colors.white,
maskColor: Colors.white,
), ),
), ),
), ),
@@ -56,6 +57,7 @@ void main() {
], ],
borderColor: Colors.black, borderColor: Colors.black,
backgroundColor: Colors.white, backgroundColor: Colors.white,
maskColor: Colors.white,
), ),
), ),
), ),
@@ -96,6 +98,7 @@ void main() {
], ],
borderColor: Colors.black, borderColor: Colors.black,
backgroundColor: Colors.white, backgroundColor: Colors.white,
maskColor: Colors.white,
), ),
), ),
), ),