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