From 3ad6a25a828566b758c27d26eb9dff133cb722e6 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 12 Nov 2020 17:04:01 +0530 Subject: [PATCH 01/29] feat: Added new image detail screen --- lib/src/full_screen_image.dart | 110 +++++++++++++++++++++++---- lib/src/giphy_attachment.dart | 12 ++- lib/src/image_attachment.dart | 6 +- lib/src/image_footer.dart | 133 +++++++++++++++++++++++++++++++++ lib/src/image_group.dart | 32 ++------ lib/src/image_header.dart | 101 +++++++++++++++++++++++++ 6 files changed, 345 insertions(+), 49 deletions(-) create mode 100644 lib/src/image_footer.dart create mode 100644 lib/src/image_header.dart diff --git a/lib/src/full_screen_image.dart b/lib/src/full_screen_image.dart index 76953b1a..e0dd79a3 100644 --- a/lib/src/full_screen_image.dart +++ b/lib/src/full_screen_image.dart @@ -1,34 +1,112 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +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 '../stream_chat_flutter.dart'; /// A full screen image widget -class FullScreenImage extends StatelessWidget { +class FullScreenImage extends StatefulWidget { /// The url of the image - final String url; + final List urls; + + final int startIndex; + final String userName; + final DateTime sentAt; /// Instantiate a new FullScreenImage const FullScreenImage({ Key key, - @required this.url, + @required this.urls, + this.startIndex = 0, + this.userName = '', + this.sentAt, }) : super(key: key); + @override + _FullScreenImageState createState() => _FullScreenImageState(); +} + +class _FullScreenImageState extends State + with SingleTickerProviderStateMixin { + bool _optionsShown = true; + + AnimationController _controller; + PageController _pageController; + + int _currentPage; + + @override + void initState() { + super.initState(); + _controller = + AnimationController(vsync: this, duration: Duration(milliseconds: 300)); + _pageController = PageController(initialPage: widget.startIndex); + _currentPage = widget.startIndex; + } + @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - backgroundColor: Colors.black, - iconTheme: IconThemeData( - color: Colors.white, - ), - ), - body: PhotoView( - imageProvider: CachedNetworkImageProvider(url), - maxScale: PhotoViewComputedScale.covered, - minScale: PhotoViewComputedScale.contained, - heroAttributes: PhotoViewHeroAttributes( - tag: url, - ), + body: Stack( + children: [ + AnimatedBuilder( + animation: _controller, + builder: (context, snapshot) { + return PageView.builder( + controller: _pageController, + onPageChanged: (val) { + setState(() { + _currentPage = val; + }); + }, + itemBuilder: (context, position) { + return PhotoView( + imageProvider: CachedNetworkImageProvider(widget.urls[position]), + maxScale: PhotoViewComputedScale.covered, + minScale: PhotoViewComputedScale.contained, + heroAttributes: PhotoViewHeroAttributes( + tag: widget.urls, + ), + 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(); + } + }, + ); + }, + itemCount: widget.urls.length, + ); + } + ), + AnimatedOpacity( + opacity: _optionsShown ? 1.0 : 0.0, + duration: Duration(milliseconds: 300), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ImageHeader(userName: widget.userName, sentAt: 'Sent at${Jiffy(widget.sentAt.toLocal()) + .format(' HH:mm')}',), + ImageFooter(currentPage: _currentPage, totalPages: widget.urls.length,), + ], + ), + ), + ], ), ); } diff --git a/lib/src/giphy_attachment.dart b/lib/src/giphy_attachment.dart index bfa21239..f99ccb27 100644 --- a/lib/src/giphy_attachment.dart +++ b/lib/src/giphy_attachment.dart @@ -67,9 +67,11 @@ class GiphyAttachment extends StatelessWidget { Navigator.push(context, MaterialPageRoute(builder: (_) { return FullScreenImage( - url: attachment.imageUrl ?? + urls: [attachment.imageUrl ?? attachment.assetUrl ?? - attachment.thumbUrl, + attachment.thumbUrl], + userName: message.user.name, + sentAt: message.createdAt, ); })); }, @@ -299,9 +301,11 @@ class GiphyAttachment extends StatelessWidget { onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) { return FullScreenImage( - url: attachment.imageUrl ?? + urls: [attachment.imageUrl ?? attachment.assetUrl ?? - attachment.thumbUrl, + attachment.thumbUrl], + userName: message.user.name, + sentAt: message.createdAt, ); })); }, diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart index ba11252a..d493fa66 100644 --- a/lib/src/image_attachment.dart +++ b/lib/src/image_attachment.dart @@ -44,9 +44,11 @@ class ImageAttachment extends StatelessWidget { MaterialPageRoute( builder: (_) { return FullScreenImage( - url: attachment.imageUrl ?? + urls: [attachment.imageUrl ?? attachment.assetUrl ?? - attachment.thumbUrl, + attachment.thumbUrl], + userName: message.user.name, + sentAt: message.createdAt, ); }, ), diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart new file mode 100644 index 00000000..fade96bf --- /dev/null +++ b/lib/src/image_footer.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/back_button.dart'; +import 'package:stream_chat_flutter/src/channel_info.dart'; +import 'package:stream_chat_flutter/src/channel_name.dart'; +import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; +import 'package:stream_chat_flutter/src/stream_icons.dart'; + +import './channel_name.dart'; +import 'channel_image.dart'; +import 'stream_channel.dart'; + +class ImageFooter extends StatelessWidget { + /// Callback to call when pressing the back button. + /// By default it calls [Navigator.pop] + final VoidCallback onBackPressed; + + /// Callback to call when the header is tapped. + final VoidCallback onTitleTap; + + /// Callback to call when the image is tapped. + final VoidCallback onImageTap; + + final int currentPage; + final int totalPages; + + /// Creates a channel header + ImageFooter({ + Key key, + this.onBackPressed, + this.onTitleTap, + this.onImageTap, + this.currentPage = 0, + this.totalPages = 0, + }) : preferredSize = Size.fromHeight(kToolbarHeight), + super(key: key); + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Container( + color: StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, + padding: EdgeInsets.symmetric(vertical: 8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: Icon( + StreamIcons.share, + size: 24.0, + color: Colors.black, + ), + onPressed: onBackPressed, + ), + InkWell( + onTap: onTitleTap, + child: Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '${currentPage + 1} of $totalPages', + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w700, + color: Colors.black, + ), + ), + ], + ), + ), + ), + IconButton( + icon: Icon( + StreamIcons.grid, + color: Colors.black, + ), + onPressed: () {}, + ), + ], + ), + ), + ); + + return AppBar( + brightness: Theme.of(context).brightness, + elevation: 1, + leading: IconButton( + icon: Icon(StreamIcons.share), + onPressed: onBackPressed, + ), + backgroundColor: + StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, + actions: [ + IconButton( + icon: Icon(StreamIcons.grid), + onPressed: () {}, + ), + ], + centerTitle: true, + title: InkWell( + onTap: onTitleTap, + child: Container( + height: preferredSize.height, + width: preferredSize.width, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Demo name', + style: StreamChatTheme.of(context) + .channelTheme + .channelHeaderTheme + .title, + ), + Text( + '1 of 1', + style: StreamChatTheme.of(context).channelPreviewTheme.subtitle, + ), + ], + ), + ), + ), + ); + } + + @override + final Size preferredSize; +} diff --git a/lib/src/image_group.dart b/lib/src/image_group.dart index 1a7144d8..4592d94d 100644 --- a/lib/src/image_group.dart +++ b/lib/src/image_group.dart @@ -110,34 +110,12 @@ class ImageGroup extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Colors.transparent, - iconTheme: IconThemeData( - color: Colors.white, - ), + builder: (context) => + FullScreenImage(urls: images.map((e) => e.imageUrl).toList(), + startIndex: index, + userName: message.user.name, + sentAt: message.createdAt, ), - backgroundColor: Colors.black, - body: SizedBox.expand( - child: CarouselSlider( - items: images - .map((image) => FullScreenImage( - url: image.imageUrl ?? - image.thumbUrl ?? - image.assetUrl, - )) - .toList(), - options: CarouselOptions( - initialPage: index ?? 0, - enableInfiniteScroll: false, - viewportFraction: 0.95, - height: MediaQuery.of(context).size.height, - ), - ), - ), - ); - }, ), ); } diff --git a/lib/src/image_header.dart b/lib/src/image_header.dart new file mode 100644 index 00000000..39f2ccb9 --- /dev/null +++ b/lib/src/image_header.dart @@ -0,0 +1,101 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/back_button.dart'; +import 'package:stream_chat_flutter/src/channel_info.dart'; +import 'package:stream_chat_flutter/src/channel_name.dart'; +import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; +import 'package:stream_chat_flutter/src/stream_icons.dart'; + +import './channel_name.dart'; +import 'channel_image.dart'; +import 'stream_channel.dart'; + +class ImageHeader extends StatelessWidget implements PreferredSizeWidget { + /// True if this header shows the leading back button + final bool showBackButton; + + /// Callback to call when pressing the back button. + /// By default it calls [Navigator.pop] + final VoidCallback onBackPressed; + + /// Callback to call when the header is tapped. + final VoidCallback onTitleTap; + + /// Callback to call when the image is tapped. + final VoidCallback onImageTap; + + final String userName; + final String sentAt; + + /// Creates a channel header + ImageHeader({ + Key key, + this.showBackButton = true, + this.onBackPressed, + this.onTitleTap, + this.onImageTap, + this.userName = '', + this.sentAt = '', + }) : preferredSize = Size.fromHeight(kToolbarHeight), + super(key: key); + + @override + Widget build(BuildContext context) { + return AppBar( + brightness: Theme.of(context).brightness, + elevation: 1, + leading: showBackButton + ? IconButton( + icon: Icon( + StreamIcons.close, + color: Colors.black, + size: 24.0, + ), + onPressed: onBackPressed, + ) + : SizedBox(), + backgroundColor: + StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, + actions: [ + IconButton( + icon: Icon( + StreamIcons.menu_point_v, + color: Colors.black, + size: 24.0, + ), + onPressed: () {}, + ), + ], + centerTitle: true, + title: InkWell( + onTap: onTitleTap, + child: Container( + height: preferredSize.height, + width: preferredSize.width, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + userName, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w700, + color: Colors.black, + ), + ), + Text( + sentAt, + style: StreamChatTheme.of(context).channelPreviewTheme.subtitle, + ), + ], + ), + ), + ), + ); + } + + @override + final Size preferredSize; +} From 64ed2b2187d8eac5a6343bd779ed172c545b2c96 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 13 Nov 2020 19:41:45 +0530 Subject: [PATCH 02/29] feat: Added image actions modal --- lib/src/full_screen_image.dart | 100 +++++++++++---------- lib/src/image_actions_modal.dart | 145 +++++++++++++++++++++++++++++++ lib/src/image_header.dart | 16 +++- 3 files changed, 215 insertions(+), 46 deletions(-) create mode 100644 lib/src/image_actions_modal.dart diff --git a/lib/src/full_screen_image.dart b/lib/src/full_screen_image.dart index e0dd79a3..b616c17a 100644 --- a/lib/src/full_screen_image.dart +++ b/lib/src/full_screen_image.dart @@ -35,7 +35,7 @@ class _FullScreenImageState extends State AnimationController _controller; PageController _pageController; - + int _currentPage; @override @@ -53,56 +53,66 @@ class _FullScreenImageState extends State body: Stack( children: [ AnimatedBuilder( - animation: _controller, - builder: (context, snapshot) { - return PageView.builder( - controller: _pageController, - onPageChanged: (val) { - setState(() { - _currentPage = val; - }); - }, - itemBuilder: (context, position) { - return PhotoView( - imageProvider: CachedNetworkImageProvider(widget.urls[position]), - maxScale: PhotoViewComputedScale.covered, - minScale: PhotoViewComputedScale.contained, - heroAttributes: PhotoViewHeroAttributes( - tag: widget.urls, - ), - 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(); - } - }, - ); - }, - itemCount: widget.urls.length, - ); - } - ), + animation: _controller, + builder: (context, snapshot) { + return PageView.builder( + controller: _pageController, + onPageChanged: (val) { + setState(() { + _currentPage = val; + }); + }, + itemBuilder: (context, position) { + return PhotoView( + imageProvider: + CachedNetworkImageProvider(widget.urls[position]), + maxScale: PhotoViewComputedScale.covered, + minScale: PhotoViewComputedScale.contained, + heroAttributes: PhotoViewHeroAttributes( + tag: widget.urls, + ), + 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(); + } + }, + ); + }, + itemCount: widget.urls.length, + ); + }), AnimatedOpacity( opacity: _optionsShown ? 1.0 : 0.0, duration: Duration(milliseconds: 300), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - ImageHeader(userName: widget.userName, sentAt: 'Sent at${Jiffy(widget.sentAt.toLocal()) - .format(' HH:mm')}',), - ImageFooter(currentPage: _currentPage, totalPages: widget.urls.length,), + ImageHeader( + userName: widget.userName, + sentAt: + 'Sent at${Jiffy(widget.sentAt.toLocal()).format(' HH:mm')}', + onBackPressed: () { + Navigator.of(context).pop(); + }, + ), + ImageFooter( + currentPage: _currentPage, + totalPages: widget.urls.length, + ), ], ), ), diff --git a/lib/src/image_actions_modal.dart b/lib/src/image_actions_modal.dart new file mode 100644 index 00000000..03603795 --- /dev/null +++ b/lib/src/image_actions_modal.dart @@ -0,0 +1,145 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; + +import '../stream_chat_flutter.dart'; +import 'stream_icons.dart'; + +class ImageActionsModal extends StatelessWidget { + final String userName; + final String sentAt; + + ImageActionsModal({this.userName, this.sentAt}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + Navigator.pop(context); + }, + child: Stack( + children: [ + Positioned.fill( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.black.withOpacity(0.8), + Colors.transparent, + ], + stops: [0.0, 0.4], + ) + ), + ) + ), + _buildPage(context), + ], + ), + ); + } + + Widget _buildPage(context) { + return Material( + color: Colors.transparent, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Container( + width: 40.0, + ), + ), + Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + userName, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w700, + color: Colors.white, + ), + ), + ), + Text( + sentAt, + style: + StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(color: Colors.white.withOpacity(0.5)), + ), + ], + ), + IconButton( + icon: Icon( + StreamIcons.close, + size: 24.0, + color: Colors.white, + ), + onPressed: () { + Navigator.pop(context); + }, + ), + ], + ), + Align( + alignment: Alignment.centerRight, + child: Container( + width: MediaQuery.of(context).size.width / 1.8, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Material( + clipBehavior: Clip.hardEdge, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + color: Color(0xffe5e5e5), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: ListTile.divideTiles( + context: context, + tiles: [ + _buildButton(context, 'Reply', StreamIcons.curve_line_down_left, () {}), + _buildButton(context, 'Show in Chat', StreamIcons.eye, () {}), + _buildButton(context, 'Save Image', StreamIcons.save_1, () {}), + _buildButton(context, 'Copy', StreamIcons.copy, () {}), + _buildButton(context, 'Delete', StreamIcons.delete, () {}, color: Colors.red), + ], + ).toList(), + ), + ), + ), + ), + ), + ], + ), + ); + } + + Widget _buildButton(context, String title, IconData iconData, VoidCallback onTap, {Color color}) { + var titleStyle = TextStyle( + fontSize: 14.5, + color: Colors.black, + fontWeight: FontWeight.w500, + ); + + return ListTile( + dense: true, + title: Text( + title, + style: color == null ? titleStyle : titleStyle.copyWith(color: color), + ), + leading: Icon( + iconData, + color: color ?? StreamChatTheme.of(context).primaryIconTheme.color, + size: 24.0, + ), + onTap: onTap, + ); + } +} diff --git a/lib/src/image_header.dart b/lib/src/image_header.dart index 39f2ccb9..b4dfb36d 100644 --- a/lib/src/image_header.dart +++ b/lib/src/image_header.dart @@ -8,6 +8,7 @@ import 'package:stream_chat_flutter/src/stream_icons.dart'; import './channel_name.dart'; import 'channel_image.dart'; +import 'image_actions_modal.dart'; import 'stream_channel.dart'; class ImageHeader extends StatelessWidget implements PreferredSizeWidget { @@ -63,7 +64,9 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { color: Colors.black, size: 24.0, ), - onPressed: () {}, + onPressed: () { + _showMessageActionModalBottomSheet(context); + }, ), ], centerTitle: true, @@ -98,4 +101,15 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { @override final Size preferredSize; + + void _showMessageActionModalBottomSheet(BuildContext context) { + showDialog( + context: context, + builder: (context) { + return ImageActionsModal( + userName: userName, + sentAt: sentAt, + ); + }); + } } From d7e6978ccf001b041943bf072d7ccbb623b7575f Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 16 Nov 2020 19:33:11 +0530 Subject: [PATCH 03/29] feat: Added images modal, fixed image group errors --- lib/src/full_screen_image.dart | 1 + lib/src/image_footer.dart | 78 +++++++++++++++++++++++++++++++++- lib/src/image_group.dart | 2 +- 3 files changed, 78 insertions(+), 3 deletions(-) diff --git a/lib/src/full_screen_image.dart b/lib/src/full_screen_image.dart index b616c17a..c88053f9 100644 --- a/lib/src/full_screen_image.dart +++ b/lib/src/full_screen_image.dart @@ -112,6 +112,7 @@ class _FullScreenImageState extends State ImageFooter( currentPage: _currentPage, totalPages: widget.urls.length, + urls: widget.urls, ), ], ), diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index fade96bf..668f556f 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -1,3 +1,4 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/back_button.dart'; @@ -24,6 +25,8 @@ class ImageFooter extends StatelessWidget { final int currentPage; final int totalPages; + final List urls; + /// Creates a channel header ImageFooter({ Key key, @@ -32,6 +35,7 @@ class ImageFooter extends StatelessWidget { this.onImageTap, this.currentPage = 0, this.totalPages = 0, + this.urls, }) : preferredSize = Size.fromHeight(kToolbarHeight), super(key: key); @@ -39,7 +43,8 @@ class ImageFooter extends StatelessWidget { Widget build(BuildContext context) { return SafeArea( child: Container( - color: StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, + color: + StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, padding: EdgeInsets.symmetric(vertical: 8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -77,7 +82,9 @@ class ImageFooter extends StatelessWidget { StreamIcons.grid, color: Colors.black, ), - onPressed: () {}, + onPressed: () { + _buildPhotosModal(context); + }, ), ], ), @@ -128,6 +135,73 @@ class ImageFooter extends StatelessWidget { ); } + Widget _buildPhotosModal(context) { + showModalBottomSheet( + context: context, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.0), + ), + builder: (context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topRight: Radius.circular(8.0), + topLeft: Radius.circular(8.0), + ) + ), + child: Stack( + children: [ + Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'Photos', + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w700, + color: Colors.black, + ), + ), + ), + ), + Align( + alignment: Alignment.centerRight, + child: IconButton( + icon: Icon(StreamIcons.close, color: Colors.black,), + onPressed: () { + Navigator.pop(context); + }, + ), + ), + ], + ), + ), + Container( + color: Colors.white, + child: GridView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, position) { + return Padding( + padding: const EdgeInsets.all(1.0), + child: AspectRatio(child: CachedNetworkImage(imageUrl: urls[position]), aspectRatio: 1.0,), + ); + }, + itemCount: urls.length, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3), + ), + ), + ], + ); + }, + ); + } + @override final Size preferredSize; } diff --git a/lib/src/image_group.dart b/lib/src/image_group.dart index 4592d94d..a97bde81 100644 --- a/lib/src/image_group.dart +++ b/lib/src/image_group.dart @@ -111,7 +111,7 @@ class ImageGroup extends StatelessWidget { context, MaterialPageRoute( builder: (context) => - FullScreenImage(urls: images.map((e) => e.imageUrl).toList(), + FullScreenImage(urls: images.map((e) => e.imageUrl ?? e.thumbUrl ?? e.assetUrl).toList(), startIndex: index, userName: message.user.name, sentAt: message.createdAt, From 989ea91554e0062aa3bf79164b88dbfa9b5f2f81 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 17 Nov 2020 14:47:32 +0530 Subject: [PATCH 04/29] feat: Added user share section ui --- lib/src/image_footer.dart | 208 ++++++++++++++++++++++++++++++++++---- 1 file changed, 189 insertions(+), 19 deletions(-) diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 668f556f..5898208c 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -4,14 +4,16 @@ import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/back_button.dart'; import 'package:stream_chat_flutter/src/channel_info.dart'; import 'package:stream_chat_flutter/src/channel_name.dart'; +import 'package:stream_chat_flutter/src/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_icons.dart'; +import 'package:stream_chat_flutter/src/user_avatar.dart'; import './channel_name.dart'; import 'channel_image.dart'; import 'stream_channel.dart'; -class ImageFooter extends StatelessWidget { +class ImageFooter extends StatefulWidget { /// Callback to call when pressing the back button. /// By default it calls [Navigator.pop] final VoidCallback onBackPressed; @@ -39,6 +41,22 @@ class ImageFooter extends StatelessWidget { }) : preferredSize = Size.fromHeight(kToolbarHeight), super(key: key); + @override + _ImageFooterState createState() => _ImageFooterState(); + + @override + final Size preferredSize; +} + +class _ImageFooterState extends State { + Future _userQuery; + + @override + void initState() { + super.initState(); + _userQuery = _queryUsers(context); + } + @override Widget build(BuildContext context) { return SafeArea( @@ -55,10 +73,12 @@ class ImageFooter extends StatelessWidget { size: 24.0, color: Colors.black, ), - onPressed: onBackPressed, + onPressed: () { + _buildShareModal(context); + }, ), InkWell( - onTap: onTitleTap, + onTap: widget.onTitleTap, child: Container( child: Column( crossAxisAlignment: CrossAxisAlignment.center, @@ -66,7 +86,7 @@ class ImageFooter extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - '${currentPage + 1} of $totalPages', + '${widget.currentPage + 1} of ${widget.totalPages}', style: TextStyle( fontSize: 16.0, fontWeight: FontWeight.w700, @@ -96,7 +116,7 @@ class ImageFooter extends StatelessWidget { elevation: 1, leading: IconButton( icon: Icon(StreamIcons.share), - onPressed: onBackPressed, + onPressed: widget.onBackPressed, ), backgroundColor: StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, @@ -108,10 +128,10 @@ class ImageFooter extends StatelessWidget { ], centerTitle: true, title: InkWell( - onTap: onTitleTap, + onTap: widget.onTitleTap, child: Container( - height: preferredSize.height, - width: preferredSize.width, + height: widget.preferredSize.height, + width: widget.preferredSize.width, child: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, @@ -147,12 +167,11 @@ class ImageFooter extends StatelessWidget { children: [ Container( decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only( - topRight: Radius.circular(8.0), - topLeft: Radius.circular(8.0), - ) - ), + color: Colors.white, + borderRadius: BorderRadius.only( + topRight: Radius.circular(8.0), + topLeft: Radius.circular(8.0), + )), child: Stack( children: [ Center( @@ -171,7 +190,10 @@ class ImageFooter extends StatelessWidget { Align( alignment: Alignment.centerRight, child: IconButton( - icon: Icon(StreamIcons.close, color: Colors.black,), + icon: Icon( + StreamIcons.close, + color: Colors.black, + ), onPressed: () { Navigator.pop(context); }, @@ -188,10 +210,16 @@ class ImageFooter extends StatelessWidget { itemBuilder: (context, position) { return Padding( padding: const EdgeInsets.all(1.0), - child: AspectRatio(child: CachedNetworkImage(imageUrl: urls[position]), aspectRatio: 1.0,), + child: AspectRatio( + child: CachedNetworkImage( + imageUrl: widget.urls[position], + fit: BoxFit.cover, + ), + aspectRatio: 1.0, + ), ); }, - itemCount: urls.length, + itemCount: widget.urls.length, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3), ), @@ -202,6 +230,148 @@ class ImageFooter extends StatelessWidget { ); } - @override - final Size preferredSize; + Widget _buildShareModal(context) { + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.0), + ), + builder: (context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + margin: EdgeInsets.symmetric( + horizontal: 8.0, + ), + padding: EdgeInsets.symmetric( + vertical: 4.0, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topRight: Radius.circular(16.0), + topLeft: Radius.circular(16.0), + )), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 6.0), + child: IconButton( + icon: Icon( + StreamIcons.search, + color: Colors.black, + ), + iconSize: 24.0, + onPressed: () {}, + ), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'Select a Chat to Share', + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w700, + color: Colors.black, + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 6.0), + child: IconButton( + icon: Icon( + StreamIcons.share, + color: Colors.black, + ), + iconSize: 24.0, + onPressed: () {}, + ), + ), + ], + ), + ), + Expanded( + child: Container( + color: Colors.white, + margin: EdgeInsets.symmetric( + horizontal: 8.0, + ), + child: FutureBuilder( + future: _userQuery, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return Center( + child: CircularProgressIndicator(), + ); + } + + return GridView.builder( + shrinkWrap: true, + itemBuilder: (context, position) { + var user = snapshot.data.users[position]; + + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Stack( + children: [ + UserAvatar( + onTap: (user) {}, + user: user, + constraints: BoxConstraints.tightFor( + height: 64, + width: 64, + ), + borderRadius: BorderRadius.circular(32), + ), + ], + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + user.name, + style: Theme.of(context).textTheme.subtitle2, + textAlign: TextAlign.center, + maxLines: 2, + ), + ), + ], + ), + ); + }, + itemCount: snapshot.data.users.length, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + childAspectRatio: 0.75, + ), + ); + }), + ), + ), + ], + ); + }, + ); + } + + Future _queryUsers(context) { + return StreamChat.of(context).client.queryUsers( + pagination: PaginationParams( + limit: 25, + ), + sort: [ + SortOption( + 'name', + direction: SortOption.ASC, + ), + ], + ); + } } From e6b930fe2b429b336e98ad7a9ccc8290a8cf065e Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 17 Nov 2020 18:15:40 +0530 Subject: [PATCH 05/29] feat: Added ui for search functionality --- lib/src/full_screen_image.dart | 1 + lib/src/image_footer.dart | 413 ++++++++++++++++++++------------- 2 files changed, 256 insertions(+), 158 deletions(-) diff --git a/lib/src/full_screen_image.dart b/lib/src/full_screen_image.dart index c88053f9..0001585f 100644 --- a/lib/src/full_screen_image.dart +++ b/lib/src/full_screen_image.dart @@ -50,6 +50,7 @@ class _FullScreenImageState extends State @override Widget build(BuildContext context) { return Scaffold( + resizeToAvoidBottomInset: false, body: Stack( children: [ AnimatedBuilder( diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 5898208c..37ddf58f 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -50,6 +50,8 @@ class ImageFooter extends StatefulWidget { class _ImageFooterState extends State { Future _userQuery; + bool _userSearchMode = false; + TextEditingController _searchController = TextEditingController(); @override void initState() { @@ -110,49 +112,6 @@ class _ImageFooterState extends State { ), ), ); - - return AppBar( - brightness: Theme.of(context).brightness, - elevation: 1, - leading: IconButton( - icon: Icon(StreamIcons.share), - onPressed: widget.onBackPressed, - ), - backgroundColor: - StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, - actions: [ - IconButton( - icon: Icon(StreamIcons.grid), - onPressed: () {}, - ), - ], - centerTitle: true, - title: InkWell( - onTap: widget.onTitleTap, - child: Container( - height: widget.preferredSize.height, - width: widget.preferredSize.width, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Demo name', - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .title, - ), - Text( - '1 of 1', - style: StreamChatTheme.of(context).channelPreviewTheme.subtitle, - ), - ], - ), - ), - ), - ); } Widget _buildPhotosModal(context) { @@ -231,136 +190,274 @@ class _ImageFooterState extends State { } Widget _buildShareModal(context) { - showModalBottomSheet( + showDialog( context: context, - backgroundColor: Colors.transparent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16.0), - ), builder: (context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - margin: EdgeInsets.symmetric( - horizontal: 8.0, + return StatefulBuilder(builder: (context, modalSetState) { + return Align( + alignment: + _userSearchMode ? Alignment.topCenter : Alignment.bottomCenter, + child: Material( + color: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.0), ), - padding: EdgeInsets.symmetric( - vertical: 4.0, - ), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only( - topRight: Radius.circular(16.0), - topLeft: Radius.circular(16.0), - )), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Stack( children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 6.0), - child: IconButton( - icon: Icon( - StreamIcons.search, - color: Colors.black, + ListView( + children: [ + if (!_userSearchMode) + GestureDetector( + child: Container( + height: MediaQuery.of(context).size.height / 2, + color: Colors.transparent, + ), + onTapUp: (val) { + Navigator.pop(context); + }, + ), + Container( + margin: EdgeInsets.symmetric( + horizontal: 8.0, + ), + padding: EdgeInsets.symmetric( + vertical: 4.0, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topRight: Radius.circular(16.0), + topLeft: Radius.circular(16.0), + )), + child: _buildTextInputSection(modalSetState), ), - iconSize: 24.0, - onPressed: () {}, - ), + Container( + margin: EdgeInsets.symmetric( + horizontal: 8.0, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + bottomRight: + Radius.circular(_userSearchMode ? 16.0 : 0.0), + bottomLeft: + Radius.circular(_userSearchMode ? 16.0 : 0.0), + ), + ), + child: FutureBuilder( + future: _userQuery, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return Center( + child: CircularProgressIndicator(), + ); + } + + var filteredData = snapshot.data.users.toList(); + + if (_userSearchMode && + _searchController.text.length > 0) { + filteredData = snapshot.data.users + .where((element) => element.name + .toLowerCase() + .contains(_searchController.text + .toLowerCase())) + .toList(); + } + + return GridView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, position) { + var user = filteredData[position]; + + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + Stack( + children: [ + UserAvatar( + onTap: (user) {}, + user: user, + constraints: + BoxConstraints.tightFor( + height: 64, + width: 64, + ), + borderRadius: + BorderRadius.circular(32), + ), + ], + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + user.name, + style: Theme.of(context) + .textTheme + .subtitle2, + textAlign: TextAlign.center, + maxLines: 2, + ), + ), + ], + ), + ); + }, + itemCount: filteredData.length, + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + childAspectRatio: 0.75, + ), + ); + }), + ), + ], ), - Padding( - padding: const EdgeInsets.all(16.0), - child: Text( - 'Select a Chat to Share', - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w700, - color: Colors.black, + if (!_userSearchMode) + Align( + alignment: Alignment.bottomCenter, + child: Container( + color: Colors.white, + height: 48.0, + margin: EdgeInsets.symmetric( + horizontal: 8.0, + ), + child: SizedBox.expand( + child: Center( + child: Text( + 'Save to Photos', + style: TextStyle( + color: StreamChatTheme.of(context).accentColor, + fontWeight: FontWeight.bold, + ), + ), + ), + ), ), ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 6.0), - child: IconButton( - icon: Icon( - StreamIcons.share, - color: Colors.black, - ), - iconSize: 24.0, - onPressed: () {}, - ), - ), ], ), ), - Expanded( - child: Container( - color: Colors.white, - margin: EdgeInsets.symmetric( - horizontal: 8.0, - ), - child: FutureBuilder( - future: _userQuery, - builder: (context, snapshot) { - if (!snapshot.hasData) { - return Center( - child: CircularProgressIndicator(), - ); - } - - return GridView.builder( - shrinkWrap: true, - itemBuilder: (context, position) { - var user = snapshot.data.users[position]; - - return Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Stack( - children: [ - UserAvatar( - onTap: (user) {}, - user: user, - constraints: BoxConstraints.tightFor( - height: 64, - width: 64, - ), - borderRadius: BorderRadius.circular(32), - ), - ], - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - user.name, - style: Theme.of(context).textTheme.subtitle2, - textAlign: TextAlign.center, - maxLines: 2, - ), - ), - ], - ), - ); - }, - itemCount: snapshot.data.users.length, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, - childAspectRatio: 0.75, - ), - ); - }), - ), - ), - ], - ); + ); + }); }, ); } + Widget _buildTextInputSection(modalSetState) { + if (_userSearchMode) { + return Column( + children: [ + SizedBox( + height: 16.0, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: 16.0, + ), + Expanded( + child: TextField( + controller: _searchController, + onChanged: (val) { + modalSetState(() {}); + }, + cursorColor: Colors.black, + autofocus: true, + decoration: InputDecoration( + isDense: true, + prefixIcon: Icon( + StreamIcons.search, + color: Colors.black, + ), + hintText: 'Search', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(32.0), + borderSide: + BorderSide(color: Colors.black.withOpacity(0.08)), + gapPadding: 0.0, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(32.0), + borderSide: + BorderSide(color: Colors.black.withOpacity(0.08)), + gapPadding: 0.0, + ), + contentPadding: EdgeInsets.zero, + ), + ), + ), + SizedBox( + width: 8.0, + ), + IconButton( + icon: Icon(StreamIcons.close_circle), + onPressed: () { + modalSetState(() { + _userSearchMode = false; + }); + setState(() {}); + }, + ) + ], + ), + ], + ); + } else { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 6.0), + child: IconButton( + icon: Icon( + StreamIcons.search, + color: Colors.black, + ), + iconSize: 24.0, + onPressed: () { + modalSetState(() { + _userSearchMode = true; + }); + }, + ), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'Select a Chat to Share', + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w700, + color: Colors.black, + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 6.0), + child: IconButton( + icon: Icon( + StreamIcons.share, + color: Colors.black, + ), + iconSize: 24.0, + onPressed: () {}, + ), + ), + ], + ); + } + } + Future _queryUsers(context) { return StreamChat.of(context).client.queryUsers( pagination: PaginationParams( From dd829e18bccbf7c0649a360ac5823f21b706684c Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 18 Nov 2020 19:32:54 +0530 Subject: [PATCH 06/29] feat: Added share user selection and text input --- lib/src/image_footer.dart | 400 ++++++++++++++++++++++++-------------- 1 file changed, 258 insertions(+), 142 deletions(-) diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 37ddf58f..1d74d017 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -1,9 +1,12 @@ +import 'dart:math'; + import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/back_button.dart'; import 'package:stream_chat_flutter/src/channel_info.dart'; import 'package:stream_chat_flutter/src/channel_name.dart'; +import 'package:stream_chat_flutter/src/message_input.dart'; import 'package:stream_chat_flutter/src/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_icons.dart'; @@ -52,6 +55,9 @@ class _ImageFooterState extends State { Future _userQuery; bool _userSearchMode = false; TextEditingController _searchController = TextEditingController(); + TextEditingController _messageController = TextEditingController(); + + List selectedUsers = []; @override void initState() { @@ -193,161 +199,182 @@ class _ImageFooterState extends State { showDialog( context: context, builder: (context) { - return StatefulBuilder(builder: (context, modalSetState) { - return Align( - alignment: - _userSearchMode ? Alignment.topCenter : Alignment.bottomCenter, - child: Material( - color: Colors.transparent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16.0), - ), - child: Stack( - children: [ - ListView( - children: [ - if (!_userSearchMode) - GestureDetector( - child: Container( - height: MediaQuery.of(context).size.height / 2, - color: Colors.transparent, + print('KEY ${MediaQuery.of(context).viewInsets.bottom}'); + print(MediaQuery.of(context).viewInsets.bottom == 0.0); + return Scaffold( + backgroundColor: Colors.transparent, + body: StatefulBuilder(builder: (modalContext, modalSetState) { + return Align( + alignment: + _userSearchMode ? Alignment.topCenter : Alignment.bottomCenter, + child: Material( + color: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.0), + ), + child: Stack( + children: [ + ListView( + children: [ + if (!_userSearchMode && MediaQuery.of(context).viewInsets.bottom == 0.0) + GestureDetector( + child: Container( + height: MediaQuery.of(modalContext).size.height / 2, + color: Colors.transparent, + ), + onTapUp: (val) { + Navigator.pop(modalContext); + }, ), - onTapUp: (val) { - Navigator.pop(context); - }, + Container( + margin: EdgeInsets.symmetric( + horizontal: 8.0, + ), + padding: EdgeInsets.symmetric( + vertical: 4.0, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topRight: Radius.circular(16.0), + topLeft: Radius.circular(16.0), + )), + child: _buildTextInputSection(modalSetState), ), - Container( - margin: EdgeInsets.symmetric( - horizontal: 8.0, - ), - padding: EdgeInsets.symmetric( - vertical: 4.0, - ), - decoration: BoxDecoration( + Container( + margin: EdgeInsets.symmetric( + horizontal: 8.0, + ), + decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( - topRight: Radius.circular(16.0), - topLeft: Radius.circular(16.0), - )), - child: _buildTextInputSection(modalSetState), - ), - Container( - margin: EdgeInsets.symmetric( - horizontal: 8.0, - ), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only( - bottomRight: - Radius.circular(_userSearchMode ? 16.0 : 0.0), - bottomLeft: - Radius.circular(_userSearchMode ? 16.0 : 0.0), + bottomRight: + Radius.circular(_userSearchMode ? 16.0 : 0.0), + bottomLeft: + Radius.circular(_userSearchMode ? 16.0 : 0.0), + ), ), - ), - child: FutureBuilder( - future: _userQuery, - builder: (context, snapshot) { - if (!snapshot.hasData) { - return Center( - child: CircularProgressIndicator(), - ); - } - - var filteredData = snapshot.data.users.toList(); - - if (_userSearchMode && - _searchController.text.length > 0) { - filteredData = snapshot.data.users - .where((element) => element.name - .toLowerCase() - .contains(_searchController.text - .toLowerCase())) - .toList(); - } - - return GridView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemBuilder: (context, position) { - var user = filteredData[position]; - - return Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: - MainAxisAlignment.start, - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - Stack( - children: [ - UserAvatar( - onTap: (user) {}, - user: user, - constraints: - BoxConstraints.tightFor( - height: 64, - width: 64, - ), - borderRadius: - BorderRadius.circular(32), - ), - ], - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - user.name, - style: Theme.of(context) - .textTheme - .subtitle2, - textAlign: TextAlign.center, - maxLines: 2, - ), - ), - ], - ), + child: FutureBuilder( + future: _userQuery, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return Center( + child: CircularProgressIndicator(), ); - }, - itemCount: filteredData.length, - gridDelegate: - SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, - childAspectRatio: 0.75, - ), - ); - }), - ), - ], - ), - if (!_userSearchMode) - Align( - alignment: Alignment.bottomCenter, - child: Container( - color: Colors.white, - height: 48.0, - margin: EdgeInsets.symmetric( - horizontal: 8.0, + } + + var filteredData = snapshot.data.users.toList(); + + if (_userSearchMode && + _searchController.text.length > 0) { + filteredData = snapshot.data.users + .where((element) => element.name + .toLowerCase() + .contains(_searchController.text + .toLowerCase())) + .toList(); + } + + return GridView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, position) { + var user = filteredData[position]; + var isUserSelected = + selectedUsers.contains(user); + + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + Stack( + children: [ + CircleAvatar( + maxRadius: 32.0, + child: UserAvatar( + onTap: (user) { + if (isUserSelected) { + selectedUsers.remove(user); + } else { + selectedUsers.add(user); + } + modalSetState(() {}); + }, + user: user, + constraints: + BoxConstraints.tightFor( + height: + isUserSelected ? 56 : 64, + width: + isUserSelected ? 56 : 64, + ), + borderRadius: + BorderRadius.circular(32), + ), + ), + ], + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + user.name, + style: Theme.of(context) + .textTheme + .subtitle2, + textAlign: TextAlign.center, + maxLines: 2, + ), + ), + ], + ), + ); + }, + itemCount: filteredData.length, + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + childAspectRatio: 0.75, + ), + ); + }), ), - child: SizedBox.expand( - child: Center( - child: Text( - 'Save to Photos', - style: TextStyle( - color: StreamChatTheme.of(context).accentColor, - fontWeight: FontWeight.bold, + ], + ), + if (!_userSearchMode && selectedUsers.isEmpty) + Align( + alignment: Alignment.bottomCenter, + child: Container( + color: Colors.white, + height: 48.0, + margin: EdgeInsets.symmetric( + horizontal: 8.0, + ), + child: SizedBox.expand( + child: Center( + child: Text( + 'Save to Photos', + style: TextStyle( + color: StreamChatTheme.of(modalContext).accentColor, + fontWeight: FontWeight.bold, + ), ), ), ), ), ), - ), - ], + if (!_userSearchMode && selectedUsers.isNotEmpty) + _buildShareTextInputSection(modalSetState), + ], + ), ), - ), - ); - }); + ); + }), + ); }, ); } @@ -458,6 +485,95 @@ class _ImageFooterState extends State { } } + Widget _buildShareTextInputSection(modalSetState) { + return Align( + alignment: Alignment.bottomCenter, + child: Container( + color: Colors.white, + height: 56.0, + child: Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: TextField( + controller: _messageController, + onChanged: (val) { + modalSetState(() {}); + }, + onTap: () { + modalSetState(() {}); + setState(() {}); + }, + decoration: InputDecoration( + isDense: true, + prefixText: ' ', + hintText: 'Add a comment', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(32.0), + borderSide: BorderSide( + color: Colors.black.withOpacity(0.16), + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(32.0), + borderSide: BorderSide( + color: Colors.black.withOpacity(0.16), + ) + ), + contentPadding: EdgeInsets.symmetric(vertical: 12.0), + ), + ), + ), + ), + AnimatedCrossFade( + crossFadeState: _messageController.text.isNotEmpty + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: IconTheme( + data: + StreamChatTheme.of(context).channelTheme.messageInputButtonIconTheme, + child: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: InkWell( + onTap: () { + // sendMessage(); + }, + child: Transform.rotate( + angle: -pi / 2, + child: Icon( + StreamIcons.send_message, + color: StreamChatTheme.of(context).accentColor, + ),), + ), + ), + ), + ), + secondChild: IconTheme( + data: + StreamChatTheme.of(context).channelTheme.messageInputButtonIconTheme, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: InkWell( + onTap: () {}, + child: Icon( + StreamIcons.send_message, + color: Colors.grey, + ), + )), + ), + ), + duration: Duration(milliseconds: 300), + alignment: Alignment.center, + ), + ], + ), + ), + ); + } + Future _queryUsers(context) { return StreamChat.of(context).client.queryUsers( pagination: PaginationParams( From 412c721ab8abe5274ccd901902f4294053da5c56 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 19 Nov 2020 21:01:17 +0530 Subject: [PATCH 07/29] feat: Added sharing and added actions for buttons --- lib/src/full_screen_image.dart | 4 + lib/src/giphy_attachment.dart | 36 ++- lib/src/image_actions_modal.dart | 60 ++-- lib/src/image_attachment.dart | 18 +- lib/src/image_footer.dart | 533 +++++++++++++++++-------------- lib/src/image_group.dart | 15 +- lib/src/image_header.dart | 15 +- 7 files changed, 397 insertions(+), 284 deletions(-) diff --git a/lib/src/full_screen_image.dart b/lib/src/full_screen_image.dart index 0001585f..ca302790 100644 --- a/lib/src/full_screen_image.dart +++ b/lib/src/full_screen_image.dart @@ -11,6 +11,7 @@ import '../stream_chat_flutter.dart'; class FullScreenImage extends StatefulWidget { /// The url of the image final List urls; + final Message message; final int startIndex; final String userName; @@ -20,6 +21,7 @@ class FullScreenImage extends StatefulWidget { const FullScreenImage({ Key key, @required this.urls, + this.message, this.startIndex = 0, this.userName = '', this.sentAt, @@ -109,11 +111,13 @@ class _FullScreenImageState extends State onBackPressed: () { Navigator.of(context).pop(); }, + message: widget.message, ), ImageFooter( currentPage: _currentPage, totalPages: widget.urls.length, urls: widget.urls, + message: widget.message, ), ], ), diff --git a/lib/src/giphy_attachment.dart b/lib/src/giphy_attachment.dart index f99ccb27..e5464987 100644 --- a/lib/src/giphy_attachment.dart +++ b/lib/src/giphy_attachment.dart @@ -64,14 +64,20 @@ class GiphyAttachment extends StatelessWidget { padding: const EdgeInsets.all(8.0), child: GestureDetector( onTap: () { + final channel = StreamChannel.of(context).channel; + Navigator.push(context, MaterialPageRoute(builder: (_) { - return FullScreenImage( - urls: [attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl], - userName: message.user.name, - sentAt: message.createdAt, + return StreamChannel( + channel: channel, + child: FullScreenImage( + urls: [attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl], + userName: message.user.name, + sentAt: message.createdAt, + message: message, + ), ); })); }, @@ -299,13 +305,19 @@ class GiphyAttachment extends StatelessWidget { clipBehavior: Clip.antiAlias, child: GestureDetector( onTap: () { + final channel = StreamChannel.of(context).channel; + Navigator.push(context, MaterialPageRoute(builder: (_) { - return FullScreenImage( - urls: [attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl], - userName: message.user.name, - sentAt: message.createdAt, + return StreamChannel( + channel: channel, + child: FullScreenImage( + urls: [attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl], + userName: message.user.name, + sentAt: message.createdAt, + message: message, + ), ); })); }, diff --git a/lib/src/image_actions_modal.dart b/lib/src/image_actions_modal.dart index 03603795..8b39737d 100644 --- a/lib/src/image_actions_modal.dart +++ b/lib/src/image_actions_modal.dart @@ -6,10 +6,11 @@ import '../stream_chat_flutter.dart'; import 'stream_icons.dart'; class ImageActionsModal extends StatelessWidget { + final Message message; final String userName; final String sentAt; - ImageActionsModal({this.userName, this.sentAt}); + ImageActionsModal({this.message, this.userName, this.sentAt}); @override Widget build(BuildContext context) { @@ -21,20 +22,18 @@ class ImageActionsModal extends StatelessWidget { child: Stack( children: [ Positioned.fill( - child: Container( - decoration: BoxDecoration( + child: Container( + decoration: BoxDecoration( gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.black.withOpacity(0.8), - Colors.transparent, - ], - stops: [0.0, 0.4], - ) - ), - ) - ), + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.black.withOpacity(0.8), + Colors.transparent, + ], + stops: [0.0, 0.4], + )), + )), _buildPage(context), ], ), @@ -70,8 +69,10 @@ class ImageActionsModal extends StatelessWidget { ), Text( sentAt, - style: - StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(color: Colors.white.withOpacity(0.5)), + style: StreamChatTheme.of(context) + .channelPreviewTheme + .subtitle + .copyWith(color: Colors.white.withOpacity(0.5)), ), ], ), @@ -104,11 +105,26 @@ class ImageActionsModal extends StatelessWidget { children: ListTile.divideTiles( context: context, tiles: [ - _buildButton(context, 'Reply', StreamIcons.curve_line_down_left, () {}), - _buildButton(context, 'Show in Chat', StreamIcons.eye, () {}), - _buildButton(context, 'Save Image', StreamIcons.save_1, () {}), + _buildButton(context, 'Reply', + StreamIcons.curve_line_down_left, () {}), + _buildButton(context, 'Show in Chat', StreamIcons.eye, + () { + Navigator.pop(context); + Navigator.pop(context); + }), + _buildButton( + context, 'Save Image', StreamIcons.save_1, () {}), _buildButton(context, 'Copy', StreamIcons.copy, () {}), - _buildButton(context, 'Delete', StreamIcons.delete, () {}, color: Colors.red), + if (StreamChat.of(context).user.id == message.user.id) + _buildButton(context, 'Delete', StreamIcons.delete, + () { + Navigator.pop(context); + Navigator.pop(context); + StreamChat.of(context).client.deleteMessage( + message, + StreamChannel.of(context).channel.cid, + ); + }, color: Colors.red), ], ).toList(), ), @@ -121,7 +137,9 @@ class ImageActionsModal extends StatelessWidget { ); } - Widget _buildButton(context, String title, IconData iconData, VoidCallback onTap, {Color color}) { + Widget _buildButton( + context, String title, IconData iconData, VoidCallback onTap, + {Color color}) { var titleStyle = TextStyle( fontSize: 14.5, color: Colors.black, diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart index d493fa66..b429e064 100644 --- a/lib/src/image_attachment.dart +++ b/lib/src/image_attachment.dart @@ -43,12 +43,18 @@ class ImageAttachment extends StatelessWidget { context, MaterialPageRoute( builder: (_) { - return FullScreenImage( - urls: [attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl], - userName: message.user.name, - sentAt: message.createdAt, + final channel = StreamChannel.of(context).channel; + + return StreamChannel( + channel: channel, + child: FullScreenImage( + urls: [attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl], + userName: message.user.name, + sentAt: message.createdAt, + message: message, + ), ); }, ), diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 1d74d017..02a8a6ef 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -11,6 +11,7 @@ import 'package:stream_chat_flutter/src/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_icons.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import './channel_name.dart'; import 'channel_image.dart'; @@ -31,6 +32,7 @@ class ImageFooter extends StatefulWidget { final int totalPages; final List urls; + final Message message; /// Creates a channel header ImageFooter({ @@ -41,6 +43,7 @@ class ImageFooter extends StatefulWidget { this.currentPage = 0, this.totalPages = 0, this.urls, + this.message, }) : preferredSize = Size.fromHeight(kToolbarHeight), super(key: key); @@ -57,7 +60,7 @@ class _ImageFooterState extends State { TextEditingController _searchController = TextEditingController(); TextEditingController _messageController = TextEditingController(); - List selectedUsers = []; + List _selectedUsers = []; @override void initState() { @@ -134,8 +137,8 @@ class _ImageFooterState extends State { decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( - topRight: Radius.circular(8.0), - topLeft: Radius.circular(8.0), + topRight: Radius.circular(16.0), + topLeft: Radius.circular(16.0), )), child: Stack( children: [ @@ -196,184 +199,197 @@ class _ImageFooterState extends State { } Widget _buildShareModal(context) { + final channel = StreamChannel.of(context).channel; + showDialog( context: context, builder: (context) { - print('KEY ${MediaQuery.of(context).viewInsets.bottom}'); - print(MediaQuery.of(context).viewInsets.bottom == 0.0); - return Scaffold( - backgroundColor: Colors.transparent, - body: StatefulBuilder(builder: (modalContext, modalSetState) { - return Align( - alignment: - _userSearchMode ? Alignment.topCenter : Alignment.bottomCenter, - child: Material( - color: Colors.transparent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16.0), - ), - child: Stack( - children: [ - ListView( - children: [ - if (!_userSearchMode && MediaQuery.of(context).viewInsets.bottom == 0.0) - GestureDetector( - child: Container( - height: MediaQuery.of(modalContext).size.height / 2, - color: Colors.transparent, + return StreamChannel( + channel: channel, + child: Scaffold( + backgroundColor: Colors.transparent, + body: StatefulBuilder(builder: (modalContext, modalSetState) { + return Align( + alignment: _userSearchMode + ? Alignment.topCenter + : Alignment.bottomCenter, + child: Material( + color: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.0), + ), + child: Stack( + children: [ + ListView( + children: [ + if (!_userSearchMode && + MediaQuery.of(context).viewInsets.bottom == 0.0) + GestureDetector( + child: Container( + height: + MediaQuery.of(modalContext).size.height / 2, + color: Colors.transparent, + ), + onTapUp: (val) { + Navigator.pop(modalContext); + }, ), - onTapUp: (val) { - Navigator.pop(modalContext); - }, + Container( + margin: EdgeInsets.symmetric( + horizontal: 8.0, + ), + padding: EdgeInsets.symmetric( + vertical: 4.0, + ), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topRight: Radius.circular(16.0), + topLeft: Radius.circular(16.0), + )), + child: _buildTextInputSection(modalSetState), ), - Container( - margin: EdgeInsets.symmetric( - horizontal: 8.0, - ), - padding: EdgeInsets.symmetric( - vertical: 4.0, - ), - decoration: BoxDecoration( + Container( + margin: EdgeInsets.symmetric( + horizontal: 8.0, + ), + decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.only( - topRight: Radius.circular(16.0), - topLeft: Radius.circular(16.0), - )), - child: _buildTextInputSection(modalSetState), - ), - Container( - margin: EdgeInsets.symmetric( - horizontal: 8.0, - ), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only( - bottomRight: - Radius.circular(_userSearchMode ? 16.0 : 0.0), - bottomLeft: - Radius.circular(_userSearchMode ? 16.0 : 0.0), + bottomRight: Radius.circular( + _userSearchMode ? 16.0 : 0.0), + bottomLeft: Radius.circular( + _userSearchMode ? 16.0 : 0.0), + ), ), - ), - child: FutureBuilder( - future: _userQuery, - builder: (context, snapshot) { - if (!snapshot.hasData) { - return Center( - child: CircularProgressIndicator(), - ); - } - - var filteredData = snapshot.data.users.toList(); - - if (_userSearchMode && - _searchController.text.length > 0) { - filteredData = snapshot.data.users - .where((element) => element.name - .toLowerCase() - .contains(_searchController.text - .toLowerCase())) - .toList(); - } - - return GridView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemBuilder: (context, position) { - var user = filteredData[position]; - var isUserSelected = - selectedUsers.contains(user); - - return Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: - MainAxisAlignment.start, - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - Stack( - children: [ - CircleAvatar( - maxRadius: 32.0, - child: UserAvatar( - onTap: (user) { - if (isUserSelected) { - selectedUsers.remove(user); - } else { - selectedUsers.add(user); - } - modalSetState(() {}); - }, - user: user, - constraints: - BoxConstraints.tightFor( - height: - isUserSelected ? 56 : 64, - width: - isUserSelected ? 56 : 64, - ), - borderRadius: - BorderRadius.circular(32), - ), - ), - ], - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - user.name, - style: Theme.of(context) - .textTheme - .subtitle2, - textAlign: TextAlign.center, - maxLines: 2, - ), - ), - ], - ), + child: FutureBuilder( + future: _userQuery, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return Center( + child: CircularProgressIndicator(), ); - }, - itemCount: filteredData.length, - gridDelegate: - SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, - childAspectRatio: 0.75, - ), - ); - }), - ), - ], - ), - if (!_userSearchMode && selectedUsers.isEmpty) - Align( - alignment: Alignment.bottomCenter, - child: Container( - color: Colors.white, - height: 48.0, - margin: EdgeInsets.symmetric( - horizontal: 8.0, + } + + var filteredData = + snapshot.data.users.toList(); + + if (_userSearchMode && + _searchController.text.length > 0) { + filteredData = snapshot.data.users + .where((element) => element.name + .toLowerCase() + .contains(_searchController.text + .toLowerCase())) + .toList(); + } + + return GridView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, position) { + var user = filteredData[position]; + var isUserSelected = + _selectedUsers.contains(user); + + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment.start, + crossAxisAlignment: + CrossAxisAlignment.center, + children: [ + Stack( + children: [ + CircleAvatar( + maxRadius: 32.0, + child: UserAvatar( + onTap: (user) { + if (isUserSelected) { + _selectedUsers + .remove(user); + } else { + _selectedUsers.add(user); + } + modalSetState(() {}); + }, + user: user, + constraints: + BoxConstraints.tightFor( + height: isUserSelected + ? 56 + : 64, + width: isUserSelected + ? 56 + : 64, + ), + borderRadius: + BorderRadius.circular( + 32), + ), + ), + ], + ), + Padding( + padding: + const EdgeInsets.all(8.0), + child: Text( + user.name, + style: Theme.of(context) + .textTheme + .subtitle2, + textAlign: TextAlign.center, + maxLines: 2, + ), + ), + ], + ), + ); + }, + itemCount: filteredData.length, + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 4, + childAspectRatio: 0.75, + ), + ); + }), ), - child: SizedBox.expand( - child: Center( - child: Text( - 'Save to Photos', - style: TextStyle( - color: StreamChatTheme.of(modalContext).accentColor, - fontWeight: FontWeight.bold, + ], + ), + if (!_userSearchMode && _selectedUsers.isEmpty) + Align( + alignment: Alignment.bottomCenter, + child: Container( + color: Colors.white, + height: 48.0, + margin: EdgeInsets.symmetric( + horizontal: 8.0, + ), + child: SizedBox.expand( + child: Center( + child: Text( + 'Save to Photos', + style: TextStyle( + color: StreamChatTheme.of(modalContext) + .accentColor, + fontWeight: FontWeight.bold, + ), ), ), ), ), ), - ), - if (!_userSearchMode && selectedUsers.isNotEmpty) - _buildShareTextInputSection(modalSetState), - ], + if (!_userSearchMode && _selectedUsers.isNotEmpty) + _buildShareTextInputSection(modalSetState), + ], + ), ), - ), - ); - }), + ); + }), + ), ); }, ); @@ -487,91 +503,93 @@ class _ImageFooterState extends State { Widget _buildShareTextInputSection(modalSetState) { return Align( - alignment: Alignment.bottomCenter, - child: Container( - color: Colors.white, - height: 56.0, - child: Row( - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.only(left: 8.0), - child: TextField( - controller: _messageController, - onChanged: (val) { - modalSetState(() {}); - }, - onTap: () { - modalSetState(() {}); - setState(() {}); - }, - decoration: InputDecoration( - isDense: true, - prefixText: ' ', - hintText: 'Add a comment', - border: OutlineInputBorder( + alignment: Alignment.bottomCenter, + child: Container( + color: Colors.white, + height: 56.0, + child: Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: TextField( + controller: _messageController, + onChanged: (val) { + modalSetState(() {}); + }, + onTap: () { + modalSetState(() {}); + setState(() {}); + }, + decoration: InputDecoration( + isDense: true, + prefixText: ' ', + hintText: 'Add a comment', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(32.0), + borderSide: BorderSide( + color: Colors.black.withOpacity(0.16), + ), + ), + focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(32.0), borderSide: BorderSide( color: Colors.black.withOpacity(0.16), - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(32.0), - borderSide: BorderSide( - color: Colors.black.withOpacity(0.16), - ) - ), - contentPadding: EdgeInsets.symmetric(vertical: 12.0), - ), + )), + contentPadding: EdgeInsets.symmetric(vertical: 12.0), ), ), ), - AnimatedCrossFade( - crossFadeState: _messageController.text.isNotEmpty - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: IconTheme( - data: - StreamChatTheme.of(context).channelTheme.messageInputButtonIconTheme, - child: Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: InkWell( - onTap: () { - // sendMessage(); - }, - child: Transform.rotate( - angle: -pi / 2, - child: Icon( - StreamIcons.send_message, - color: StreamChatTheme.of(context).accentColor, - ),), - ), - ), - ), - ), - secondChild: IconTheme( - data: - StreamChatTheme.of(context).channelTheme.messageInputButtonIconTheme, + ), + AnimatedCrossFade( + crossFadeState: _messageController.text.isNotEmpty + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: IconTheme( + data: StreamChatTheme.of(context) + .channelTheme + .messageInputButtonIconTheme, + child: Center( child: Padding( padding: const EdgeInsets.all(8.0), - child: Center( - child: InkWell( - onTap: () {}, - child: Icon( - StreamIcons.send_message, - color: Colors.grey, - ), - )), + child: InkWell( + onTap: () { + sendMessage(); + }, + child: Transform.rotate( + angle: -pi / 2, + child: Icon( + StreamIcons.send_message, + color: StreamChatTheme.of(context).accentColor, + ), + ), + ), ), ), - duration: Duration(milliseconds: 300), - alignment: Alignment.center, ), - ], - ), + secondChild: IconTheme( + data: StreamChatTheme.of(context) + .channelTheme + .messageInputButtonIconTheme, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: InkWell( + onTap: () {}, + child: Icon( + StreamIcons.send_message, + color: Colors.grey, + ), + )), + ), + ), + duration: Duration(milliseconds: 300), + alignment: Alignment.center, + ), + ], ), - ); + ), + ); } Future _queryUsers(context) { @@ -587,4 +605,43 @@ class _ImageFooterState extends State { ], ); } + + /// Sends the current message + void sendMessage() async { + var text = _messageController.text.trim(); + + final attachments = widget.message.attachments; + + _messageController.clear(); + + final client = StreamChat.of(context).client; + + for(var user in _selectedUsers) { + var c = client.channel('messaging', extraData: { + 'members': [ + user.id, + StreamChat.of(context).user.id, + ], + }); + + await c.create(); + + Future sendingFuture; + Message message; + + message = (Message()).copyWith( + text: text, + attachments: attachments, + mentionedUsers: [], + showInChannel: false, + ); + + sendingFuture = c.sendMessage(message); + + await sendingFuture; + } + + Navigator.pop(context); + Navigator.pop(context); + } } diff --git a/lib/src/image_group.dart b/lib/src/image_group.dart index a97bde81..afccaf7a 100644 --- a/lib/src/image_group.dart +++ b/lib/src/image_group.dart @@ -4,6 +4,7 @@ import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/full_screen_image.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class ImageGroup extends StatelessWidget { const ImageGroup({ @@ -107,14 +108,20 @@ class ImageGroup extends StatelessWidget { BuildContext context, [ int index, ]) { + final channel = StreamChannel.of(context).channel; + Navigator.push( context, MaterialPageRoute( builder: (context) => - FullScreenImage(urls: images.map((e) => e.imageUrl ?? e.thumbUrl ?? e.assetUrl).toList(), - startIndex: index, - userName: message.user.name, - sentAt: message.createdAt, + StreamChannel( + channel: channel, + child: FullScreenImage(urls: images.map((e) => e.imageUrl ?? e.thumbUrl ?? e.assetUrl).toList(), + startIndex: index, + userName: message.user.name, + sentAt: message.createdAt, + message: message, + ), ), ), ); diff --git a/lib/src/image_header.dart b/lib/src/image_header.dart index b4dfb36d..29c36ad9 100644 --- a/lib/src/image_header.dart +++ b/lib/src/image_header.dart @@ -25,12 +25,15 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { /// Callback to call when the image is tapped. final VoidCallback onImageTap; + final Message message; + final String userName; final String sentAt; /// Creates a channel header ImageHeader({ Key key, + this.message, this.showBackButton = true, this.onBackPressed, this.onTitleTap, @@ -103,12 +106,18 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { final Size preferredSize; void _showMessageActionModalBottomSheet(BuildContext context) { + final channel = StreamChannel.of(context).channel; + showDialog( context: context, builder: (context) { - return ImageActionsModal( - userName: userName, - sentAt: sentAt, + return StreamChannel( + channel: channel, + child: ImageActionsModal( + userName: userName, + sentAt: sentAt, + message: message, + ), ); }); } From fbb1d52db9c343eb5900810d2d4d4cf01dfe47ea Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 19 Nov 2020 21:05:23 +0530 Subject: [PATCH 08/29] fix: Fixed behavior after sharing --- lib/src/image_footer.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 02a8a6ef..83960091 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -641,7 +641,7 @@ class _ImageFooterState extends State { await sendingFuture; } - Navigator.pop(context); + _selectedUsers.clear(); Navigator.pop(context); } } From 68e9f98b7fdadd8211b151041a350de3908d2162 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 20 Nov 2020 16:36:29 +0530 Subject: [PATCH 09/29] feat: Added download and sharing of images --- .../android/app/src/main/AndroidManifest.xml | 1 + lib/src/full_screen_image.dart | 2 + lib/src/image_actions_modal.dart | 14 +++- lib/src/image_footer.dart | 66 ++++++++++++++----- lib/src/image_header.dart | 7 ++ pubspec.yaml | 2 + 6 files changed, 73 insertions(+), 19 deletions(-) diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index a90d828e..ca0d2720 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> + Navigator.of(context).pop(); }, message: widget.message, + urls: widget.urls, + currentIndex: _currentPage, ), ImageFooter( currentPage: _currentPage, diff --git a/lib/src/image_actions_modal.dart b/lib/src/image_actions_modal.dart index 8b39737d..f07efcb6 100644 --- a/lib/src/image_actions_modal.dart +++ b/lib/src/image_actions_modal.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:gallery_saver/gallery_saver.dart'; import '../stream_chat_flutter.dart'; import 'stream_icons.dart'; @@ -9,8 +10,11 @@ class ImageActionsModal extends StatelessWidget { final Message message; final String userName; final String sentAt; + final List urls; + final currentIndex; - ImageActionsModal({this.message, this.userName, this.sentAt}); + ImageActionsModal( + {this.message, this.userName, this.sentAt, this.urls, this.currentIndex}); @override Widget build(BuildContext context) { @@ -112,8 +116,12 @@ class ImageActionsModal extends StatelessWidget { Navigator.pop(context); Navigator.pop(context); }), - _buildButton( - context, 'Save Image', StreamIcons.save_1, () {}), + _buildButton(context, 'Save Image', StreamIcons.save_1, + () async { + await GallerySaver.saveImage( + urls[currentIndex].split('?')[0]); + Navigator.pop(context); + }), _buildButton(context, 'Copy', StreamIcons.copy, () {}), if (StreamChat.of(context).user.id == message.user.id) _buildButton(context, 'Delete', StreamIcons.delete, diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 83960091..57119f8e 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -1,7 +1,12 @@ +import 'dart:io'; import 'dart:math'; +import 'dart:typed_data'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:esys_flutter_share/esys_flutter_share.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:gallery_saver/gallery_saver.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/back_button.dart'; import 'package:stream_chat_flutter/src/channel_info.dart'; @@ -61,6 +66,7 @@ class _ImageFooterState extends State { TextEditingController _messageController = TextEditingController(); List _selectedUsers = []; + bool _loading = false; @override void initState() { @@ -311,7 +317,8 @@ class _ImageFooterState extends State { _selectedUsers .remove(user); } else { - _selectedUsers.add(user); + _selectedUsers + .add(user); } modalSetState(() {}); }, @@ -368,14 +375,23 @@ class _ImageFooterState extends State { margin: EdgeInsets.symmetric( horizontal: 8.0, ), - child: SizedBox.expand( - child: Center( - child: Text( - 'Save to Photos', - style: TextStyle( - color: StreamChatTheme.of(modalContext) - .accentColor, - fontWeight: FontWeight.bold, + child: Material( + child: InkWell( + onTap: () async { + await GallerySaver.saveImage( + widget.urls[widget.currentPage].split('?')[0]); + Navigator.pop(context); + }, + child: SizedBox.expand( + child: Center( + child: Text( + 'Save to Photos', + style: TextStyle( + color: StreamChatTheme.of(modalContext) + .accentColor, + fontWeight: FontWeight.bold, + ), + ), ), ), ), @@ -493,7 +509,14 @@ class _ImageFooterState extends State { color: Colors.black, ), iconSize: 24.0, - onPressed: () {}, + onPressed: () async { + var url = widget.urls[widget.currentPage]; + var type = url?.split('?')?.first?.split('.')?.last ?? 'jpg'; + var request = await HttpClient().getUrl(Uri.parse(url)); + var response = await request.close(); + var bytes = await consolidateHttpClientResponseBytes(response); + await Share.file('File', 'image.$type', bytes, 'image/$type'); + }, ), ), ], @@ -507,7 +530,12 @@ class _ImageFooterState extends State { child: Container( color: Colors.white, height: 56.0, - child: Row( + child: _loading ? Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: CircularProgressIndicator(), + ), + ) : Row( children: [ Expanded( child: Padding( @@ -553,8 +581,14 @@ class _ImageFooterState extends State { child: Padding( padding: const EdgeInsets.all(8.0), child: InkWell( - onTap: () { - sendMessage(); + onTap: () async { + modalSetState(() { + _loading = true; + }); + await sendMessage(); + modalSetState(() { + _loading = false; + }); }, child: Transform.rotate( angle: -pi / 2, @@ -607,7 +641,7 @@ class _ImageFooterState extends State { } /// Sends the current message - void sendMessage() async { + Future sendMessage() async { var text = _messageController.text.trim(); final attachments = widget.message.attachments; @@ -615,8 +649,8 @@ class _ImageFooterState extends State { _messageController.clear(); final client = StreamChat.of(context).client; - - for(var user in _selectedUsers) { + + for (var user in _selectedUsers) { var c = client.channel('messaging', extraData: { 'members': [ user.id, diff --git a/lib/src/image_header.dart b/lib/src/image_header.dart index 29c36ad9..707750c5 100644 --- a/lib/src/image_header.dart +++ b/lib/src/image_header.dart @@ -30,10 +30,15 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { final String userName; final String sentAt; + final List urls; + final currentIndex; + /// Creates a channel header ImageHeader({ Key key, this.message, + this.urls, + this.currentIndex, this.showBackButton = true, this.onBackPressed, this.onTitleTap, @@ -117,6 +122,8 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { userName: userName, sentAt: sentAt, message: message, + urls: urls, + currentIndex: currentIndex, ), ); }); diff --git a/pubspec.yaml b/pubspec.yaml index 241025d0..78add1a5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,8 @@ dependencies: flutter_slidable: ^0.5.4 carousel_slider: ^2.2.1 clipboard: ^0.1.2+8 + gallery_saver: ^2.0.1 + esys_flutter_share: ^1.0.2 flutter: assets: From 3c7b10173d80342dcba29535e973bfc79c649c94 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 20 Nov 2020 17:17:52 +0530 Subject: [PATCH 10/29] fix: Fixed icons, timing, copy button --- lib/src/full_screen_image.dart | 14 +++++++++++++- lib/src/image_actions_modal.dart | 1 - lib/src/image_footer.dart | 2 +- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/src/full_screen_image.dart b/lib/src/full_screen_image.dart index a55cb187..52e75871 100644 --- a/lib/src/full_screen_image.dart +++ b/lib/src/full_screen_image.dart @@ -107,7 +107,7 @@ class _FullScreenImageState extends State ImageHeader( userName: widget.userName, sentAt: - 'Sent at${Jiffy(widget.sentAt.toLocal()).format(' HH:mm')}', + 'Sent ${getDay(widget.message.createdAt)} at ${Jiffy(widget.sentAt.toLocal()).format('HH:mm')}', onBackPressed: () { Navigator.of(context).pop(); }, @@ -128,4 +128,16 @@ class _FullScreenImageState extends State ), ); } + + String getDay(DateTime dateTime) { + var now = DateTime.now(); + + if(DateTime(dateTime.year, dateTime.month, dateTime.day) == DateTime(now.year, now.month, now.day)) { + return 'today'; + } else if(DateTime(now.year, now.month, now.day).difference(dateTime).inHours < 24) { + return 'yesterday'; + } else { + return 'on ${Jiffy(dateTime).format("MMM do")}'; + } + } } diff --git a/lib/src/image_actions_modal.dart b/lib/src/image_actions_modal.dart index f07efcb6..f46cb008 100644 --- a/lib/src/image_actions_modal.dart +++ b/lib/src/image_actions_modal.dart @@ -122,7 +122,6 @@ class ImageActionsModal extends StatelessWidget { urls[currentIndex].split('?')[0]); Navigator.pop(context); }), - _buildButton(context, 'Copy', StreamIcons.copy, () {}), if (StreamChat.of(context).user.id == message.user.id) _buildButton(context, 'Delete', StreamIcons.delete, () { diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 57119f8e..b1cf65d3 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -86,7 +86,7 @@ class _ImageFooterState extends State { children: [ IconButton( icon: Icon( - StreamIcons.share, + StreamIcons.share_1, size: 24.0, color: Colors.black, ), From 98f3853a0828d04f641a7c3a6863d544ddabba72 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 20 Nov 2020 17:58:06 +0530 Subject: [PATCH 11/29] fix: Fixed textfield and actions color and font weight --- lib/src/image_actions_modal.dart | 2 +- lib/src/image_footer.dart | 41 +++++++++++++++++++++++++++----- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/lib/src/image_actions_modal.dart b/lib/src/image_actions_modal.dart index f46cb008..0328209e 100644 --- a/lib/src/image_actions_modal.dart +++ b/lib/src/image_actions_modal.dart @@ -150,7 +150,6 @@ class ImageActionsModal extends StatelessWidget { var titleStyle = TextStyle( fontSize: 14.5, color: Colors.black, - fontWeight: FontWeight.w500, ); return ListTile( @@ -165,6 +164,7 @@ class ImageActionsModal extends StatelessWidget { size: 24.0, ), onTap: onTap, + tileColor: Colors.white, ); } } diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index b1cf65d3..5f4710d1 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -434,22 +434,25 @@ class _ImageFooterState extends State { autofocus: true, decoration: InputDecoration( isDense: true, - prefixIcon: Icon( - StreamIcons.search, - color: Colors.black, + prefixIconConstraints: BoxConstraints.tight(Size(38.0, 38.0)), + prefixIcon: Transform.scale( + scale: 1.2, + alignment: Alignment.center, + child: Icon( + StreamIcons.search, + color: Colors.black, + ), ), hintText: 'Search', border: OutlineInputBorder( borderRadius: BorderRadius.circular(32.0), borderSide: BorderSide(color: Colors.black.withOpacity(0.08)), - gapPadding: 0.0, ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(32.0), borderSide: BorderSide(color: Colors.black.withOpacity(0.08)), - gapPadding: 0.0, ), contentPadding: EdgeInsets.zero, ), @@ -459,7 +462,7 @@ class _ImageFooterState extends State { width: 8.0, ), IconButton( - icon: Icon(StreamIcons.close_circle), + icon: Icon(StreamIcons.close_circle, color: Colors.black.withOpacity(0.5),), onPressed: () { modalSetState(() { _userSearchMode = false; @@ -679,3 +682,29 @@ class _ImageFooterState extends State { Navigator.pop(context); } } + +/// Used for clipping textfield prefix icon +class IconClipper extends CustomClipper { + @override + Path getClip(Size size) { + var leftX = size.width / 5; + var rightX = 4 * size.width / 5; + var topY = size.height / 5; + var bottomY = 4 * size.height / 5; + + final path = Path(); + path.moveTo(leftX, topY); + path.lineTo(leftX, bottomY); + path.lineTo(rightX, bottomY); + path.lineTo(rightX, topY); + path.lineTo(leftX, topY); + path.lineTo(0.0, 0.0); + path.close(); + return path; + } + + @override + bool shouldReclip(CustomClipper oldClipper) { + return false; + } +} From d9507214d772cd5be6e199a9c6662ec89d30529c Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 23 Nov 2020 16:44:44 +0530 Subject: [PATCH 12/29] fmt --- lib/src/full_screen_image.dart | 8 +- lib/src/giphy_attachment.dart | 28 ++--- lib/src/image_attachment.dart | 8 +- lib/src/image_footer.dart | 185 +++++++++++++++++---------------- lib/src/image_group.dart | 22 ++-- 5 files changed, 135 insertions(+), 116 deletions(-) diff --git a/lib/src/full_screen_image.dart b/lib/src/full_screen_image.dart index 52e75871..8ab0415f 100644 --- a/lib/src/full_screen_image.dart +++ b/lib/src/full_screen_image.dart @@ -132,9 +132,13 @@ class _FullScreenImageState extends State String getDay(DateTime dateTime) { var now = DateTime.now(); - if(DateTime(dateTime.year, dateTime.month, dateTime.day) == DateTime(now.year, now.month, now.day)) { + if (DateTime(dateTime.year, dateTime.month, dateTime.day) == + DateTime(now.year, now.month, now.day)) { return 'today'; - } else if(DateTime(now.year, now.month, now.day).difference(dateTime).inHours < 24) { + } else if (DateTime(now.year, now.month, now.day) + .difference(dateTime) + .inHours < + 24) { return 'yesterday'; } else { return 'on ${Jiffy(dateTime).format("MMM do")}'; diff --git a/lib/src/giphy_attachment.dart b/lib/src/giphy_attachment.dart index f6d1d473..bb859263 100644 --- a/lib/src/giphy_attachment.dart +++ b/lib/src/giphy_attachment.dart @@ -64,12 +64,14 @@ class GiphyAttachment extends StatelessWidget { onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) { return FullScreenImage( - urls: [attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl], - userName: message.user.name, - sentAt: message.createdAt, - message: message, + urls: [ + attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl + ], + userName: message.user.name, + sentAt: message.createdAt, + message: message, ); })); }, @@ -285,12 +287,14 @@ class GiphyAttachment extends StatelessWidget { onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) { return FullScreenImage( - urls: [attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl], - userName: message.user.name, - sentAt: message.createdAt, - message: message, + urls: [ + attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl + ], + userName: message.user.name, + sentAt: message.createdAt, + message: message, ); })); }, diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart index b429e064..83f747db 100644 --- a/lib/src/image_attachment.dart +++ b/lib/src/image_attachment.dart @@ -48,9 +48,11 @@ class ImageAttachment extends StatelessWidget { return StreamChannel( channel: channel, child: FullScreenImage( - urls: [attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl], + urls: [ + attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl + ], userName: message.user.name, sentAt: message.createdAt, message: message, diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 5f4710d1..d71ee3af 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -378,8 +378,9 @@ class _ImageFooterState extends State { child: Material( child: InkWell( onTap: () async { - await GallerySaver.saveImage( - widget.urls[widget.currentPage].split('?')[0]); + await GallerySaver.saveImage(widget + .urls[widget.currentPage] + .split('?')[0]); Navigator.pop(context); }, child: SizedBox.expand( @@ -434,7 +435,8 @@ class _ImageFooterState extends State { autofocus: true, decoration: InputDecoration( isDense: true, - prefixIconConstraints: BoxConstraints.tight(Size(38.0, 38.0)), + prefixIconConstraints: + BoxConstraints.tight(Size(38.0, 38.0)), prefixIcon: Transform.scale( scale: 1.2, alignment: Alignment.center, @@ -462,7 +464,10 @@ class _ImageFooterState extends State { width: 8.0, ), IconButton( - icon: Icon(StreamIcons.close_circle, color: Colors.black.withOpacity(0.5),), + icon: Icon( + StreamIcons.close_circle, + color: Colors.black.withOpacity(0.5), + ), onPressed: () { modalSetState(() { _userSearchMode = false; @@ -533,98 +538,100 @@ class _ImageFooterState extends State { child: Container( color: Colors.white, height: 56.0, - child: _loading ? Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: CircularProgressIndicator(), - ), - ) : Row( - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.only(left: 8.0), - child: TextField( - controller: _messageController, - onChanged: (val) { - modalSetState(() {}); - }, - onTap: () { - modalSetState(() {}); - setState(() {}); - }, - decoration: InputDecoration( - isDense: true, - prefixText: ' ', - hintText: 'Add a comment', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(32.0), - borderSide: BorderSide( - color: Colors.black.withOpacity(0.16), - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(32.0), - borderSide: BorderSide( - color: Colors.black.withOpacity(0.16), - )), - contentPadding: EdgeInsets.symmetric(vertical: 12.0), - ), + child: _loading + ? Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: CircularProgressIndicator(), ), - ), - ), - AnimatedCrossFade( - crossFadeState: _messageController.text.isNotEmpty - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: IconTheme( - data: StreamChatTheme.of(context) - .channelTheme - .messageInputButtonIconTheme, - child: Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: InkWell( - onTap: () async { - modalSetState(() { - _loading = true; - }); - await sendMessage(); - modalSetState(() { - _loading = false; - }); - }, - child: Transform.rotate( - angle: -pi / 2, - child: Icon( - StreamIcons.send_message, - color: StreamChatTheme.of(context).accentColor, + ) + : Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 8.0), + child: TextField( + controller: _messageController, + onChanged: (val) { + modalSetState(() {}); + }, + onTap: () { + modalSetState(() {}); + setState(() {}); + }, + decoration: InputDecoration( + isDense: true, + prefixText: ' ', + hintText: 'Add a comment', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(32.0), + borderSide: BorderSide( + color: Colors.black.withOpacity(0.16), + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(32.0), + borderSide: BorderSide( + color: Colors.black.withOpacity(0.16), + )), + contentPadding: EdgeInsets.symmetric(vertical: 12.0), ), ), ), ), - ), - ), - secondChild: IconTheme( - data: StreamChatTheme.of(context) - .channelTheme - .messageInputButtonIconTheme, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: InkWell( - onTap: () {}, - child: Icon( - StreamIcons.send_message, - color: Colors.grey, + AnimatedCrossFade( + crossFadeState: _messageController.text.isNotEmpty + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + firstChild: IconTheme( + data: StreamChatTheme.of(context) + .channelTheme + .messageInputButtonIconTheme, + child: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: InkWell( + onTap: () async { + modalSetState(() { + _loading = true; + }); + await sendMessage(); + modalSetState(() { + _loading = false; + }); + }, + child: Transform.rotate( + angle: -pi / 2, + child: Icon( + StreamIcons.send_message, + color: StreamChatTheme.of(context).accentColor, + ), + ), + ), + ), + ), ), - )), - ), + secondChild: IconTheme( + data: StreamChatTheme.of(context) + .channelTheme + .messageInputButtonIconTheme, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: InkWell( + onTap: () {}, + child: Icon( + StreamIcons.send_message, + color: Colors.grey, + ), + )), + ), + ), + duration: Duration(milliseconds: 300), + alignment: Alignment.center, + ), + ], ), - duration: Duration(milliseconds: 300), - alignment: Alignment.center, - ), - ], - ), ), ); } diff --git a/lib/src/image_group.dart b/lib/src/image_group.dart index afccaf7a..1b6e48f2 100644 --- a/lib/src/image_group.dart +++ b/lib/src/image_group.dart @@ -113,16 +113,18 @@ class ImageGroup extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => - StreamChannel( - channel: channel, - child: FullScreenImage(urls: images.map((e) => e.imageUrl ?? e.thumbUrl ?? e.assetUrl).toList(), - startIndex: index, - userName: message.user.name, - sentAt: message.createdAt, - message: message, - ), - ), + builder: (context) => StreamChannel( + channel: channel, + child: FullScreenImage( + urls: images + .map((e) => e.imageUrl ?? e.thumbUrl ?? e.assetUrl) + .toList(), + startIndex: index, + userName: message.user.name, + sentAt: message.createdAt, + message: message, + ), + ), ), ); } From 8640f2b4f91b1376afa00115408747a44e74e729 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 24 Nov 2020 14:21:08 +0530 Subject: [PATCH 13/29] fix: Fixed listtile splash --- lib/src/image_actions_modal.dart | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/src/image_actions_modal.dart b/lib/src/image_actions_modal.dart index 0328209e..1bfb6aa3 100644 --- a/lib/src/image_actions_modal.dart +++ b/lib/src/image_actions_modal.dart @@ -152,19 +152,22 @@ class ImageActionsModal extends StatelessWidget { color: Colors.black, ); - return ListTile( - dense: true, - title: Text( - title, - style: color == null ? titleStyle : titleStyle.copyWith(color: color), + return Material( + child: InkWell( + onTap: onTap, + child: ListTile( + dense: true, + title: Text( + title, + style: color == null ? titleStyle : titleStyle.copyWith(color: color), + ), + leading: Icon( + iconData, + color: color ?? StreamChatTheme.of(context).primaryIconTheme.color, + size: 24.0, + ), + ), ), - leading: Icon( - iconData, - color: color ?? StreamChatTheme.of(context).primaryIconTheme.color, - size: 24.0, - ), - onTap: onTap, - tileColor: Colors.white, ); } } From 146204ed3a3f27bed9bfec76462b52a14af27496 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 24 Nov 2020 16:32:50 +0530 Subject: [PATCH 14/29] fix: giphy fix, fmt --- lib/src/giphy_attachment.dart | 46 +++++++++++++++++++------------- lib/src/image_actions_modal.dart | 3 ++- 2 files changed, 30 insertions(+), 19 deletions(-) diff --git a/lib/src/giphy_attachment.dart b/lib/src/giphy_attachment.dart index bb859263..43e0ce68 100644 --- a/lib/src/giphy_attachment.dart +++ b/lib/src/giphy_attachment.dart @@ -63,15 +63,20 @@ class GiphyAttachment extends StatelessWidget { child: GestureDetector( onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) { - return FullScreenImage( - urls: [ - attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl - ], - userName: message.user.name, - sentAt: message.createdAt, - message: message, + final channel = StreamChannel.of(context).channel; + + return StreamChannel( + channel: channel, + child: FullScreenImage( + urls: [ + attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl + ], + userName: message.user.name, + sentAt: message.createdAt, + message: message, + ), ); })); }, @@ -286,15 +291,20 @@ class GiphyAttachment extends StatelessWidget { child: GestureDetector( onTap: () { Navigator.push(context, MaterialPageRoute(builder: (_) { - return FullScreenImage( - urls: [ - attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl - ], - userName: message.user.name, - sentAt: message.createdAt, - message: message, + var channel = StreamChannel.of(context).channel; + + return StreamChannel( + channel: channel, + child: FullScreenImage( + urls: [ + attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl + ], + userName: message.user.name, + sentAt: message.createdAt, + message: message, + ), ); })); }, diff --git a/lib/src/image_actions_modal.dart b/lib/src/image_actions_modal.dart index 1bfb6aa3..c1163af8 100644 --- a/lib/src/image_actions_modal.dart +++ b/lib/src/image_actions_modal.dart @@ -159,7 +159,8 @@ class ImageActionsModal extends StatelessWidget { dense: true, title: Text( title, - style: color == null ? titleStyle : titleStyle.copyWith(color: color), + style: + color == null ? titleStyle : titleStyle.copyWith(color: color), ), leading: Icon( iconData, From f409845818f3feaf65edf39538898fda122eaae0 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 26 Nov 2020 18:32:46 +0530 Subject: [PATCH 15/29] feat: Now compatible with videos --- lib/src/full_screen_image.dart | 169 +++++++++++++++++++++++-------- lib/src/giphy_attachment.dart | 16 ++- lib/src/image_actions_modal.dart | 25 ++++- lib/src/image_attachment.dart | 8 +- lib/src/image_footer.dart | 19 +++- lib/src/image_group.dart | 6 +- lib/src/image_header.dart | 2 +- lib/src/message_widget.dart | 1 + lib/src/video_attachment.dart | 10 +- 9 files changed, 186 insertions(+), 70 deletions(-) diff --git a/lib/src/full_screen_image.dart b/lib/src/full_screen_image.dart index 8ab0415f..4ddc8513 100644 --- a/lib/src/full_screen_image.dart +++ b/lib/src/full_screen_image.dart @@ -1,16 +1,18 @@ import 'package:cached_network_image/cached_network_image.dart'; +import 'package:chewie/chewie.dart'; import 'package:flutter/material.dart'; 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:video_player/video_player.dart'; import '../stream_chat_flutter.dart'; /// A full screen image widget -class FullScreenImage extends StatefulWidget { +class FullScreenMedia extends StatefulWidget { /// The url of the image - final List urls; + final List mediaAttachments; final Message message; final int startIndex; @@ -18,9 +20,9 @@ class FullScreenImage extends StatefulWidget { final DateTime sentAt; /// Instantiate a new FullScreenImage - const FullScreenImage({ + const FullScreenMedia({ Key key, - @required this.urls, + @required this.mediaAttachments, this.message, this.startIndex = 0, this.userName = '', @@ -28,10 +30,10 @@ class FullScreenImage extends StatefulWidget { }) : super(key: key); @override - _FullScreenImageState createState() => _FullScreenImageState(); + _FullScreenMediaState createState() => _FullScreenMediaState(); } -class _FullScreenImageState extends State +class _FullScreenMediaState extends State with SingleTickerProviderStateMixin { bool _optionsShown = true; @@ -40,6 +42,8 @@ class _FullScreenImageState extends State int _currentPage; + List videoPackages = []; + @override void initState() { super.initState(); @@ -47,6 +51,14 @@ class _FullScreenImageState extends State AnimationController(vsync: this, duration: Duration(milliseconds: 300)); _pageController = PageController(initialPage: widget.startIndex); _currentPage = widget.startIndex; + widget.mediaAttachments + .where((element) => element.type == 'video') + .toList() + .forEach((element) { + videoPackages.add(VideoPackage(context, element, () { + setState(() {}); + })); + }); } @override @@ -66,36 +78,63 @@ class _FullScreenImageState extends State }); }, itemBuilder: (context, position) { - return PhotoView( - imageProvider: - CachedNetworkImageProvider(widget.urls[position]), - maxScale: PhotoViewComputedScale.covered, - minScale: PhotoViewComputedScale.contained, - heroAttributes: PhotoViewHeroAttributes( - tag: widget.urls, - ), - 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(); - } - }, - ); + if (widget.mediaAttachments[position].type == 'image') { + return PhotoView( + imageProvider: CachedNetworkImageProvider( + widget.mediaAttachments[position].imageUrl ?? + widget.mediaAttachments[position].assetUrl ?? + widget.mediaAttachments[position].thumbUrl), + 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 (widget.mediaAttachments[position].type == + 'video') { + if (!videoPackages[position].initialised) { + return Center( + child: CircularProgressIndicator(), + ); + } + return InkWell( + onTap: () { + setState(() { + _optionsShown = !_optionsShown; + }); + if (_controller.isCompleted) { + _controller.reverse(); + } else { + _controller.forward(); + } + }, + child: Chewie( + controller: videoPackages[position].chewieController, + ), + ); + } + return Container(); }, - itemCount: widget.urls.length, + itemCount: widget.mediaAttachments.length, ); }), AnimatedOpacity( @@ -106,19 +145,20 @@ class _FullScreenImageState extends State children: [ ImageHeader( userName: widget.userName, - sentAt: - 'Sent ${getDay(widget.message.createdAt)} at ${Jiffy(widget.sentAt.toLocal()).format('HH:mm')}', + sentAt: widget.message.createdAt == null + ? '' + : 'Sent ${getDay(widget.message.createdAt)} at ${Jiffy(widget.sentAt.toLocal()).format('HH:mm')}', onBackPressed: () { Navigator.of(context).pop(); }, message: widget.message, - urls: widget.urls, + urls: widget.mediaAttachments, currentIndex: _currentPage, ), ImageFooter( currentPage: _currentPage, - totalPages: widget.urls.length, - urls: widget.urls, + totalPages: widget.mediaAttachments.length, + urls: widget.mediaAttachments, message: widget.message, ), ], @@ -144,4 +184,53 @@ class _FullScreenImageState extends State return 'on ${Jiffy(dateTime).format("MMM do")}'; } } + + @override + void dispose() { + videoPackages.forEach((element) { + element.dispose(); + }); + super.dispose(); + } +} + +class VideoPackage { + VideoPlayerController _videoPlayerController; + ChewieController _chewieController; + bool initialised = false; + VoidCallback onInit; + BuildContext context; + + /// + VideoPackage(this.context, Attachment attachment, this.onInit) { + _videoPlayerController = VideoPlayerController.network(attachment.assetUrl); + _videoPlayerController.initialize().whenComplete(() { + initialised = true; + _chewieController = ChewieController( + videoPlayerController: _videoPlayerController, + autoInitialize: false, + aspectRatio: _videoPlayerController.value.aspectRatio, + ); + onInit(); + }); + + VoidCallback errorListener; + errorListener = () { + if (_videoPlayerController.value.hasError) { + Navigator.pop(context); + launchURL(context, attachment.titleLink); + } + _videoPlayerController.removeListener(errorListener); + }; + _videoPlayerController.addListener(errorListener); + } + + get videoPlayer => _videoPlayerController; + + get chewieController => _chewieController; + + void dispose() { + _videoPlayerController.dispose(); + _chewieController.dispose(); + } } diff --git a/lib/src/giphy_attachment.dart b/lib/src/giphy_attachment.dart index 43e0ce68..323a3ae1 100644 --- a/lib/src/giphy_attachment.dart +++ b/lib/src/giphy_attachment.dart @@ -67,11 +67,9 @@ class GiphyAttachment extends StatelessWidget { return StreamChannel( channel: channel, - child: FullScreenImage( - urls: [ - attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl + child: FullScreenMedia( + mediaAttachments: [ + attachment, ], userName: message.user.name, sentAt: message.createdAt, @@ -295,11 +293,9 @@ class GiphyAttachment extends StatelessWidget { return StreamChannel( channel: channel, - child: FullScreenImage( - urls: [ - attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl + child: FullScreenMedia( + mediaAttachments: [ + attachment, ], userName: message.user.name, sentAt: message.createdAt, diff --git a/lib/src/image_actions_modal.dart b/lib/src/image_actions_modal.dart index c1163af8..415fc22d 100644 --- a/lib/src/image_actions_modal.dart +++ b/lib/src/image_actions_modal.dart @@ -1,16 +1,22 @@ +import 'dart:io'; import 'dart:ui'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:gallery_saver/gallery_saver.dart'; import '../stream_chat_flutter.dart'; import 'stream_icons.dart'; +import 'package:http/http.dart' as http; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart'; + class ImageActionsModal extends StatelessWidget { final Message message; final String userName; final String sentAt; - final List urls; + final List urls; final currentIndex; ImageActionsModal( @@ -118,8 +124,11 @@ class ImageActionsModal extends StatelessWidget { }), _buildButton(context, 'Save Image', StreamIcons.save_1, () async { - await GallerySaver.saveImage( - urls[currentIndex].split('?')[0]); + var url = urls[currentIndex].imageUrl ?? + urls[currentIndex].assetUrl ?? + urls[currentIndex].thumbUrl; + var file = await _downloadFile(url); + Navigator.pop(context); }), if (StreamChat.of(context).user.id == message.user.id) @@ -171,4 +180,14 @@ class ImageActionsModal extends StatelessWidget { ), ); } + + static Future _downloadFile(String url) async { + var _client = http.Client(); + var req = await _client.get(Uri.parse(url)); + var bytes = req.bodyBytes; + var dir = (await getTemporaryDirectory()).path; + var file = File('$dir/${basename(url)}'); + await file.writeAsBytes(bytes); + return file; + } } diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart index 83f747db..a7dde6ef 100644 --- a/lib/src/image_attachment.dart +++ b/lib/src/image_attachment.dart @@ -47,11 +47,9 @@ class ImageAttachment extends StatelessWidget { return StreamChannel( channel: channel, - child: FullScreenImage( - urls: [ - attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl + child: FullScreenMedia( + mediaAttachments: [ + attachment, ], userName: message.user.name, sentAt: message.createdAt, diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index d71ee3af..459e7b26 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -36,7 +36,7 @@ class ImageFooter extends StatefulWidget { final int currentPage; final int totalPages; - final List urls; + final List urls; final Message message; /// Creates a channel header @@ -186,7 +186,9 @@ class _ImageFooterState extends State { padding: const EdgeInsets.all(1.0), child: AspectRatio( child: CachedNetworkImage( - imageUrl: widget.urls[position], + imageUrl: widget.urls[position].imageUrl ?? + widget.urls[position].assetUrl ?? + widget.urls[position].thumbUrl, fit: BoxFit.cover, ), aspectRatio: 1.0, @@ -378,8 +380,13 @@ class _ImageFooterState extends State { child: Material( child: InkWell( onTap: () async { - await GallerySaver.saveImage(widget - .urls[widget.currentPage] + await GallerySaver.saveImage((widget + .urls[widget.currentPage] + .imageUrl ?? + widget.urls[widget.currentPage] + .assetUrl ?? + widget.urls[widget.currentPage] + .thumbUrl) .split('?')[0]); Navigator.pop(context); }, @@ -518,7 +525,9 @@ class _ImageFooterState extends State { ), iconSize: 24.0, onPressed: () async { - var url = widget.urls[widget.currentPage]; + var url = widget.urls[widget.currentPage].imageUrl ?? + widget.urls[widget.currentPage].assetUrl ?? + widget.urls[widget.currentPage].thumbUrl; var type = url?.split('?')?.first?.split('.')?.last ?? 'jpg'; var request = await HttpClient().getUrl(Uri.parse(url)); var response = await request.close(); diff --git a/lib/src/image_group.dart b/lib/src/image_group.dart index 1b6e48f2..6702e839 100644 --- a/lib/src/image_group.dart +++ b/lib/src/image_group.dart @@ -115,10 +115,8 @@ class ImageGroup extends StatelessWidget { MaterialPageRoute( builder: (context) => StreamChannel( channel: channel, - child: FullScreenImage( - urls: images - .map((e) => e.imageUrl ?? e.thumbUrl ?? e.assetUrl) - .toList(), + child: FullScreenMedia( + mediaAttachments: images, startIndex: index, userName: message.user.name, sentAt: message.createdAt, diff --git a/lib/src/image_header.dart b/lib/src/image_header.dart index 707750c5..77f56460 100644 --- a/lib/src/image_header.dart +++ b/lib/src/image_header.dart @@ -30,7 +30,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { final String userName; final String sentAt; - final List urls; + final List urls; final currentIndex; /// Creates a channel header diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 86479ebe..53bc454e 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -180,6 +180,7 @@ class MessageWidget extends StatefulWidget { MediaQuery.of(context).size.width * 0.8, MediaQuery.of(context).size.height * 0.3, ), + message: message, ); }, 'giphy': (context, message, attachment) { diff --git a/lib/src/video_attachment.dart b/lib/src/video_attachment.dart index b0d1f60d..b6caed53 100644 --- a/lib/src/video_attachment.dart +++ b/lib/src/video_attachment.dart @@ -1,6 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:chewie/chewie.dart'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/full_screen_image.dart'; import 'package:stream_chat_flutter/src/full_screen_video.dart'; import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -13,11 +14,13 @@ class VideoAttachment extends StatefulWidget { final Attachment attachment; final MessageTheme messageTheme; final Size size; + final Message message; VideoAttachment({ Key key, @required this.attachment, @required this.messageTheme, + this.message, this.size, }) : super(key: key); @@ -84,8 +87,11 @@ class _VideoAttachmentState extends State { Navigator.push( context, MaterialPageRoute( - builder: (_) => FullScreenVideo( - attachment: widget.attachment, + builder: (_) => FullScreenMedia( + mediaAttachments: [widget.attachment], + userName: widget.message.user.name, + sentAt: widget.message.createdAt, + message: widget.message, ), ), ); From 8922ab62415df4287a56233c670d8f9538beb921 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 26 Nov 2020 18:35:20 +0530 Subject: [PATCH 16/29] hotfix: giphy --- lib/src/full_screen_image.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/full_screen_image.dart b/lib/src/full_screen_image.dart index 4ddc8513..ba2b56bf 100644 --- a/lib/src/full_screen_image.dart +++ b/lib/src/full_screen_image.dart @@ -78,7 +78,8 @@ class _FullScreenMediaState extends State }); }, itemBuilder: (context, position) { - if (widget.mediaAttachments[position].type == 'image') { + print(widget.mediaAttachments[position].type); + if (widget.mediaAttachments[position].type == 'image' || widget.mediaAttachments[position].type == 'giphy') { return PhotoView( imageProvider: CachedNetworkImageProvider( widget.mediaAttachments[position].imageUrl ?? From fd9c7e9045c1bc1f1888ad7439dc5363133d4d81 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 26 Nov 2020 20:42:05 +0530 Subject: [PATCH 17/29] feat: Fixed download --- ...reen_image.dart => full_screen_media.dart} | 3 +- lib/src/full_screen_video.dart | 84 ------------------- lib/src/giphy_attachment.dart | 2 +- lib/src/image_actions_modal.dart | 46 ++++++---- lib/src/image_attachment.dart | 2 +- lib/src/image_footer.dart | 47 ++++++++--- lib/src/image_group.dart | 2 +- lib/src/video_attachment.dart | 18 ++-- lib/stream_chat_flutter.dart | 4 +- pubspec.yaml | 2 +- 10 files changed, 88 insertions(+), 122 deletions(-) rename lib/src/{full_screen_image.dart => full_screen_media.dart} (98%) delete mode 100644 lib/src/full_screen_video.dart diff --git a/lib/src/full_screen_image.dart b/lib/src/full_screen_media.dart similarity index 98% rename from lib/src/full_screen_image.dart rename to lib/src/full_screen_media.dart index ba2b56bf..88f8d2b9 100644 --- a/lib/src/full_screen_image.dart +++ b/lib/src/full_screen_media.dart @@ -79,7 +79,8 @@ class _FullScreenMediaState extends State }, itemBuilder: (context, position) { print(widget.mediaAttachments[position].type); - if (widget.mediaAttachments[position].type == 'image' || widget.mediaAttachments[position].type == 'giphy') { + if (widget.mediaAttachments[position].type == 'image' || + widget.mediaAttachments[position].type == 'giphy') { return PhotoView( imageProvider: CachedNetworkImageProvider( widget.mediaAttachments[position].imageUrl ?? diff --git a/lib/src/full_screen_video.dart b/lib/src/full_screen_video.dart deleted file mode 100644 index 98729243..00000000 --- a/lib/src/full_screen_video.dart +++ /dev/null @@ -1,84 +0,0 @@ -import 'package:chewie/chewie.dart'; -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:video_player/video_player.dart'; - -import 'utils.dart'; - -class FullScreenVideo extends StatefulWidget { - final Attachment attachment; - - FullScreenVideo({ - Key key, - @required this.attachment, - }) : super(key: key); - - @override - _FullScreenVideoState createState() => _FullScreenVideoState(); -} - -class _FullScreenVideoState extends State { - ChewieController _chewieController; - VideoPlayerController _videoPlayerController; - bool initialized = false; - final GlobalKey _scaffoldKey = GlobalKey(); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Colors.black, - iconTheme: IconThemeData( - color: Colors.white, - ), - ), - body: Builder( - key: _scaffoldKey, - builder: (context) { - if (!initialized) { - return Center( - child: CircularProgressIndicator(), - ); - } - return Chewie( - controller: _chewieController, - ); - }, - ), - ); - } - - @override - void initState() { - super.initState(); - _videoPlayerController = - VideoPlayerController.network(widget.attachment.assetUrl); - _videoPlayerController.initialize().whenComplete(() { - setState(() { - initialized = true; - _chewieController = ChewieController( - videoPlayerController: _videoPlayerController, - autoInitialize: false, - aspectRatio: _videoPlayerController.value.aspectRatio, - ); - }); - }); - - VoidCallback errorListener; - errorListener = () { - if (_videoPlayerController.value.hasError) { - Navigator.pop(context); - launchURL(_scaffoldKey.currentContext, widget.attachment.titleLink); - } - _videoPlayerController.removeListener(errorListener); - }; - _videoPlayerController.addListener(errorListener); - } - - @override - void dispose() { - _videoPlayerController?.dispose(); - _chewieController?.dispose(); - super.dispose(); - } -} diff --git a/lib/src/giphy_attachment.dart b/lib/src/giphy_attachment.dart index 323a3ae1..8f02b8a8 100644 --- a/lib/src/giphy_attachment.dart +++ b/lib/src/giphy_attachment.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import '../stream_chat_flutter.dart'; import 'attachment_error.dart'; -import 'full_screen_image.dart'; +import 'full_screen_media.dart'; class GiphyAttachment extends StatelessWidget { final Attachment attachment; diff --git a/lib/src/image_actions_modal.dart b/lib/src/image_actions_modal.dart index 415fc22d..c3f699a5 100644 --- a/lib/src/image_actions_modal.dart +++ b/lib/src/image_actions_modal.dart @@ -1,10 +1,10 @@ -import 'dart:io'; +import 'dart:typed_data'; import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:gallery_saver/gallery_saver.dart'; - +import 'package:dio/dio.dart'; +import 'package:image_gallery_saver/image_gallery_saver.dart'; import '../stream_chat_flutter.dart'; import 'stream_icons.dart'; @@ -122,14 +122,21 @@ class ImageActionsModal extends StatelessWidget { Navigator.pop(context); Navigator.pop(context); }), - _buildButton(context, 'Save Image', StreamIcons.save_1, - () async { + _buildButton( + context, + 'Save ${urls[currentIndex].type == 'video' ? 'Video' : 'Image'}', + StreamIcons.save_1, () async { var url = urls[currentIndex].imageUrl ?? urls[currentIndex].assetUrl ?? urls[currentIndex].thumbUrl; - var file = await _downloadFile(url); - Navigator.pop(context); + if (urls[currentIndex].type == 'video') { + await _saveVideo(url); + Navigator.pop(context); + } else { + await _saveImage(url); + Navigator.pop(context); + } }), if (StreamChat.of(context).user.id == message.user.id) _buildButton(context, 'Delete', StreamIcons.delete, @@ -181,13 +188,22 @@ class ImageActionsModal extends StatelessWidget { ); } - static Future _downloadFile(String url) async { - var _client = http.Client(); - var req = await _client.get(Uri.parse(url)); - var bytes = req.bodyBytes; - var dir = (await getTemporaryDirectory()).path; - var file = File('$dir/${basename(url)}'); - await file.writeAsBytes(bytes); - return file; + Future _saveImage(String url) async { + var response = await Dio() + .get(url, options: Options(responseType: ResponseType.bytes)); + final result = await ImageGallerySaver.saveImage( + Uint8List.fromList(response.data), + quality: 60, + name: "${DateTime.now().millisecondsSinceEpoch}"); + return result; + } + + Future _saveVideo(String url) async { + var appDocDir = await getTemporaryDirectory(); + var savePath = + appDocDir.path + "/${DateTime.now().millisecondsSinceEpoch}.mp4"; + await Dio().download(url, savePath); + final result = await ImageGallerySaver.saveFile(savePath); + print(result); } } diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart index a7dde6ef..2345b2b8 100644 --- a/lib/src/image_attachment.dart +++ b/lib/src/image_attachment.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import '../stream_chat_flutter.dart'; import 'attachment_error.dart'; import 'attachment_title.dart'; -import 'full_screen_image.dart'; +import 'full_screen_media.dart'; import 'utils.dart'; class ImageAttachment extends StatelessWidget { diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 459e7b26..ed82e09a 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -6,7 +6,10 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:esys_flutter_share/esys_flutter_share.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:gallery_saver/gallery_saver.dart'; +import 'package:dio/dio.dart'; +import 'dart:typed_data'; +import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/back_button.dart'; import 'package:stream_chat_flutter/src/channel_info.dart'; @@ -380,15 +383,20 @@ class _ImageFooterState extends State { child: Material( child: InkWell( onTap: () async { - await GallerySaver.saveImage((widget - .urls[widget.currentPage] - .imageUrl ?? - widget.urls[widget.currentPage] - .assetUrl ?? - widget.urls[widget.currentPage] - .thumbUrl) - .split('?')[0]); - Navigator.pop(context); + var url = widget + .urls[widget.currentPage].imageUrl ?? + widget + .urls[widget.currentPage].assetUrl ?? + widget.urls[widget.currentPage].thumbUrl; + + if (widget.urls[widget.currentPage].type == + 'video') { + await _saveVideo(url); + Navigator.pop(context); + } else { + await _saveImage(url); + Navigator.pop(context); + } }, child: SizedBox.expand( child: Center( @@ -697,6 +705,25 @@ class _ImageFooterState extends State { _selectedUsers.clear(); Navigator.pop(context); } + + Future _saveImage(String url) async { + var response = await Dio() + .get(url, options: Options(responseType: ResponseType.bytes)); + final result = await ImageGallerySaver.saveImage( + Uint8List.fromList(response.data), + quality: 60, + name: "${DateTime.now().millisecondsSinceEpoch}"); + return result; + } + + Future _saveVideo(String url) async { + var appDocDir = await getTemporaryDirectory(); + var savePath = + appDocDir.path + "/${DateTime.now().millisecondsSinceEpoch}.mp4"; + await Dio().download(url, savePath); + final result = await ImageGallerySaver.saveFile(savePath); + print(result); + } } /// Used for clipping textfield prefix icon diff --git a/lib/src/image_group.dart b/lib/src/image_group.dart index 6702e839..44c15623 100644 --- a/lib/src/image_group.dart +++ b/lib/src/image_group.dart @@ -3,7 +3,7 @@ import 'package:carousel_slider/carousel_options.dart'; import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter/src/full_screen_image.dart'; +import 'package:stream_chat_flutter/src/full_screen_media.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class ImageGroup extends StatelessWidget { diff --git a/lib/src/video_attachment.dart b/lib/src/video_attachment.dart index b6caed53..782b81cb 100644 --- a/lib/src/video_attachment.dart +++ b/lib/src/video_attachment.dart @@ -1,8 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:chewie/chewie.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/full_screen_image.dart'; -import 'package:stream_chat_flutter/src/full_screen_video.dart'; +import 'package:stream_chat_flutter/src/full_screen_media.dart'; import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:video_player/video_player.dart'; @@ -84,14 +83,19 @@ class _VideoAttachmentState extends State { return GestureDetector( onTap: () { + final channel = StreamChannel.of(context).channel; + Navigator.push( context, MaterialPageRoute( - builder: (_) => FullScreenMedia( - mediaAttachments: [widget.attachment], - userName: widget.message.user.name, - sentAt: widget.message.createdAt, - message: widget.message, + builder: (_) => StreamChannel( + channel: channel, + child: FullScreenMedia( + mediaAttachments: [widget.attachment], + userName: widget.message.user.name, + sentAt: widget.message.createdAt, + message: widget.message, + ), ), ), ); diff --git a/lib/stream_chat_flutter.dart b/lib/stream_chat_flutter.dart index 91ac68c8..fcb34121 100644 --- a/lib/stream_chat_flutter.dart +++ b/lib/stream_chat_flutter.dart @@ -10,7 +10,9 @@ export 'src/channels_bloc.dart'; export 'src/date_divider.dart'; export 'src/deleted_message.dart'; export 'src/file_attachment.dart'; -export 'src/full_screen_video.dart'; +export 'src/full_screen_media.dart'; +export 'src/image_header.dart'; +export 'src/image_footer.dart'; export 'src/giphy_attachment.dart'; export 'src/image_attachment.dart'; export 'src/message_input.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 012a0293..b7e11a5b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,7 +36,7 @@ dependencies: flutter_slidable: ^0.5.4 carousel_slider: ^2.2.1 clipboard: ^0.1.2+8 - gallery_saver: ^2.0.1 + image_gallery_saver: ^1.6.6 esys_flutter_share: ^1.0.2 media_gallery: git: https://github.com/imtoori/media_gallery.git From e746a6e16f2f536955a82620a3ef4463a2bd0d89 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 26 Nov 2020 21:05:32 +0530 Subject: [PATCH 18/29] rfac: cleanup --- lib/src/image_actions_modal.dart | 4 ---- lib/src/image_footer.dart | 7 ------- lib/src/image_header.dart | 6 ------ 3 files changed, 17 deletions(-) diff --git a/lib/src/image_actions_modal.dart b/lib/src/image_actions_modal.dart index c3f699a5..19f27055 100644 --- a/lib/src/image_actions_modal.dart +++ b/lib/src/image_actions_modal.dart @@ -6,11 +6,7 @@ import 'package:flutter/material.dart'; import 'package:dio/dio.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; import '../stream_chat_flutter.dart'; -import 'stream_icons.dart'; - -import 'package:http/http.dart' as http; import 'package:path_provider/path_provider.dart'; -import 'package:path/path.dart'; class ImageActionsModal extends StatelessWidget { final Message message; diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index ed82e09a..8d2bf30f 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -7,22 +7,15 @@ import 'package:esys_flutter_share/esys_flutter_share.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:dio/dio.dart'; -import 'dart:typed_data'; import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:path_provider/path_provider.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter/src/back_button.dart'; -import 'package:stream_chat_flutter/src/channel_info.dart'; -import 'package:stream_chat_flutter/src/channel_name.dart'; -import 'package:stream_chat_flutter/src/message_input.dart'; import 'package:stream_chat_flutter/src/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_icons.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import './channel_name.dart'; -import 'channel_image.dart'; import 'stream_channel.dart'; class ImageFooter extends StatefulWidget { diff --git a/lib/src/image_header.dart b/lib/src/image_header.dart index 77f56460..512d8b14 100644 --- a/lib/src/image_header.dart +++ b/lib/src/image_header.dart @@ -1,13 +1,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter/src/back_button.dart'; -import 'package:stream_chat_flutter/src/channel_info.dart'; -import 'package:stream_chat_flutter/src/channel_name.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_icons.dart'; - -import './channel_name.dart'; -import 'channel_image.dart'; import 'image_actions_modal.dart'; import 'stream_channel.dart'; From 3d1ea5d5881e4768385ef973bf73a5f0f0586aa3 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 30 Nov 2020 14:23:57 +0530 Subject: [PATCH 19/29] feat: Switched to svgicon --- lib/src/image_actions_modal.dart | 44 ++++++++++++------- lib/src/image_footer.dart | 37 ++++++---------- lib/src/image_header.dart | 9 ++-- lib/src/stream_svg_icon.dart | 72 ++++++++++++++++++++++++++++++++ lib/svgs/Icon_menu_point_v.svg | 5 +++ lib/svgs/Icon_save.svg | 4 ++ lib/svgs/Icon_send_message.svg | 3 ++ 7 files changed, 129 insertions(+), 45 deletions(-) create mode 100644 lib/svgs/Icon_menu_point_v.svg create mode 100644 lib/svgs/Icon_save.svg create mode 100644 lib/svgs/Icon_send_message.svg diff --git a/lib/src/image_actions_modal.dart b/lib/src/image_actions_modal.dart index 19f27055..7e50a4f7 100644 --- a/lib/src/image_actions_modal.dart +++ b/lib/src/image_actions_modal.dart @@ -83,8 +83,7 @@ class ImageActionsModal extends StatelessWidget { ], ), IconButton( - icon: Icon( - StreamIcons.close, + icon: StreamSvgIcon.close( size: 24.0, color: Colors.white, ), @@ -111,17 +110,31 @@ class ImageActionsModal extends StatelessWidget { children: ListTile.divideTiles( context: context, tiles: [ - _buildButton(context, 'Reply', - StreamIcons.curve_line_down_left, () {}), - _buildButton(context, 'Show in Chat', StreamIcons.eye, - () { + _buildButton( + context, + 'Reply', + StreamSvgIcon.Icon_curve_line_left_up( + size: 24.0, + color: Colors.black.withOpacity(0.5), + ), + () {}), + _buildButton( + context, + 'Show in Chat', + StreamSvgIcon.eye( + size: 24.0, + color: Colors.black, + ), () { Navigator.pop(context); Navigator.pop(context); }), _buildButton( context, 'Save ${urls[currentIndex].type == 'video' ? 'Video' : 'Image'}', - StreamIcons.save_1, () async { + StreamSvgIcon.Icon_save( + size: 24.0, + color: Colors.black.withOpacity(0.5), + ), () async { var url = urls[currentIndex].imageUrl ?? urls[currentIndex].assetUrl ?? urls[currentIndex].thumbUrl; @@ -135,8 +148,13 @@ class ImageActionsModal extends StatelessWidget { } }), if (StreamChat.of(context).user.id == message.user.id) - _buildButton(context, 'Delete', StreamIcons.delete, - () { + _buildButton( + context, + 'Delete', + StreamSvgIcon.delete( + size: 24.0, + color: Colors.black, + ), () { Navigator.pop(context); Navigator.pop(context); StreamChat.of(context).client.deleteMessage( @@ -157,7 +175,7 @@ class ImageActionsModal extends StatelessWidget { } Widget _buildButton( - context, String title, IconData iconData, VoidCallback onTap, + context, String title, StreamSvgIcon icon, VoidCallback onTap, {Color color}) { var titleStyle = TextStyle( fontSize: 14.5, @@ -174,11 +192,7 @@ class ImageActionsModal extends StatelessWidget { style: color == null ? titleStyle : titleStyle.copyWith(color: color), ), - leading: Icon( - iconData, - color: color ?? StreamChatTheme.of(context).primaryIconTheme.color, - size: 24.0, - ), + leading: icon, ), ), ); diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 8d2bf30f..5461e139 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -12,7 +12,6 @@ import 'package:path_provider/path_provider.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; -import 'package:stream_chat_flutter/src/stream_icons.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -81,8 +80,7 @@ class _ImageFooterState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( - icon: Icon( - StreamIcons.share_1, + icon: StreamSvgIcon.icon_SHARE( size: 24.0, color: Colors.black, ), @@ -111,8 +109,7 @@ class _ImageFooterState extends State { ), ), IconButton( - icon: Icon( - StreamIcons.grid, + icon: StreamSvgIcon.Icon_grid( color: Colors.black, ), onPressed: () { @@ -160,8 +157,7 @@ class _ImageFooterState extends State { Align( alignment: Alignment.centerRight, child: IconButton( - icon: Icon( - StreamIcons.close, + icon: StreamSvgIcon.close( color: Colors.black, ), onPressed: () { @@ -446,13 +442,11 @@ class _ImageFooterState extends State { prefixIconConstraints: BoxConstraints.tight(Size(38.0, 38.0)), prefixIcon: Transform.scale( - scale: 1.2, - alignment: Alignment.center, - child: Icon( - StreamIcons.search, - color: Colors.black, - ), - ), + scale: 1.2, + alignment: Alignment.center, + child: StreamSvgIcon.search( + color: Colors.black, + )), hintText: 'Search', border: OutlineInputBorder( borderRadius: BorderRadius.circular(32.0), @@ -472,8 +466,7 @@ class _ImageFooterState extends State { width: 8.0, ), IconButton( - icon: Icon( - StreamIcons.close_circle, + icon: StreamSvgIcon.close_small( color: Colors.black.withOpacity(0.5), ), onPressed: () { @@ -494,8 +487,7 @@ class _ImageFooterState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 6.0), child: IconButton( - icon: Icon( - StreamIcons.search, + icon: StreamSvgIcon.search( color: Colors.black, ), iconSize: 24.0, @@ -520,8 +512,7 @@ class _ImageFooterState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 6.0), child: IconButton( - icon: Icon( - StreamIcons.share, + icon: StreamSvgIcon.search( color: Colors.black, ), iconSize: 24.0, @@ -612,8 +603,7 @@ class _ImageFooterState extends State { }, child: Transform.rotate( angle: -pi / 2, - child: Icon( - StreamIcons.send_message, + child: StreamSvgIcon.Icon_send_message( color: StreamChatTheme.of(context).accentColor, ), ), @@ -630,8 +620,7 @@ class _ImageFooterState extends State { child: Center( child: InkWell( onTap: () {}, - child: Icon( - StreamIcons.send_message, + child: StreamSvgIcon.Icon_send_message( color: Colors.grey, ), )), diff --git a/lib/src/image_header.dart b/lib/src/image_header.dart index 512d8b14..62c3f8ec 100644 --- a/lib/src/image_header.dart +++ b/lib/src/image_header.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; -import 'package:stream_chat_flutter/src/stream_icons.dart'; +import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'image_actions_modal.dart'; import 'stream_channel.dart'; @@ -49,8 +49,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { elevation: 1, leading: showBackButton ? IconButton( - icon: Icon( - StreamIcons.close, + icon: StreamSvgIcon.close( color: Colors.black, size: 24.0, ), @@ -61,10 +60,8 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, actions: [ IconButton( - icon: Icon( - StreamIcons.menu_point_v, + icon: StreamSvgIcon.Icon_menu_point_v( color: Colors.black, - size: 24.0, ), onPressed: () { _showMessageActionModalBottomSheet(context); diff --git a/lib/src/stream_svg_icon.dart b/lib/src/stream_svg_icon.dart index 9a87ce9d..87d21c14 100644 --- a/lib/src/stream_svg_icon.dart +++ b/lib/src/stream_svg_icon.dart @@ -397,4 +397,76 @@ class StreamSvgIcon extends StatelessWidget { height: size, ); } + + factory StreamSvgIcon.Icon_curve_line_left_up({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'Icon_curve_line_left_up.svg', + color: color, + width: size, + height: size, + ); + } + + factory StreamSvgIcon.icon_SHARE({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'icon_SHARE.svg', + color: color, + width: size, + height: size, + ); + } + + factory StreamSvgIcon.Icon_grid({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'Icon_grid.svg', + color: color, + width: size, + height: size, + ); + } + + factory StreamSvgIcon.Icon_send_message({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'Icon_send_message.svg', + color: color, + width: size, + height: size, + ); + } + + factory StreamSvgIcon.Icon_menu_point_v({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'Icon_menu_point_v.svg', + color: color, + width: size, + height: size, + ); + } + + factory StreamSvgIcon.Icon_save({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'Icon_save.svg', + color: color, + width: size, + height: size, + ); + } } diff --git a/lib/svgs/Icon_menu_point_v.svg b/lib/svgs/Icon_menu_point_v.svg new file mode 100644 index 00000000..f7b61163 --- /dev/null +++ b/lib/svgs/Icon_menu_point_v.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/svgs/Icon_save.svg b/lib/svgs/Icon_save.svg new file mode 100644 index 00000000..14bedc6f --- /dev/null +++ b/lib/svgs/Icon_save.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/svgs/Icon_send_message.svg b/lib/svgs/Icon_send_message.svg new file mode 100644 index 00000000..0d504a40 --- /dev/null +++ b/lib/svgs/Icon_send_message.svg @@ -0,0 +1,3 @@ + + + From 832e31cbc67be91f92beda3588d191a558199696 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 30 Nov 2020 15:56:25 +0530 Subject: [PATCH 20/29] feat: Switched to UserListView --- lib/src/image_footer.dart | 326 ++++++++++++----------------------- lib/src/stream_svg_icon.dart | 12 ++ 2 files changed, 124 insertions(+), 214 deletions(-) diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 5461e139..1605206c 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -60,7 +60,7 @@ class _ImageFooterState extends State { TextEditingController _searchController = TextEditingController(); TextEditingController _messageController = TextEditingController(); - List _selectedUsers = []; + Set _selectedUsers = {}; bool _loading = false; @override @@ -204,171 +204,93 @@ class _ImageFooterState extends State { showDialog( context: context, builder: (context) { - return StreamChannel( - channel: channel, - child: Scaffold( - backgroundColor: Colors.transparent, - body: StatefulBuilder(builder: (modalContext, modalSetState) { - return Align( - alignment: _userSearchMode - ? Alignment.topCenter - : Alignment.bottomCenter, - child: Material( - color: Colors.transparent, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16.0), - ), - child: Stack( + return StatefulBuilder(builder: (context, modalSetState) { + return Padding( + padding: const EdgeInsets.only(top: 16.0, left: 8.0, right: 8.0), + child: Material( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16.0), + topRight: Radius.circular(16.0), + ), + clipBehavior: Clip.antiAlias, + child: Scaffold( + body: UsersBloc( + child: Column( children: [ - ListView( - children: [ - if (!_userSearchMode && - MediaQuery.of(context).viewInsets.bottom == 0.0) - GestureDetector( - child: Container( - height: - MediaQuery.of(modalContext).size.height / 2, - color: Colors.transparent, - ), - onTapUp: (val) { - Navigator.pop(modalContext); + _buildTextInputSection(modalSetState), + Expanded( + child: UserListView( + selectedUsers: _selectedUsers, + onUserTap: (user, _) { + _searchController.clear(); + if (!_selectedUsers.contains(user)) { + modalSetState(() { + _selectedUsers.add(user); + }); + } else { + modalSetState(() { + _selectedUsers.remove(user); + }); + } + }, + crossAxisCount: 3, + pagination: PaginationParams( + limit: 25, + ), + filter: { + if (_searchController.text.isNotEmpty) + 'name': { + r'$autocomplete': _searchController.text, }, + 'id': { + r'$ne': StreamChat.of(context).user.id, + }, + }, + sort: [ + SortOption( + 'name', + direction: 1, ), - Container( - margin: EdgeInsets.symmetric( - horizontal: 8.0, - ), - padding: EdgeInsets.symmetric( - vertical: 4.0, - ), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only( - topRight: Radius.circular(16.0), - topLeft: Radius.circular(16.0), - )), - child: _buildTextInputSection(modalSetState), - ), - Container( - margin: EdgeInsets.symmetric( - horizontal: 8.0, - ), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only( - bottomRight: Radius.circular( - _userSearchMode ? 16.0 : 0.0), - bottomLeft: Radius.circular( - _userSearchMode ? 16.0 : 0.0), - ), - ), - child: FutureBuilder( - future: _userQuery, - builder: (context, snapshot) { - if (!snapshot.hasData) { - return Center( - child: CircularProgressIndicator(), - ); - } - - var filteredData = - snapshot.data.users.toList(); - - if (_userSearchMode && - _searchController.text.length > 0) { - filteredData = snapshot.data.users - .where((element) => element.name - .toLowerCase() - .contains(_searchController.text - .toLowerCase())) - .toList(); - } - - return GridView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemBuilder: (context, position) { - var user = filteredData[position]; - var isUserSelected = - _selectedUsers.contains(user); - - return Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: - MainAxisAlignment.start, - crossAxisAlignment: - CrossAxisAlignment.center, - children: [ - Stack( - children: [ - CircleAvatar( - maxRadius: 32.0, - child: UserAvatar( - onTap: (user) { - if (isUserSelected) { - _selectedUsers - .remove(user); - } else { - _selectedUsers - .add(user); - } - modalSetState(() {}); - }, - user: user, - constraints: - BoxConstraints.tightFor( - height: isUserSelected - ? 56 - : 64, - width: isUserSelected - ? 56 - : 64, - ), - borderRadius: - BorderRadius.circular( - 32), - ), - ), - ], - ), - Padding( - padding: - const EdgeInsets.all(8.0), - child: Text( - user.name, - style: Theme.of(context) - .textTheme - .subtitle2, - textAlign: TextAlign.center, - maxLines: 2, - ), - ), - ], - ), - ); - }, - itemCount: filteredData.length, - gridDelegate: - SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 4, - childAspectRatio: 0.75, + ], + emptyBuilder: (_) { + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, ), - ); - }), - ), - ], + child: Center( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(24), + child: StreamSvgIcon.search( + size: 96, + color: Colors.grey, + ), + ), + Text( + 'No user matches these keywords...'), + ], + ), + ), + ), + ); + }, + ); + }, + ), ), + if (_selectedUsers.isNotEmpty) + _buildShareTextInputSection(modalSetState), if (!_userSearchMode && _selectedUsers.isEmpty) Align( alignment: Alignment.bottomCenter, child: Container( color: Colors.white, height: 48.0, - margin: EdgeInsets.symmetric( - horizontal: 8.0, - ), child: Material( child: InkWell( onTap: () async { @@ -392,7 +314,7 @@ class _ImageFooterState extends State { child: Text( 'Save to Photos', style: TextStyle( - color: StreamChatTheme.of(modalContext) + color: StreamChatTheme.of(context) .accentColor, fontWeight: FontWeight.bold, ), @@ -403,15 +325,13 @@ class _ImageFooterState extends State { ), ), ), - if (!_userSearchMode && _selectedUsers.isNotEmpty) - _buildShareTextInputSection(modalSetState), ], ), ), - ); - }), - ), - ); + ), + ), + ); + }); }, ); } @@ -440,13 +360,14 @@ class _ImageFooterState extends State { decoration: InputDecoration( isDense: true, prefixIconConstraints: - BoxConstraints.tight(Size(38.0, 38.0)), - prefixIcon: Transform.scale( - scale: 1.2, - alignment: Alignment.center, - child: StreamSvgIcon.search( - color: Colors.black, - )), + BoxConstraints.tight(Size(36.0, 44.0)), + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + vertical: 2.0, horizontal: 6.0), + child: StreamSvgIcon.search( + color: Colors.black, + ), + ), hintText: 'Search', border: OutlineInputBorder( borderRadius: BorderRadius.circular(32.0), @@ -512,10 +433,9 @@ class _ImageFooterState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 6.0), child: IconButton( - icon: StreamSvgIcon.search( + icon: StreamSvgIcon.share_arrow( color: Colors.black, ), - iconSize: 24.0, onPressed: () async { var url = widget.urls[widget.currentPage].imageUrl ?? widget.urls[widget.currentPage].assetUrl ?? @@ -580,54 +500,32 @@ class _ImageFooterState extends State { ), ), ), - AnimatedCrossFade( - crossFadeState: _messageController.text.isNotEmpty - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - firstChild: IconTheme( - data: StreamChatTheme.of(context) - .channelTheme - .messageInputButtonIconTheme, - child: Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: InkWell( - onTap: () async { - modalSetState(() { - _loading = true; - }); - await sendMessage(); - modalSetState(() { - _loading = false; - }); - }, - child: Transform.rotate( - angle: -pi / 2, - child: StreamSvgIcon.Icon_send_message( - color: StreamChatTheme.of(context).accentColor, - ), + IconTheme( + data: StreamChatTheme.of(context) + .channelTheme + .messageInputButtonIconTheme, + child: Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: InkWell( + onTap: () async { + modalSetState(() { + _loading = true; + }); + await sendMessage(); + modalSetState(() { + _loading = false; + }); + }, + child: Transform.rotate( + angle: -pi / 2, + child: StreamSvgIcon.Icon_send_message( + color: StreamChatTheme.of(context).accentColor, ), ), ), ), ), - secondChild: IconTheme( - data: StreamChatTheme.of(context) - .channelTheme - .messageInputButtonIconTheme, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Center( - child: InkWell( - onTap: () {}, - child: StreamSvgIcon.Icon_send_message( - color: Colors.grey, - ), - )), - ), - ), - duration: Duration(milliseconds: 300), - alignment: Alignment.center, ), ], ), diff --git a/lib/src/stream_svg_icon.dart b/lib/src/stream_svg_icon.dart index 87d21c14..e5ef7ec9 100644 --- a/lib/src/stream_svg_icon.dart +++ b/lib/src/stream_svg_icon.dart @@ -469,4 +469,16 @@ class StreamSvgIcon extends StatelessWidget { height: size, ); } + + factory StreamSvgIcon.share_arrow({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'share_arrow.svg', + color: color, + width: size, + height: size, + ); + } } From 058ecc9a0d659e582d55b0a1c9796e89cf106913 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 30 Nov 2020 19:20:15 +0530 Subject: [PATCH 21/29] fixes --- lib/src/full_screen_media.dart | 14 ++++- lib/src/image_footer.dart | 101 ++++++++++++++++++--------------- 2 files changed, 67 insertions(+), 48 deletions(-) diff --git a/lib/src/full_screen_media.dart b/lib/src/full_screen_media.dart index 88f8d2b9..fed1b9a7 100644 --- a/lib/src/full_screen_media.dart +++ b/lib/src/full_screen_media.dart @@ -63,6 +63,10 @@ class _FullScreenMediaState extends State @override Widget build(BuildContext context) { + var videoAttachments = widget.mediaAttachments + .where((element) => element.type == 'video') + .toList(); + return Scaffold( resizeToAvoidBottomInset: false, body: Stack( @@ -113,7 +117,10 @@ class _FullScreenMediaState extends State ); } else if (widget.mediaAttachments[position].type == 'video') { - if (!videoPackages[position].initialised) { + var controllerPackage = videoPackages[videoAttachments + .indexOf(widget.mediaAttachments[position])]; + + if (!controllerPackage.initialised) { return Center( child: CircularProgressIndicator(), ); @@ -130,7 +137,7 @@ class _FullScreenMediaState extends State } }, child: Chewie( - controller: videoPackages[position].chewieController, + controller: controllerPackage.chewieController, ), ); } @@ -160,8 +167,9 @@ class _FullScreenMediaState extends State ImageFooter( currentPage: _currentPage, totalPages: widget.mediaAttachments.length, - urls: widget.mediaAttachments, + mediaAttachments: widget.mediaAttachments, message: widget.message, + videoPackages: videoPackages, ), ], ), diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 1605206c..a4651cf7 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'dart:typed_data'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:chewie/chewie.dart'; import 'package:esys_flutter_share/esys_flutter_share.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -12,7 +13,6 @@ import 'package:path_provider/path_provider.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; -import 'package:stream_chat_flutter/src/user_avatar.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'stream_channel.dart'; @@ -31,9 +31,11 @@ class ImageFooter extends StatefulWidget { final int currentPage; final int totalPages; - final List urls; + final List mediaAttachments; final Message message; + final List videoPackages; + /// Creates a channel header ImageFooter({ Key key, @@ -42,8 +44,9 @@ class ImageFooter extends StatefulWidget { this.onImageTap, this.currentPage = 0, this.totalPages = 0, - this.urls, + this.mediaAttachments, this.message, + this.videoPackages, }) : preferredSize = Size.fromHeight(kToolbarHeight), super(key: key); @@ -55,7 +58,6 @@ class ImageFooter extends StatefulWidget { } class _ImageFooterState extends State { - Future _userQuery; bool _userSearchMode = false; TextEditingController _searchController = TextEditingController(); TextEditingController _messageController = TextEditingController(); @@ -63,12 +65,6 @@ class _ImageFooterState extends State { Set _selectedUsers = {}; bool _loading = false; - @override - void initState() { - super.initState(); - _userQuery = _queryUsers(context); - } - @override Widget build(BuildContext context) { return SafeArea( @@ -123,6 +119,10 @@ class _ImageFooterState extends State { } Widget _buildPhotosModal(context) { + var videoAttachments = widget.mediaAttachments + .where((element) => element.type == 'video') + .toList(); + showModalBottomSheet( context: context, shape: RoundedRectangleBorder( @@ -174,20 +174,33 @@ class _ImageFooterState extends State { shrinkWrap: true, physics: NeverScrollableScrollPhysics(), itemBuilder: (context, position) { - return Padding( - padding: const EdgeInsets.all(1.0), - child: AspectRatio( - child: CachedNetworkImage( - imageUrl: widget.urls[position].imageUrl ?? - widget.urls[position].assetUrl ?? - widget.urls[position].thumbUrl, - fit: BoxFit.cover, + if (widget.mediaAttachments[position].type == 'video') { + var controllerPackage = widget.videoPackages[ + videoAttachments + .indexOf(widget.mediaAttachments[position])]; + + return FittedBox( + child: Chewie( + controller: controllerPackage.chewieController, ), - aspectRatio: 1.0, - ), - ); + ); + } else { + return Padding( + padding: const EdgeInsets.all(1.0), + child: AspectRatio( + child: CachedNetworkImage( + imageUrl: + widget.mediaAttachments[position].imageUrl ?? + widget.mediaAttachments[position].assetUrl ?? + widget.mediaAttachments[position].thumbUrl, + fit: BoxFit.cover, + ), + aspectRatio: 1.0, + ), + ); + } }, - itemCount: widget.urls.length, + itemCount: widget.mediaAttachments.length, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 3), ), @@ -206,7 +219,12 @@ class _ImageFooterState extends State { builder: (context) { return StatefulBuilder(builder: (context, modalSetState) { return Padding( - padding: const EdgeInsets.only(top: 16.0, left: 8.0, right: 8.0), + padding: EdgeInsets.only( + top: _userSearchMode + ? 16.0 + : MediaQuery.of(context).size.height / 2, + left: 8.0, + right: 8.0), child: Material( borderRadius: BorderRadius.only( topLeft: Radius.circular(16.0), @@ -233,7 +251,7 @@ class _ImageFooterState extends State { }); } }, - crossAxisCount: 3, + crossAxisCount: 4, pagination: PaginationParams( limit: 25, ), @@ -295,12 +313,18 @@ class _ImageFooterState extends State { child: InkWell( onTap: () async { var url = widget - .urls[widget.currentPage].imageUrl ?? + .mediaAttachments[widget.currentPage] + .imageUrl ?? widget - .urls[widget.currentPage].assetUrl ?? - widget.urls[widget.currentPage].thumbUrl; + .mediaAttachments[widget.currentPage] + .assetUrl ?? + widget + .mediaAttachments[widget.currentPage] + .thumbUrl; - if (widget.urls[widget.currentPage].type == + if (widget + .mediaAttachments[widget.currentPage] + .type == 'video') { await _saveVideo(url); Navigator.pop(context); @@ -437,9 +461,10 @@ class _ImageFooterState extends State { color: Colors.black, ), onPressed: () async { - var url = widget.urls[widget.currentPage].imageUrl ?? - widget.urls[widget.currentPage].assetUrl ?? - widget.urls[widget.currentPage].thumbUrl; + var url = + widget.mediaAttachments[widget.currentPage].imageUrl ?? + widget.mediaAttachments[widget.currentPage].assetUrl ?? + widget.mediaAttachments[widget.currentPage].thumbUrl; var type = url?.split('?')?.first?.split('.')?.last ?? 'jpg'; var request = await HttpClient().getUrl(Uri.parse(url)); var response = await request.close(); @@ -533,20 +558,6 @@ class _ImageFooterState extends State { ); } - Future _queryUsers(context) { - return StreamChat.of(context).client.queryUsers( - pagination: PaginationParams( - limit: 25, - ), - sort: [ - SortOption( - 'name', - direction: SortOption.ASC, - ), - ], - ); - } - /// Sends the current message Future sendMessage() async { var text = _messageController.text.trim(); From 37ad5d72be89dc6860f7a4ae5b609f798e749476 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 30 Nov 2020 20:30:32 +0530 Subject: [PATCH 22/29] fixes --- lib/src/full_screen_media.dart | 1 - lib/src/image_footer.dart | 7 +++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/src/full_screen_media.dart b/lib/src/full_screen_media.dart index fed1b9a7..a0c4a03f 100644 --- a/lib/src/full_screen_media.dart +++ b/lib/src/full_screen_media.dart @@ -82,7 +82,6 @@ class _FullScreenMediaState extends State }); }, itemBuilder: (context, position) { - print(widget.mediaAttachments[position].type); if (widget.mediaAttachments[position].type == 'image' || widget.mediaAttachments[position].type == 'giphy') { return PhotoView( diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index a4651cf7..dcc5c5d8 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -465,7 +465,10 @@ class _ImageFooterState extends State { widget.mediaAttachments[widget.currentPage].imageUrl ?? widget.mediaAttachments[widget.currentPage].assetUrl ?? widget.mediaAttachments[widget.currentPage].thumbUrl; - var type = url?.split('?')?.first?.split('.')?.last ?? 'jpg'; + var type = + widget.mediaAttachments[widget.currentPage].type == 'image' + ? 'jpg' + : url?.split('?')?.first?.split('.')?.last ?? 'jpg'; var request = await HttpClient().getUrl(Uri.parse(url)); var response = await request.close(); var bytes = await consolidateHttpClientResponseBytes(response); @@ -583,7 +586,7 @@ class _ImageFooterState extends State { message = (Message()).copyWith( text: text, - attachments: attachments, + attachments: [attachments[widget.currentPage]], mentionedUsers: [], showInChannel: false, ); From 27ef529c25e49406d4b0bcb63d1a6adf41cbbdab Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 1 Dec 2020 10:34:21 +0100 Subject: [PATCH 23/29] simplify send message --- lib/src/image_footer.dart | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index dcc5c5d8..3a223de2 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -212,8 +212,6 @@ class _ImageFooterState extends State { } Widget _buildShareModal(context) { - final channel = StreamChannel.of(context).channel; - showDialog( context: context, builder: (context) { @@ -581,19 +579,14 @@ class _ImageFooterState extends State { await c.create(); - Future sendingFuture; - Message message; - - message = (Message()).copyWith( + final message = Message( text: text, attachments: [attachments[widget.currentPage]], mentionedUsers: [], showInChannel: false, ); - sendingFuture = c.sendMessage(message); - - await sendingFuture; + await c.sendMessage(message); } _selectedUsers.clear(); From 267793bd47015098e0b366bd6454852d753914fb Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 1 Dec 2020 10:35:44 +0100 Subject: [PATCH 24/29] remove useless params --- lib/src/image_footer.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 3a223de2..a24288e1 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -582,8 +582,6 @@ class _ImageFooterState extends State { final message = Message( text: text, attachments: [attachments[widget.currentPage]], - mentionedUsers: [], - showInChannel: false, ); await c.sendMessage(message); From 9cb2ad884f24425be87048fc3bd50fb0504bb1dd Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 2 Dec 2020 17:13:48 +0530 Subject: [PATCH 25/29] fix: Added debounce logic --- lib/src/image_footer.dart | 42 ++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index dcc5c5d8..c6df304a 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; @@ -59,12 +60,45 @@ class ImageFooter extends StatefulWidget { class _ImageFooterState extends State { bool _userSearchMode = false; - TextEditingController _searchController = TextEditingController(); + TextEditingController _searchController; TextEditingController _messageController = TextEditingController(); + String _userNameQuery; + bool _isSearchActive = false; + Set _selectedUsers = {}; bool _loading = false; + Timer _debounce; + + Function modalSetStateCallback; + + void _userNameListener() { + if (_debounce?.isActive ?? false) _debounce.cancel(); + _debounce = Timer(const Duration(milliseconds: 350), () { + if (mounted && modalSetStateCallback != null) { + modalSetStateCallback(() { + _userNameQuery = _searchController.text; + _isSearchActive = _userNameQuery.isNotEmpty; + }); + } + }); + } + + @override + void initState() { + super.initState(); + _searchController = TextEditingController()..addListener(_userNameListener); + } + + @override + void dispose() { + _searchController?.clear(); + _searchController?.removeListener(_userNameListener); + _searchController?.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return SafeArea( @@ -218,6 +252,7 @@ class _ImageFooterState extends State { context: context, builder: (context) { return StatefulBuilder(builder: (context, modalSetState) { + modalSetStateCallback = modalSetState; return Padding( padding: EdgeInsets.only( top: _userSearchMode @@ -258,7 +293,7 @@ class _ImageFooterState extends State { filter: { if (_searchController.text.isNotEmpty) 'name': { - r'$autocomplete': _searchController.text, + r'$autocomplete': _userNameQuery, }, 'id': { r'$ne': StreamChat.of(context).user.id, @@ -376,9 +411,6 @@ class _ImageFooterState extends State { Expanded( child: TextField( controller: _searchController, - onChanged: (val) { - modalSetState(() {}); - }, cursorColor: Colors.black, autofocus: true, decoration: InputDecoration( From 4eadc49e474b795dbfb666f78215e833cdb12a1a Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 2 Dec 2020 14:41:08 +0100 Subject: [PATCH 26/29] fix button color and watch channel --- .gitignore | 3 ++- lib/src/image_actions_modal.dart | 29 ++++++++++++++++------------- lib/src/image_footer.dart | 2 +- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index 3a158cd4..1d3bae26 100644 --- a/.gitignore +++ b/.gitignore @@ -61,4 +61,5 @@ doc/api/ fvm google-services.json -example/ios/dist \ No newline at end of file +example/ios/dist +.vscode/ \ No newline at end of file diff --git a/lib/src/image_actions_modal.dart b/lib/src/image_actions_modal.dart index 7e50a4f7..fc81635c 100644 --- a/lib/src/image_actions_modal.dart +++ b/lib/src/image_actions_modal.dart @@ -149,19 +149,22 @@ class ImageActionsModal extends StatelessWidget { }), if (StreamChat.of(context).user.id == message.user.id) _buildButton( - context, - 'Delete', - StreamSvgIcon.delete( - size: 24.0, - color: Colors.black, - ), () { - Navigator.pop(context); - Navigator.pop(context); - StreamChat.of(context).client.deleteMessage( - message, - StreamChannel.of(context).channel.cid, - ); - }, color: Colors.red), + context, + 'Delete', + StreamSvgIcon.delete( + size: 24.0, + color: Color(0xffFF3742), + ), + () { + Navigator.pop(context); + Navigator.pop(context); + StreamChat.of(context).client.deleteMessage( + message, + StreamChannel.of(context).channel.cid, + ); + }, + color: Color(0xffFF3742), + ), ], ).toList(), ), diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 9455d8f3..09a32e78 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -609,7 +609,7 @@ class _ImageFooterState extends State { ], }); - await c.create(); + await c.watch(); final message = Message( text: text, From c99d908e69af62fb3f86a773cde5efdc3069bbc6 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 2 Dec 2020 20:09:17 +0530 Subject: [PATCH 27/29] feat: Added pageview animation when picture modal clicked --- lib/src/full_screen_media.dart | 8 +++++++ lib/src/image_footer.dart | 38 ++++++++++++++++++++++------------ 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/lib/src/full_screen_media.dart b/lib/src/full_screen_media.dart index a0c4a03f..0857531a 100644 --- a/lib/src/full_screen_media.dart +++ b/lib/src/full_screen_media.dart @@ -169,6 +169,14 @@ class _FullScreenMediaState extends State mediaAttachments: widget.mediaAttachments, message: widget.message, videoPackages: videoPackages, + mediaSelectedCallBack: (val) { + setState(() { + _currentPage = val; + _pageController.animateToPage(val, + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut); + }); + }, ), ], ), diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 9455d8f3..51bb9009 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -36,6 +36,7 @@ class ImageFooter extends StatefulWidget { final Message message; final List videoPackages; + final ValueChanged mediaSelectedCallBack; /// Creates a channel header ImageFooter({ @@ -48,6 +49,7 @@ class ImageFooter extends StatefulWidget { this.mediaAttachments, this.message, this.videoPackages, + this.mediaSelectedCallBack, }) : preferredSize = Size.fromHeight(kToolbarHeight), super(key: key); @@ -213,23 +215,33 @@ class _ImageFooterState extends State { videoAttachments .indexOf(widget.mediaAttachments[position])]; - return FittedBox( - child: Chewie( - controller: controllerPackage.chewieController, + return InkWell( + onTap: () { + widget.mediaSelectedCallBack(position); + }, + child: FittedBox( + child: Chewie( + controller: controllerPackage.chewieController, + ), ), ); } else { - return Padding( - padding: const EdgeInsets.all(1.0), - child: AspectRatio( - child: CachedNetworkImage( - imageUrl: - widget.mediaAttachments[position].imageUrl ?? - widget.mediaAttachments[position].assetUrl ?? - widget.mediaAttachments[position].thumbUrl, - fit: BoxFit.cover, + return InkWell( + onTap: () { + widget.mediaSelectedCallBack(position); + }, + child: Padding( + padding: const EdgeInsets.all(1.0), + child: AspectRatio( + child: CachedNetworkImage( + imageUrl: widget + .mediaAttachments[position].imageUrl ?? + widget.mediaAttachments[position].assetUrl ?? + widget.mediaAttachments[position].thumbUrl, + fit: BoxFit.cover, + ), + aspectRatio: 1.0, ), - aspectRatio: 1.0, ), ); } From fc171ccdea90cd4ef3a56f14c75c67a157ed17d9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 2 Dec 2020 16:38:32 +0100 Subject: [PATCH 28/29] fix video controls --- example/ios/Podfile.lock | 16 ++++++++++++++-- example/ios/Runner.xcodeproj/project.pbxproj | 4 ++++ example/pubspec.yaml | 2 +- lib/src/full_screen_media.dart | 10 ++++++++-- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 707e9fcb..223bab2b 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -30,6 +30,8 @@ PODS: - DKPhotoGallery/Resource (0.0.17): - SDWebImage - SwiftyGif + - esys_flutter_share (0.0.1): + - Flutter - file_picker (0.0.1): - DKImagePickerController/PhotoGallery - Flutter @@ -38,7 +40,7 @@ PODS: - Firebase/Messaging (6.33.0): - Firebase/CoreOnly - FirebaseMessaging (~> 4.7.0) - - firebase_core (0.5.2): + - firebase_core (0.5.2-1): - Firebase/CoreOnly (~> 6.33.0) - Flutter - firebase_messaging (7.0.3): @@ -106,6 +108,8 @@ PODS: - GoogleUtilities/Logger - GoogleUtilities/UserDefaults (6.7.2): - GoogleUtilities/Logger + - image_gallery_saver (1.5.0): + - Flutter - image_picker (0.0.1): - Flutter - nanopb (1.30906.0): @@ -161,6 +165,7 @@ PODS: - Flutter DEPENDENCIES: + - esys_flutter_share (from `.symlinks/plugins/esys_flutter_share/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) @@ -170,6 +175,7 @@ DEPENDENCIES: - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) + - image_gallery_saver (from `.symlinks/plugins/image_gallery_saver/ios`) - image_picker (from `.symlinks/plugins/image_picker/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) - permission_handler (from `.symlinks/plugins/permission_handler/ios`) @@ -206,6 +212,8 @@ SPEC REPOS: - SwiftyGif EXTERNAL SOURCES: + esys_flutter_share: + :path: ".symlinks/plugins/esys_flutter_share/ios" file_picker: :path: ".symlinks/plugins/file_picker/ios" firebase_core: @@ -224,6 +232,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_local_notifications/ios" flutter_secure_storage: :path: ".symlinks/plugins/flutter_secure_storage/ios" + image_gallery_saver: + :path: ".symlinks/plugins/image_gallery_saver/ios" image_picker: :path: ".symlinks/plugins/image_picker/ios" path_provider: @@ -250,9 +260,10 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: DKImagePickerController: b5eb7f7a388e4643264105d648d01f727110fc3d DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 + esys_flutter_share: 403498dab005b36ce1f8d7aff377e81f0621b0b4 file_picker: 3e6c3790de664ccf9b882732d9db5eaf6b8d4eb1 Firebase: 8db6f2d1b2c5e2984efba4949a145875a8f65fe5 - firebase_core: 350ba329d1641211bc6183a3236893cafdacfea7 + firebase_core: 7423d688a1c6f2f2d859d64ae26991be39989781 firebase_messaging: 0aea2cd5885b65e19ede58ee3507f485c992cc75 FirebaseCore: d889d9e12535b7f36ac8bfbf1713a0836a3012cd FirebaseCoreDiagnostics: 770ac5958e1372ce67959ae4b4f31d8e127c3ac1 @@ -268,6 +279,7 @@ SPEC CHECKSUMS: FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a GoogleDataTransport: f56af7caa4ed338dc8e138a5d7c5973e66440833 GoogleUtilities: 7f2f5a07f888cdb145101d6042bc4422f57e70b3 + image_gallery_saver: 259eab68fb271cfd57d599904f7acdc7832e7ef2 image_picker: 9c3312491f862b28d21ecd8fdf0ee14e601b3f09 nanopb: 59317e09cf1f1a0af72f12af412d54edf52603fc path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 1a50ae5b..1e7527eb 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -320,12 +320,14 @@ "${BUILT_PRODUCTS_DIR}/Protobuf/Protobuf.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework", + "${BUILT_PRODUCTS_DIR}/esys_flutter_share/esys_flutter_share.framework", "${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework", "${BUILT_PRODUCTS_DIR}/flutter_apns/flutter_apns.framework", "${BUILT_PRODUCTS_DIR}/flutter_app_badger/flutter_app_badger.framework", "${BUILT_PRODUCTS_DIR}/flutter_keyboard_visibility/flutter_keyboard_visibility.framework", "${BUILT_PRODUCTS_DIR}/flutter_local_notifications/flutter_local_notifications.framework", "${BUILT_PRODUCTS_DIR}/flutter_secure_storage/flutter_secure_storage.framework", + "${BUILT_PRODUCTS_DIR}/image_gallery_saver/image_gallery_saver.framework", "${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework", "${BUILT_PRODUCTS_DIR}/nanopb/nanopb.framework", "${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework", @@ -352,12 +354,14 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Protobuf.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/esys_flutter_share.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_apns.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_app_badger.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_keyboard_visibility.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_local_notifications.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_secure_storage.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_gallery_saver.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/nanopb.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework", diff --git a/example/pubspec.yaml b/example/pubspec.yaml index e0be8fc9..f5beccb2 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.78+80 +version: 1.0.79+81 environment: sdk: ">=2.2.2 <3.0.0" diff --git a/lib/src/full_screen_media.dart b/lib/src/full_screen_media.dart index 0857531a..7e3f3d7d 100644 --- a/lib/src/full_screen_media.dart +++ b/lib/src/full_screen_media.dart @@ -135,8 +135,13 @@ class _FullScreenMediaState extends State _controller.forward(); } }, - child: Chewie( - controller: controllerPackage.chewieController, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 50.0, + ), + child: Chewie( + controller: controllerPackage.chewieController, + ), ), ); } @@ -175,6 +180,7 @@ class _FullScreenMediaState extends State _pageController.animateToPage(val, duration: Duration(milliseconds: 300), curve: Curves.easeInOut); + Navigator.pop(context); }); }, ), From e29b8fe9f1cc27dc13eed2a61166da90becb9734 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 2 Dec 2020 16:46:05 +0100 Subject: [PATCH 29/29] fix video boxfit --- lib/src/video_attachment.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/video_attachment.dart b/lib/src/video_attachment.dart index 782b81cb..21ede87a 100644 --- a/lib/src/video_attachment.dart +++ b/lib/src/video_attachment.dart @@ -110,7 +110,7 @@ class _VideoAttachmentState extends State { children: [ Expanded( child: FittedBox( - fit: BoxFit.cover, + fit: BoxFit.none, child: Stack( children: [ Chewie(