diff --git a/packages/stream_chat_flutter/lib/src/full_screen_media.dart b/packages/stream_chat_flutter/lib/src/full_screen_media.dart index 4b56adc5..edebfa09 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -8,24 +8,24 @@ import 'package:jiffy/jiffy.dart'; import 'package:photo_view/photo_view.dart'; import 'package:stream_chat_flutter/src/image_footer.dart'; import 'package:stream_chat_flutter/src/image_header.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:video_player/video_player.dart'; -import '../stream_chat_flutter.dart'; +/// Return action for coming back from pages +enum ReturnActionType { -enum ReturnActionType { none, reply } + /// No return action + none, + /// Go to reply message action + reply, +} + +/// Callback when show message is tapped typedef ShowMessageCallback = void Function(Message message, Channel channel); /// A full screen image widget class FullScreenMedia extends StatefulWidget { - /// The url of the image - final List mediaAttachments; - final Message message; - - final int startIndex; - final String userName; - final ShowMessageCallback? onShowMessage; - /// Instantiate a new FullScreenImage const FullScreenMedia({ Key? key, @@ -37,6 +37,21 @@ class FullScreenMedia extends StatefulWidget { }) : userName = userName ?? '', super(key: key); + /// The url of the image + final List mediaAttachments; + + /// Message where attachments are attached + final Message message; + + /// First index of media shown + final int startIndex; + + /// Username of sender + final String userName; + + /// Callback for when show message is tapped + final ShowMessageCallback? onShowMessage; + @override _FullScreenMediaState createState() => _FullScreenMediaState(); } @@ -57,7 +72,7 @@ class _FullScreenMediaState extends State super.initState(); _controller = AnimationController( vsync: this, - duration: Duration(milliseconds: 300), + duration: const Duration(milliseconds: 300), ); _pageController = PageController(initialPage: widget.startIndex); _currentPage = widget.startIndex; @@ -77,139 +92,136 @@ class _FullScreenMediaState extends State } @override - Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - body: Stack( - children: [ - AnimatedBuilder( - animation: _controller, - builder: (context, snapshot) { - return PageView.builder( - controller: _pageController, - onPageChanged: (val) { - setState(() { - _currentPage = val; - }); - }, - itemBuilder: (context, index) { - final attachment = widget.mediaAttachments[index]; - if (attachment.type == 'image' || - attachment.type == 'giphy') { - final imageUrl = attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl; - return PhotoView( - imageProvider: (imageUrl == null && - attachment.localUri != null && - attachment.file?.bytes != null) - ? Image.memory(attachment.file!.bytes!).image - : CachedNetworkImageProvider(imageUrl!), - maxScale: PhotoViewComputedScale.covered, - minScale: PhotoViewComputedScale.contained, - heroAttributes: PhotoViewHeroAttributes( - tag: widget.mediaAttachments, - ), - backgroundDecoration: BoxDecoration( - color: ColorTween( - begin: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .color, - end: Colors.black, - ).lerp(_controller.value), - ), - onTapUp: (a, b, c) { - setState(() { - _optionsShown = !_optionsShown; - }); - if (_controller.isCompleted) { - _controller.reverse(); - } else { - _controller.forward(); + Widget build(BuildContext context) => Scaffold( + resizeToAvoidBottomInset: false, + body: Stack( + children: [ + AnimatedBuilder( + animation: _controller, + builder: (context, snapshot) => PageView.builder( + controller: _pageController, + onPageChanged: (val) { + setState(() { + _currentPage = val; + }); + }, + itemBuilder: (context, index) { + final attachment = widget.mediaAttachments[index]; + if (attachment.type == 'image' || + attachment.type == 'giphy') { + final imageUrl = attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl; + return PhotoView( + imageProvider: (imageUrl == null && + attachment.localUri != null && + attachment.file?.bytes != null) + ? Image.memory(attachment.file!.bytes!).image + : CachedNetworkImageProvider(imageUrl!), + maxScale: PhotoViewComputedScale.covered, + minScale: PhotoViewComputedScale.contained, + heroAttributes: PhotoViewHeroAttributes( + tag: widget.mediaAttachments, + ), + backgroundDecoration: BoxDecoration( + color: ColorTween( + begin: StreamChatTheme.of(context) + .channelTheme + .channelHeaderTheme + .color, + end: Colors.black, + ).lerp(_controller.value), + ), + onTapUp: (a, b, c) { + setState(() { + _optionsShown = !_optionsShown; + }); + if (_controller.isCompleted) { + _controller.reverse(); + } else { + _controller.forward(); + } + }, + ); + } else if (attachment.type == 'video') { + final controller = videoPackages[attachment.id]!; + if (!controller.initialized) { + return const Center( + child: CircularProgressIndicator(), + ); } - }, - ); - } else if (attachment.type == 'video') { - final controller = videoPackages[attachment.id]!; - if (!controller.initialized) { - return Center( - child: CircularProgressIndicator(), - ); - } - return InkWell( - onTap: () { - setState(() { - _optionsShown = !_optionsShown; - }); - if (_controller.isCompleted) { - _controller.reverse(); - } else { - _controller.forward(); - } - }, - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 50, - ), - child: Chewie( - controller: controller.chewieController!, - ), - ), - ); - } - return Container(); - }, - itemCount: widget.mediaAttachments.length, - ); - }), - AnimatedOpacity( - opacity: _optionsShown ? 1.0 : 0.0, - duration: Duration(milliseconds: 300), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ImageHeader( - userName: widget.userName, - sentAt: - 'Sent ${getDay(widget.message.createdAt.toLocal())} at ${Jiffy(widget.message.createdAt.toLocal()).format('HH:mm')}', - onBackPressed: () { - Navigator.of(context).pop(); - }, - message: widget.message, - currentIndex: _currentPage, - onShowMessage: () { - widget.onShowMessage?.call( - widget.message, - StreamChannel.of(context).channel, - ); - }, - ), - if (widget.message.type != 'ephemeral') - ImageFooter( - currentPage: _currentPage, - totalPages: widget.mediaAttachments.length, - mediaAttachments: widget.mediaAttachments, + return InkWell( + onTap: () { + setState(() { + _optionsShown = !_optionsShown; + }); + if (_controller.isCompleted) { + _controller.reverse(); + } else { + _controller.forward(); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 50, + ), + child: Chewie( + controller: controller.chewieController!, + ), + ), + ); + } + return Container(); + }, + itemCount: widget.mediaAttachments.length, + )), + AnimatedOpacity( + opacity: _optionsShown ? 1.0 : 0.0, + duration: const Duration(milliseconds: 300), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ImageHeader( + userName: widget.userName, + sentAt: + // ignore: lines_longer_than_80_chars + 'Sent ${getDay(widget.message.createdAt.toLocal())} at ${Jiffy(widget.message.createdAt.toLocal()).format('HH:mm')}', + onBackPressed: () { + Navigator.of(context).pop(); + }, message: widget.message, - mediaSelectedCallBack: (val) { - setState(() { - _currentPage = val; - _pageController.animateToPage( - val, - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - ); - Navigator.pop(context); - }); + currentIndex: _currentPage, + onShowMessage: () { + widget.onShowMessage?.call( + widget.message, + StreamChannel.of(context).channel, + ); }, ), - ], + if (widget.message.type != 'ephemeral') + ImageFooter( + currentPage: _currentPage, + totalPages: widget.mediaAttachments.length, + mediaAttachments: widget.mediaAttachments, + message: widget.message, + mediaSelectedCallBack: (val) { + setState(() { + _currentPage = val; + _pageController.animateToPage( + val, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + Navigator.pop(context); + }); + }, + ), + ], + ), ), - ), - ], - ), - ); - } + ], + ), + ); String getDay(DateTime dateTime) { final now = DateTime.now(); @@ -236,47 +248,52 @@ class _FullScreenMediaState extends State } } +/// Class for packaging up things required for videos class VideoPackage { - final bool _showControls; - final bool _autoInitialize; - final VideoPlayerController _videoPlayerController; - ChewieController? _chewieController; - - VideoPlayerController get videoPlayer => _videoPlayerController; - - ChewieController? get chewieController => _chewieController; - - bool get initialized => _videoPlayerController.value.isInitialized; - + /// Constructor for creating [VideoPackage] VideoPackage( - Attachment attachment, { - bool showControls = false, - bool autoInitialize = true, - }) : _showControls = showControls, + Attachment attachment, { + bool showControls = false, + bool autoInitialize = true, + }) : _showControls = showControls, _autoInitialize = autoInitialize, _videoPlayerController = attachment.localUri != null ? VideoPlayerController.file(File.fromUri(attachment.localUri!)) : VideoPlayerController.network(attachment.assetUrl!); - Future initialize() { - return _videoPlayerController.initialize().then((_) { - _chewieController = ChewieController( - videoPlayerController: _videoPlayerController, - autoInitialize: _autoInitialize, - showControls: _showControls, - aspectRatio: _videoPlayerController.value.aspectRatio, - ); - }); - } + final bool _showControls; + final bool _autoInitialize; + final VideoPlayerController _videoPlayerController; + ChewieController? _chewieController; - void addListener(VoidCallback listener) { - return _videoPlayerController.addListener(listener); - } + /// Get video player for video + VideoPlayerController get videoPlayer => _videoPlayerController; - void removeListener(VoidCallback listener) { - return _videoPlayerController.removeListener(listener); - } + /// Get [ChewieController] for video + ChewieController? get chewieController => _chewieController; + /// Check if controller is initialised + bool get initialized => _videoPlayerController.value.isInitialized; + + /// Initialize all things required for [VideoPackage] + Future initialize() => _videoPlayerController.initialize().then((_) { + _chewieController = ChewieController( + videoPlayerController: _videoPlayerController, + autoInitialize: _autoInitialize, + showControls: _showControls, + aspectRatio: _videoPlayerController.value.aspectRatio, + ); + }); + + /// Add a listener to video player controller + void addListener(VoidCallback listener) => + _videoPlayerController.addListener(listener); + + /// Remove a listener to video player controller + void removeListener(VoidCallback listener) => + _videoPlayerController.removeListener(listener); + + /// Dispose controllers Future dispose() { _chewieController?.dispose(); return _videoPlayerController.dispose(); diff --git a/packages/stream_chat_flutter/lib/src/group_image.dart b/packages/stream_chat_flutter/lib/src/group_image.dart index 3e56eeae..e5c6ecd2 100644 --- a/packages/stream_chat_flutter/lib/src/group_image.dart +++ b/packages/stream_chat_flutter/lib/src/group_image.dart @@ -1,9 +1,10 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import '../stream_chat_flutter.dart'; - +/// Widget for constructing a group of images class GroupImage extends StatelessWidget { + /// Constructor for creating a [GroupImage] const GroupImage({ Key? key, required this.images, @@ -15,17 +16,30 @@ class GroupImage extends StatelessWidget { this.selectionThickness = 4, }) : super(key: key); + /// List of images to display final List images; + + /// Constraints on the widget final BoxConstraints? constraints; + + /// Callback when widget is tapped final VoidCallback? onTap; + + /// Highlights if selected final bool selected; + + /// [BorderRadius] to pass to the widget final BorderRadius? borderRadius; + + /// Color of selection if selected final Color? selectionColor; + + /// Thickness with which color of selection is shown final double selectionThickness; @override Widget build(BuildContext context) { - var avatar; + Widget? avatar; final streamChatTheme = StreamChatTheme.of(context); avatar = GestureDetector(