fix: attachment builder (#326)

* feat: Changed attachment builder structure

* fix: fixed image attachments

* feat: Added onTaps to remaining attachments

* fix: merged fixes

* add trailing comma

Co-authored-by: Salvatore Giordano <[email protected]>
This commit is contained in:
Deven Joshi
2021-03-11 11:38:00 +01:00
committed by GitHub
co-authored by Salvatore Giordano
parent 1ed11a753f
commit 4b6f1f54b6
4 changed files with 220 additions and 146 deletions
@@ -14,6 +14,7 @@ import 'attachment_widget.dart';
class FileAttachment extends AttachmentWidget { class FileAttachment extends AttachmentWidget {
final Widget title; final Widget title;
final Widget trailing; final Widget trailing;
final VoidCallback onAttachmentTap;
const FileAttachment({ const FileAttachment({
Key key, Key key,
@@ -22,6 +23,7 @@ class FileAttachment extends AttachmentWidget {
Size size, Size size,
this.title, this.title,
this.trailing, this.trailing,
this.onAttachmentTap,
}) : super(key: key, message: message, attachment: attachment, size: size); }) : super(key: key, message: message, attachment: attachment, size: size);
bool get isVideoAttachment => attachment.title?.mimeType?.type == 'video'; bool get isVideoAttachment => attachment.title?.mimeType?.type == 'video';
@@ -31,45 +33,48 @@ class FileAttachment extends AttachmentWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Material( return Material(
child: Container( child: GestureDetector(
width: size?.width ?? 100, onTap: onAttachmentTap,
height: 56.0, child: Container(
decoration: BoxDecoration( width: size?.width ?? 100,
color: StreamChatTheme.of(context).colorTheme.white, height: 56.0,
borderRadius: BorderRadius.circular(12), decoration: BoxDecoration(
border: Border.all( color: StreamChatTheme.of(context).colorTheme.white,
color: StreamChatTheme.of(context).colorTheme.greyWhisper, borderRadius: BorderRadius.circular(12),
border: Border.all(
color: StreamChatTheme.of(context).colorTheme.greyWhisper,
),
), ),
), child: Row(
child: Row( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ Container(
Container( height: 40.0,
height: 40.0, width: 33.33,
width: 33.33, margin: EdgeInsets.all(8.0),
margin: EdgeInsets.all(8.0), child: _getFileTypeImage(context),
child: _getFileTypeImage(context),
),
SizedBox(width: 8.0),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
attachment?.title ?? 'File',
style: StreamChatTheme.of(context).textTheme.bodyBold,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 3.0),
_buildSubtitle(context),
],
), ),
), SizedBox(width: 8.0),
SizedBox(width: 8.0), Expanded(
_buildTrailing(context), child: Column(
], mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
attachment?.title ?? 'File',
style: StreamChatTheme.of(context).textTheme.bodyBold,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 3.0),
_buildSubtitle(context),
],
),
),
SizedBox(width: 8.0),
_buildTrailing(context),
],
),
), ),
), ),
); );
@@ -15,6 +15,7 @@ class GiphyAttachment extends AttachmentWidget {
final MessageTheme messageTheme; final MessageTheme messageTheme;
final ShowMessageCallback onShowMessage; final ShowMessageCallback onShowMessage;
final ValueChanged<ReturnActionType> onReturnAction; final ValueChanged<ReturnActionType> onReturnAction;
final VoidCallback onAttachmentTap;
const GiphyAttachment({ const GiphyAttachment({
Key key, Key key,
@@ -24,6 +25,7 @@ class GiphyAttachment extends AttachmentWidget {
this.messageTheme, this.messageTheme,
this.onShowMessage, this.onShowMessage,
this.onReturnAction, this.onReturnAction,
this.onAttachmentTap,
}) : super(key: key, message: message, attachment: attachment, size: size); }) : super(key: key, message: message, attachment: attachment, size: size);
@override @override
@@ -65,7 +67,7 @@ class GiphyAttachment extends AttachmentWidget {
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: GestureDetector( child: GestureDetector(
onTap: () => _onImageTap(context), onTap: () => onAttachmentTap ?? _onImageTap(context),
child: ClipRRect( child: ClipRRect(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(8), topLeft: Radius.circular(8),
@@ -20,7 +20,11 @@ import 'extension.dart';
import 'image_group.dart'; import 'image_group.dart';
import 'message_text.dart'; import 'message_text.dart';
typedef AttachmentBuilder = Widget Function(BuildContext, Message, Attachment); typedef AttachmentBuilder = Widget Function(
BuildContext,
Message,
List<Attachment>,
);
typedef OnQuotedMessageTap = void Function(String); typedef OnQuotedMessageTap = void Function(String);
/// The display behaviour of a widget /// The display behaviour of a widget
@@ -205,63 +209,142 @@ class MessageWidget extends StatefulWidget {
this.customActions = const [], this.customActions = const [],
this.onAttachmentTap, this.onAttachmentTap,
}) : attachmentBuilders = { }) : attachmentBuilders = {
'image': (context, message, attachment) { 'image': (context, message, attachments) {
return ImageAttachment( var border = RoundedRectangleBorder(
attachment: attachment, side: BorderSide.none,
message: message, borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero,
messageTheme: messageTheme, );
size: Size(
MediaQuery.of(context).size.width * 0.8, if (attachments.length > 1) {
MediaQuery.of(context).size.height * 0.3, return Padding(
padding: attachmentPadding,
child: wrapAttachmentWidget(
context,
Material(
color: messageTheme.messageBackgroundColor,
child: ImageGroup(
size: Size(
MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.height * 0.3,
),
images: attachments,
message: message,
messageTheme: messageTheme,
onShowMessage: onShowMessage,
),
),
border,
reverse,
),
);
}
return wrapAttachmentWidget(
context,
ImageAttachment(
attachment: attachments[0],
message: message,
messageTheme: messageTheme,
size: Size(
MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.height * 0.3,
),
onShowMessage: onShowMessage,
onReturnAction: onReturnAction,
onAttachmentTap: onAttachmentTap != null
? () {
onAttachmentTap?.call(message, attachments[0]);
}
: null,
), ),
onShowMessage: onShowMessage, border,
onReturnAction: onReturnAction, reverse,
onAttachmentTap: onAttachmentTap != null
? () {
onAttachmentTap?.call(message, attachment);
}
: null,
); );
}, },
'video': (context, message, attachment) { 'video': (context, message, attachments) {
return VideoAttachment( var border = RoundedRectangleBorder(
attachment: attachment, side: BorderSide.none,
messageTheme: messageTheme, borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero,
size: Size( );
MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.height * 0.3, return wrapAttachmentWidget(
context,
Column(
children: attachments.map((attachment) {
return VideoAttachment(
attachment: attachment,
messageTheme: messageTheme,
size: Size(
MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.height * 0.3,
),
message: message,
onShowMessage: onShowMessage,
onReturnAction: onReturnAction,
onAttachmentTap: onAttachmentTap != null
? () {
onAttachmentTap?.call(message, attachment);
}
: null,
);
}).toList(),
), ),
message: message, border,
onShowMessage: onShowMessage, reverse,
onReturnAction: onReturnAction,
onAttachmentTap: onAttachmentTap != null
? () {
onAttachmentTap?.call(message, attachment);
}
: null,
); );
}, },
'giphy': (context, message, attachment) { 'giphy': (context, message, attachments) {
return GiphyAttachment( var border = RoundedRectangleBorder(
attachment: attachment, side: BorderSide.none,
messageTheme: messageTheme, borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero,
message: message, );
size: Size(
MediaQuery.of(context).size.width * 0.8, return wrapAttachmentWidget(
MediaQuery.of(context).size.height * 0.3, context,
Column(
children: attachments.map((attachment) {
return GiphyAttachment(
attachment: attachment,
messageTheme: messageTheme,
message: message,
size: Size(
MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.height * 0.3,
),
onShowMessage: onShowMessage,
onReturnAction: onReturnAction,
);
}).toList(),
), ),
onShowMessage: onShowMessage, border,
onReturnAction: onReturnAction, reverse,
); );
}, },
'file': (context, message, attachment) { 'file': (context, message, attachments) {
return FileAttachment( var border = RoundedRectangleBorder(
message: message, side: attachmentBorderSide ??
attachment: attachment, BorderSide(
size: Size( color: StreamChatTheme.of(context).colorTheme.greyWhisper,
MediaQuery.of(context).size.width * 0.8, ),
MediaQuery.of(context).size.height * 0.3, borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero,
);
return wrapAttachmentWidget(
context,
Column(
children: attachments.map((attachment) {
return FileAttachment(
message: message,
attachment: attachment,
size: Size(
MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.height * 0.3,
),
);
}).toList(),
), ),
border,
reverse,
); );
}, },
}..addAll(customAttachmentBuilders ?? {}), }..addAll(customAttachmentBuilders ?? {}),
@@ -830,55 +913,37 @@ class _MessageWidgetState extends State<MessageWidget>
} }
Widget _parseAttachments() { Widget _parseAttachments() {
final images = widget.message.attachments Map<String, List<Attachment>> attachmentGroups = {};
?.where((element) =>
element.type == 'image' && element.ogScrapeUrl == null)
?.toList() ??
[];
if (images.length > 1) { widget.message.attachments
return Padding( .where((element) => element.ogScrapeUrl == null)
padding: widget.attachmentPadding, .forEach((e) {
child: wrapAttachmentWidget( if (attachmentGroups[e.type] == null) {
context, attachmentGroups[e.type] = [];
Material( }
color: widget.messageTheme.messageBackgroundColor,
child: ImageGroup( attachmentGroups[e.type].add(e);
size: Size( });
MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.height * 0.3, List<Widget> attachmentList = [];
),
images: images, attachmentGroups.forEach((type, attachments) {
message: widget.message, final attachmentBuilder = widget.attachmentBuilders[type];
messageTheme: widget.messageTheme,
onShowMessage: widget.onShowMessage, if (attachmentBuilder == null) return SizedBox();
), final attachmentWidget = attachmentBuilder(
), context,
), widget.message,
attachments,
); );
} attachmentList.add(attachmentWidget);
});
return Padding( return Padding(
padding: widget.attachmentPadding, padding: widget.attachmentPadding,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: widget.message.attachments children: attachmentList?.insertBetween(SizedBox(
?.where((element) => element.ogScrapeUrl == null)
?.map((attachment) {
final attachmentBuilder =
widget.attachmentBuilders[attachment.type];
if (attachmentBuilder == null) return SizedBox();
final attachmentWidget = attachmentBuilder(
context,
widget.message,
attachment,
);
return wrapAttachmentWidget(
context,
attachmentWidget,
);
})?.insertBetween(SizedBox(
height: widget.attachmentPadding.vertical / 2, height: widget.attachmentPadding.vertical / 2,
)) ?? )) ??
[], [],
@@ -886,24 +951,6 @@ class _MessageWidgetState extends State<MessageWidget>
); );
} }
Widget wrapAttachmentWidget(
BuildContext context,
Widget attachmentWidget,
) {
final attachmentShape =
widget.attachmentShape ?? _getDefaultAttachmentShape(context);
return Material(
clipBehavior: Clip.antiAlias,
shape: attachmentShape,
type: MaterialType.transparency,
child: Transform(
transform: Matrix4.rotationY(widget.reverse ? pi : 0),
alignment: Alignment.center,
child: attachmentWidget,
),
);
}
void onLongPress(BuildContext context) { void onLongPress(BuildContext context) {
if (widget.message.isEphemeral || if (widget.message.isEphemeral ||
widget.message.status == MessageSendingStatus.sending) { widget.message.status == MessageSendingStatus.sending) {
@@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.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:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@@ -348,3 +350,21 @@ StreamSvgIcon getFileTypeImage(String type) {
break; break;
} }
} }
Widget wrapAttachmentWidget(
BuildContext context,
Widget attachmentWidget,
ShapeBorder attachmentShape,
bool reverse,
) {
return Material(
clipBehavior: Clip.antiAlias,
shape: attachmentShape,
type: MaterialType.transparency,
child: Transform(
transform: Matrix4.rotationY(reverse ? pi : 0),
alignment: Alignment.center,
child: attachmentWidget,
),
);
}