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