Merge pull request #159 from GetStream/feature/file-attachment-new
Feature/file attachment new
This commit is contained in:
+156
-10
@@ -1,32 +1,178 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||
import 'package:stream_chat_flutter/src/utils.dart';
|
||||
|
||||
class FileAttachment extends StatelessWidget {
|
||||
final Attachment attachment;
|
||||
final Size size;
|
||||
final Widget trailing;
|
||||
|
||||
const FileAttachment({
|
||||
Key key,
|
||||
@required this.attachment,
|
||||
this.size,
|
||||
this.trailing,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
launchURL(context, attachment.assetUrl);
|
||||
},
|
||||
child: Container(
|
||||
width: size?.width ?? 100,
|
||||
height: size?.height ?? 100,
|
||||
child: Center(
|
||||
child: Icon(Icons.attach_file),
|
||||
),
|
||||
child: Container(
|
||||
width: size?.width ?? 100,
|
||||
height: 56.0,
|
||||
margin: trailing != null ? EdgeInsets.only(top: 4.0) : null,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: trailing != null ? BorderRadius.circular(16.0) : null,
|
||||
border: trailing != null
|
||||
? Border.fromBorderSide(BorderSide(color: Color(0xFFE6E6E6)))
|
||||
: null,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
child: _getFileTypeImage(attachment.extraData['mime_type']),
|
||||
height: 40.0,
|
||||
width: 33.33,
|
||||
margin: EdgeInsets.all(8.0),
|
||||
),
|
||||
SizedBox(
|
||||
width: 6.0,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
attachment?.title ?? 'File',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14.0,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
SizedBox(
|
||||
height: 3.0,
|
||||
),
|
||||
Text(
|
||||
'${attachment.extraData['file_size'] ?? 'N/A'} bytes',
|
||||
style: TextStyle(
|
||||
color: Colors.black.withOpacity(0.5),
|
||||
fontSize: 14.0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
trailing ??
|
||||
IconButton(
|
||||
icon: StreamSvgIcon.cloud_download(
|
||||
color: Colors.black,
|
||||
),
|
||||
onPressed: () {
|
||||
launchURL(context, attachment.assetUrl);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
// ListTile(
|
||||
// dense: true,
|
||||
// leading: Container(
|
||||
// child: _getFileTypeImage(attachment.extraData['mime_type']),
|
||||
// height: 40.0,
|
||||
// width: 33.33,
|
||||
// ),
|
||||
// title: Text(
|
||||
// attachment?.title ?? 'File',
|
||||
// style: TextStyle(
|
||||
// fontWeight: FontWeight.bold,
|
||||
// ),
|
||||
// maxLines: 3,
|
||||
// ),
|
||||
// subtitle: Text(
|
||||
// '${attachment.extraData['file_size'] ?? 'N/A'} bytes',
|
||||
// style: TextStyle(
|
||||
// color: Colors.black.withOpacity(0.5),
|
||||
// ),
|
||||
// ),
|
||||
// trailing: trailing ??
|
||||
// IconButton(
|
||||
// icon: StreamSvgIcon.cloud_download(
|
||||
// color: Colors.black,
|
||||
// ),
|
||||
// onPressed: () {
|
||||
// launchURL(context, attachment.assetUrl);
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
StreamSvgIcon _getFileTypeImage(String type) {
|
||||
switch (type) {
|
||||
case '7z':
|
||||
return StreamSvgIcon.filetype_7z();
|
||||
break;
|
||||
case 'csv':
|
||||
return StreamSvgIcon.filetype_csv();
|
||||
break;
|
||||
case 'doc':
|
||||
return StreamSvgIcon.filetype_doc();
|
||||
break;
|
||||
case 'docx':
|
||||
return StreamSvgIcon.filetype_docx();
|
||||
break;
|
||||
case 'html':
|
||||
return StreamSvgIcon.filetype_html();
|
||||
break;
|
||||
case 'md':
|
||||
return StreamSvgIcon.filetype_md();
|
||||
break;
|
||||
case 'odt':
|
||||
return StreamSvgIcon.filetype_odt();
|
||||
break;
|
||||
case 'pdf':
|
||||
return StreamSvgIcon.filetype_pdf();
|
||||
break;
|
||||
case 'ppt':
|
||||
return StreamSvgIcon.filetype_ppt();
|
||||
break;
|
||||
case 'pptx':
|
||||
return StreamSvgIcon.filetype_pptx();
|
||||
break;
|
||||
case 'rar':
|
||||
return StreamSvgIcon.filetype_rar();
|
||||
break;
|
||||
case 'rtf':
|
||||
return StreamSvgIcon.filetype_rtf();
|
||||
break;
|
||||
case 'tar':
|
||||
return StreamSvgIcon.filetype_tar();
|
||||
break;
|
||||
case 'txt':
|
||||
return StreamSvgIcon.filetype_txt();
|
||||
break;
|
||||
case 'xls':
|
||||
return StreamSvgIcon.filetype_xls();
|
||||
break;
|
||||
case 'xlsx':
|
||||
return StreamSvgIcon.filetype_xlsx();
|
||||
break;
|
||||
case 'zip':
|
||||
return StreamSvgIcon.filetype_zip();
|
||||
break;
|
||||
default:
|
||||
return StreamSvgIcon.filetype_Generic();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+180
-80
@@ -658,6 +658,34 @@ class MessageInputState extends State<MessageInput> {
|
||||
}
|
||||
|
||||
Widget _buildFilePickerSection() {
|
||||
var _attachmentContainsFile =
|
||||
_attachments.any((element) => element?.attachment?.type == 'file');
|
||||
|
||||
Color _getIconColor(int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return _attachmentContainsFile && _attachments.isNotEmpty
|
||||
? Colors.black.withOpacity(0.2)
|
||||
: Colors.black.withOpacity(0.5);
|
||||
break;
|
||||
case 1:
|
||||
return !_attachmentContainsFile && _attachments.isNotEmpty
|
||||
? Colors.black.withOpacity(0.2)
|
||||
: Colors.black.withOpacity(0.5);
|
||||
break;
|
||||
case 2:
|
||||
return _attachmentContainsFile && _attachments.isNotEmpty
|
||||
? Colors.black.withOpacity(0.2)
|
||||
: Colors.black.withOpacity(0.5);
|
||||
break;
|
||||
case 3:
|
||||
return _attachmentContainsFile && _attachments.isNotEmpty
|
||||
? Colors.black.withOpacity(0.2)
|
||||
: Colors.black.withOpacity(0.5);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return AnimatedContainer(
|
||||
duration: _animateContainer ? Duration(milliseconds: 300) : Duration.zero,
|
||||
height: _openFilePickerSection ? _filePickerSize : 0,
|
||||
@@ -671,49 +699,49 @@ class MessageInputState extends State<MessageInput> {
|
||||
IconButton(
|
||||
iconSize: 24,
|
||||
icon: StreamSvgIcon.pictures(
|
||||
color: _filePickerIndex == 0
|
||||
? StreamChatTheme.of(context).accentColor
|
||||
: Colors.black.withOpacity(0.5),
|
||||
color: _getIconColor(0),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_filePickerIndex = 0;
|
||||
});
|
||||
},
|
||||
onPressed: _attachmentContainsFile && _attachments.isNotEmpty
|
||||
? null
|
||||
: () {
|
||||
setState(() {
|
||||
_filePickerIndex = 0;
|
||||
});
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 32,
|
||||
icon: StreamSvgIcon.files(
|
||||
color: _filePickerIndex == 1
|
||||
? StreamChatTheme.of(context).accentColor
|
||||
: Colors.black.withOpacity(0.5),
|
||||
color: _getIconColor(1),
|
||||
),
|
||||
onPressed: () {
|
||||
pickFile(DefaultAttachmentTypes.file, false);
|
||||
},
|
||||
onPressed: !_attachmentContainsFile && _attachments.isNotEmpty
|
||||
? null
|
||||
: () {
|
||||
pickFile(DefaultAttachmentTypes.file, false);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
iconSize: 24,
|
||||
icon: StreamSvgIcon.camera(
|
||||
color: _filePickerIndex == 2
|
||||
? StreamChatTheme.of(context).accentColor
|
||||
: Colors.black.withOpacity(0.5),
|
||||
color: _getIconColor(2),
|
||||
),
|
||||
onPressed: () {
|
||||
pickFile(DefaultAttachmentTypes.image, true);
|
||||
},
|
||||
onPressed: _attachmentContainsFile && _attachments.isNotEmpty
|
||||
? null
|
||||
: () {
|
||||
pickFile(DefaultAttachmentTypes.image, true);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
padding: const EdgeInsets.all(0),
|
||||
iconSize: 24,
|
||||
icon: StreamSvgIcon.record(
|
||||
color: _filePickerIndex == 3
|
||||
? StreamChatTheme.of(context).accentColor
|
||||
: Colors.black.withOpacity(0.5),
|
||||
color: _getIconColor(3),
|
||||
),
|
||||
onPressed: () {
|
||||
pickFile(DefaultAttachmentTypes.video, true);
|
||||
},
|
||||
onPressed: _attachmentContainsFile && _attachments.isNotEmpty
|
||||
? null
|
||||
: () {
|
||||
pickFile(DefaultAttachmentTypes.video, true);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -770,6 +798,9 @@ class MessageInputState extends State<MessageInput> {
|
||||
}
|
||||
|
||||
Widget _buildPickerSection() {
|
||||
var _attachmentContainsFile =
|
||||
_attachments.any((element) => element.attachment.type == 'file');
|
||||
|
||||
switch (_filePickerIndex) {
|
||||
case 0:
|
||||
return FutureBuilder<bool>(
|
||||
@@ -782,19 +813,22 @@ class MessageInputState extends State<MessageInput> {
|
||||
}
|
||||
|
||||
if (snapshot.data) {
|
||||
return MediaListView(
|
||||
selectedIds: _attachments.map((e) => e.id).toList(),
|
||||
onSelect: (media) async {
|
||||
if (!_attachments
|
||||
.any((element) => element.id == media.id)) {
|
||||
_addAttachment(media);
|
||||
} else {
|
||||
setState(() {
|
||||
_attachments
|
||||
.removeWhere((element) => element.id == media.id);
|
||||
});
|
||||
}
|
||||
},
|
||||
return IgnorePointer(
|
||||
ignoring: _attachmentContainsFile,
|
||||
child: MediaListView(
|
||||
selectedIds: _attachments.map((e) => e.id).toList(),
|
||||
onSelect: (media) async {
|
||||
if (!_attachments
|
||||
.any((element) => element.id == media.id)) {
|
||||
_addAttachment(media);
|
||||
} else {
|
||||
setState(() {
|
||||
_attachments
|
||||
.removeWhere((element) => element.id == media.id);
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -850,7 +884,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
);
|
||||
|
||||
if (file.size > _kMaxAttachmentSize) {
|
||||
if (medium.type == AssetType.video) {
|
||||
if (medium?.type == AssetType.video) {
|
||||
final mediaInfo = await CompressVideoService.compressVideo(file.path);
|
||||
|
||||
if (mediaInfo.filesize / (1024 * 1024) > _kMaxAttachmentSize) {
|
||||
@@ -890,7 +924,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
..file = file
|
||||
..attachment = Attachment(
|
||||
localUri: file.path != null ? Uri.parse(file.path) : null,
|
||||
type: medium.type == AssetType.image ? 'image' : 'video',
|
||||
type: medium?.type == AssetType.image ? 'image' : 'video',
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1053,7 +1087,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
offset: rejoin.length,
|
||||
),
|
||||
);
|
||||
|
||||
_debounce.cancel();
|
||||
_mentionsOverlay?.remove();
|
||||
_mentionsOverlay = null;
|
||||
},
|
||||
@@ -1201,44 +1235,98 @@ class MessageInputState extends State<MessageInput> {
|
||||
Widget _buildAttachments() {
|
||||
return _attachments.isEmpty
|
||||
? Container()
|
||||
: LimitedBox(
|
||||
maxHeight: 104.0,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: _attachments
|
||||
.map(
|
||||
(attachment) => Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
child: Container(
|
||||
height: 104,
|
||||
width: 104,
|
||||
child: _buildAttachment(attachment),
|
||||
),
|
||||
),
|
||||
_buildRemoveButton(attachment),
|
||||
attachment.uploaded
|
||||
? SizedBox()
|
||||
: Positioned.fill(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: CircularProgressIndicator(),
|
||||
: Column(
|
||||
children: [
|
||||
if (_attachments.any((e) => e.attachment?.type == 'file'))
|
||||
LimitedBox(
|
||||
maxHeight: 73.0,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: _attachments
|
||||
.where((e) => e.attachment?.type == 'file')
|
||||
.map(
|
||||
(e) => Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: FileAttachment(
|
||||
attachment: e.attachment,
|
||||
size: Size(
|
||||
MediaQuery.of(context).size.width * 0.55,
|
||||
MediaQuery.of(context).size.height * 0.3,
|
||||
),
|
||||
trailing: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: InkWell(
|
||||
child: CircleAvatar(
|
||||
backgroundColor:
|
||||
Colors.black.withOpacity(0.2),
|
||||
maxRadius: 12.0,
|
||||
child: StreamSvgIcon.close(
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_attachments.remove(e);
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
if (_attachments.any((e) => e.attachment?.type != 'file'))
|
||||
LimitedBox(
|
||||
maxHeight: 104.0,
|
||||
child: ListView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
children: _attachments
|
||||
.where((e) => e.attachment?.type != 'file')
|
||||
.map(
|
||||
(attachment) => Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
child: Container(
|
||||
height: 104,
|
||||
width: 104,
|
||||
child: _buildAttachment(attachment),
|
||||
),
|
||||
),
|
||||
_buildRemoveButton(attachment),
|
||||
attachment.uploaded
|
||||
? SizedBox()
|
||||
: Positioned.fill(
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding:
|
||||
const EdgeInsets.all(16.0),
|
||||
child:
|
||||
CircularProgressIndicator(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1275,9 +1363,9 @@ class MessageInputState extends State<MessageInput> {
|
||||
|
||||
Widget _buildAttachment(_SendingAttachment attachment) {
|
||||
if (widget.attachmentThumbnailBuilders
|
||||
?.containsKey(attachment.attachment.type) ==
|
||||
?.containsKey(attachment.attachment?.type) ==
|
||||
true) {
|
||||
return widget.attachmentThumbnailBuilders[attachment.attachment.type](
|
||||
return widget.attachmentThumbnailBuilders[attachment.attachment?.type](
|
||||
context,
|
||||
attachment,
|
||||
);
|
||||
@@ -1287,7 +1375,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
return SizedBox();
|
||||
}
|
||||
|
||||
switch (attachment.attachment.type) {
|
||||
switch (attachment.attachment?.type) {
|
||||
case 'image':
|
||||
case 'giphy':
|
||||
return attachment.file != null
|
||||
@@ -1562,12 +1650,24 @@ class MessageInputState extends State<MessageInput> {
|
||||
attachmentType = mimeType.type;
|
||||
}
|
||||
|
||||
Map<String, dynamic> extraDataMap = {};
|
||||
|
||||
if (mimeType?.subtype != null) {
|
||||
extraDataMap['mime_type'] = mimeType.subtype.toLowerCase();
|
||||
}
|
||||
|
||||
if (file.size != null) {
|
||||
extraDataMap['file_size'] = file.size;
|
||||
}
|
||||
|
||||
final channel = StreamChannel.of(context).channel;
|
||||
final attachment = _SendingAttachment(
|
||||
file: file,
|
||||
attachment: Attachment(
|
||||
localUri: file.path != null ? Uri.parse(file.path) : null,
|
||||
type: attachmentType,
|
||||
extraData: extraDataMap.isNotEmpty ? extraDataMap : null,
|
||||
title: file.name ?? 'File',
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -617,7 +617,12 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
showDeleteMessage: isMyMessage,
|
||||
borderSide: isMyMessage ? BorderSide.none : null,
|
||||
onThreadTap: _onThreadTap,
|
||||
attachmentBorderRadiusGeometry: BorderRadius.circular(16),
|
||||
attachmentBorderRadiusGeometry: BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
bottomLeft: Radius.circular(!isNextUser ? 0 : 16),
|
||||
topRight: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16),
|
||||
),
|
||||
attachmentPadding: const EdgeInsets.all(2),
|
||||
borderRadiusGeometry: BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
|
||||
+81
-73
@@ -223,6 +223,13 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
widget.message.attachments?.any((element) => element.type == 'giphy') ==
|
||||
true;
|
||||
|
||||
final isOnlyEmoji =
|
||||
widget.message.text.characters.every((c) => Emoji.byChar(c) != null);
|
||||
|
||||
final hasFiles =
|
||||
widget.message.attachments?.any((element) => element.type == 'file') ==
|
||||
true;
|
||||
|
||||
return Portal(
|
||||
child: Padding(
|
||||
padding: widget.padding ?? EdgeInsets.all(8),
|
||||
@@ -295,18 +302,46 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
messageTheme: widget.messageTheme,
|
||||
),
|
||||
)
|
||||
: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
..._parseAttachments(context),
|
||||
if (widget.message.text
|
||||
.trim()
|
||||
.isNotEmpty &&
|
||||
!isGiphy)
|
||||
_buildTextBubble(context),
|
||||
],
|
||||
: Material(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: widget.shape ??
|
||||
RoundedRectangleBorder(
|
||||
side: isOnlyEmoji
|
||||
? BorderSide.none
|
||||
: widget.borderSide ??
|
||||
BorderSide(
|
||||
color: Theme.of(context)
|
||||
.brightness ==
|
||||
Brightness
|
||||
.dark
|
||||
? Colors.white
|
||||
.withAlpha(24)
|
||||
: Colors.black
|
||||
.withAlpha(
|
||||
24),
|
||||
),
|
||||
borderRadius: widget
|
||||
.borderRadiusGeometry ??
|
||||
BorderRadius.zero,
|
||||
),
|
||||
color: _getBackgroundColor(),
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(
|
||||
hasFiles ? 2.0 : 0.0),
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
..._parseAttachments(context),
|
||||
if (widget.message.text
|
||||
.trim()
|
||||
.isNotEmpty &&
|
||||
!isGiphy)
|
||||
_buildTextBubble(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.showReactionPickerIndicator)
|
||||
@@ -629,46 +664,40 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
[];
|
||||
}
|
||||
|
||||
Padding wrapAttachmentWidget(
|
||||
Widget wrapAttachmentWidget(
|
||||
BuildContext context,
|
||||
Widget attachmentWidget, {
|
||||
Attachment attachment,
|
||||
}) {
|
||||
final attachmentShape =
|
||||
widget.attachmentShape ?? widget.shape ?? _getDefaultShape(context);
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: 4,
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: () => retryMessage(context),
|
||||
onLongPress: () => onLongPress(context),
|
||||
child: Material(
|
||||
color: attachment?.type == 'giphy'
|
||||
? Colors.white
|
||||
: _getBackgroundColor(),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: attachmentShape,
|
||||
child: Padding(
|
||||
padding: widget.attachmentPadding,
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: attachmentShape,
|
||||
type: MaterialType.transparency,
|
||||
child: Transform(
|
||||
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
getFailedMessageWidget(
|
||||
context,
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
),
|
||||
attachmentWidget,
|
||||
],
|
||||
),
|
||||
return GestureDetector(
|
||||
onTap: () => retryMessage(context),
|
||||
onLongPress: () => onLongPress(context),
|
||||
child: Material(
|
||||
color:
|
||||
attachment?.type == 'giphy' ? Colors.white : _getBackgroundColor(),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: attachmentShape,
|
||||
child: Padding(
|
||||
padding: widget.attachmentPadding,
|
||||
child: Material(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: attachmentShape,
|
||||
type: MaterialType.transparency,
|
||||
child: Transform(
|
||||
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
|
||||
alignment: Alignment.center,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
getFailedMessageWidget(
|
||||
context,
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
),
|
||||
attachmentWidget,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -798,26 +827,6 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
return SizedBox();
|
||||
}
|
||||
|
||||
Widget _wrapTextInBubble({
|
||||
BuildContext context,
|
||||
Widget child,
|
||||
}) {
|
||||
return Material(
|
||||
shape: widget.shape ??
|
||||
RoundedRectangleBorder(
|
||||
side: widget.borderSide ??
|
||||
BorderSide(
|
||||
color: Theme.of(context).brightness == Brightness.dark
|
||||
? Colors.white.withAlpha(24)
|
||||
: Colors.black.withAlpha(24),
|
||||
),
|
||||
borderRadius: widget.borderRadiusGeometry ?? BorderRadius.zero,
|
||||
),
|
||||
color: _getBackgroundColor(),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTextBubble(BuildContext context) {
|
||||
final isOnlyEmoji =
|
||||
widget.message.text.characters.every((c) => Emoji.byChar(c) != null);
|
||||
@@ -858,13 +867,6 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
if (!isOnlyEmoji) {
|
||||
child = _wrapTextInBubble(
|
||||
context: context,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
return GestureDetector(
|
||||
onTap: () => retryMessage(context),
|
||||
onLongPress: () => onLongPress(context),
|
||||
@@ -873,6 +875,9 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
}
|
||||
|
||||
Color _getBackgroundColor() {
|
||||
final isOnlyEmoji =
|
||||
widget.message.text.characters.every((c) => Emoji.byChar(c) != null);
|
||||
|
||||
if ((widget.message.status == MessageSendingStatus.FAILED ||
|
||||
widget.message.status == MessageSendingStatus.FAILED_UPDATE ||
|
||||
widget.message.status == MessageSendingStatus.FAILED_DELETE)) {
|
||||
@@ -885,6 +890,9 @@ class _MessageWidgetState extends State<MessageWidget> {
|
||||
return Color(0xFFE9F2FF);
|
||||
}
|
||||
|
||||
if (isOnlyEmoji) {
|
||||
return Colors.transparent;
|
||||
}
|
||||
return widget.messageTheme.messageBackgroundColor;
|
||||
}
|
||||
|
||||
|
||||
@@ -338,6 +338,30 @@ class StreamSvgIcon extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.download({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'Icon_download.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.cloud_download({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'Icon_cloud_download.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.copy({
|
||||
double size,
|
||||
Color color,
|
||||
@@ -481,4 +505,220 @@ class StreamSvgIcon extends StatelessWidget {
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_7z({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_7z.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_csv({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_CSV.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_doc({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_DOC.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_docx({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_DOCX.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_Generic({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_Generic.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_html({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_html.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_md({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_MD.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_odt({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_ODT.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_pdf({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_PDF.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_ppt({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_PPT.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_pptx({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_PPTX.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_rar({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_RAR.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_rtf({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_RTF.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_tar({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_TAR.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_txt({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_TXT.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_xls({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_XLS.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_xlsx({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_XLSX.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
|
||||
factory StreamSvgIcon.filetype_zip({
|
||||
double size,
|
||||
Color color,
|
||||
}) {
|
||||
return StreamSvgIcon(
|
||||
assetName: 'filetype_ZIP.svg',
|
||||
color: color,
|
||||
width: size,
|
||||
height: size,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user