From 16ed0c5bb46541877d624298e82ae176a1947ffc Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 4 Dec 2020 16:29:43 +0530 Subject: [PATCH 01/35] feat: Added chat info page --- lib/src/channel_header.dart | 23 ++- lib/src/chat_info_screen.dart | 319 +++++++++++++++++++++++++++++++++ lib/src/stream_svg_icon.dart | 36 ++++ lib/svgs/Icon_group.svg | 3 + lib/svgs/Icon_notification.svg | 6 + 5 files changed, 386 insertions(+), 1 deletion(-) create mode 100644 lib/src/chat_info_screen.dart create mode 100644 lib/svgs/Icon_group.svg create mode 100644 lib/svgs/Icon_notification.svg diff --git a/lib/src/channel_header.dart b/lib/src/channel_header.dart index ebaaf20f..09833ad3 100644 --- a/lib/src/channel_header.dart +++ b/lib/src/channel_header.dart @@ -5,8 +5,10 @@ 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 '../stream_chat_flutter.dart'; import './channel_name.dart'; import 'channel_image.dart'; +import 'chat_info_screen.dart'; import 'stream_channel.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header.png) @@ -97,7 +99,26 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { padding: const EdgeInsets.only(right: 10.0), child: Center( child: ChannelImage( - onTap: onImageTap, + onTap: onImageTap ?? + () { + var currentUser = StreamChat.of(context).user; + var otherUser = channel.state.members.firstWhere( + (element) => element.user.id != currentUser.id); + + if (channel.memberCount == 2) { + if (otherUser != null) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: ChatInfoScreen( + user: otherUser.user, + ), + ))); + } + } + }, ), ), ), diff --git a/lib/src/chat_info_screen.dart b/lib/src/chat_info_screen.dart new file mode 100644 index 00000000..18efdc1b --- /dev/null +++ b/lib/src/chat_info_screen.dart @@ -0,0 +1,319 @@ +import 'package:emojis/emojis.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../stream_chat_flutter.dart'; + +/// Detail screen for a 1:1 chat correspondence +class ChatInfoScreen extends StatefulWidget { + /// User in consideration + final User user; + + const ChatInfoScreen({Key key, this.user}) : super(key: key); + + @override + _ChatInfoScreenState createState() => _ChatInfoScreenState(); +} + +class _ChatInfoScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Color(0xFFe6e6e6), + body: ListView( + children: [ + _buildUserHeader(), + SizedBox( + height: 8.0, + ), + _buildOptionListTiles(), + SizedBox( + height: 8.0, + ), + _buildDeleteListTile(), + ], + ), + ); + } + + Widget _buildUserHeader() { + return Material( + color: Colors.white, + child: SafeArea( + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: UserAvatar( + user: widget.user, + constraints: BoxConstraints( + maxWidth: 72.0, + maxHeight: 72.0, + ), + borderRadius: BorderRadius.circular(36.0), + ), + ), + SizedBox(height: 15.0), + Text( + widget.user.name, + style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold), + ), + SizedBox(height: 7.0), + Text('Online for 5 minutes'), + SizedBox(height: 15.0), + _OptionListTile( + title: '@user', + trailing: Text(widget.user.name), + onTap: () {}, + ), + ], + ), + Positioned( + top: 21, + left: 16, + child: InkWell( + child: StreamSvgIcon.left(), + onTap: () { + Navigator.of(context).pop(); + }, + ), + ), + ], + ), + ), + ); + } + + Widget _buildOptionListTiles() { + var channel = StreamChannel.of(context); + + return Column( + children: [ + _OptionListTile( + title: 'Notifications', + leading: StreamSvgIcon.Icon_notification( + size: 24.0, + color: Colors.black.withOpacity(0.5), + ), + trailing: CupertinoSwitch( + value: true, + onChanged: (val) {}, + ), + onTap: () {}, + ), + StreamBuilder( + stream: StreamChannel.of(context).channel.isMutedStream, + builder: (context, snapshot) { + return _OptionListTile( + title: 'Mute user', + leading: StreamSvgIcon.mute( + size: 23.0, + color: Colors.black.withOpacity(0.5), + ), + trailing: snapshot.data == null + ? CircularProgressIndicator() + : CupertinoSwitch( + value: snapshot.data, + onChanged: (val) { + if (snapshot.data) { + channel.channel.unmute(); + } else { + channel.channel.mute(); + } + }, + ), + onTap: () {}, + ); + }), + _OptionListTile( + title: 'Block User', + leading: StreamSvgIcon.Icon_user_delete( + size: 24.0, + color: Colors.black.withOpacity(0.5), + ), + trailing: CupertinoSwitch( + value: true, + onChanged: (val) {}, + ), + onTap: () {}, + ), + _OptionListTile( + title: '615 Photos & Videos', + leading: StreamSvgIcon.pictures( + size: 24.0, + color: Colors.black.withOpacity(0.5), + ), + trailing: StreamSvgIcon.right(), + onTap: () {}, + ), + _OptionListTile( + title: '8 Files', + leading: StreamSvgIcon.files( + size: 24.0, + color: Colors.black.withOpacity(0.5), + ), + trailing: StreamSvgIcon.right(), + onTap: () {}, + ), + _OptionListTile( + title: '2 Shared groups', + leading: StreamSvgIcon.Icon_group( + size: 24.0, + color: Colors.black.withOpacity(0.5), + ), + trailing: StreamSvgIcon.right(), + onTap: () {}, + ), + ], + ); + } + + Widget _buildDeleteListTile() { + return _OptionListTile( + title: 'Delete', + leading: StreamSvgIcon.delete( + color: Colors.red, + size: 20.0, + ), + onTap: () { + _showDeleteDialog(); + }, + titleColor: Colors.red, + ); + } + + void _showDeleteDialog() { + var channel = StreamChannel.of(context).channel; + + showModalBottomSheet( + backgroundColor: Colors.white, + context: context, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16.0), + topRight: Radius.circular(16.0), + )), + builder: (context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 26.0, + ), + StreamSvgIcon.delete( + color: Colors.red, + ), + SizedBox( + height: 26.0, + ), + Text( + 'Delete Conversation', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0), + ), + SizedBox( + height: 7.0, + ), + Text('Are you sure you want to delete this conversation?'), + SizedBox( + height: 36.0, + ), + Container( + color: Color(0xffe6e6e6), + height: 1.0, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + FlatButton( + child: Text( + 'CANCEL', + style: TextStyle( + color: Colors.black.withOpacity(0.5), + fontWeight: FontWeight.w400), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + FlatButton( + child: Text( + 'DELETE', + style: TextStyle( + color: Colors.red, fontWeight: FontWeight.w400), + ), + onPressed: () { + channel.delete().then((value) { + Navigator.pop(context); + Navigator.pop(context); + Navigator.pop(context); + }); + }, + ), + ], + ), + ], + ); + }); + } +} + +class _OptionListTile extends StatelessWidget { + final String title; + final StreamSvgIcon leading; + final Widget trailing; + final VoidCallback onTap; + final Color titleColor; + + _OptionListTile({ + this.title, + this.leading, + this.trailing, + this.onTap, + this.titleColor, + }); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + color: Color(0xffe6e6e6), + height: 2.0, + ), + Material( + color: Colors.white, + child: InkWell( + onTap: onTap, + child: Row( + children: [ + if (leading != null) + Padding( + padding: const EdgeInsets.all(22.0), + child: leading, + ), + if (leading == null) + SizedBox( + width: 16.0, + ), + Expanded( + child: Text( + title, + style: + TextStyle(fontWeight: FontWeight.w600, color: titleColor), + )), + if (trailing != null) + Padding( + padding: const EdgeInsets.all(16.0), + child: trailing, + ), + ], + ), + ), + ), + ], + ); + } +} diff --git a/lib/src/stream_svg_icon.dart b/lib/src/stream_svg_icon.dart index 42fed46e..98ce0f8e 100644 --- a/lib/src/stream_svg_icon.dart +++ b/lib/src/stream_svg_icon.dart @@ -721,4 +721,40 @@ class StreamSvgIcon extends StatelessWidget { height: size, ); } + + factory StreamSvgIcon.Icon_group({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'Icon_group.svg', + color: color, + width: size, + height: size, + ); + } + + factory StreamSvgIcon.Icon_notification({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'Icon_notification.svg', + color: color, + width: size, + height: size, + ); + } + + factory StreamSvgIcon.Icon_user_delete({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'Icon_user_delete.svg', + color: color, + width: size, + height: size, + ); + } } diff --git a/lib/svgs/Icon_group.svg b/lib/svgs/Icon_group.svg new file mode 100644 index 00000000..6db40129 --- /dev/null +++ b/lib/svgs/Icon_group.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/svgs/Icon_notification.svg b/lib/svgs/Icon_notification.svg new file mode 100644 index 00000000..213f33c0 --- /dev/null +++ b/lib/svgs/Icon_notification.svg @@ -0,0 +1,6 @@ + + + + + + From 75262dbd4995a28e37940dfa8ed4ef6b223f5c50 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 7 Dec 2020 20:30:54 +0530 Subject: [PATCH 02/35] feat: Added shared groups, removed notifications, fixed listtile --- lib/src/chat_info_screen.dart | 185 +++++++++++++++++++++++++--------- 1 file changed, 138 insertions(+), 47 deletions(-) diff --git a/lib/src/chat_info_screen.dart b/lib/src/chat_info_screen.dart index 18efdc1b..8310d1b9 100644 --- a/lib/src/chat_info_screen.dart +++ b/lib/src/chat_info_screen.dart @@ -92,18 +92,18 @@ class _ChatInfoScreenState extends State { return Column( children: [ - _OptionListTile( - title: 'Notifications', - leading: StreamSvgIcon.Icon_notification( - size: 24.0, - color: Colors.black.withOpacity(0.5), - ), - trailing: CupertinoSwitch( - value: true, - onChanged: (val) {}, - ), - onTap: () {}, - ), + // _OptionListTile( + // title: 'Notifications', + // leading: StreamSvgIcon.Icon_notification( + // size: 24.0, + // color: Colors.black.withOpacity(0.5), + // ), + // trailing: CupertinoSwitch( + // value: true, + // onChanged: (val) {}, + // ), + // onTap: () {}, + // ), StreamBuilder( stream: StreamChannel.of(context).channel.isMutedStream, builder: (context, snapshot) { @@ -143,7 +143,7 @@ class _ChatInfoScreenState extends State { _OptionListTile( title: '615 Photos & Videos', leading: StreamSvgIcon.pictures( - size: 24.0, + size: 32.0, color: Colors.black.withOpacity(0.5), ), trailing: StreamSvgIcon.right(), @@ -152,21 +152,36 @@ class _ChatInfoScreenState extends State { _OptionListTile( title: '8 Files', leading: StreamSvgIcon.files( - size: 24.0, - color: Colors.black.withOpacity(0.5), - ), - trailing: StreamSvgIcon.right(), - onTap: () {}, - ), - _OptionListTile( - title: '2 Shared groups', - leading: StreamSvgIcon.Icon_group( - size: 24.0, + size: 32.0, color: Colors.black.withOpacity(0.5), ), trailing: StreamSvgIcon.right(), onTap: () {}, ), + StreamBuilder>( + stream: StreamChat.of(context).client.queryChannels( + filter: { + 'members': [StreamChat.of(context).user.id, widget.user.id], + }, + ), + builder: (context, snapshot) { + return _OptionListTile( + title: + '${snapshot.data == null ? '0' : snapshot.data.length} Shared groups', + leading: StreamSvgIcon.Icon_group( + size: 24.0, + color: Colors.black.withOpacity(0.5), + ), + trailing: StreamSvgIcon.right(), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => _SharedGroupsScreen( + StreamChat.of(context).user, widget.user))); + }, + ); + }), ], ); } @@ -285,31 +300,34 @@ class _OptionListTile extends StatelessWidget { ), Material( color: Colors.white, - child: InkWell( - onTap: onTap, - child: Row( - children: [ - if (leading != null) - Padding( - padding: const EdgeInsets.all(22.0), - child: leading, + child: Container( + height: 56.0, + child: InkWell( + onTap: onTap, + child: Row( + children: [ + if (leading != null) + Expanded( + child: Center(child: leading), + ), + if (leading == null) + SizedBox( + width: 16.0, + ), + Expanded( + flex: 4, + child: Text( + title, + style: TextStyle( + fontWeight: FontWeight.w600, color: titleColor), + )), + Expanded( + child: Center( + child: trailing ?? Container(), + ), ), - if (leading == null) - SizedBox( - width: 16.0, - ), - Expanded( - child: Text( - title, - style: - TextStyle(fontWeight: FontWeight.w600, color: titleColor), - )), - if (trailing != null) - Padding( - padding: const EdgeInsets.all(16.0), - child: trailing, - ), - ], + ], + ), ), ), ), @@ -317,3 +335,76 @@ class _OptionListTile extends StatelessWidget { ); } } + +class _SharedGroupsScreen extends StatefulWidget { + final User mainUser; + final User otherUser; + + _SharedGroupsScreen(this.mainUser, this.otherUser); + + @override + __SharedGroupsScreenState createState() => __SharedGroupsScreenState(); +} + +class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { + @override + Widget build(BuildContext context) { + var chat = StreamChat.of(context); + + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + brightness: Theme.of(context).brightness, + elevation: 1, + centerTitle: true, + title: Text( + 'Shared Groups', + style: TextStyle(color: Colors.black, fontSize: 16.0), + ), + leading: Center( + child: InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: Container( + child: StreamSvgIcon.left( + color: Colors.black, + size: 24.0, + ), + width: 24.0, + height: 24.0, + ), + ), + ), + backgroundColor: StreamChatTheme.of(context).primaryColor, + ), + body: StreamBuilder>( + stream: chat.client.queryChannels( + filter: { + 'members': [widget.mainUser.id, widget.otherUser.id], + }, + ), + builder: (context, snapshot) { + if (snapshot.data == null) { + return Center( + child: CircularProgressIndicator(), + ); + } + + return ListView.builder( + itemCount: snapshot.data.length, + itemBuilder: (context, position) { + return StreamChannel( + channel: snapshot.data[position], + child: ChannelPreview( + channel: snapshot.data[position], + onTap: (val) {}, + ), + ); + }, + ); + }, + ), + ); + } +} From 3394126adfbb1df2d441cfd9cdac2058b16b738b Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 8 Dec 2020 17:21:20 +0530 Subject: [PATCH 03/35] fix: Fixed message input --- lib/src/message_input.dart | 47 ++++++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 4e30afe1..1d220eaf 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -354,7 +354,7 @@ class MessageInputState extends State { child: Container( clipBehavior: Clip.antiAlias, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20.0), + borderRadius: BorderRadius.circular(24.0), border: Border.all( color: Colors.grey, ), @@ -404,26 +404,45 @@ class MessageInputState extends State { child: Chip( backgroundColor: StreamChatTheme.of(context).accentColor, - label: Text( - _chosenCommand?.name ?? "", - style: TextStyle(color: Colors.white), - ), - avatar: StreamSvgIcon.lightning( - color: Colors.white, + padding: EdgeInsets.zero, + labelPadding: + EdgeInsets.symmetric(horizontal: 9.0), + label: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + StreamSvgIcon.lightning( + color: Colors.white, + size: 16.0, + ), + Text( + _chosenCommand?.name?.toUpperCase() ?? "", + style: TextStyle( + color: Colors.white, fontSize: 12.0), + ), + ], ), ), ) : null, suffixIcon: _commandEnabled - ? IconButton( - icon: Icon(Icons.cancel_outlined), - onPressed: () { + ? InkWell( + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8.0), + child: StreamSvgIcon.close_small(), + ), + onTap: () { setState(() { _commandEnabled = false; }); }, ) : null, + suffixIconConstraints: BoxConstraints( + maxHeight: 24.0, + maxWidth: 40.0, + ), ), textCapitalization: TextCapitalization.sentences, ), @@ -1784,7 +1803,7 @@ class MessageInputState extends State { Widget _buildIdleSendButton(BuildContext context) { return Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8.0) + EdgeInsets.only(bottom: 3.0), child: Center( child: InkWell( onTap: () { @@ -1793,6 +1812,8 @@ class MessageInputState extends State { child: StreamSvgIcon( assetName: _getIdleSendIcon(), color: Colors.grey, + height: 24.0, + width: 24.0, ), )), ); @@ -1801,7 +1822,7 @@ class MessageInputState extends State { Widget _buildSendButton(BuildContext context) { return Center( child: Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(8.0) + EdgeInsets.only(bottom: 3.0), child: InkWell( onTap: () { sendMessage(); @@ -1809,6 +1830,8 @@ class MessageInputState extends State { child: StreamSvgIcon( assetName: _getSendIcon(), color: StreamChatTheme.of(context).accentColor, + height: 24.0, + width: 24.0, ), ), ), From 08c7b281983082c4adec0caf12c13a331166c0a4 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 8 Dec 2020 17:36:21 +0530 Subject: [PATCH 04/35] fix: Fixed command picker --- lib/src/message_input.dart | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 1d220eaf..2eb5b706 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -1461,16 +1461,31 @@ class MessageInputState extends State { padding: const EdgeInsets.only(left: 4.0, right: 8.0, top: 8.0, bottom: 8.0), child: StreamSvgIcon.lightning( - color: Color(0xFF000000).withAlpha(128), + color: _commandsOverlay != null + ? StreamChatTheme.of(context).accentColor + : Color(0xFF000000).withAlpha(128), ), ), - onTap: () { + onTap: () async { + if (_openFilePickerSection) { + setState(() { + _animateContainer = false; + _openFilePickerSection = false; + _filePickerSize = _kMinMediaPickerSize; + }); + await Future.delayed(Duration(milliseconds: 300)); + } + if (_commandsOverlay == null) { - _commandsOverlay = _buildCommandsOverlayEntry(); - Overlay.of(context).insert(_commandsOverlay); + setState(() { + _commandsOverlay = _buildCommandsOverlayEntry(); + Overlay.of(context).insert(_commandsOverlay); + }); } else { - _commandsOverlay?.remove(); - _commandsOverlay = null; + setState(() { + _commandsOverlay?.remove(); + _commandsOverlay = null; + }); } }, ); From b9a1f813ac1affc12c90cbd68f7f295b54b4deff Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 8 Dec 2020 19:25:04 +0530 Subject: [PATCH 05/35] fix: Fixed file title --- lib/src/message_input.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 2eb5b706..43bfec97 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -1701,7 +1701,7 @@ class MessageInputState extends State { localUri: file.path != null ? Uri.parse(file.path) : null, type: attachmentType, extraData: extraDataMap.isNotEmpty ? extraDataMap : null, - title: file.name ?? 'File', + title: mimeType.type == 'file' ? file.name : null, ), ); From c6a97751316bdebcc05f1a6c19535a3aa0315c8d Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 8 Dec 2020 20:00:30 +0530 Subject: [PATCH 06/35] feat: Added dummy pages for files and media --- lib/src/chat_info_screen.dart | 78 ++++++++++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/lib/src/chat_info_screen.dart b/lib/src/chat_info_screen.dart index 8310d1b9..92ee4446 100644 --- a/lib/src/chat_info_screen.dart +++ b/lib/src/chat_info_screen.dart @@ -147,7 +147,10 @@ class _ChatInfoScreenState extends State { color: Colors.black.withOpacity(0.5), ), trailing: StreamSvgIcon.right(), - onTap: () {}, + onTap: () { + Navigator.push(context, + MaterialPageRoute(builder: (context) => _MediaDisplayScreen())); + }, ), _OptionListTile( title: '8 Files', @@ -156,7 +159,10 @@ class _ChatInfoScreenState extends State { color: Colors.black.withOpacity(0.5), ), trailing: StreamSvgIcon.right(), - onTap: () {}, + onTap: () { + Navigator.push(context, + MaterialPageRoute(builder: (context) => _FileDisplayScreen())); + }, ), StreamBuilder>( stream: StreamChat.of(context).client.queryChannels( @@ -408,3 +414,71 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { ); } } + +class _MediaDisplayScreen extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + brightness: Theme.of(context).brightness, + elevation: 1, + centerTitle: true, + title: Text( + 'Photos & Videos', + style: TextStyle(color: Colors.black, fontSize: 16.0), + ), + leading: Center( + child: InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: Container( + child: StreamSvgIcon.left( + color: Colors.black, + size: 24.0, + ), + width: 24.0, + height: 24.0, + ), + ), + ), + backgroundColor: StreamChatTheme.of(context).primaryColor, + ), + ); + } +} + +class _FileDisplayScreen extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + appBar: AppBar( + brightness: Theme.of(context).brightness, + elevation: 1, + centerTitle: true, + title: Text( + 'Files', + style: TextStyle(color: Colors.black, fontSize: 16.0), + ), + leading: Center( + child: InkWell( + onTap: () { + Navigator.of(context).pop(); + }, + child: Container( + child: StreamSvgIcon.left( + color: Colors.black, + size: 24.0, + ), + width: 24.0, + height: 24.0, + ), + ), + ), + backgroundColor: StreamChatTheme.of(context).primaryColor, + ), + ); + } +} From 5cef917e511538342e855e0d22d1a4d2f4d79077 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 9 Dec 2020 13:35:28 +0530 Subject: [PATCH 07/35] feat: Added implementation for last seen --- lib/src/chat_info_screen.dart | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/src/chat_info_screen.dart b/lib/src/chat_info_screen.dart index 92ee4446..9ef80c9e 100644 --- a/lib/src/chat_info_screen.dart +++ b/lib/src/chat_info_screen.dart @@ -1,6 +1,7 @@ import 'package:emojis/emojis.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:jiffy/jiffy.dart'; import '../stream_chat_flutter.dart'; @@ -62,7 +63,7 @@ class _ChatInfoScreenState extends State { style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold), ), SizedBox(height: 7.0), - Text('Online for 5 minutes'), + _buildConnectedTitleState(), SizedBox(height: 15.0), _OptionListTile( title: '@user', @@ -279,6 +280,31 @@ class _ChatInfoScreenState extends State { ); }); } + + Widget _buildConnectedTitleState() { + var alternativeWidget; + + final otherMember = widget.user; + + if (otherMember != null) { + if (otherMember.online) { + alternativeWidget = Text( + 'Online', + style: StreamChatTheme.of(context) + .channelTheme + .channelHeaderTheme + .lastMessageAt, + ); + } else { + alternativeWidget = Text( + 'Last seen ${Jiffy(otherMember.lastActive).fromNow()}', + //style: textStyle, + ); + } + } + + return alternativeWidget; + } } class _OptionListTile extends StatelessWidget { From fe541892d9ee8dacbd42818544ba301c5629cba4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 9 Dec 2020 12:03:01 +0100 Subject: [PATCH 08/35] fix .type on null bug --- lib/src/message_input.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 43bfec97..96439be9 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -818,7 +818,7 @@ class MessageInputState extends State { Widget _buildPickerSection() { var _attachmentContainsFile = - _attachments.any((element) => element.attachment.type == 'file'); + _attachments.any((element) => element.attachment?.type == 'file'); switch (_filePickerIndex) { case 0: From aa09fd380ef08eebe48986dd920121e9fb8e856f Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 9 Dec 2020 16:56:26 +0530 Subject: [PATCH 09/35] fix: UI fixes --- lib/src/chat_info_screen.dart | 95 +++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 11 deletions(-) diff --git a/lib/src/chat_info_screen.dart b/lib/src/chat_info_screen.dart index 9ef80c9e..cbda9d9b 100644 --- a/lib/src/chat_info_screen.dart +++ b/lib/src/chat_info_screen.dart @@ -67,7 +67,11 @@ class _ChatInfoScreenState extends State { SizedBox(height: 15.0), _OptionListTile( title: '@user', - trailing: Text(widget.user.name), + trailing: Text( + widget.user.name, + style: TextStyle( + color: Colors.black.withOpacity(0.5), fontSize: 16.0), + ), onTap: () {}, ), ], @@ -198,7 +202,7 @@ class _ChatInfoScreenState extends State { title: 'Delete', leading: StreamSvgIcon.delete( color: Colors.red, - size: 20.0, + size: 24.0, ), onTap: () { _showDeleteDialog(); @@ -290,15 +294,12 @@ class _ChatInfoScreenState extends State { if (otherMember.online) { alternativeWidget = Text( 'Online', - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .lastMessageAt, + style: TextStyle(color: Colors.black.withOpacity(0.5)), ); } else { alternativeWidget = Text( 'Last seen ${Jiffy(otherMember.lastActive).fromNow()}', - //style: textStyle, + style: TextStyle(color: Colors.black.withOpacity(0.5)), ); } } @@ -428,10 +429,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { itemBuilder: (context, position) { return StreamChannel( channel: snapshot.data[position], - child: ChannelPreview( - channel: snapshot.data[position], - onTap: (val) {}, - ), + child: _buildListTile(snapshot.data[position]), ); }, ); @@ -439,6 +437,81 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { ), ); } + + Widget _buildListTile(Channel channel) { + var extraData = channel.extraData; + var members = channel.state.members; + + var textStyle = TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold); + + return Container( + height: 64.0, + child: LayoutBuilder(builder: (context, constraints) { + String title; + if (extraData['name'] == null) { + final otherMembers = members.where( + (member) => member.userId != StreamChat.of(context).user.id); + if (otherMembers.isNotEmpty) { + final maxWidth = constraints.maxWidth; + final maxChars = maxWidth / textStyle.fontSize; + var currentChars = 0; + final currentMembers = []; + otherMembers.forEach((element) { + final newLength = currentChars + element.user.name.length; + if (newLength < maxChars) { + currentChars = newLength; + currentMembers.add(element); + } + }); + + final exceedingMembers = + otherMembers.length - currentMembers.length; + title = + '${currentMembers.map((e) => e.user.name).join(', ')} ${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; + } else { + title = 'No title'; + } + } else { + title = extraData['name']; + } + + return Column( + children: [ + Expanded( + child: Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: ChannelImage( + channel: channel, + constraints: + BoxConstraints(maxWidth: 40.0, maxHeight: 40.0), + ), + ), + Expanded( + child: Text( + title, + style: textStyle, + )), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + '${channel.memberCount} members', + style: TextStyle(color: Colors.black.withOpacity(0.5)), + ), + ) + ], + ), + ), + Container( + height: 1.0, + color: Color(0xffe6e6e6), + ), + ], + ); + }), + ); + } } class _MediaDisplayScreen extends StatelessWidget { From 9f56730409cb49063518a239019adbeae2df5c54 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 9 Dec 2020 17:13:59 +0530 Subject: [PATCH 10/35] fix: UI fixes --- lib/src/chat_info_screen.dart | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/lib/src/chat_info_screen.dart b/lib/src/chat_info_screen.dart index cbda9d9b..c961b0e0 100644 --- a/lib/src/chat_info_screen.dart +++ b/lib/src/chat_info_screen.dart @@ -55,6 +55,7 @@ class _ChatInfoScreenState extends State { maxHeight: 72.0, ), borderRadius: BorderRadius.circular(36.0), + showOnlineStatus: false, ), ), SizedBox(height: 15.0), @@ -304,7 +305,28 @@ class _ChatInfoScreenState extends State { } } - return alternativeWidget; + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (widget.user.online) + Material( + type: MaterialType.circle, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + constraints: BoxConstraints.tightFor( + width: 28, + height: 12, + ), + child: Material( + shape: CircleBorder(), + color: Color(0xff20E070), + ), + ), + color: Colors.white, + ), + alternativeWidget, + ], + ); } } From 839582d72b4b905a77d2a3570bc39322ead00869 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 9 Dec 2020 17:16:48 +0530 Subject: [PATCH 11/35] fix: UI fixes --- lib/src/chat_info_screen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/chat_info_screen.dart b/lib/src/chat_info_screen.dart index c961b0e0..aad1cd05 100644 --- a/lib/src/chat_info_screen.dart +++ b/lib/src/chat_info_screen.dart @@ -58,7 +58,7 @@ class _ChatInfoScreenState extends State { showOnlineStatus: false, ), ), - SizedBox(height: 15.0), + //SizedBox(height: 4.0), Text( widget.user.name, style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold), From ca6a90c97f3ada6d9bdcdf24cb6fe57d05a37436 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 9 Dec 2020 17:47:24 +0530 Subject: [PATCH 12/35] fix: Fixed colors of message attachments --- lib/src/message_input.dart | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 96439be9..b19211ba 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -683,14 +683,10 @@ class MessageInputState extends State { Color _getIconColor(int index) { switch (index) { case 0: - return _attachmentContainsFile && _attachments.isNotEmpty - ? Colors.black.withOpacity(0.2) - : Colors.black.withOpacity(0.5); + return _attachments.isEmpty ? StreamChatTheme.of(context).accentColor : (!_attachmentContainsFile ? StreamChatTheme.of(context).accentColor : Colors.black.withOpacity(0.2)); break; case 1: - return !_attachmentContainsFile && _attachments.isNotEmpty - ? Colors.black.withOpacity(0.2) - : Colors.black.withOpacity(0.5); + return _attachmentContainsFile ? StreamChatTheme.of(context).accentColor : (_attachments.isEmpty ? Colors.black.withOpacity(0.5) : Colors.black.withOpacity(0.2)); break; case 2: return _attachmentContainsFile && _attachments.isNotEmpty From 79b1401700a3d5cd2ec1090f46d3b34f1e4c93ae Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 9 Dec 2020 17:54:13 +0530 Subject: [PATCH 13/35] fix: Fixed padding --- lib/src/chat_info_screen.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/src/chat_info_screen.dart b/lib/src/chat_info_screen.dart index aad1cd05..3881e4a3 100644 --- a/lib/src/chat_info_screen.dart +++ b/lib/src/chat_info_screen.dart @@ -68,10 +68,13 @@ class _ChatInfoScreenState extends State { SizedBox(height: 15.0), _OptionListTile( title: '@user', - trailing: Text( - widget.user.name, - style: TextStyle( - color: Colors.black.withOpacity(0.5), fontSize: 16.0), + trailing: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Text( + widget.user.name, + style: TextStyle( + color: Colors.black.withOpacity(0.5), fontSize: 16.0), + ), ), onTap: () {}, ), From 39a2eeea32407f72074271d80dccfc6c06bc36c0 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 10 Dec 2020 13:40:01 +0530 Subject: [PATCH 14/35] fix: File fix --- lib/src/message_input.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index b19211ba..e21ab4b1 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -1697,7 +1697,7 @@ class MessageInputState extends State { localUri: file.path != null ? Uri.parse(file.path) : null, type: attachmentType, extraData: extraDataMap.isNotEmpty ? extraDataMap : null, - title: mimeType.type == 'file' ? file.name : null, + title: file.name, ), ); From 328bc4d1bcf02b56c63706f7e19991cc424849db Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 10 Dec 2020 14:42:58 +0530 Subject: [PATCH 15/35] fix: Removed block button --- lib/src/chat_info_screen.dart | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/src/chat_info_screen.dart b/lib/src/chat_info_screen.dart index 3881e4a3..434b0021 100644 --- a/lib/src/chat_info_screen.dart +++ b/lib/src/chat_info_screen.dart @@ -137,18 +137,24 @@ class _ChatInfoScreenState extends State { onTap: () {}, ); }), - _OptionListTile( - title: 'Block User', - leading: StreamSvgIcon.Icon_user_delete( - size: 24.0, - color: Colors.black.withOpacity(0.5), - ), - trailing: CupertinoSwitch( - value: true, - onChanged: (val) {}, - ), - onTap: () {}, - ), + // _OptionListTile( + // title: 'Block User', + // leading: StreamSvgIcon.Icon_user_delete( + // size: 24.0, + // color: Colors.black.withOpacity(0.5), + // ), + // trailing: CupertinoSwitch( + // value: widget.user.banned, + // onChanged: (val) { + // if (widget.user.banned) { + // channel.channel.shadowBan(widget.user.id, {}); + // } else { + // channel.channel.unbanUser(widget.user.id); + // } + // }, + // ), + // onTap: () {}, + // ), _OptionListTile( title: '615 Photos & Videos', leading: StreamSvgIcon.pictures( From e0e98299cae1fa51b1b88d4fb54d7e6f7d945643 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 10 Dec 2020 15:06:32 +0530 Subject: [PATCH 16/35] fix: Filter and alignment fix --- lib/src/chat_info_screen.dart | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/src/chat_info_screen.dart b/lib/src/chat_info_screen.dart index 434b0021..e56e812c 100644 --- a/lib/src/chat_info_screen.dart +++ b/lib/src/chat_info_screen.dart @@ -182,7 +182,18 @@ class _ChatInfoScreenState extends State { StreamBuilder>( stream: StreamChat.of(context).client.queryChannels( filter: { - 'members': [StreamChat.of(context).user.id, widget.user.id], + r'$and': [ + { + 'members': { + r'$in': [widget.user.id], + }, + }, + { + 'members': { + r'$in': [StreamChat.of(context).user.id], + }, + } + ], }, ), builder: (context, snapshot) { @@ -386,8 +397,13 @@ class _OptionListTile extends StatelessWidget { fontWeight: FontWeight.w600, color: titleColor), )), Expanded( - child: Center( - child: trailing ?? Container(), + flex: 2, + child: Padding( + padding: const EdgeInsets.only(right: 16.0), + child: Align( + alignment: Alignment.centerRight, + child: trailing ?? Container(), + ), ), ), ], From d97f7ecebffe821fce9edca057ad1ec368a21378 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 10 Dec 2020 15:18:14 +0530 Subject: [PATCH 17/35] fix: Filter and alignment fix --- lib/src/chat_info_screen.dart | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/src/chat_info_screen.dart b/lib/src/chat_info_screen.dart index e56e812c..f9ee70cd 100644 --- a/lib/src/chat_info_screen.dart +++ b/lib/src/chat_info_screen.dart @@ -461,7 +461,18 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { body: StreamBuilder>( stream: chat.client.queryChannels( filter: { - 'members': [widget.mainUser.id, widget.otherUser.id], + r'$and': [ + { + 'members': { + r'$in': [widget.otherUser.id], + }, + }, + { + 'members': { + r'$in': [widget.mainUser.id], + }, + } + ], }, ), builder: (context, snapshot) { From df2d82231502f32551612c81ef095a447ac7c71e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 10 Dec 2020 11:42:01 +0100 Subject: [PATCH 18/35] add ontap to channelpreview --- lib/src/channel_header.dart | 29 ++++++++++++++++------------- lib/src/channel_preview.dart | 25 ++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 14 deletions(-) diff --git a/lib/src/channel_header.dart b/lib/src/channel_header.dart index 09833ad3..96ed9769 100644 --- a/lib/src/channel_header.dart +++ b/lib/src/channel_header.dart @@ -101,21 +101,24 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { child: ChannelImage( onTap: onImageTap ?? () { - var currentUser = StreamChat.of(context).user; - var otherUser = channel.state.members.firstWhere( - (element) => element.user.id != currentUser.id); - - if (channel.memberCount == 2) { + if (channel.memberCount == 2 && channel.isDistinct) { + final currentUser = StreamChat.of(context).user; + final otherUser = channel.state.members.firstWhere( + (element) => element.user.id != currentUser.id, + orElse: () => null, + ); if (otherUser != null) { Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: channel, - child: ChatInfoScreen( - user: otherUser.user, - ), - ))); + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: ChatInfoScreen( + user: otherUser.user, + ), + ), + ), + ); } } }, diff --git a/lib/src/channel_preview.dart b/lib/src/channel_preview.dart index 26f68812..5f58e254 100644 --- a/lib/src/channel_preview.dart +++ b/lib/src/channel_preview.dart @@ -7,6 +7,7 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import '../stream_chat_flutter.dart'; import 'channel_name.dart'; import 'channel_unread_indicator.dart'; +import 'chat_info_screen.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview_paint.png) @@ -60,7 +61,29 @@ class ChannelPreview extends StatelessWidget { } }, leading: ChannelImage( - onTap: onImageTap, + onTap: onImageTap ?? + () { + if (channel.memberCount == 2 && channel.isDistinct) { + final currentUser = StreamChat.of(context).user; + final otherUser = channel.state.members.firstWhere( + (element) => element.user.id != currentUser.id, + orElse: () => null, + ); + if (otherUser != null) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: ChatInfoScreen( + user: otherUser.user, + ), + ), + ), + ); + } + } + }, ), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, From b85ed04170019289b8d1aa06bc4ddcde5ec12935 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 10 Dec 2020 12:29:51 +0100 Subject: [PATCH 19/35] update 1:1 chatinfo ui --- lib/src/chat_info_screen.dart | 56 +++++++++++------------------------ 1 file changed, 18 insertions(+), 38 deletions(-) diff --git a/lib/src/chat_info_screen.dart b/lib/src/chat_info_screen.dart index f9ee70cd..0ba300ee 100644 --- a/lib/src/chat_info_screen.dart +++ b/lib/src/chat_info_screen.dart @@ -67,7 +67,7 @@ class _ChatInfoScreenState extends State { _buildConnectedTitleState(), SizedBox(height: 15.0), _OptionListTile( - title: '@user', + title: '@${widget.user.id}', trailing: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Text( @@ -156,7 +156,7 @@ class _ChatInfoScreenState extends State { // onTap: () {}, // ), _OptionListTile( - title: '615 Photos & Videos', + title: 'Photos & Videos', leading: StreamSvgIcon.pictures( size: 32.0, color: Colors.black.withOpacity(0.5), @@ -168,7 +168,7 @@ class _ChatInfoScreenState extends State { }, ), _OptionListTile( - title: '8 Files', + title: 'Files', leading: StreamSvgIcon.files( size: 32.0, color: Colors.black.withOpacity(0.5), @@ -179,41 +179,21 @@ class _ChatInfoScreenState extends State { MaterialPageRoute(builder: (context) => _FileDisplayScreen())); }, ), - StreamBuilder>( - stream: StreamChat.of(context).client.queryChannels( - filter: { - r'$and': [ - { - 'members': { - r'$in': [widget.user.id], - }, - }, - { - 'members': { - r'$in': [StreamChat.of(context).user.id], - }, - } - ], - }, - ), - builder: (context, snapshot) { - return _OptionListTile( - title: - '${snapshot.data == null ? '0' : snapshot.data.length} Shared groups', - leading: StreamSvgIcon.Icon_group( - size: 24.0, - color: Colors.black.withOpacity(0.5), - ), - trailing: StreamSvgIcon.right(), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => _SharedGroupsScreen( - StreamChat.of(context).user, widget.user))); - }, - ); - }), + _OptionListTile( + title: 'Shared groups', + leading: StreamSvgIcon.Icon_group( + size: 24.0, + color: Colors.black.withOpacity(0.5), + ), + trailing: StreamSvgIcon.right(), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => _SharedGroupsScreen( + StreamChat.of(context).user, widget.user))); + }, + ), ], ); } From 0534488b9480c9ff3503b7d6bc83cdc61c8d0c2b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 10 Dec 2020 14:42:15 +0100 Subject: [PATCH 20/35] fix delete conversation logic --- lib/src/channel_bottom_sheet.dart | 72 +++++++++++------------ lib/src/channel_header.dart | 8 ++- lib/src/channel_list_view.dart | 55 +++++++----------- lib/src/chat_info_screen.dart | 97 ++++++++----------------------- lib/src/utils.dart | 93 +++++++++++++++++++++-------- 5 files changed, 158 insertions(+), 167 deletions(-) diff --git a/lib/src/channel_bottom_sheet.dart b/lib/src/channel_bottom_sheet.dart index 48ce8887..2d16bf6d 100644 --- a/lib/src/channel_bottom_sheet.dart +++ b/lib/src/channel_bottom_sheet.dart @@ -69,40 +69,26 @@ class ChannelBottomSheet extends StatelessWidget { ), ), Divider(), - StreamBuilder( - stream: channel.isMutedStream, - initialData: channel.isMuted, - builder: (context, snapshot) { - return ListTile( - leading: StreamSvgIcon.mute( - size: 22, - color: StreamChatTheme.of(context).primaryIconTheme.color, - ), - title: Text('Mute ${channel.isGroup ? 'group' : 'user'}'), - trailing: Switch( - onChanged: (bool muted) async { - if (muted) { - await channel.mute(); - } else { - await channel.unmute(); - } - }, - value: snapshot.data, - ), - ); - }), - Divider(), if (channel.isGroup && !channel.isDistinct) ListTile( leading: StreamSvgIcon.userRemove( - size: 22, - color: Colors.black, + size: 24, + color: Color(0xff7A7A7A), + ), + title: Text( + 'Leave Group', + style: TextStyle(fontWeight: FontWeight.bold), ), - title: Text('Leave Group'), onTap: () async { final confirm = await showConfirmationDialog( context, - 'Do you want to leave the group?', + title: 'Leave Group', + okText: 'LEAVE', + question: 'Are you sure you want to leave this group?', + cancelText: 'CANCEL', + icon: StreamSvgIcon.userRemove( + color: Colors.red, + ), ); if (confirm == true) { await channel @@ -111,11 +97,17 @@ class ChannelBottomSheet extends StatelessWidget { } }, ), - if (!channel.isGroup && !channel.isDistinct) + if ([ + 'admin', + 'owner', + ].contains(channel.state.members + .firstWhere((m) => m.userId == channel.client.state.user.id, + orElse: () => null) + ?.role)) ListTile( - leading: Icon( - Icons.delete_outline, + leading: StreamSvgIcon.delete( color: Color(0xFFFF3742), + size: 24, ), title: Text( 'Delete chat', @@ -124,14 +116,22 @@ class ChannelBottomSheet extends StatelessWidget { ), ), onTap: () async { - final confirm = await showConfirmationDialog( + final res = await showConfirmationDialog( context, - 'Do you want to delete the chat?', + title: 'Delete Conversation', + okText: 'DELETE', + question: + 'Are you sure you want to delete this conversation?', + cancelText: 'CANCEL', + icon: StreamSvgIcon.delete( + color: Colors.red, + ), ); - if (confirm == true) { - await channel - .removeMembers([StreamChat.of(context).user.id]); - Navigator.pop(context); + var channel = StreamChannel.of(context).channel; + if (res == true) { + await channel.delete().then((value) { + Navigator.pop(context); + }); } }, ), diff --git a/lib/src/channel_header.dart b/lib/src/channel_header.dart index 96ed9769..65f63cc4 100644 --- a/lib/src/channel_header.dart +++ b/lib/src/channel_header.dart @@ -100,7 +100,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { child: Center( child: ChannelImage( onTap: onImageTap ?? - () { + () async { if (channel.memberCount == 2 && channel.isDistinct) { final currentUser = StreamChat.of(context).user; final otherUser = channel.state.members.firstWhere( @@ -108,7 +108,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { orElse: () => null, ); if (otherUser != null) { - Navigator.push( + final pop = await Navigator.push( context, MaterialPageRoute( builder: (context) => StreamChannel( @@ -119,6 +119,10 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { ), ), ); + + if (pop == true) { + Navigator.pop(context); + } } } }, diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 03284c6d..5dbc9f90 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -524,44 +524,33 @@ class _ChannelListViewState extends State ); }, ), - IconSlideAction( - color: backgroundColor, - iconWidget: StreamSvgIcon.mute(), - onTap: () async { - if (!channel.isMuted) { - await channel.mute(); - } else { - await channel.unmute(); - } - }, - ), - if (channel.isGroup && !channel.isDistinct) + if ([ + 'admin', + 'owner', + ].contains(channel.state.members + .firstWhere( + (m) => m.userId == channel.client.state.user.id, + orElse: () => null) + ?.role)) IconSlideAction( color: backgroundColor, - iconWidget: StreamSvgIcon.userRemove(), + iconWidget: StreamSvgIcon.delete( + color: Color(0xFFFF3742), + ), onTap: () async { - final confirm = await showConfirmationDialog( + final res = await showConfirmationDialog( context, - 'Do you want to leave the group?', + title: 'Delete Conversation', + okText: 'DELETE', + question: + 'Are you sure you want to delete this conversation?', + cancelText: 'CANCEL', + icon: StreamSvgIcon.delete( + color: Color(0xFFFF3742), + ), ); - if (confirm == true) { - await channel - .removeMembers([StreamChat.of(context).user.id]); - } - }, - ), - if (!channel.isGroup && !channel.isDistinct) - IconSlideAction( - color: backgroundColor, - icon: Icons.delete_outline, - onTap: () async { - final confirm = await showConfirmationDialog( - context, - 'Do you want to delete the chat?', - ); - if (confirm == true) { - await channel - .removeMembers([StreamChat.of(context).user.id]); + if (res == true) { + await channel.delete(); } }, ), diff --git a/lib/src/chat_info_screen.dart b/lib/src/chat_info_screen.dart index 0ba300ee..0ec62c36 100644 --- a/lib/src/chat_info_screen.dart +++ b/lib/src/chat_info_screen.dart @@ -19,6 +19,7 @@ class ChatInfoScreen extends StatefulWidget { class _ChatInfoScreenState extends State { @override Widget build(BuildContext context) { + final channel = StreamChannel.of(context).channel; return Scaffold( backgroundColor: Color(0xFFe6e6e6), body: ListView( @@ -31,7 +32,14 @@ class _ChatInfoScreenState extends State { SizedBox( height: 8.0, ), - _buildDeleteListTile(), + if ([ + 'admin', + 'owner', + ].contains(channel.state.members + .firstWhere((m) => m.userId == channel.client.state.user.id, + orElse: () => null) + ?.role)) + _buildDeleteListTile(), ], ), ); @@ -212,78 +220,23 @@ class _ChatInfoScreenState extends State { ); } - void _showDeleteDialog() { + void _showDeleteDialog() async { + final res = await showConfirmationDialog( + context, + title: 'Delete Conversation', + okText: 'DELETE', + question: 'Are you sure you want to delete this conversation?', + cancelText: 'CANCEL', + icon: StreamSvgIcon.delete( + color: Colors.red, + ), + ); var channel = StreamChannel.of(context).channel; - - showModalBottomSheet( - backgroundColor: Colors.white, - context: context, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16.0), - topRight: Radius.circular(16.0), - )), - builder: (context) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - height: 26.0, - ), - StreamSvgIcon.delete( - color: Colors.red, - ), - SizedBox( - height: 26.0, - ), - Text( - 'Delete Conversation', - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0), - ), - SizedBox( - height: 7.0, - ), - Text('Are you sure you want to delete this conversation?'), - SizedBox( - height: 36.0, - ), - Container( - color: Color(0xffe6e6e6), - height: 1.0, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - FlatButton( - child: Text( - 'CANCEL', - style: TextStyle( - color: Colors.black.withOpacity(0.5), - fontWeight: FontWeight.w400), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - FlatButton( - child: Text( - 'DELETE', - style: TextStyle( - color: Colors.red, fontWeight: FontWeight.w400), - ), - onPressed: () { - channel.delete().then((value) { - Navigator.pop(context); - Navigator.pop(context); - Navigator.pop(context); - }); - }, - ), - ], - ), - ], - ); - }); + if (res == true) { + await channel.delete().then((value) { + Navigator.pop(context); + }); + } } Widget _buildConnectedTitleState() { diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 20e3ce1f..74b1da64 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:url_launcher/url_launcher.dart'; +import '../stream_chat_flutter.dart'; + Future launchURL(BuildContext context, String url) async { if (await canLaunch(url)) { await launch(url); @@ -15,33 +17,76 @@ Future launchURL(BuildContext context, String url) async { } Future showConfirmationDialog( - BuildContext context, + BuildContext context, { + String title, + Widget icon, String question, -) { - return showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text(question), - actions: [ - FlatButton( - child: Text('Ok'), - onPressed: () => Navigator.pop( - context, - true, + String okText, + String cancelText, +}) { + return showModalBottomSheet( + backgroundColor: Colors.white, + context: context, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16.0), + topRight: Radius.circular(16.0), + )), + builder: (context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 26.0, ), - ), - FlatButton( - child: Text('Cancel'), - onPressed: () => Navigator.pop( - context, - false, + if (icon != null) icon, + SizedBox( + height: 26.0, ), - ), - ], - ); - }, - ); + Text( + title, + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16.0), + ), + SizedBox( + height: 7.0, + ), + Text(question), + SizedBox( + height: 36.0, + ), + Container( + color: Color(0xffe6e6e6), + height: 1.0, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + FlatButton( + child: Text( + cancelText, + style: TextStyle( + color: Colors.black.withOpacity(0.5), + fontWeight: FontWeight.w400), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + FlatButton( + child: Text( + okText, + style: TextStyle( + color: Colors.red, fontWeight: FontWeight.w400), + ), + onPressed: () { + Navigator.pop(context, true); + }, + ), + ], + ), + ], + ); + }); } /// Get random png with initials From 54cb49eac198142eec3d239f480e768f4264c47b Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 10 Dec 2020 19:52:32 +0530 Subject: [PATCH 21/35] feat: Added file previews --- lib/src/file_attachment.dart | 46 +++++++++++++++++++++++++++++++++--- lib/src/media_utils.dart | 17 +++++++++++++ lib/src/message_input.dart | 26 +++++++++++++++----- 3 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 lib/src/media_utils.dart diff --git a/lib/src/file_attachment.dart b/lib/src/file_attachment.dart index fff13bbe..cef966c5 100644 --- a/lib/src/file_attachment.dart +++ b/lib/src/file_attachment.dart @@ -1,18 +1,28 @@ +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:file_picker/file_picker.dart'; 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_svg_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; +import 'media_utils.dart'; + +enum FileAttachmentType { local, online } class FileAttachment extends StatelessWidget { final Attachment attachment; final Size size; final Widget trailing; + final FileAttachmentType attachmentType; + final PlatformFile file; const FileAttachment({ Key key, @required this.attachment, this.size, this.trailing, + this.attachmentType = FileAttachmentType.online, + this.file, }) : super(key: key); @override @@ -32,7 +42,7 @@ class FileAttachment extends StatelessWidget { child: Row( children: [ Container( - child: _getFileTypeImage(attachment.extraData['mime_type']), + child: _getFileTypeImage(), height: 40.0, width: 33.33, margin: EdgeInsets.all(8.0), @@ -117,8 +127,38 @@ class FileAttachment extends StatelessWidget { ); } - StreamSvgIcon _getFileTypeImage(String type) { - switch (type) { + Widget _getFileTypeImage() { + if ((MediaUtils.getMimeType(attachment.title).type == 'image')) { + switch (attachmentType) { + case FileAttachmentType.local: + return Image.memory( + file.bytes, + fit: BoxFit.cover, + ); + break; + case FileAttachmentType.online: + return CachedNetworkImage( + imageUrl: attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl, + fit: BoxFit.cover, + progressIndicatorBuilder: (context, _, progress) { + return Center( + child: Container( + width: 20.0, + height: 20.0, + child: CircularProgressIndicator( + backgroundColor: StreamChatTheme.of(context).accentColor, + ), + ), + ); + }, + ); + break; + } + } + + switch (attachment.extraData['mime_type']) { case '7z': return StreamSvgIcon.filetype_7z(); break; diff --git a/lib/src/media_utils.dart b/lib/src/media_utils.dart new file mode 100644 index 00000000..e91a47d6 --- /dev/null +++ b/lib/src/media_utils.dart @@ -0,0 +1,17 @@ +import 'package:http_parser/http_parser.dart' as httpParser; +import 'package:mime/mime.dart'; + +class MediaUtils { + static httpParser.MediaType getMimeType(String filename) { + httpParser.MediaType mimeType; + if (filename != null) { + if (filename.toLowerCase().endsWith('heic')) { + mimeType = httpParser.MediaType.parse('image/heic'); + } else { + mimeType = httpParser.MediaType.parse(lookupMimeType(filename)); + } + } + + return mimeType; + } +} diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index e21ab4b1..870c1ed9 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -683,10 +683,18 @@ class MessageInputState extends State { Color _getIconColor(int index) { switch (index) { case 0: - return _attachments.isEmpty ? StreamChatTheme.of(context).accentColor : (!_attachmentContainsFile ? StreamChatTheme.of(context).accentColor : Colors.black.withOpacity(0.2)); + return _attachments.isEmpty + ? StreamChatTheme.of(context).accentColor + : (!_attachmentContainsFile + ? StreamChatTheme.of(context).accentColor + : Colors.black.withOpacity(0.2)); break; case 1: - return _attachmentContainsFile ? StreamChatTheme.of(context).accentColor : (_attachments.isEmpty ? Colors.black.withOpacity(0.5) : Colors.black.withOpacity(0.2)); + return _attachmentContainsFile + ? StreamChatTheme.of(context).accentColor + : (_attachments.isEmpty + ? Colors.black.withOpacity(0.5) + : Colors.black.withOpacity(0.2)); break; case 2: return _attachmentContainsFile && _attachments.isNotEmpty @@ -1268,6 +1276,8 @@ class MessageInputState extends State { clipBehavior: Clip.antiAlias, child: FileAttachment( attachment: e.attachment, + attachmentType: FileAttachmentType.local, + file: e.file, size: Size( MediaQuery.of(context).size.width * 0.55, MediaQuery.of(context).size.height * 0.3, @@ -1676,12 +1686,16 @@ class MessageInputState extends State { final mimeType = _getMimeType(file.path.split('/').last); - if (mimeType.type == 'video' || mimeType.type == 'image') { - attachmentType = mimeType.type; - } - Map extraDataMap = {}; + if (camera) { + if (mimeType.type == 'video' || mimeType.type == 'image') { + attachmentType = mimeType.type; + } + } else { + attachmentType = 'file'; + } + if (mimeType?.subtype != null) { extraDataMap['mime_type'] = mimeType.subtype.toLowerCase(); } From 5302b16115e7af9d272a3f3605c65468e5b67152 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 11 Dec 2020 13:04:12 +0530 Subject: [PATCH 22/35] fix: keyboard fix --- lib/src/message_input.dart | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 870c1ed9..dd9e2718 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -326,14 +326,17 @@ class MessageInputState extends State { return AnimatedCrossFade( crossFadeState: _actionsShrunk ? CrossFadeState.showFirst : CrossFadeState.showSecond, - firstChild: IconButton( - onPressed: () { + firstChild: InkWell( + onTap: () { setState(() { _actionsShrunk = false; }); }, - icon: StreamSvgIcon.emptyCircleLeft( - color: StreamChatTheme.of(context).accentColor, + child: Padding( + padding: const EdgeInsets.all(8.0) + EdgeInsets.only(bottom: 3.0), + child: StreamSvgIcon.emptyCircleLeft( + color: StreamChatTheme.of(context).accentColor, + ), ), ), secondChild: Row( From bcdc034a2d7f9f1e9c3de93d3cc1e435bf75c2cd Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 11 Dec 2020 09:40:33 +0100 Subject: [PATCH 23/35] fix medialistview null bug --- lib/src/media_list_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/media_list_view.dart b/lib/src/media_list_view.dart index 507238da..5f52e9b5 100644 --- a/lib/src/media_list_view.dart +++ b/lib/src/media_list_view.dart @@ -180,7 +180,7 @@ class MediaThumbnailProvider extends ImageProvider { MediaThumbnailProvider key, DecoderCallback decode) async { assert(key == this); final bytes = await media.thumbData; - if (bytes.isEmpty) return null; + if (bytes?.isNotEmpty != true) return null; return await decode(bytes); } From 9fd79a3e00055e4b3671e4a55fa94cfe56aac36c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 11 Dec 2020 10:15:07 +0100 Subject: [PATCH 24/35] fix urlattachment title --- lib/src/url_attachment.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/url_attachment.dart b/lib/src/url_attachment.dart index d735ad67..ad314455 100644 --- a/lib/src/url_attachment.dart +++ b/lib/src/url_attachment.dart @@ -78,8 +78,9 @@ class UrlAttachment extends StatelessWidget { children: [ if (urlAttachment.title != null) Text( - urlAttachment.title, + urlAttachment.title.trim(), maxLines: 1, + overflow: TextOverflow.ellipsis, style: TextStyle( fontWeight: FontWeight.w700, fontSize: 12.0, From 4b122a7e5460f16ee671fad205caa505c135e533 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 11 Dec 2020 17:01:00 +0530 Subject: [PATCH 25/35] feat: Added new picker implementation --- lib/src/message_input.dart | 47 +++++++++++++++++++++++++------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index dd9e2718..b6a74f11 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -839,22 +839,37 @@ class MessageInputState extends State { } if (snapshot.data) { - return IgnorePointer( - ignoring: _attachmentContainsFile, - child: MediaListView( - selectedIds: _attachments.map((e) => e.id).toList(), - onSelect: (media) async { - if (!_attachments - .any((element) => element.id == media.id)) { - _addAttachment(media); - } else { - setState(() { - _attachments - .removeWhere((element) => element.id == media.id); - }); - } - }, - ), + if (_attachmentContainsFile) { + return Container( + color: Color(0xfff2f2f2), + child: InkWell( + onTap: () { + pickFile(DefaultAttachmentTypes.file); + }, + child: Text( + 'Add more files', + style: TextStyle( + color: StreamChatTheme.of(context).accentColor, + fontWeight: FontWeight.bold, + ), + ), + ), + alignment: Alignment.center, + ); + } + return MediaListView( + selectedIds: _attachments.map((e) => e.id).toList(), + onSelect: (media) async { + if (!_attachments + .any((element) => element.id == media.id)) { + _addAttachment(media); + } else { + setState(() { + _attachments + .removeWhere((element) => element.id == media.id); + }); + } + }, ); } From edeb38a2553dac733d48135c919ad2e3cbfd86bb Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 11 Dec 2020 17:10:12 +0530 Subject: [PATCH 26/35] fix: Fixed file clip bug --- lib/src/message_input.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index b6a74f11..befed883 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -362,6 +362,7 @@ class MessageInputState extends State { color: Colors.grey, ), ), + padding: EdgeInsets.all(6.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ From 102e6ce4d09dcc1485c9274a32b5a1a6a226db7c Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 11 Dec 2020 17:11:19 +0530 Subject: [PATCH 27/35] fix: Fixed file clip bug --- lib/src/message_input.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index befed883..e60c6187 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -362,7 +362,7 @@ class MessageInputState extends State { color: Colors.grey, ), ), - padding: EdgeInsets.all(6.0), + padding: _attachments.isEmpty ? null : EdgeInsets.all(6.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ From 18781201e37d28d09fcdce0c24bd8569a1ab53fc Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 11 Dec 2020 18:18:35 +0530 Subject: [PATCH 28/35] feat: Added video thumbnails and playback for attachments --- lib/src/file_attachment.dart | 101 +++++++++++++++++++++++++++++------ 1 file changed, 85 insertions(+), 16 deletions(-) diff --git a/lib/src/file_attachment.dart b/lib/src/file_attachment.dart index cef966c5..95e628f0 100644 --- a/lib/src/file_attachment.dart +++ b/lib/src/file_attachment.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:cached_network_image/cached_network_image.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; @@ -5,11 +7,13 @@ import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; +import 'package:video_compress/video_compress.dart'; +import 'package:video_player/video_player.dart'; import 'media_utils.dart'; enum FileAttachmentType { local, online } -class FileAttachment extends StatelessWidget { +class FileAttachment extends StatefulWidget { final Attachment attachment; final Size size; final Widget trailing; @@ -25,17 +29,44 @@ class FileAttachment extends StatelessWidget { this.file, }) : super(key: key); + @override + _FileAttachmentState createState() => _FileAttachmentState(); +} + +class _FileAttachmentState extends State { + VideoPlayerController _controller; + Future _initializeVideoPlayerFuture; + + @override + void initState() { + super.initState(); + if (MediaUtils.getMimeType(widget.attachment.title).type == 'video') { + if (widget.attachmentType == FileAttachmentType.online) { + _controller = VideoPlayerController.network( + widget.attachment.assetUrl, + ); + } else { + _controller = VideoPlayerController.file( + File.fromRawPath(widget.file.bytes), + ); + } + + _initializeVideoPlayerFuture = _controller.initialize(); + } + } + @override Widget build(BuildContext context) { return Material( child: Container( - width: size?.width ?? 100, + width: widget.size?.width ?? 100, height: 56.0, - margin: trailing != null ? EdgeInsets.only(top: 4.0) : null, + margin: widget.trailing != null ? EdgeInsets.only(top: 4.0) : null, decoration: BoxDecoration( color: Colors.white, - borderRadius: trailing != null ? BorderRadius.circular(16.0) : null, - border: trailing != null + borderRadius: + widget.trailing != null ? BorderRadius.circular(16.0) : null, + border: widget.trailing != null ? Border.fromBorderSide(BorderSide(color: Color(0xFFE6E6E6))) : null, ), @@ -56,7 +87,7 @@ class FileAttachment extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - attachment?.title ?? 'File', + widget.attachment?.title ?? 'File', style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14.0, @@ -68,7 +99,7 @@ class FileAttachment extends StatelessWidget { height: 3.0, ), Text( - '${attachment.extraData['file_size'] ?? 'N/A'} bytes', + '${widget.attachment.extraData['file_size'] ?? 'N/A'} bytes', style: TextStyle( color: Colors.black.withOpacity(0.5), fontSize: 14.0, @@ -79,13 +110,13 @@ class FileAttachment extends StatelessWidget { ), Column( children: [ - trailing ?? + widget.trailing ?? IconButton( icon: StreamSvgIcon.cloud_download( color: Colors.black, ), onPressed: () { - launchURL(context, attachment.assetUrl); + launchURL(context, widget.attachment.assetUrl); }, ), ], @@ -128,19 +159,19 @@ class FileAttachment extends StatelessWidget { } Widget _getFileTypeImage() { - if ((MediaUtils.getMimeType(attachment.title).type == 'image')) { - switch (attachmentType) { + if ((MediaUtils.getMimeType(widget.attachment.title).type == 'image')) { + switch (widget.attachmentType) { case FileAttachmentType.local: return Image.memory( - file.bytes, + widget.file.bytes, fit: BoxFit.cover, ); break; case FileAttachmentType.online: return CachedNetworkImage( - imageUrl: attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl, + imageUrl: widget.attachment.imageUrl ?? + widget.attachment.assetUrl ?? + widget.attachment.thumbUrl, fit: BoxFit.cover, progressIndicatorBuilder: (context, _, progress) { return Center( @@ -158,7 +189,45 @@ class FileAttachment extends StatelessWidget { } } - switch (attachment.extraData['mime_type']) { + if ((MediaUtils.getMimeType(widget.attachment.title).type == 'video')) { + switch (widget.attachmentType) { + case FileAttachmentType.local: + return FutureBuilder( + future: VideoCompress.getFileThumbnail(widget.file.path), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return Image.asset( + 'images/placeholder.png', + package: 'stream_chat_flutter', + ); + } + + return Image.file( + snapshot.data, + fit: BoxFit.cover, + ); + }, + ); + break; + case FileAttachmentType.online: + return FutureBuilder( + future: _initializeVideoPlayerFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + return AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: VideoPlayer(_controller), + ); + } else { + return Center(child: CircularProgressIndicator()); + } + }, + ); + break; + } + } + + switch (widget.attachment.extraData['mime_type']) { case '7z': return StreamSvgIcon.filetype_7z(); break; From c487ce92d0fd1f458f757e445811c8fd14760c18 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 11 Dec 2020 19:54:02 +0530 Subject: [PATCH 29/35] feat: Added file size in proper format --- lib/src/file_attachment.dart | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/src/file_attachment.dart b/lib/src/file_attachment.dart index 95e628f0..4bba7779 100644 --- a/lib/src/file_attachment.dart +++ b/lib/src/file_attachment.dart @@ -99,7 +99,7 @@ class _FileAttachmentState extends State { height: 3.0, ), Text( - '${widget.attachment.extraData['file_size'] ?? 'N/A'} bytes', + '${_getSizeText(widget.attachment.extraData['file_size'])}', style: TextStyle( color: Colors.black.withOpacity(0.5), fontSize: 14.0, @@ -284,4 +284,18 @@ class _FileAttachmentState extends State { break; } } + + String _getSizeText(int bytes) { + if (bytes == null) { + return 'Size N/A'; + } + + if (bytes <= 1000) { + return '${bytes} bytes'; + } else if (bytes <= 100000) { + return '${(bytes / 1000).toStringAsFixed(2)} KB'; + } else { + return '${(bytes / 1000000).toStringAsFixed(2)} MB'; + } + } } From bdd94693717ca31c660c95845734877630921227 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 11 Dec 2020 15:09:25 +0100 Subject: [PATCH 30/35] use expanded gesturedetector --- lib/src/message_input.dart | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index e60c6187..542f63e7 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -841,12 +841,13 @@ class MessageInputState extends State { if (snapshot.data) { if (_attachmentContainsFile) { - return Container( - color: Color(0xfff2f2f2), - child: InkWell( - onTap: () { - pickFile(DefaultAttachmentTypes.file); - }, + return GestureDetector( + onTap: () { + pickFile(DefaultAttachmentTypes.file); + }, + child: Container( + constraints: BoxConstraints.expand(), + color: Color(0xfff2f2f2), child: Text( 'Add more files', style: TextStyle( @@ -854,8 +855,8 @@ class MessageInputState extends State { fontWeight: FontWeight.bold, ), ), + alignment: Alignment.center, ), - alignment: Alignment.center, ); } return MediaListView( From 255f9c1608a10d6b0e5619fdff70f864f7128f91 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 14 Dec 2020 10:02:14 +0100 Subject: [PATCH 31/35] add enforce_unique: true in send reaction --- lib/src/reaction_picker.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/reaction_picker.dart b/lib/src/reaction_picker.dart index ff23b8a3..b019ee79 100644 --- a/lib/src/reaction_picker.dart +++ b/lib/src/reaction_picker.dart @@ -136,7 +136,9 @@ class _ReactionPickerState extends State void sendReaction(BuildContext context, String reactionType) { StreamChannel.of(context) .channel - .sendReaction(widget.message, reactionType); + .sendReaction(widget.message, reactionType, extraData: { + 'enforce_unique': true, + }); pop(); } From 984d3ec104c614d975c0536a61f0d71815b898e8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 14 Dec 2020 10:03:54 +0100 Subject: [PATCH 32/35] don't show copy message for empty messages --- lib/src/message_widget.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index f761f756..c7c6acdb 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -558,6 +558,7 @@ class _MessageWidgetState extends State { message: widget.message, editMessageInputBuilder: widget.editMessageInputBuilder, onThreadTap: widget.onThreadTap, + showCopyMessage: widget.message.text?.trim()?.isNotEmpty == true, showEditMessage: widget.showEditMessage && widget.message.attachments ?.any((element) => element.type == 'giphy') != From faafb21481a92eeb7630343c07cff568a27e54ac Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 14 Dec 2020 10:35:33 +0100 Subject: [PATCH 33/35] trigger ci --- example/lib/main.dart | 2 +- example/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 4b529e23..3fb47b66 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -57,7 +57,7 @@ class MyApp extends StatelessWidget { debugShowCheckedModeBanner: false, theme: ThemeData.light(), darkTheme: ThemeData.dark(), - //TODO change to system once dark theme is implemented + //TODO change to system once dark theme is implemented themeMode: ThemeMode.light, onGenerateRoute: AppRoutes.generateRoute, initialRoute: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index c5bfa14f..5459debc 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.88+90 +version: 1.0.89+91 environment: sdk: ">=2.2.2 <3.0.0" From 2903cae4e84f3742c43f68d6107f9b443325b958 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 14 Dec 2020 11:47:27 +0100 Subject: [PATCH 34/35] trigger ci --- example/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 5459debc..2e0c0ae3 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.89+91 +version: 1.0.90+93 environment: sdk: ">=2.2.2 <3.0.0" From 5e7e5aaac03c0821309565c839d394596cb15bcb Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 15 Dec 2020 12:18:16 +0100 Subject: [PATCH 35/35] fix new chat screen flow --- example/ios/fastlane/report.xml | 22 ++------ example/lib/new_chat_screen.dart | 87 ++++++++++++++++++++------------ example/pubspec.yaml | 2 +- lib/src/message_input.dart | 31 +++++++----- lib/src/message_list_view.dart | 8 +-- lib/src/user_list_view.dart | 2 +- 6 files changed, 85 insertions(+), 67 deletions(-) diff --git a/example/ios/fastlane/report.xml b/example/ios/fastlane/report.xml index d6fdf2d7..17688944 100644 --- a/example/ios/fastlane/report.xml +++ b/example/ios/fastlane/report.xml @@ -5,39 +5,27 @@ - + - + - + - + - - - - - - - - - - - - - + diff --git a/example/lib/new_chat_screen.dart b/example/lib/new_chat_screen.dart index 5fbfbf07..a2ce9ebf 100644 --- a/example/lib/new_chat_screen.dart +++ b/example/lib/new_chat_screen.dart @@ -37,6 +37,8 @@ class _NewChatScreenState extends State { bool _showUserList = true; + bool _channelExisted = false; + void _userNameListener() { if (_debounce?.isActive ?? false) _debounce.cancel(); _debounce = Timer(const Duration(milliseconds: 350), () { @@ -56,11 +58,6 @@ class _NewChatScreenState extends State { _searchFocusNode.addListener(() async { if (_searchFocusNode.hasFocus && !_showUserList) { - if (channel.extraData['draft'] == true) { - await channel.stopWatching(); - channel.dispose(); - channel.client.state.channels.remove(channel.cid); - } setState(() { _showUserList = true; }); @@ -71,19 +68,38 @@ class _NewChatScreenState extends State { if (_messageInputFocusNode.hasFocus && _selectedUsers.isNotEmpty) { final chatState = StreamChat.of(context); - channel = chatState.client.channel( - 'messaging', - extraData: { + final res = await chatState.client.queryChannels( + options: { + 'state': false, + 'watch': false, + }, + filter: { 'members': [ ..._selectedUsers.map((e) => e.id), chatState.user.id, ], - 'draft': true, + 'distinct': true, }, - ); + messageLimit: 0, + paginationParams: PaginationParams( + limit: 1, + ), + ).first; - if (!chatState.client.state.channels.containsKey(channel.cid)) { + final _channelExisted = res.length == 1; + if (_channelExisted) { + channel = res.first; await channel.watch(); + } else { + channel = chatState.client.channel( + 'messaging', + extraData: { + 'members': [ + ..._selectedUsers.map((e) => e.id), + chatState.user.id, + ], + }, + ); } setState(() { @@ -134,6 +150,7 @@ class _NewChatScreenState extends State { return GestureDetector( onTap: () { _chipInputTextFieldState.removeItem(user); + _searchFocusNode.requestFocus(); }, child: Stack( alignment: AlignmentDirectional.centerStart, @@ -311,18 +328,38 @@ class _NewChatScreenState extends State { ), ), ) - : MessageListView(), + : FutureBuilder( + future: channel.initialized, + builder: (context, snapshot) { + if (snapshot.data == true) { + return MessageListView(); + } + + return Center( + child: Text( + 'No chats here yet...', + style: TextStyle( + fontSize: 12, + color: Colors.black.withOpacity(.5), + ), + ), + ); + }, + ), ), MessageInput( focusNode: _messageInputFocusNode, + preMessageSending: (message) async { + await channel.watch(); + return message; + }, onMessageSent: (m) { - if (!m.isEphemeral) { - _updateChannelAndNavigate(context); - } else { - channel.on('message.new').first.then((_) { - _updateChannelAndNavigate(context); - }); - } + Navigator.pushNamedAndRemoveUntil( + context, + Routes.CHANNEL_PAGE, + ModalRoute.withName(Routes.HOME), + arguments: channel, + ); }, ), ], @@ -330,16 +367,4 @@ class _NewChatScreenState extends State { ), ); } - - void _updateChannelAndNavigate(BuildContext context) { - channel.update({ - 'draft': false, - }); - Navigator.pushNamedAndRemoveUntil( - context, - Routes.CHANNEL_PAGE, - ModalRoute.withName(Routes.HOME), - arguments: channel, - ); - } } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 2e0c0ae3..f9c71355 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.90+93 +version: 1.0.91+94 environment: sdk: ">=2.2.2 <3.0.0" diff --git a/lib/src/message_input.dart b/lib/src/message_input.dart index 542f63e7..447d49d9 100644 --- a/lib/src/message_input.dart +++ b/lib/src/message_input.dart @@ -343,7 +343,10 @@ class MessageInputState extends State { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ if (!widget.disableAttachments) _buildAttachmentButton(), - if (widget.editMessage == null) _buildCommandButton(), + if (widget.editMessage == null && + StreamChannel.of(context).channel?.config?.commands?.isNotEmpty == + true) + _buildCommandButton(), ], ), duration: Duration(milliseconds: 300), @@ -543,11 +546,12 @@ class MessageInputState extends State { void _checkCommands(String s, BuildContext context) { if (s.startsWith('/')) { var matchedCommandsList = StreamChannel.of(context) - .channel - .config - .commands - .where((element) => element.name == s.substring(1)) - .toList(); + .channel + .config + ?.commands + ?.where((element) => element.name == s.substring(1)) + ?.toList() ?? + []; if (matchedCommandsList.length == 1) { _chosenCommand = matchedCommandsList[0]; @@ -568,11 +572,12 @@ class MessageInputState extends State { OverlayEntry _buildCommandsOverlayEntry() { final text = textEditingController.text.trimLeft(); final commands = StreamChannel.of(context) - .channel - .config - .commands - .where((c) => c.name.contains(text.replaceFirst('/', ''))) - .toList(); + .channel + .config + ?.commands + ?.where((c) => c.name.contains(text.replaceFirst('/', ''))) + ?.toList() ?? + []; RenderBox renderBox = context.findRenderObject(); final size = renderBox.size; @@ -1927,8 +1932,6 @@ class MessageInputState extends State { _mentionsOverlay?.remove(); _mentionsOverlay = null; - final channel = StreamChannel.of(context).channel; - Future sendingFuture; Message message; if (widget.editMessage != null) { @@ -1953,6 +1956,8 @@ class MessageInputState extends State { message = await widget.preMessageSending(message); } + final channel = StreamChannel.of(context).channel; + if (widget.editMessage == null || widget.editMessage.status == MessageSendingStatus.FAILED) { sendingFuture = channel.sendMessage(message); diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 430e0b85..5eaccc6a 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -173,15 +173,15 @@ class _MessageListViewState extends State { ? streamChannel.channel.state.threadsStream .where((threads) => threads.containsKey(widget.parentMessage.id)) .map((threads) => threads[widget.parentMessage.id]) - : streamChannel.channel.state.messagesStream; + : streamChannel.channel.state?.messagesStream; return StreamBuilder>( - stream: messagesStream.map((messages) => messages - .where((e) => + stream: messagesStream?.map((messages) => messages + ?.where((e) => !e.isDeleted || (e.isDeleted && e.user.id == streamChannel.channel.client.state.user.id)) - .toList()), + ?.toList()), builder: (context, snapshot) { if (!snapshot.hasData) { return Center( diff --git a/lib/src/user_list_view.dart b/lib/src/user_list_view.dart index b57e2d55..8ea0a72c 100644 --- a/lib/src/user_list_view.dart +++ b/lib/src/user_list_view.dart @@ -190,7 +190,7 @@ class _UserListViewState extends State } final groupedUsers = >{}; for (var e in temp) { - final alphabet = e.name[0]; + final alphabet = e.name[0]?.toUpperCase(); groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e]; } final items = [];