diff --git a/lib/src/file_attachment.dart b/lib/src/file_attachment.dart index 05318862..fff13bbe 100644 --- a/lib/src/file_attachment.dart +++ b/lib/src/file_attachment.dart @@ -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; + } + } } diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 1dbc45df..4e30afe1 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -658,6 +658,34 @@ class MessageInputState extends State { } 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 { 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 { } Widget _buildPickerSection() { + var _attachmentContainsFile = + _attachments.any((element) => element.attachment.type == 'file'); + switch (_filePickerIndex) { case 0: return FutureBuilder( @@ -782,19 +813,22 @@ class MessageInputState extends State { } 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 { ); 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 { ..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 { offset: rejoin.length, ), ); - + _debounce.cancel(); _mentionsOverlay?.remove(); _mentionsOverlay = null; }, @@ -1201,44 +1235,98 @@ class MessageInputState extends State { 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: [ - 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: [ + 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 { 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 { 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 { attachmentType = mimeType.type; } + Map 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', ), ); diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 3feda8ce..430e0b85 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -617,7 +617,12 @@ class _MessageListViewState extends State { 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), diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 64dab4ac..e5fe146f 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -223,6 +223,13 @@ class _MessageWidgetState extends State { 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 { messageTheme: widget.messageTheme, ), ) - : Column( - crossAxisAlignment: - CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - ..._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: [ + ..._parseAttachments(context), + if (widget.message.text + .trim() + .isNotEmpty && + !isGiphy) + _buildTextBubble(context), + ], + ), + ), ), ), if (widget.showReactionPickerIndicator) @@ -629,46 +664,40 @@ class _MessageWidgetState extends State { []; } - 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: [ - 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: [ + getFailedMessageWidget( + context, + padding: const EdgeInsets.all(8.0), + ), + attachmentWidget, + ], ), ), ), @@ -798,26 +827,6 @@ class _MessageWidgetState extends State { 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 { ], ), ); - - if (!isOnlyEmoji) { - child = _wrapTextInBubble( - context: context, - child: child, - ); - } return GestureDetector( onTap: () => retryMessage(context), onLongPress: () => onLongPress(context), @@ -873,6 +875,9 @@ class _MessageWidgetState extends State { } 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 { return Color(0xFFE9F2FF); } + if (isOnlyEmoji) { + return Colors.transparent; + } return widget.messageTheme.messageBackgroundColor; } diff --git a/lib/src/stream_svg_icon.dart b/lib/src/stream_svg_icon.dart index e5ef7ec9..42fed46e 100644 --- a/lib/src/stream_svg_icon.dart +++ b/lib/src/stream_svg_icon.dart @@ -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, + ); + } }