diff --git a/example/lib/main.dart b/example/lib/main.dart index 0cb0f098..7de283d0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -67,7 +67,6 @@ void main() async { final client = Client( 'b67pax5b2wdq', logLevel: Level.INFO, - persistenceEnabled: false, showLocalNotification: Platform.isAndroid ? showLocalNotification : null, ); diff --git a/lib/src/attachment_actions.dart b/lib/src/attachment_actions.dart new file mode 100644 index 00000000..15d4665f --- /dev/null +++ b/lib/src/attachment_actions.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +class AttachmentActions extends StatelessWidget { + final Attachment attachment; + final Message message; + + const AttachmentActions({ + Key key, + this.attachment, + this.message, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final streamChannel = StreamChannel.of(context); + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: attachment.actions?.map((action) { + if (action.style == 'primary') { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: FlatButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Text('${action.text}'), + color: action.style == 'primary' + ? StreamChatTheme.of(context).accentColor + : null, + textColor: Colors.white, + onPressed: () { + streamChannel.channel.sendAction(message, { + action.name: action.value, + }); + }, + ), + ); + } + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: OutlineButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + child: Text('${action.text}'), + color: StreamChatTheme.of(context).accentColor, + onPressed: () { + streamChannel.channel.sendAction(message, { + action.name: action.value, + }); + }, + ), + ); + })?.toList(), + ); + } +} diff --git a/lib/src/attachment_title.dart b/lib/src/attachment_title.dart new file mode 100644 index 00000000..d23c4bf8 --- /dev/null +++ b/lib/src/attachment_title.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; + +import 'stream_chat_theme.dart'; +import 'utils.dart'; + +class AttachmentTitle extends StatelessWidget { + const AttachmentTitle({ + Key key, + @required this.attachment, + @required this.messageTheme, + }) : super(key: key); + + final MessageTheme messageTheme; + final Attachment attachment; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + if (attachment.titleLink != null) { + launchURL(context, attachment.titleLink); + } + }, + child: Container( + constraints: BoxConstraints.loose( + Size( + MediaQuery.of(context).size.width * 0.7, + 500, + ), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + attachment.title, + overflow: TextOverflow.ellipsis, + style: messageTheme.messageText.copyWith( + color: StreamChatTheme.of(context).accentColor, + fontWeight: FontWeight.bold, + ), + ), + if (attachment.titleLink != null || + attachment.ogScrapeUrl != null) + Text( + Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl) + .authority + .split('.') + .reversed + .take(2) + .toList() + .reversed + .join('.'), + overflow: TextOverflow.ellipsis, + style: messageTheme.createdAt, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/file_attachment.dart b/lib/src/file_attachment.dart new file mode 100644 index 00000000..d41cd13c --- /dev/null +++ b/lib/src/file_attachment.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/utils.dart'; + +class FileAttachment extends StatelessWidget { + final Attachment attachment; + + const FileAttachment({ + Key key, + @required this.attachment, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Material( + child: InkWell( + onTap: () { + launchURL(context, attachment.assetUrl); + }, + child: Container( + width: 100, + height: 100, + child: Center( + child: Icon(Icons.attach_file), + ), + ), + ), + ); + } +} diff --git a/lib/src/giphy_attachment.dart b/lib/src/giphy_attachment.dart new file mode 100644 index 00000000..4ad9e7ab --- /dev/null +++ b/lib/src/giphy_attachment.dart @@ -0,0 +1,100 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/attachment_actions.dart'; + +import '../stream_chat_flutter.dart'; +import 'attachment_error.dart'; +import 'attachment_title.dart'; +import 'full_screen_image.dart'; +import 'utils.dart'; + +class GiphyAttachment extends StatelessWidget { + final Attachment attachment; + final MessageTheme messageTheme; + final Message message; + + const GiphyAttachment({ + Key key, + this.attachment, + this.messageTheme, + this.message, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + if (attachment.thumbUrl == null && + attachment.imageUrl == null && + attachment.assetUrl == null) { + return AttachmentError( + attachment: attachment, + ); + } + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Stack( + children: [ + Hero( + tag: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + child: CachedNetworkImage( + imageBuilder: (context, provider) { + return GestureDetector( + child: Image(image: provider), + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (_) { + return FullScreenImage( + url: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + ); + })); + }, + ); + }, + placeholder: (_, __) { + return Container( + width: 200, + height: 140, + ); + }, + imageUrl: attachment.thumbUrl ?? + attachment.imageUrl ?? + attachment.assetUrl, + errorWidget: (context, url, error) => AttachmentError( + attachment: attachment, + ), + fit: BoxFit.cover, + ), + ), + if (attachment.titleLink != null || attachment.ogScrapeUrl != null) + Positioned.fill( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => launchURL( + context, + attachment.titleLink ?? attachment.ogScrapeUrl, + ), + ), + ), + ), + ], + ), + if (attachment.title != null) + AttachmentTitle( + messageTheme: messageTheme, + attachment: attachment, + ), + if (attachment.actions != null) + AttachmentActions( + attachment: attachment, + message: message, + ), + ], + ); + } +} diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart new file mode 100644 index 00000000..6ff653ff --- /dev/null +++ b/lib/src/image_attachment.dart @@ -0,0 +1,92 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/material.dart'; + +import '../stream_chat_flutter.dart'; +import 'attachment_error.dart'; +import 'attachment_title.dart'; +import 'full_screen_image.dart'; +import 'utils.dart'; + +class ImageAttachment extends StatelessWidget { + final Attachment attachment; + final MessageTheme messageTheme; + + const ImageAttachment({ + Key key, + this.attachment, + this.messageTheme, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + if (attachment.thumbUrl == null && + attachment.imageUrl == null && + attachment.assetUrl == null) { + return AttachmentError( + attachment: attachment, + ); + } + + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Stack( + children: [ + Hero( + tag: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + child: CachedNetworkImage( + imageBuilder: (context, provider) { + return GestureDetector( + child: Image(image: provider), + onTap: () { + Navigator.push(context, MaterialPageRoute(builder: (_) { + return FullScreenImage( + url: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + ); + })); + }, + ); + }, + placeholder: (_, __) { + return Container( + width: 200, + height: 140, + ); + }, + imageUrl: attachment.thumbUrl ?? + attachment.imageUrl ?? + attachment.assetUrl, + errorWidget: (context, url, error) => AttachmentError( + attachment: attachment, + ), + fit: BoxFit.cover, + ), + ), + if (attachment.titleLink != null || attachment.ogScrapeUrl != null) + Positioned.fill( + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => launchURL( + context, + attachment.titleLink ?? attachment.ogScrapeUrl, + ), + ), + ), + ), + ], + ), + if (attachment.title != null) + AttachmentTitle( + messageTheme: messageTheme, + attachment: attachment, + ), + ], + ); + } +} diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 8ff582f6..d5603370 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -1,12 +1,11 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter/src/attachment_error.dart'; -import 'package:stream_chat_flutter/src/full_screen_image.dart'; +import 'package:stream_chat_flutter/src/giphy_attachment.dart'; +import 'package:stream_chat_flutter/src/image_attachment.dart'; import 'package:stream_chat_flutter/src/message_input.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; @@ -20,6 +19,7 @@ import 'package:stream_chat_flutter/src/video_attachment.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'deleted_message.dart'; +import 'file_attachment.dart'; import 'stream_chat.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_widget.png) @@ -216,25 +216,23 @@ class _MessageWidgetState extends State if (attachment.type == 'video') { attachmentWidget = VideoAttachment( attachment: attachment, + messageTheme: _messageTheme, enableFullScreen: widget.showVideoFullScreen, ); - } else if (attachment.type == 'image' || - attachment.type == 'giphy') { - attachmentWidget = _buildImage(attachment); + } else if (attachment.type == 'giphy') { + attachmentWidget = GiphyAttachment( + attachment: attachment, + message: widget.message, + messageTheme: _messageTheme, + ); + } else if (attachment.type == 'image') { + attachmentWidget = ImageAttachment( + attachment: attachment, + messageTheme: _messageTheme, + ); } else if (attachment.type == 'file') { - attachmentWidget = Material( - child: InkWell( - onTap: () { - launchURL(context, attachment.assetUrl); - }, - child: Container( - width: 100, - height: 100, - child: Center( - child: Icon(Icons.attach_file), - ), - ), - ), + attachmentWidget = FileAttachment( + attachment: attachment, ); } @@ -375,29 +373,12 @@ class _MessageWidgetState extends State constraints: BoxConstraints.loose( Size.fromWidth(MediaQuery.of(context).size.width * 0.7), ), - child: Stack( - children: [ - Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - attachmentWidget, - if (attachment.title != null) - _buildAttachmentTitle(attachment), - ], - ), - if (attachment.type == 'image' && - attachment.titleLink != null) - _buildPreviewInkwell(attachment), - ], - ), + child: attachmentWidget, margin: EdgeInsets.only( top: nOfAttachmentWidgets > 1 ? 5 : 0, ), ), ), - if (attachment.actions != null) - _buildAttachmentActions(attachment, context), ], ), ); @@ -508,109 +489,6 @@ class _MessageWidgetState extends State return text; } - Row _buildAttachmentActions(Attachment attachment, BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.end, - children: attachment.actions?.map((action) { - if (action.style == 'primary') { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: FlatButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - child: Text('${action.text}'), - color: action.style == 'primary' - ? StreamChatTheme.of(context).accentColor - : null, - textColor: Colors.white, - onPressed: () { - _streamChannel.channel.sendAction(widget.message, { - action.name: action.value, - }); - }, - ), - ); - } - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: OutlineButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - child: Text('${action.text}'), - color: StreamChatTheme.of(context).accentColor, - onPressed: () { - _streamChannel.channel.sendAction(widget.message, { - action.name: action.value, - }); - }, - ), - ); - })?.toList(), - ); - } - - Positioned _buildPreviewInkwell(Attachment attachment) { - return Positioned.fill( - child: Material( - color: Colors.transparent, - child: InkWell( - onTap: () => launchURL(context, attachment.titleLink), - ), - ), - ); - } - - GestureDetector _buildAttachmentTitle(Attachment attachment) { - return GestureDetector( - onTap: () { - if (attachment.titleLink != null) { - launchURL(context, attachment.titleLink); - } - }, - child: Container( - constraints: BoxConstraints.loose( - Size( - MediaQuery.of(context).size.width * 0.7, - 500, - ), - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - attachment.title, - overflow: TextOverflow.ellipsis, - style: _messageTheme.messageText.copyWith( - color: StreamChatTheme.of(context).accentColor, - fontWeight: FontWeight.bold, - ), - ), - if (attachment.titleLink != null || - attachment.ogScrapeUrl != null) - Text( - Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl) - .authority - .split('.') - .reversed - .take(2) - .toList() - .reversed - .join('.'), - overflow: TextOverflow.ellipsis, - style: _messageTheme.createdAt, - ), - ], - ), - ), - ), - ); - } - Widget _buildReactionPaint() { return widget.message.reactionCounts?.isNotEmpty == true ? Positioned( @@ -888,50 +766,6 @@ class _MessageWidgetState extends State 'wow': '😲', }; - Widget _buildImage( - Attachment attachment, - ) { - if (attachment.thumbUrl == null && - attachment.imageUrl == null && - attachment.assetUrl == null) { - return AttachmentError( - attachment: attachment, - ); - } - - return Hero( - tag: attachment.imageUrl ?? attachment.assetUrl ?? attachment.thumbUrl, - child: CachedNetworkImage( - imageBuilder: (context, provider) { - return GestureDetector( - child: Image(image: provider), - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (_) { - return FullScreenImage( - url: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl, - ); - })); - }, - ); - }, - placeholder: (_, __) { - return Container( - width: 200, - height: 140, - ); - }, - imageUrl: - attachment.thumbUrl ?? attachment.imageUrl ?? attachment.assetUrl, - errorWidget: (context, url, error) => AttachmentError( - attachment: attachment, - ), - fit: BoxFit.cover, - ), - ); - } - @override void dispose() { super.dispose(); diff --git a/lib/src/video_attachment.dart b/lib/src/video_attachment.dart index 60967184..0d3339bb 100644 --- a/lib/src/video_attachment.dart +++ b/lib/src/video_attachment.dart @@ -6,14 +6,17 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:video_player/video_player.dart'; import 'attachment_error.dart'; +import 'attachment_title.dart'; class VideoAttachment extends StatefulWidget { final Attachment attachment; final bool enableFullScreen; + final MessageTheme messageTheme; VideoAttachment({ Key key, @required this.attachment, + @required this.messageTheme, this.enableFullScreen = true, }) : super(key: key); @@ -72,8 +75,19 @@ class _VideoAttachmentState extends State { ); }); - return Chewie( - controller: _chewieController, + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Chewie( + controller: _chewieController, + ), + if (widget.attachment.title != null) + AttachmentTitle( + messageTheme: widget.messageTheme, + attachment: widget.attachment, + ), + ], ); } @@ -82,12 +96,10 @@ class _VideoAttachmentState extends State { super.initState(); _videoPlayerController = VideoPlayerController.network( widget.attachment.localUri ?? widget.attachment.assetUrl); - _videoPlayerController.initialize().then((_) { - if (_videoPlayerController.value.initialized) { - setState(() { - initialized = true; - }); - } + _videoPlayerController.initialize().whenComplete(() { + setState(() { + initialized = true; + }); }); }