lint changes message

This commit is contained in:
Deven Joshi
2021-05-05 15:33:42 +05:30
parent 5ad74b8f2b
commit bda64e8764
2 changed files with 899 additions and 878 deletions
@@ -8,37 +8,11 @@ import 'package:stream_chat_flutter/src/reaction_picker.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/utils.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'extension.dart';
import 'message_input.dart';
import 'message_widget.dart';
import 'stream_chat.dart';
import 'stream_chat_theme.dart';
/// Constructs a modal with actions for a message
class MessageActionsModal extends StatefulWidget {
final Widget Function(BuildContext, Message)? editMessageInputBuilder;
final OnMessageTap? onThreadReplyTap;
final OnMessageTap? onReplyTap;
final Message message;
final MessageTheme messageTheme;
final bool showReactions;
final OnMessageTap? onCopyTap;
final bool showDeleteMessage;
final bool showCopyMessage;
final bool showEditMessage;
final bool showResendMessage;
final bool showReplyMessage;
final bool showThreadReplyMessage;
final bool showFlagButton;
final bool reverse;
final ShapeBorder? messageShape;
final ShapeBorder? attachmentShape;
final DisplayWidget showUserAvatar;
final BorderRadius? attachmentBorderRadiusGeometry;
/// List of custom actions
final List<MessageAction> customActions;
/// Constructor for creating a [MessageActionsModal] widget
const MessageActionsModal({
Key? key,
required this.message,
@@ -63,6 +37,66 @@ class MessageActionsModal extends StatefulWidget {
this.onCopyTap,
}) : super(key: key);
/// Builder for edit message
final Widget Function(BuildContext, Message)? editMessageInputBuilder;
/// Callback for when thread reply is tapped
final OnMessageTap? onThreadReplyTap;
/// Callback for when reply is tapped
final OnMessageTap? onReplyTap;
/// Message in focus for actions
final Message message;
/// [MessageTheme] for message
final MessageTheme messageTheme;
/// Flag for showing reactions
final bool showReactions;
/// Callback when copy is tapped
final OnMessageTap? onCopyTap;
/// Callback when delete is tapped
final bool showDeleteMessage;
/// Flag for showing copy action
final bool showCopyMessage;
/// Flag for showing edit action
final bool showEditMessage;
/// Flag for showing resend action
final bool showResendMessage;
/// Flag for showing reply action
final bool showReplyMessage;
/// Flag for showing thread reply action
final bool showThreadReplyMessage;
/// Flag for showing flag action
final bool showFlagButton;
/// Flag for reversing message
final bool reverse;
/// [ShapeBorder] to apply to the widget
final ShapeBorder? messageShape;
/// [ShapeBorder] to apply to attachment
final ShapeBorder? attachmentShape;
/// Enum for displaying user avatar
final DisplayWidget showUserAvatar;
/// [BorderRadius] for attachment border
final BorderRadius? attachmentBorderRadiusGeometry;
/// List of custom actions
final List<MessageAction> customActions;
@override
_MessageActionsModalState createState() => _MessageActionsModalState();
}
@@ -71,9 +105,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
bool _showActions = true;
@override
Widget build(BuildContext context) {
return _showMessageOptionsModal();
}
Widget build(BuildContext context) => _showMessageOptionsModal();
Widget _showMessageOptionsModal() {
final size = MediaQuery.of(context).size;
@@ -120,10 +152,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
if (_showActions)
TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: 1),
duration: Duration(milliseconds: 300),
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOutBack,
builder: (context, val, snapshot) {
return Transform.scale(
builder: (context, val, snapshot) => Transform.scale(
scale: val,
child: Center(
child: SingleChildScrollView(
@@ -151,15 +182,16 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
message: widget.message,
),
),
SizedBox(height: 8),
const SizedBox(height: 8),
IgnorePointer(
child: MessageWidget(
key: Key('MessageWidget'),
key: const Key('MessageWidget'),
reverse: widget.reverse,
attachmentBorderRadiusGeometry:
widget.attachmentBorderRadiusGeometry,
message: widget.message.copyWith(
text: widget.message.text!.length > 200
// ignore: lines_longer_than_80_chars
? '${widget.message.text!.substring(0, 200)}...'
: widget.message.text,
),
@@ -176,9 +208,8 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
padding: const EdgeInsets.all(0),
textPadding: EdgeInsets.symmetric(
vertical: 8,
horizontal: widget.message.text!.isOnlyEmoji
? 0
: 16.0,
horizontal:
widget.message.text!.isOnlyEmoji ? 0 : 16.0,
),
showReactionPickerIndicator:
widget.showReactions &&
@@ -189,7 +220,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
attachmentShape: widget.attachmentShape,
),
),
SizedBox(height: 8),
const SizedBox(height: 8),
Padding(
padding: EdgeInsets.only(
left: widget.reverse ? 0 : 40,
@@ -227,12 +258,11 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
_buildFlagButton(context),
if (widget.showDeleteMessage)
_buildDeleteButton(context),
...widget.customActions.map((action) {
return _buildCustomAction(
...widget.customActions
.map((action) => _buildCustomAction(
context,
action,
);
})
))
].insertBetween(
Container(
height: 1,
@@ -250,8 +280,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
),
),
),
);
},
),
),
],
),
@@ -261,8 +290,8 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
InkWell _buildCustomAction(
BuildContext context,
MessageAction messageAction,
) {
return InkWell(
) =>
InkWell(
onTap: () {
messageAction.onTap?.call(widget.message);
},
@@ -270,14 +299,13 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
child: Row(
children: [
messageAction.leading ?? Offstage(),
messageAction.leading ?? const Offstage(),
const SizedBox(width: 16),
messageAction.title ?? Offstage(),
messageAction.title ?? const Offstage(),
],
),
),
);
}
void _showFlagDialog() async {
final client = StreamChat.of(context).client;
@@ -290,6 +318,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
size: 24,
),
question:
// ignore: lines_longer_than_80_chars
'Do you want to send a copy of this message to a\nmoderator for further investigation?',
okText: 'FLAG',
cancelText: 'CANCEL',
@@ -371,8 +400,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
);
}
Widget _buildReplyButton(BuildContext context) {
return InkWell(
Widget _buildReplyButton(BuildContext context) => InkWell(
onTap: () {
Navigator.pop(context);
if (widget.onReplyTap != null) {
@@ -395,10 +423,8 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
),
),
);
}
Widget _buildFlagButton(BuildContext context) {
return InkWell(
Widget _buildFlagButton(BuildContext context) => InkWell(
onTap: _showFlagDialog,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
@@ -416,7 +442,6 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
),
),
);
}
Widget _buildDeleteButton(BuildContext context) {
final isDeleteFailed =
@@ -444,8 +469,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
);
}
Widget _buildCopyButton(BuildContext context) {
return InkWell(
Widget _buildCopyButton(BuildContext context) => InkWell(
onTap: () async {
widget.onCopyTap?.call(widget.message);
Navigator.pop(context);
@@ -467,10 +491,8 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
),
),
);
}
Widget _buildEditMessage(BuildContext context) {
return InkWell(
Widget _buildEditMessage(BuildContext context) => InkWell(
onTap: () async {
Navigator.pop(context);
_showEditBottomSheet(context);
@@ -491,7 +513,6 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
),
),
);
}
Widget _buildResendMessage(BuildContext context) {
final isUpdateFailed =
@@ -533,14 +554,13 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
isScrollControlled: true,
backgroundColor:
StreamChatTheme.of(context).messageInputTheme.inputBackground,
shape: RoundedRectangleBorder(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
),
builder: (context) {
return StreamChannel(
builder: (context) => StreamChannel(
channel: channel,
child: Flex(
direction: Axis.vertical,
@@ -555,12 +575,11 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
Padding(
padding: const EdgeInsets.all(8),
child: StreamSvgIcon.edit(
color: StreamChatTheme.of(context)
.colorTheme
.greyGainsboro,
color:
StreamChatTheme.of(context).colorTheme.greyGainsboro,
),
),
Text(
const Text(
'Edit Message',
style: TextStyle(fontWeight: FontWeight.bold),
),
@@ -585,13 +604,11 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
),
],
),
);
},
),
);
}
Widget _buildThreadReplyButton(BuildContext context) {
return InkWell(
Widget _buildThreadReplyButton(BuildContext context) => InkWell(
onTap: () {
Navigator.pop(context);
if (widget.onThreadReplyTap != null) {
@@ -614,5 +631,4 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
),
),
);
}
}
@@ -14,19 +14,18 @@ import 'package:shimmer/shimmer.dart';
import 'package:stream_chat_flutter/src/emoji/emoji.dart';
import 'package:stream_chat_flutter/src/media_list_view.dart';
import 'package:stream_chat_flutter/src/message_list_view.dart';
import 'package:stream_chat_flutter/src/quoted_message_widget.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/video_service.dart';
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:substring_highlight/substring_highlight.dart';
import 'package:video_compress/video_compress.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import '../stream_chat_flutter.dart';
import 'attachment/attachment.dart';
import 'extension.dart';
import 'quoted_message_widget.dart';
import 'video_thumbnail_image.dart';
/// Builder for attachment thumbnails
typedef AttachmentThumbnailBuilder = Widget Function(
BuildContext,
Attachment,
@@ -39,16 +38,30 @@ typedef MentionTileBuilder = Widget Function(
Member member,
);
/// Location for actions on the [MessageInput]
enum ActionsLocation {
/// Align to left
left,
/// Align to right
right,
/// Align to left but inside the [TextField]
leftInside,
/// Align to right but inside the [TextField]
rightInside,
}
/// Default attachments for widget
enum DefaultAttachmentTypes {
/// Image Attachment
image,
/// Video Attachment
video,
/// File Attachment
file,
}
@@ -103,9 +116,11 @@ const _kMaxAttachmentSize = 20971520; // 20MB in Bytes
/// }
/// ```
///
/// You usually put this widget in the same page of a [MessageListView] as the bottom widget.
/// You usually put this widget in the same page of a [MessageListView]
/// as the bottom widget.
///
/// The widget renders the ui based on the first ancestor of type [StreamChatTheme].
/// The widget renders the ui based on the first ancestor of
/// type [StreamChatTheme].
/// Modify it to change the widget appearance.
class MessageInput extends StatefulWidget {
/// Instantiate a new MessageInput
@@ -213,6 +228,7 @@ class MessageInput extends StatefulWidget {
if (messageInputState == null) {
throw Exception(
// ignore: lines_longer_than_80_chars
'You must have a MessageInput widget as ancestor of your widget tree');
}
@@ -220,6 +236,7 @@ class MessageInput extends StatefulWidget {
}
}
/// State of [MessageInput]
class MessageInputState extends State<MessageInput> {
final _attachments = <String, Attachment>{};
final List<User> _mentionedUsers = [];
@@ -313,7 +330,7 @@ class MessageInputState extends State<MessageInput> {
.greyGainsboro,
),
),
Text(
const Text(
'Reply to Message',
style: TextStyle(fontWeight: FontWeight.bold),
),
@@ -353,23 +370,22 @@ class MessageInputState extends State<MessageInput> {
return child;
}
Flex _buildTextField(BuildContext context) {
return Flex(
Flex _buildTextField(BuildContext context) => Flex(
direction: Axis.horizontal,
children: <Widget>[
if (!_commandEnabled && widget.actionsLocation == ActionsLocation.left)
if (!_commandEnabled &&
widget.actionsLocation == ActionsLocation.left)
_buildExpandActionsButton(),
_buildTextInput(context),
if (!_commandEnabled && widget.actionsLocation == ActionsLocation.right)
if (!_commandEnabled &&
widget.actionsLocation == ActionsLocation.right)
_buildExpandActionsButton(),
if (widget.sendButtonLocation == SendButtonLocation.outside)
_animateSendButton(context),
],
);
}
Widget _buildDmCheckbox() {
return Row(
Widget _buildDmCheckbox() => Row(
children: [
Container(
height: 16,
@@ -399,8 +415,8 @@ class MessageInputState extends State<MessageInput> {
});
},
child: AnimatedCrossFade(
duration: Duration(milliseconds: 300),
reverseDuration: Duration(milliseconds: 300),
duration: const Duration(milliseconds: 300),
reverseDuration: const Duration(milliseconds: 300),
crossFadeState: _sendAsDm
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
@@ -408,7 +424,7 @@ class MessageInputState extends State<MessageInput> {
size: 16,
color: StreamChatTheme.of(context).colorTheme.white,
),
secondChild: SizedBox(
secondChild: const SizedBox(
height: 16,
width: 16,
),
@@ -431,7 +447,6 @@ class MessageInputState extends State<MessageInput> {
),
],
);
}
Widget _animateSendButton(BuildContext context) {
final sendButton = widget.activeSendButton != null
@@ -452,8 +467,7 @@ class MessageInputState extends State<MessageInput> {
);
}
Widget _buildExpandActionsButton() {
return Padding(
Widget _buildExpandActionsButton() => Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: AnimatedCrossFade(
crossFadeState: _actionsShrunk
@@ -473,7 +487,7 @@ class MessageInputState extends State<MessageInput> {
),
),
padding: const EdgeInsets.all(0),
constraints: BoxConstraints.tightFor(
constraints: const BoxConstraints.tightFor(
height: 24,
width: 24,
),
@@ -497,11 +511,10 @@ class MessageInputState extends State<MessageInput> {
].insertBetween(const SizedBox(width: 8)),
),
),
duration: Duration(milliseconds: 300),
duration: const Duration(milliseconds: 300),
alignment: Alignment.center,
),
);
}
Expanded _buildTextInput(BuildContext context) {
final theme = StreamChatTheme.of(context);
@@ -539,7 +552,7 @@ class MessageInputState extends State<MessageInput> {
LimitedBox(
maxHeight: widget.maxHeight,
child: TextField(
key: Key('messageInputText'),
key: const Key('messageInputText'),
enabled: _inputEnabled,
maxLines: null,
onSubmitted: (_) => sendMessage(),
@@ -571,27 +584,27 @@ class MessageInputState extends State<MessageInput> {
hintStyle: theme.messageInputTheme.inputTextStyle!.copyWith(
color: theme.colorTheme.grey,
),
border: OutlineInputBorder(
border: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.transparent,
),
),
focusedBorder: OutlineInputBorder(
focusedBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.transparent,
),
),
enabledBorder: OutlineInputBorder(
enabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.transparent,
),
),
errorBorder: OutlineInputBorder(
errorBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.transparent,
),
),
disabledBorder: OutlineInputBorder(
disabledBorder: const OutlineInputBorder(
borderSide: BorderSide(
color: Colors.transparent,
),
@@ -604,7 +617,7 @@ class MessageInputState extends State<MessageInput> {
Padding(
padding: const EdgeInsets.all(8),
child: Container(
constraints: BoxConstraints.tight(Size(64, 24)),
constraints: BoxConstraints.tight(const Size(64, 24)),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: theme.colorTheme.accentBlue,
@@ -640,8 +653,8 @@ class MessageInputState extends State<MessageInput> {
],
)
: null),
suffixIconConstraints: BoxConstraints.tightFor(height: 40),
prefixIconConstraints: BoxConstraints.tightFor(height: 40),
suffixIconConstraints: const BoxConstraints.tightFor(height: 40),
prefixIconConstraints: const BoxConstraints.tightFor(height: 40),
suffixIcon: Row(
mainAxisSize: MainAxisSize.min,
children: [
@@ -652,7 +665,7 @@ class MessageInputState extends State<MessageInput> {
icon: StreamSvgIcon.closeSmall(),
splashRadius: 24,
padding: const EdgeInsets.all(0),
constraints: BoxConstraints.tightFor(
constraints: const BoxConstraints.tightFor(
height: 24,
width: 24,
),
@@ -809,6 +822,7 @@ class MessageInputState extends State<MessageInput> {
return null;
}
// ignore: cast_nullable_to_non_nullable
final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size;
@@ -819,7 +833,7 @@ class MessageInputState extends State<MessageInput> {
right: 0,
child: TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: 1),
duration: Duration(milliseconds: 300),
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOutExpo,
builder: (context, val, wid) => Transform.scale(
scale: val,
@@ -833,8 +847,8 @@ class MessageInputState extends State<MessageInput> {
color: StreamChatTheme.of(context).colorTheme.white,
clipBehavior: Clip.antiAlias,
child: Container(
constraints:
BoxConstraints.loose(Size.fromHeight(400)),
constraints: BoxConstraints.loose(
const Size.fromHeight(400)),
decoration: BoxDecoration(
color: StreamChatTheme.of(context)
.colorTheme
@@ -907,8 +921,11 @@ class MessageInputState extends State<MessageInput> {
.textTheme
.body
.copyWith(
// ignore: lines_longer_than_80_chars
color: StreamChatTheme.of(
// ignore: lines_longer_than_80_chars
context)
// ignore: lines_longer_than_80_chars
.colorTheme
.grey,
),
@@ -932,9 +949,8 @@ class MessageInputState extends State<MessageInput> {
}
Widget _buildFilePickerSection() {
final _attachmentContainsFile = _attachments.values.any((it) {
return it.type == 'file';
});
final _attachmentContainsFile =
_attachments.values.any((it) => it.type == 'file');
Color _getIconColor(int index) {
switch (index) {
@@ -973,7 +989,8 @@ class MessageInputState extends State<MessageInput> {
}
return AnimatedContainer(
duration: _animateContainer ? Duration(milliseconds: 300) : Duration.zero,
duration:
_animateContainer ? const Duration(milliseconds: 300) : Duration.zero,
height: _openFilePickerSection ? _filePickerSize : 0,
child: Material(
color: StreamChatTheme.of(context).colorTheme.whiteSmoke,
@@ -1095,7 +1112,7 @@ class MessageInputState extends State<MessageInput> {
void _addAttachment(AssetEntity medium) async {
final mediaFile = await medium.originFile.timeout(
Duration(seconds: 5),
const Duration(seconds: 5),
onTimeout: () => medium.originFile,
);
@@ -1116,13 +1133,14 @@ class MessageInputState extends State<MessageInput> {
if (mediaInfo.filesize! > _kMaxAttachmentSize) {
_showErrorAlert(
// ignore: lines_longer_than_80_chars
'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.',
);
return;
}
file = AttachmentFile(
name: file.name,
size: mediaInfo.filesize!,
size: mediaInfo.filesize,
bytes: await mediaInfo.file?.readAsBytes(),
path: mediaInfo.path,
);
@@ -1234,33 +1252,35 @@ class MessageInputState extends State<MessageInput> {
.then((res) => res.members);
}
final members = StreamChannel.of(context).channel.state?.members.where((m) {
return m.user?.name.toLowerCase().contains(query) == true;
}).toList() ??
final members = StreamChannel.of(context)
.channel
.state
?.members
.where((m) => m.user?.name.toLowerCase().contains(query) == true)
.toList() ??
[];
if (members.isEmpty) {
return null;
}
// ignore: cast_nullable_to_non_nullable
final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size;
return OverlayEntry(
builder: (context) {
return Positioned(
builder: (context) => Positioned(
bottom: size.height + MediaQuery.of(context).viewInsets.bottom,
left: 0,
right: 0,
child: TweenAnimationBuilder<double>(
tween: Tween(begin: 0, end: 1),
duration: Duration(milliseconds: 300),
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOutExpo,
builder: (context, val, wid) {
return Transform.scale(
builder: (context, val, wid) => Transform.scale(
scale: val,
child: Card(
margin: EdgeInsets.all(8),
margin: const EdgeInsets.all(8),
elevation: 2,
color: StreamChatTheme.of(context).colorTheme.white,
shape: RoundedRectangleBorder(
@@ -1268,29 +1288,26 @@ class MessageInputState extends State<MessageInput> {
),
clipBehavior: Clip.antiAlias,
child: Container(
constraints: BoxConstraints.loose(Size.fromHeight(240)),
constraints: BoxConstraints.loose(const Size.fromHeight(240)),
decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.white,
),
child: FutureBuilder<List<Member>>(
future: queryMembers ?? Future.value(members),
initialData: members,
builder: (context, snapshot) {
return ListView(
builder: (context, snapshot) => ListView(
padding: const EdgeInsets.all(0),
shrinkWrap: true,
children: [
SizedBox(
const SizedBox(
height: 8,
),
...snapshot.data!
.where((it) => it.user != null)
.map(
(m) {
return Material(
color: StreamChatTheme.of(context)
.colorTheme
.white,
(m) => Material(
color:
StreamChatTheme.of(context).colorTheme.white,
child: InkWell(
onTap: () {
if (m.user != null) {
@@ -1303,8 +1320,8 @@ class MessageInputState extends State<MessageInput> {
textEditingController.value =
TextEditingValue(
text: rejoin +
textEditingController.text
.substring(textEditingController
textEditingController.text.substring(
textEditingController
.selection.start),
selection: TextSelection.collapsed(
offset: rejoin.length,
@@ -1315,27 +1332,23 @@ class MessageInputState extends State<MessageInput> {
_mentionsOverlay = null;
},
child: widget.mentionsTileBuilder != null
? widget.mentionsTileBuilder!(
context, m)
? widget.mentionsTileBuilder!(context, m)
: MentionTile(m),
),
);
},
).toList(),
SizedBox(
),
)
.toList(),
const SizedBox(
height: 8,
),
],
);
},
),
),
),
);
},
),
);
},
),
),
),
);
}
@@ -1358,16 +1371,17 @@ class MessageInputState extends State<MessageInput> {
return null;
}
// ignore: cast_nullable_to_non_nullable
final renderBox = context.findRenderObject() as RenderBox;
final size = renderBox.size;
return OverlayEntry(builder: (context) {
return Positioned(
return OverlayEntry(
builder: (context) => Positioned(
bottom: size.height + MediaQuery.of(context).viewInsets.bottom,
left: 0,
right: 0,
child: Card(
margin: EdgeInsets.all(8),
margin: const EdgeInsets.all(8),
elevation: 2,
color: StreamChatTheme.of(context).colorTheme.white,
shape: RoundedRectangleBorder(
@@ -1375,9 +1389,9 @@ class MessageInputState extends State<MessageInput> {
),
clipBehavior: Clip.antiAlias,
child: Container(
constraints: BoxConstraints.loose(Size.fromHeight(200)),
constraints: BoxConstraints.loose(const Size.fromHeight(200)),
decoration: BoxDecoration(
boxShadow: [
boxShadow: const [
BoxShadow(
spreadRadius: -8,
blurRadius: 5,
@@ -1397,7 +1411,8 @@ class MessageInputState extends State<MessageInput> {
child: Row(
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
padding:
const EdgeInsets.symmetric(horizontal: 8),
child: StreamSvgIcon.smile(
color: StreamChatTheme.of(context)
.colorTheme
@@ -1423,7 +1438,9 @@ class MessageInputState extends State<MessageInput> {
final emoji = emojis.elementAt(i - 1)!;
return ListTile(
title: SubstringHighlight(
text: "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}",
text:
// ignore: lines_longer_than_80_chars
"${emoji.char} ${emoji.name!.replaceAll('_', ' ')}",
term: query,
textStyleHighlight:
Theme.of(context).textTheme.headline6!.copyWith(
@@ -1442,8 +1459,7 @@ class MessageInputState extends State<MessageInput> {
}),
),
),
);
});
));
}
void _chooseEmoji(List<String> splits, Emoji emoji) {
@@ -1474,7 +1490,7 @@ class MessageInputState extends State<MessageInput> {
}
Widget _buildReplyToMessage() {
if (!_hasQuotedMessage) return Offstage();
if (!_hasQuotedMessage) return const Offstage();
final containsUrl = widget.quotedMessage!.attachments
.any((element) => element.ogScrapeUrl != null) ==
true;
@@ -1492,7 +1508,7 @@ class MessageInputState extends State<MessageInput> {
}
Widget _buildAttachments() {
if (_attachments.isEmpty) return Offstage();
if (_attachments.isEmpty) return const Offstage();
final fileAttachments = _attachments.values
.where((it) => it.type == 'file')
.toList(growable: false);
@@ -1546,7 +1562,7 @@ class MessageInputState extends State<MessageInput> {
children: <Widget>[
AspectRatio(
aspectRatio: 1,
child: Container(
child: SizedBox(
height: 104,
width: 104,
child: _buildAttachment(attachment),
@@ -1569,8 +1585,7 @@ class MessageInputState extends State<MessageInput> {
);
}
Widget _buildRemoveButton(Attachment attachment) {
return Container(
Widget _buildRemoveButton(Attachment attachment) => SizedBox(
height: 24,
width: 24,
child: RawMaterialButton(
@@ -1584,7 +1599,8 @@ class MessageInputState extends State<MessageInput> {
onPressed: () {
setState(() => _attachments.remove(attachment.id));
},
fillColor: StreamChatTheme.of(context).colorTheme.black.withOpacity(.5),
fillColor:
StreamChatTheme.of(context).colorTheme.black.withOpacity(.5),
child: Center(
child: StreamSvgIcon.close(
size: 24,
@@ -1593,7 +1609,6 @@ class MessageInputState extends State<MessageInput> {
),
),
);
}
Widget _buildAttachment(Attachment attachment) {
if (widget.attachmentThumbnailBuilders?.containsKey(attachment.type) ==
@@ -1611,24 +1626,20 @@ class MessageInputState extends State<MessageInput> {
? Image.memory(
attachment.file!.bytes!,
fit: BoxFit.cover,
errorBuilder: (context, _, __) {
return Image.asset(
errorBuilder: (context, _, __) => Image.asset(
'images/placeholder.png',
package: 'stream_chat_flutter',
);
},
),
)
: CachedNetworkImage(
imageUrl: attachment.imageUrl ??
attachment.assetUrl ??
attachment.thumbUrl!,
fit: BoxFit.cover,
errorWidget: (_, obj, trace) {
return getFileTypeImage(
attachment.extraData['other'] as String?);
},
progressIndicatorBuilder: (context, _, progress) {
return Shimmer.fromColors(
errorWidget: (_, obj, trace) =>
getFileTypeImage(attachment.extraData['other'] as String?),
progressIndicatorBuilder: (context, _, progress) =>
Shimmer.fromColors(
baseColor:
StreamChatTheme.of(context).colorTheme.greyGainsboro,
highlightColor:
@@ -1638,8 +1649,7 @@ class MessageInputState extends State<MessageInput> {
fit: BoxFit.cover,
package: 'stream_chat_flutter',
),
);
},
),
);
case 'video':
return Stack(
@@ -1663,7 +1673,7 @@ class MessageInputState extends State<MessageInput> {
default:
return Container(
color: Colors.black26,
child: Icon(Icons.insert_drive_file),
child: const Icon(Icons.insert_drive_file),
);
}
}
@@ -1684,7 +1694,7 @@ class MessageInputState extends State<MessageInput> {
.actionButtonIdleColor),
),
padding: const EdgeInsets.all(0),
constraints: BoxConstraints.tightFor(
constraints: const BoxConstraints.tightFor(
height: 24,
width: 24,
),
@@ -1696,7 +1706,7 @@ class MessageInputState extends State<MessageInput> {
_openFilePickerSection = false;
_filePickerSize = _kMinMediaPickerSize;
});
await Future.delayed(Duration(milliseconds: 300));
await Future.delayed(const Duration(milliseconds: 300));
}
if (_commandsOverlay == null) {
@@ -1716,8 +1726,7 @@ class MessageInputState extends State<MessageInput> {
);
}
Widget _buildAttachmentButton() {
return IconButton(
Widget _buildAttachmentButton() => IconButton(
icon: StreamSvgIcon.attach(
color: _openFilePickerSection
? StreamChatTheme.of(context).messageInputTheme.actionButtonColor
@@ -1726,7 +1735,7 @@ class MessageInputState extends State<MessageInput> {
.actionButtonIdleColor,
),
padding: const EdgeInsets.all(0),
constraints: BoxConstraints.tightFor(
constraints: const BoxConstraints.tightFor(
height: 24,
width: 24,
),
@@ -1750,9 +1759,9 @@ class MessageInputState extends State<MessageInput> {
}
},
);
}
/// Show the attachment modal, making the user choose where to pick a media from
/// Show the attachment modal, making the user choose where to
/// pick a media from
void showAttachmentModal() {
if (_focusNode.hasFocus) {
_focusNode.unfocus();
@@ -1765,7 +1774,7 @@ class MessageInputState extends State<MessageInput> {
} else {
showModalBottomSheet(
clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(32),
topRight: Radius.circular(32),
@@ -1773,11 +1782,10 @@ class MessageInputState extends State<MessageInput> {
),
context: context,
isScrollControlled: true,
builder: (_) {
return Column(
builder: (_) => Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
const ListTile(
title: Text(
'Add a file',
style: TextStyle(
@@ -1786,16 +1794,16 @@ class MessageInputState extends State<MessageInput> {
),
),
ListTile(
leading: Icon(Icons.image),
title: Text('Upload a photo'),
leading: const Icon(Icons.image),
title: const Text('Upload a photo'),
onTap: () {
pickFile(DefaultAttachmentTypes.image);
Navigator.pop(context);
},
),
ListTile(
leading: Icon(Icons.video_library),
title: Text('Upload a video'),
leading: const Icon(Icons.video_library),
title: const Text('Upload a video'),
onTap: () {
pickFile(DefaultAttachmentTypes.video);
Navigator.pop(context);
@@ -1803,8 +1811,8 @@ class MessageInputState extends State<MessageInput> {
),
if (!kIsWeb)
ListTile(
leading: Icon(Icons.camera_alt),
title: Text('Photo from camera'),
leading: const Icon(Icons.camera_alt),
title: const Text('Photo from camera'),
onTap: () {
pickFile(DefaultAttachmentTypes.image, true);
Navigator.pop(context);
@@ -1812,24 +1820,23 @@ class MessageInputState extends State<MessageInput> {
),
if (!kIsWeb)
ListTile(
leading: Icon(Icons.videocam),
title: Text('Video from camera'),
leading: const Icon(Icons.videocam),
title: const Text('Video from camera'),
onTap: () {
pickFile(DefaultAttachmentTypes.video, true);
Navigator.pop(context);
},
),
ListTile(
leading: Icon(Icons.insert_drive_file),
title: Text('Upload a file'),
leading: const Icon(Icons.insert_drive_file),
title: const Text('Upload a file'),
onTap: () {
pickFile(DefaultAttachmentTypes.file);
Navigator.pop(context);
},
),
],
);
});
));
}
}
@@ -1845,6 +1852,7 @@ class MessageInputState extends State<MessageInput> {
/// Pick a file from the device
/// If [camera] is true then the camera will open
// ignore: avoid_positional_boolean_parameters
void pickFile(DefaultAttachmentTypes fileType, [bool camera = false]) async {
setState(() => _inputEnabled = false);
@@ -1920,13 +1928,14 @@ class MessageInputState extends State<MessageInput> {
if (mediaInfo.filesize! > _kMaxAttachmentSize) {
_showErrorAlert(
// ignore: lines_longer_than_80_chars
'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.',
);
return;
}
file = AttachmentFile(
name: file.name,
size: mediaInfo.filesize!,
size: mediaInfo.filesize,
bytes: await mediaInfo.file!.readAsBytes(),
path: mediaInfo.path,
);
@@ -1941,18 +1950,17 @@ class MessageInputState extends State<MessageInput> {
_attachments[attachment.id] = attachment;
setState(() {
_attachments.update(attachment.id, (it) {
return it.copyWith(
_attachments.update(
attachment.id,
(it) => it.copyWith(
file: file,
extraData: {...it.extraData}
..update('file_size', ((_) => file!.size!)),
);
});
));
});
}
Widget _buildIdleSendButton(BuildContext context) {
return Padding(
Widget _buildIdleSendButton(BuildContext context) => Padding(
padding: const EdgeInsets.all(8),
child: StreamSvgIcon(
assetName: _getIdleSendIcon(),
@@ -1960,26 +1968,24 @@ class MessageInputState extends State<MessageInput> {
StreamChatTheme.of(context).messageInputTheme.sendButtonIdleColor,
),
);
}
Widget _buildSendButton(BuildContext context) {
return Padding(
Widget _buildSendButton(BuildContext context) => Padding(
padding: const EdgeInsets.all(8),
child: IconButton(
onPressed: sendMessage,
padding: const EdgeInsets.all(0),
splashRadius: 24,
constraints: BoxConstraints.tightFor(
constraints: const BoxConstraints.tightFor(
height: 24,
width: 24,
),
icon: StreamSvgIcon(
assetName: _getSendIcon(),
color: StreamChatTheme.of(context).messageInputTheme.sendButtonColor,
color:
StreamChatTheme.of(context).messageInputTheme.sendButtonColor,
),
),
);
}
String _getIdleSendIcon() {
if (_commandEnabled) {
@@ -2009,7 +2015,7 @@ class MessageInputState extends State<MessageInput> {
final shouldUnfocus = _commandEnabled;
if (_commandEnabled) {
text = '/${_chosenCommand!.name} ' + text;
text = '${'/${_chosenCommand!.name} '}$text';
}
final attachments = [..._attachments.values];
@@ -2096,30 +2102,29 @@ class MessageInputState extends State<MessageInput> {
showModalBottomSheet(
backgroundColor: StreamChatTheme.of(context).colorTheme.white,
context: context,
shape: RoundedRectangleBorder(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
)),
builder: (context) {
return Column(
builder: (context) => Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
const SizedBox(
height: 26,
),
StreamSvgIcon.error(
color: StreamChatTheme.of(context).colorTheme.accentRed,
size: 24,
),
SizedBox(
const SizedBox(
height: 26,
),
Text(
'Something went wrong',
style: StreamChatTheme.of(context).textTheme.headlineBold,
),
SizedBox(
const SizedBox(
height: 7,
),
Padding(
@@ -2129,7 +2134,7 @@ class MessageInputState extends State<MessageInput> {
textAlign: TextAlign.center,
),
),
SizedBox(
const SizedBox(
height: 36,
),
Container(
@@ -2158,8 +2163,7 @@ class MessageInputState extends State<MessageInput> {
],
),
],
);
},
),
);
}
@@ -2196,12 +2200,6 @@ class MessageInputState extends State<MessageInput> {
/// Represents a 2-tuple, or pair.
class Tuple2<T1, T2> {
/// Returns the first item of the tuple
final T1 item1;
/// Returns the second item of the tuple
final T2 item2;
/// Creates a new tuple value with the specified items.
const Tuple2(this.item1, this.item2);
@@ -2214,6 +2212,12 @@ class Tuple2<T1, T2> {
return Tuple2<T1, T2>(items[0] as T1, items[1] as T2);
}
/// Returns the first item of the tuple
final T1 item1;
/// Returns the second item of the tuple
final T2 item2;
/// Returns a tuple with the first item set to the specified value.
Tuple2<T1, T2> withItem1(T1 v) => Tuple2<T1, T2>(v, item2);
@@ -2243,12 +2247,6 @@ class Tuple2<T1, T2> {
}
class _PickerWidget extends StatefulWidget {
final int filePickerIndex;
final bool containsFile;
final List<String> selectedMedias;
final void Function(DefaultAttachmentTypes) onAddMoreFilesClick;
final void Function(AssetEntity) onMediaSelected;
const _PickerWidget({
Key? key,
required this.filePickerIndex,
@@ -2258,6 +2256,12 @@ class _PickerWidget extends StatefulWidget {
required this.onMediaSelected,
}) : super(key: key);
final int filePickerIndex;
final bool containsFile;
final List<String> selectedMedias;
final void Function(DefaultAttachmentTypes) onAddMoreFilesClick;
final void Function(AssetEntity) onMediaSelected;
@override
__PickerWidgetState createState() => __PickerWidgetState();
}
@@ -2274,7 +2278,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
@override
Widget build(BuildContext context) {
if (widget.filePickerIndex != 0) {
return Offstage();
return const Offstage();
}
return FutureBuilder<bool>(
future: requestPermission,
@@ -2290,7 +2294,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
widget.onAddMoreFilesClick(DefaultAttachmentTypes.file);
},
child: Container(
constraints: BoxConstraints.expand(),
constraints: const BoxConstraints.expand(),
color: StreamChatTheme.of(context).colorTheme.whiteSmoke,
alignment: Alignment.center,
child: Text(
@@ -2326,12 +2330,13 @@ class __PickerWidgetState extends State<_PickerWidget> {
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
),
Text(
// ignore: lines_longer_than_80_chars
'Please enable access to your photos \nand videos so you can share them with friends.',
style: StreamChatTheme.of(context).textTheme.body.copyWith(
color: StreamChatTheme.of(context).colorTheme.grey),
textAlign: TextAlign.center,
),
SizedBox(height: 6),
const SizedBox(height: 6),
Center(
child: Text(
'Allow access to your gallery',