From d2a0fd603dd5339fd1049a7be942007f594ee1e5 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Mon, 21 Feb 2022 11:00:54 +0100 Subject: [PATCH 1/2] fix: controllers not disposed --- packages/stream_chat_flutter/lib/src/full_screen_media.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 83ae96b1..de4ee001 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -264,9 +264,11 @@ class _FullScreenMediaState extends State ); @override - void dispose() async { + void dispose() { + _controller.dispose(); + _pageController.dispose(); for (final package in videoPackages.values) { - await package.dispose(); + package.dispose(); } super.dispose(); } From 2cef5396397b828404e5fc8d78b2266b953232d6 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Mon, 21 Feb 2022 12:19:36 +0100 Subject: [PATCH 2/2] perf: improve full screen media animations --- .../lib/src/full_screen_media.dart | 232 +++++++++--------- 1 file changed, 117 insertions(+), 115 deletions(-) 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 de4ee001..aa185597 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -65,24 +65,35 @@ class FullScreenMedia extends StatefulWidget { class _FullScreenMediaState extends State with SingleTickerProviderStateMixin { - bool _optionsShown = true; - - late final AnimationController _controller; + late final AnimationController _animationController; late final PageController _pageController; - late int _currentPage; + late final _curvedAnimation = CurvedAnimation( + parent: _animationController, + curve: Curves.easeOut, + reverseCurve: Curves.easeIn, + ); + + final _opacityTween = Tween(begin: 1, end: 0); + late final _opacityAnimation = _opacityTween.animate( + CurvedAnimation( + parent: _animationController, + curve: const Interval(0, 0.6, curve: Curves.easeOut), + ), + ); + + late final ValueNotifier _currentPage = ValueNotifier(widget.startIndex); final videoPackages = {}; @override void initState() { super.initState(); - _controller = AnimationController( + _animationController = AnimationController( vsync: this, duration: const Duration(milliseconds: 300), ); _pageController = PageController(initialPage: widget.startIndex); - _currentPage = widget.startIndex; for (var i = 0; i < widget.mediaAttachments.length; i++) { final attachment = widget.mediaAttachments[i]; if (attachment.type != 'video') continue; @@ -116,41 +127,38 @@ class _FullScreenMediaState extends State resizeToAvoidBottomInset: false, body: Stack( children: [ - AnimatedBuilder( - animation: _controller, - builder: (context, snapshot) => PageView.builder( - controller: _pageController, - onPageChanged: (val) { - setState(() { - _currentPage = val; - }); + PageView.builder( + controller: _pageController, + onPageChanged: (val) { + _currentPage.value = val; - if (videoPackages.isEmpty) { - return; + if (videoPackages.isEmpty) { + return; + } + + final currentAttachment = widget.mediaAttachments[val]; + + for (final e in videoPackages.values) { + if (e._attachment != currentAttachment) { + e._chewieController?.pause(); } + } - final currentAttachment = widget.mediaAttachments[val]; - - for (final e in videoPackages.values) { - if (e._attachment != currentAttachment) { - e._chewieController?.pause(); - } - } - - if (widget.autoplayVideos && - currentAttachment.type == 'video') { - final controller = videoPackages[currentAttachment.id]!; - controller._chewieController?.play(); - } - }, - 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( + if (widget.autoplayVideos && + currentAttachment.type == 'video') { + final controller = videoPackages[currentAttachment.id]!; + controller._chewieController?.play(); + } + }, + itemBuilder: (context, index) { + final attachment = widget.mediaAttachments[index]; + if (attachment.type == 'image' || attachment.type == 'giphy') { + final imageUrl = attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl; + return AnimatedBuilder( + animation: _curvedAnimation, + builder: (context, child) => PhotoView( loadingBuilder: (context, image) => const Offstage(), imageProvider: (imageUrl == null && attachment.localUri != null && @@ -166,97 +174,91 @@ class _FullScreenMediaState extends State color: ColorTween( begin: ChannelHeaderTheme.of(context).color, end: Colors.black, - ).lerp(_controller.value), + ).lerp(_curvedAnimation.value), ), onTapUp: (a, b, c) { - setState(() { - _optionsShown = !_optionsShown; - }); - if (_controller.isCompleted) { - _controller.reverse(); + if (_animationController.isCompleted) { + _animationController.reverse(); } else { - _controller.forward(); + _animationController.forward(); } }, - ); - } else if (attachment.type == 'video') { - final controller = videoPackages[attachment.id]!; - if (!controller.initialized) { - return const 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!, - ), - ), + ), + ); + } else if (attachment.type == 'video') { + final controller = videoPackages[attachment.id]!; + if (!controller.initialized) { + return const Center( + child: CircularProgressIndicator(), ); } - return Container(); - }, - itemCount: widget.mediaAttachments.length, - ), - ), - AnimatedOpacity( - opacity: _optionsShown ? 1.0 : 0.0, - duration: const Duration(milliseconds: 300), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - GalleryHeader( - userName: widget.userName, - sentAt: context.translations.sentAtText( - date: widget.message.createdAt, - time: widget.message.createdAt, + return InkWell( + onTap: () { + if (_animationController.isCompleted) { + _animationController.reverse(); + } else { + _animationController.forward(); + } + }, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 50, + ), + child: Chewie( + controller: controller.chewieController!, + ), ), - onBackPressed: () { - Navigator.of(context).pop(); - }, - message: widget.message, - currentIndex: _currentPage, - onShowMessage: () { - widget.onShowMessage?.call( - widget.message, - StreamChannel.of(context).channel, - ); - }, - attachmentActionsModalBuilder: - widget.attachmentActionsModalBuilder, - ), - if (!widget.message.isEphemeral) - GalleryFooter( - currentPage: _currentPage, - totalPages: widget.mediaAttachments.length, - mediaAttachments: widget.mediaAttachments, + ); + } + return const SizedBox(); + }, + itemCount: widget.mediaAttachments.length, + ), + FadeTransition( + opacity: _opacityAnimation, + child: ValueListenableBuilder( + valueListenable: _currentPage, + builder: (context, value, child) => Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + GalleryHeader( + userName: widget.userName, + sentAt: context.translations.sentAtText( + date: widget.message.createdAt, + time: widget.message.createdAt, + ), + onBackPressed: () { + Navigator.of(context).pop(); + }, message: widget.message, - mediaSelectedCallBack: (val) { - setState(() { - _currentPage = val; + currentIndex: value, + onShowMessage: () { + widget.onShowMessage?.call( + widget.message, + StreamChannel.of(context).channel, + ); + }, + attachmentActionsModalBuilder: + widget.attachmentActionsModalBuilder, + ), + if (!widget.message.isEphemeral) + GalleryFooter( + currentPage: value, + totalPages: widget.mediaAttachments.length, + mediaAttachments: widget.mediaAttachments, + message: widget.message, + mediaSelectedCallBack: (val) { + _currentPage.value = val; _pageController.animateToPage( val, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut, ); Navigator.pop(context); - }); - }, - ), - ], + }, + ), + ], + ), ), ), ], @@ -265,7 +267,7 @@ class _FullScreenMediaState extends State @override void dispose() { - _controller.dispose(); + _animationController.dispose(); _pageController.dispose(); for (final package in videoPackages.values) { package.dispose();