From cbf21f669f217fb276468a4a8ae71653a92404dc Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 30 Dec 2020 17:13:17 +0530 Subject: [PATCH 1/8] Added 99 limit for unreads and changed logic for date implementation --- lib/src/channel_preview.dart | 16 +++++++++++----- lib/src/channel_unread_indicator.dart | 3 ++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/src/channel_preview.dart b/lib/src/channel_preview.dart index 5f58e254..f7f67bae 100644 --- a/lib/src/channel_preview.dart +++ b/lib/src/channel_preview.dart @@ -155,12 +155,18 @@ class ChannelPreview extends StatelessWidget { String stringDate; final now = DateTime.now(); - if (now.year != lastMessageAt.year || - now.month != lastMessageAt.month || - now.day != lastMessageAt.day) { - stringDate = Jiffy(lastMessageAt.toLocal()).format('dd/MM/yyyy'); - } else { + var startOfDay = DateTime(now.year, now.month, now.day); + + if (lastMessageAt.millisecondsSinceEpoch >= + startOfDay.millisecondsSinceEpoch) { stringDate = Jiffy(lastMessageAt.toLocal()).format('HH:mm'); + } else if (lastMessageAt.millisecondsSinceEpoch >= + startOfDay.subtract(Duration(days: 1)).millisecondsSinceEpoch) { + stringDate = 'Yesterday'; + } else if (startOfDay.difference(lastMessageAt).inDays < 7) { + stringDate = Jiffy(lastMessageAt.toLocal()).EEEE; + } else { + stringDate = Jiffy(lastMessageAt.toLocal()).format('dd/MM/yyyy'); } return Text( diff --git a/lib/src/channel_unread_indicator.dart b/lib/src/channel_unread_indicator.dart index 8080b1e9..c15db13a 100644 --- a/lib/src/channel_unread_indicator.dart +++ b/lib/src/channel_unread_indicator.dart @@ -19,6 +19,7 @@ class ChannelUnreadIndicator extends StatelessWidget { if (!snapshot.hasData || snapshot.data == 0) { return SizedBox(); } + return Material( borderRadius: BorderRadius.circular(8), color: StreamChatTheme.of(context) @@ -33,7 +34,7 @@ class ChannelUnreadIndicator extends StatelessWidget { ), child: Center( child: Text( - '${snapshot.data}', + '${snapshot.data > 99 ? '99+' : snapshot.data}', style: TextStyle( fontSize: 11, color: Colors.white, From 8a390347a789c5359c3b76a0a1170015d9186fde Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 30 Dec 2020 20:50:33 +0530 Subject: [PATCH 2/8] feat: Added fixes and modal for group --- example/lib/main.dart | 27 +++++ lib/src/channel_actions_modal.dart | 162 +++++++++++++++++++++++++++++ lib/src/option_list_tile.dart | 68 ++++++++++++ lib/stream_chat_flutter.dart | 1 + 4 files changed, 258 insertions(+) create mode 100644 lib/src/channel_actions_modal.dart create mode 100644 lib/src/option_list_tile.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index 803df8e6..ba6226c5 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -373,6 +373,10 @@ class _ChannelListPageState extends State { limit: 20, ), channelWidget: ChannelPage(), + onChannelLongPress: (channel) { + if (!channel.isDistinct) + _showChannelDetailsModal(channel); + }, ), ), ), @@ -383,6 +387,29 @@ class _ChannelListPageState extends State { ), ); } + + void _showChannelDetailsModal(Channel channel) { + showModalBottomSheet( + context: context, + builder: (context) { + return StreamChannel( + child: ChannelActionsModal( + onViewInfoTap: () { + // TODO: Go to group info screen when PR is merged + }, + ), + channel: channel); + }, + isScrollControlled: true, + clipBehavior: Clip.antiAlias, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16.0), + topRight: Radius.circular(16.0), + ), + ), + ); + } } class ChannelPageArgs { diff --git a/lib/src/channel_actions_modal.dart b/lib/src/channel_actions_modal.dart new file mode 100644 index 00000000..e8891a89 --- /dev/null +++ b/lib/src/channel_actions_modal.dart @@ -0,0 +1,162 @@ +import 'package:flutter/material.dart'; + +import '../stream_chat_flutter.dart'; +import 'channel_info.dart'; +import 'option_list_tile.dart'; + +class ChannelActionsModal extends StatefulWidget { + VoidCallback onViewInfoTap; + + ChannelActionsModal({this.onViewInfoTap}); + + @override + _ChannelActionsModalState createState() => _ChannelActionsModalState(); +} + +class _ChannelActionsModalState extends State { + @override + Widget build(BuildContext context) { + return Material( + clipBehavior: Clip.antiAlias, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16.0), + topRight: Radius.circular(16.0), + ), + ), + child: FutureBuilder( + future: StreamChannel.of(context).channel.queryMembers( + filter: {}, + ), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return Container( + height: 100.0, + alignment: Alignment.center, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: CircularProgressIndicator(), + ), + ); + } + + var userAsMember = snapshot.data.members + .firstWhere((e) => e.user.id == StreamChat.of(context).user.id); + var isOwner = userAsMember.role == 'owner'; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 24.0, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: ChannelName( + textStyle: TextStyle( + fontSize: 16.0, + color: Color(0xff000000), + fontWeight: FontWeight.bold, + ), + ), + ), + SizedBox( + height: 5.0, + ), + ChannelInfo( + showTypingIndicator: false, + channel: StreamChannel.of(context).channel, + textStyle: + StreamChatTheme.of(context).channelPreviewTheme.subtitle, + ), + SizedBox( + height: 17.0, + ), + Container( + height: 94.0, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: snapshot.data.members.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + children: [ + UserAvatar( + user: snapshot.data.members[index].user, + constraints: BoxConstraints( + maxHeight: 64.0, + maxWidth: 64.0, + ), + borderRadius: BorderRadius.circular(32.0), + ), + SizedBox( + height: 6.0, + ), + Text( + snapshot.data.members[index].user.name, + style: TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.w600, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ); + }, + ), + ), + SizedBox( + height: 24, + ), + OptionListTile( + leading: StreamSvgIcon.user( + color: Color(0xff7a7a7a), + ), + title: 'View Info', + onTap: widget.onViewInfoTap, + ), + OptionListTile( + leading: StreamSvgIcon.userRemove( + color: Color(0xff7a7a7a), + ), + title: 'Leave Group', + onTap: () async { + var channel = StreamChannel.of(context).channel; + + await channel.removeMembers([StreamChat.of(context).user.id]); + Navigator.pop(context); + }, + ), + if (isOwner) + OptionListTile( + leading: StreamSvgIcon.delete( + color: Colors.red, + ), + title: 'Delete Conversation', + titleColor: Colors.red, + onTap: () async { + var channel = StreamChannel.of(context).channel; + + await channel.delete(); + Navigator.pop(context); + }, + ), + OptionListTile( + leading: StreamSvgIcon.close( + color: Color(0xff7a7a7a), + ), + title: 'Cancel', + onTap: () { + Navigator.pop(context); + }, + ), + ], + ); + }, + ), + ); + } +} diff --git a/lib/src/option_list_tile.dart b/lib/src/option_list_tile.dart new file mode 100644 index 00000000..c1f48b71 --- /dev/null +++ b/lib/src/option_list_tile.dart @@ -0,0 +1,68 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; + +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: 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( + flex: 2, + child: Padding( + padding: const EdgeInsets.only(right: 16.0), + child: Align( + alignment: Alignment.centerRight, + child: trailing ?? Container(), + ), + ), + ), + ], + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/stream_chat_flutter.dart b/lib/stream_chat_flutter.dart index 1cef26a6..e4d4752a 100644 --- a/lib/stream_chat_flutter.dart +++ b/lib/stream_chat_flutter.dart @@ -43,3 +43,4 @@ export 'src/message_search_bloc.dart'; export 'src/message_search_item.dart'; export 'src/message_search_list_view.dart'; export 'src/unread_indicator.dart'; +export 'src/channel_actions_modal.dart'; From a766e404c585e74ced0db93e8120dce0f2ba1152 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 30 Dec 2020 21:22:03 +0530 Subject: [PATCH 3/8] fix: fixed for all chats --- example/lib/main.dart | 3 +- lib/src/channel_actions_modal.dart | 125 ++++++++++++++++++----------- 2 files changed, 81 insertions(+), 47 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index ba6226c5..c283793a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -374,8 +374,7 @@ class _ChannelListPageState extends State { ), channelWidget: ChannelPage(), onChannelLongPress: (channel) { - if (!channel.isDistinct) - _showChannelDetailsModal(channel); + _showChannelDetailsModal(channel); }, ), ), diff --git a/lib/src/channel_actions_modal.dart b/lib/src/channel_actions_modal.dart index e8891a89..ff8509d7 100644 --- a/lib/src/channel_actions_modal.dart +++ b/lib/src/channel_actions_modal.dart @@ -16,6 +16,8 @@ class ChannelActionsModal extends StatefulWidget { class _ChannelActionsModalState extends State { @override Widget build(BuildContext context) { + var channel = StreamChannel.of(context).channel; + return Material( clipBehavior: Clip.antiAlias, shape: RoundedRectangleBorder( @@ -72,42 +74,73 @@ class _ChannelActionsModalState extends State { SizedBox( height: 17.0, ), - Container( - height: 94.0, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: snapshot.data.members.length, - itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Column( - children: [ - UserAvatar( - user: snapshot.data.members[index].user, - constraints: BoxConstraints( - maxHeight: 64.0, - maxWidth: 64.0, - ), - borderRadius: BorderRadius.circular(32.0), - ), - SizedBox( - height: 6.0, - ), - Text( - snapshot.data.members[index].user.name, - style: TextStyle( - fontSize: 12.0, - fontWeight: FontWeight.w600, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], + if (channel.isDistinct && channel.memberCount == 2) + Column( + children: [ + UserAvatar( + user: snapshot.data.members + .firstWhere((e) => e.user.id != userAsMember.user.id) + .user, + constraints: BoxConstraints( + maxHeight: 64.0, + maxWidth: 64.0, ), - ); - }, + borderRadius: BorderRadius.circular(32.0), + ), + SizedBox( + height: 6.0, + ), + Text( + snapshot.data.members + .firstWhere((e) => e.user.id != userAsMember.user.id) + .user + .name, + style: TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.w600, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + if (!(channel.isDistinct && channel.memberCount == 2)) + Container( + height: 94.0, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: snapshot.data.members.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + children: [ + UserAvatar( + user: snapshot.data.members[index].user, + constraints: BoxConstraints( + maxHeight: 64.0, + maxWidth: 64.0, + ), + borderRadius: BorderRadius.circular(32.0), + ), + SizedBox( + height: 6.0, + ), + Text( + snapshot.data.members[index].user.name, + style: TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.w600, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ); + }, + ), ), - ), SizedBox( height: 24, ), @@ -118,18 +151,20 @@ class _ChannelActionsModalState extends State { title: 'View Info', onTap: widget.onViewInfoTap, ), - OptionListTile( - leading: StreamSvgIcon.userRemove( - color: Color(0xff7a7a7a), - ), - title: 'Leave Group', - onTap: () async { - var channel = StreamChannel.of(context).channel; + if (!channel.isDistinct) + OptionListTile( + leading: StreamSvgIcon.userRemove( + color: Color(0xff7a7a7a), + ), + title: 'Leave Group', + onTap: () async { + var channel = StreamChannel.of(context).channel; - await channel.removeMembers([StreamChat.of(context).user.id]); - Navigator.pop(context); - }, - ), + await channel + .removeMembers([StreamChat.of(context).user.id]); + Navigator.pop(context); + }, + ), if (isOwner) OptionListTile( leading: StreamSvgIcon.delete( From 2026058d300ad51096f33075f8aee1eea0a559c9 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 30 Dec 2020 21:23:35 +0530 Subject: [PATCH 4/8] fix: Corrected icon --- lib/src/channel_actions_modal.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/channel_actions_modal.dart b/lib/src/channel_actions_modal.dart index ff8509d7..424f5e38 100644 --- a/lib/src/channel_actions_modal.dart +++ b/lib/src/channel_actions_modal.dart @@ -180,7 +180,7 @@ class _ChannelActionsModalState extends State { }, ), OptionListTile( - leading: StreamSvgIcon.close( + leading: StreamSvgIcon.close_small( color: Color(0xff7a7a7a), ), title: 'Cancel', From ca6bd62084fd5861089e9ff5db90abad099ba0d4 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 31 Dec 2020 16:32:45 +0530 Subject: [PATCH 5/8] fix: Added confirmation dialogs and fixed alignment --- example/lib/main.dart | 26 --- lib/src/channel_actions_modal.dart | 197 ---------------- lib/src/channel_bottom_sheet.dart | 360 +++++++++++++++-------------- lib/src/channel_list_view.dart | 22 +- lib/stream_chat_flutter.dart | 2 +- 5 files changed, 210 insertions(+), 397 deletions(-) delete mode 100644 lib/src/channel_actions_modal.dart diff --git a/example/lib/main.dart b/example/lib/main.dart index c283793a..803df8e6 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -373,9 +373,6 @@ class _ChannelListPageState extends State { limit: 20, ), channelWidget: ChannelPage(), - onChannelLongPress: (channel) { - _showChannelDetailsModal(channel); - }, ), ), ), @@ -386,29 +383,6 @@ class _ChannelListPageState extends State { ), ); } - - void _showChannelDetailsModal(Channel channel) { - showModalBottomSheet( - context: context, - builder: (context) { - return StreamChannel( - child: ChannelActionsModal( - onViewInfoTap: () { - // TODO: Go to group info screen when PR is merged - }, - ), - channel: channel); - }, - isScrollControlled: true, - clipBehavior: Clip.antiAlias, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16.0), - topRight: Radius.circular(16.0), - ), - ), - ); - } } class ChannelPageArgs { diff --git a/lib/src/channel_actions_modal.dart b/lib/src/channel_actions_modal.dart deleted file mode 100644 index 424f5e38..00000000 --- a/lib/src/channel_actions_modal.dart +++ /dev/null @@ -1,197 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../stream_chat_flutter.dart'; -import 'channel_info.dart'; -import 'option_list_tile.dart'; - -class ChannelActionsModal extends StatefulWidget { - VoidCallback onViewInfoTap; - - ChannelActionsModal({this.onViewInfoTap}); - - @override - _ChannelActionsModalState createState() => _ChannelActionsModalState(); -} - -class _ChannelActionsModalState extends State { - @override - Widget build(BuildContext context) { - var channel = StreamChannel.of(context).channel; - - return Material( - clipBehavior: Clip.antiAlias, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16.0), - topRight: Radius.circular(16.0), - ), - ), - child: FutureBuilder( - future: StreamChannel.of(context).channel.queryMembers( - filter: {}, - ), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return Container( - height: 100.0, - alignment: Alignment.center, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: CircularProgressIndicator(), - ), - ); - } - - var userAsMember = snapshot.data.members - .firstWhere((e) => e.user.id == StreamChat.of(context).user.id); - var isOwner = userAsMember.role == 'owner'; - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - height: 24.0, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: ChannelName( - textStyle: TextStyle( - fontSize: 16.0, - color: Color(0xff000000), - fontWeight: FontWeight.bold, - ), - ), - ), - SizedBox( - height: 5.0, - ), - ChannelInfo( - showTypingIndicator: false, - channel: StreamChannel.of(context).channel, - textStyle: - StreamChatTheme.of(context).channelPreviewTheme.subtitle, - ), - SizedBox( - height: 17.0, - ), - if (channel.isDistinct && channel.memberCount == 2) - Column( - children: [ - UserAvatar( - user: snapshot.data.members - .firstWhere((e) => e.user.id != userAsMember.user.id) - .user, - constraints: BoxConstraints( - maxHeight: 64.0, - maxWidth: 64.0, - ), - borderRadius: BorderRadius.circular(32.0), - ), - SizedBox( - height: 6.0, - ), - Text( - snapshot.data.members - .firstWhere((e) => e.user.id != userAsMember.user.id) - .user - .name, - style: TextStyle( - fontSize: 12.0, - fontWeight: FontWeight.w600, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], - ), - if (!(channel.isDistinct && channel.memberCount == 2)) - Container( - height: 94.0, - child: ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: snapshot.data.members.length, - itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Column( - children: [ - UserAvatar( - user: snapshot.data.members[index].user, - constraints: BoxConstraints( - maxHeight: 64.0, - maxWidth: 64.0, - ), - borderRadius: BorderRadius.circular(32.0), - ), - SizedBox( - height: 6.0, - ), - Text( - snapshot.data.members[index].user.name, - style: TextStyle( - fontSize: 12.0, - fontWeight: FontWeight.w600, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ], - ), - ); - }, - ), - ), - SizedBox( - height: 24, - ), - OptionListTile( - leading: StreamSvgIcon.user( - color: Color(0xff7a7a7a), - ), - title: 'View Info', - onTap: widget.onViewInfoTap, - ), - if (!channel.isDistinct) - OptionListTile( - leading: StreamSvgIcon.userRemove( - color: Color(0xff7a7a7a), - ), - title: 'Leave Group', - onTap: () async { - var channel = StreamChannel.of(context).channel; - - await channel - .removeMembers([StreamChat.of(context).user.id]); - Navigator.pop(context); - }, - ), - if (isOwner) - OptionListTile( - leading: StreamSvgIcon.delete( - color: Colors.red, - ), - title: 'Delete Conversation', - titleColor: Colors.red, - onTap: () async { - var channel = StreamChannel.of(context).channel; - - await channel.delete(); - Navigator.pop(context); - }, - ), - OptionListTile( - leading: StreamSvgIcon.close_small( - color: Color(0xff7a7a7a), - ), - title: 'Cancel', - onTap: () { - Navigator.pop(context); - }, - ), - ], - ); - }, - ), - ); - } -} diff --git a/lib/src/channel_bottom_sheet.dart b/lib/src/channel_bottom_sheet.dart index 2d16bf6d..695f34c6 100644 --- a/lib/src/channel_bottom_sheet.dart +++ b/lib/src/channel_bottom_sheet.dart @@ -1,193 +1,209 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; -import 'package:stream_chat_flutter/src/utils.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import '../stream_chat_flutter.dart'; import 'channel_info.dart'; -import 'channel_name.dart'; -import 'stream_chat.dart'; -import 'stream_chat_theme.dart'; -import 'user_avatar.dart'; +import 'option_list_tile.dart'; -class ChannelBottomSheet extends StatelessWidget { - const ChannelBottomSheet({ - Key key, - }) : super(key: key); +class ChannelBottomSheet extends StatefulWidget { + VoidCallback onViewInfoTap; + + ChannelBottomSheet({this.onViewInfoTap}); + @override + _ChannelBottomSheetState createState() => _ChannelBottomSheetState(); +} + +class _ChannelBottomSheetState extends State { @override Widget build(BuildContext context) { - final channel = StreamChannel.of(context).channel; - return SafeArea( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - vertical: 2.0, - ), - child: Center( - child: StreamChannel( - showLoading: false, - channel: channel, - child: ChannelName( - textStyle: - StreamChatTheme.of(context).channelPreviewTheme.title, - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 2.0, - ), - child: Center( - child: ChannelInfo( - channel: channel, - textStyle: - StreamChatTheme.of(context).channelPreviewTheme.subtitle, - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 15.0, - ), - child: Center( - child: StreamBuilder>( - stream: channel.state.membersStream.map((event) => event - .where((m) => m.userId != StreamChat.of(context).user.id) - .toList()), - initialData: channel.state.members - .where((m) => m.userId != StreamChat.of(context).user.id) - .toList(), - builder: _buildMembers, - ), - ), - ), - Divider(), - if (channel.isGroup && !channel.isDistinct) - ListTile( - leading: StreamSvgIcon.userRemove( - size: 24, - color: Color(0xff7A7A7A), - ), - title: Text( - 'Leave Group', - style: TextStyle(fontWeight: FontWeight.bold), - ), - onTap: () async { - final confirm = await showConfirmationDialog( - context, - 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 - .removeMembers([StreamChat.of(context).user.id]); - Navigator.pop(context); - } - }, - ), - if ([ - 'admin', - 'owner', - ].contains(channel.state.members - .firstWhere((m) => m.userId == channel.client.state.user.id, - orElse: () => null) - ?.role)) - ListTile( - leading: StreamSvgIcon.delete( - color: Color(0xFFFF3742), - size: 24, - ), - title: Text( - 'Delete chat', - style: TextStyle( - color: Color(0xFFFF3742), - ), - ), - onTap: () 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; - if (res == true) { - await channel.delete().then((value) { - Navigator.pop(context); - }); - } - }, - ), - ], + var channel = StreamChannel.of(context).channel; + + var members = channel.state.members; + + var userAsMember = + members.firstWhere((e) => e.user.id == StreamChat.of(context).user.id); + var isOwner = userAsMember.role == 'owner'; + + return Material( + clipBehavior: Clip.antiAlias, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16.0), + topRight: Radius.circular(16.0), ), ), - ); - } - - Widget _buildMembers( - BuildContext context, - AsyncSnapshot> snapshot, - ) { - if (snapshot.data.isEmpty) { - return SizedBox(); - } - - return Container( - height: 83, - child: ListView( - padding: EdgeInsets.only( - left: (MediaQuery.of(context).size.width / 2) - 48, - ), - scrollDirection: Axis.horizontal, - children: snapshot.data.map((m) { - return Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 24.0, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: ChannelName( + textStyle: TextStyle( + fontSize: 16.0, + color: Color(0xff000000), + fontWeight: FontWeight.bold, + ), ), - child: Column( - children: [ + ), + SizedBox( + height: 5.0, + ), + ChannelInfo( + showTypingIndicator: false, + channel: StreamChannel.of(context).channel, + textStyle: StreamChatTheme.of(context).channelPreviewTheme.subtitle, + ), + SizedBox( + height: 17.0, + ), + if (channel.isDistinct && channel.memberCount == 2) + Column( + children: [ UserAvatar( - showOnlineStatus: true, - user: m.user, - borderRadius: BorderRadius.circular(32), - constraints: BoxConstraints.tight( - Size.square(64), + user: members + .firstWhere((e) => e.user.id != userAsMember.user.id) + .user, + constraints: BoxConstraints( + maxHeight: 64.0, + maxWidth: 64.0, ), + borderRadius: BorderRadius.circular(32.0), ), - Padding( - padding: const EdgeInsets.only(top: 5.0), - child: Text( - m.user.name?.split(' ')?.elementAt(0), - style: StreamChatTheme.of(context) - .channelPreviewTheme - .title - .copyWith( - fontSize: 12, - ), + SizedBox( + height: 6.0, + ), + Text( + members + .firstWhere((e) => e.user.id != userAsMember.user.id) + .user + .name, + style: TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.w600, ), + maxLines: 1, + overflow: TextOverflow.ellipsis, ), ], ), - ); - }).toList(), + if (!(channel.isDistinct && channel.memberCount == 2)) + Container( + height: 94.0, + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: members.length, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + children: [ + UserAvatar( + user: members[index].user, + constraints: BoxConstraints( + maxHeight: 64.0, + maxWidth: 64.0, + ), + borderRadius: BorderRadius.circular(32.0), + ), + SizedBox( + height: 6.0, + ), + Text( + members[index].user.name, + style: TextStyle( + fontSize: 12.0, + fontWeight: FontWeight.w600, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ); + }, + ), + ), + SizedBox( + height: 17.0, + ), + OptionListTile( + leading: StreamSvgIcon.user( + color: Color(0xff7a7a7a), + ), + title: 'View Info', + onTap: widget.onViewInfoTap, + ), + if (!channel.isDistinct) + OptionListTile( + leading: StreamSvgIcon.userRemove( + color: Color(0xff7a7a7a), + ), + title: 'Leave Group', + onTap: () async { + _showLeaveDialog(); + }, + ), + if (isOwner) + OptionListTile( + leading: StreamSvgIcon.delete( + color: Colors.red, + ), + title: 'Delete Conversation', + titleColor: Colors.red, + onTap: () async { + _showDeleteDialog(); + }, + ), + OptionListTile( + leading: StreamSvgIcon.close_small( + color: Color(0xff7a7a7a), + ), + title: 'Cancel', + onTap: () { + Navigator.pop(context); + }, + ), + ], ), ); } + + 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; + if (res == true) { + await channel.delete(); + Navigator.pop(context); + } + } + + void _showLeaveDialog() async { + final res = await showConfirmationDialog( + context, + title: 'Leave conversation', + okText: 'LEAVE', + question: 'Are you sure you want to leave this conversation?', + cancelText: 'CANCEL', + icon: StreamSvgIcon.userRemove( + color: Colors.red, + ), + ); + var channel = StreamChannel.of(context).channel; + if (res == true) { + await channel.removeMembers([StreamChat.of(context).user.id]); + Navigator.pop(context); + } + } } diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 5efa1e29..b42377cf 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -560,7 +560,27 @@ class _ChannelListViewState extends State context: context, builder: (context) { return StreamChannel( - child: ChannelBottomSheet(), + child: ChannelBottomSheet( + onViewInfoTap: () { + if (channel.memberCount == 2 && + channel.isDistinct) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: ChatInfoScreen( + user: + channel.state.members.first.user, + ), + ), + ), + ); + } + + // TODO: Add group screen + }, + ), channel: channel, ); }, diff --git a/lib/stream_chat_flutter.dart b/lib/stream_chat_flutter.dart index e4d4752a..d2dfc692 100644 --- a/lib/stream_chat_flutter.dart +++ b/lib/stream_chat_flutter.dart @@ -43,4 +43,4 @@ export 'src/message_search_bloc.dart'; export 'src/message_search_item.dart'; export 'src/message_search_list_view.dart'; export 'src/unread_indicator.dart'; -export 'src/channel_actions_modal.dart'; +export 'src/chat_info_screen.dart'; From bb9052c331ba39c2522900ba23297bcfaa697b79 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 31 Dec 2020 17:37:20 +0530 Subject: [PATCH 6/8] fix: fixed alignment and font weight --- lib/src/channel_bottom_sheet.dart | 4 +++- lib/src/option_list_tile.dart | 15 +++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/src/channel_bottom_sheet.dart b/lib/src/channel_bottom_sheet.dart index 695f34c6..2d7b726c 100644 --- a/lib/src/channel_bottom_sheet.dart +++ b/lib/src/channel_bottom_sheet.dart @@ -92,9 +92,11 @@ class _ChannelBottomSheetState extends State { if (!(channel.isDistinct && channel.memberCount == 2)) Container( height: 94.0, + alignment: Alignment.center, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: members.length, + shrinkWrap: true, itemBuilder: (context, index) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), @@ -127,7 +129,7 @@ class _ChannelBottomSheetState extends State { ), ), SizedBox( - height: 17.0, + height: 24.0, ), OptionListTile( leading: StreamSvgIcon.user( diff --git a/lib/src/option_list_tile.dart b/lib/src/option_list_tile.dart index c1f48b71..040b1a00 100644 --- a/lib/src/option_list_tile.dart +++ b/lib/src/option_list_tile.dart @@ -41,12 +41,15 @@ class OptionListTile extends StatelessWidget { width: 16.0, ), Expanded( - flex: 4, - child: Text( - title, - style: TextStyle( - fontWeight: FontWeight.w600, color: titleColor), - )), + flex: 4, + child: Text( + title, + style: TextStyle( + fontWeight: FontWeight.bold, + color: titleColor, + ), + ), + ), Expanded( flex: 2, child: Padding( From 607a51daad46143d794136ae88257865f312abaa Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 31 Dec 2020 18:01:55 +0530 Subject: [PATCH 7/8] feat: added themes --- lib/src/channel_bottom_sheet.dart | 33 ++++++++++++------------------- lib/src/option_list_tile.dart | 21 ++++++++++++-------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/lib/src/channel_bottom_sheet.dart b/lib/src/channel_bottom_sheet.dart index 2d7b726c..130738d8 100644 --- a/lib/src/channel_bottom_sheet.dart +++ b/lib/src/channel_bottom_sheet.dart @@ -25,6 +25,7 @@ class _ChannelBottomSheetState extends State { var isOwner = userAsMember.role == 'owner'; return Material( + color: StreamChatTheme.of(context).colorTheme.white, clipBehavior: Clip.antiAlias, shape: RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -41,11 +42,7 @@ class _ChannelBottomSheetState extends State { Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: ChannelName( - textStyle: TextStyle( - fontSize: 16.0, - color: Color(0xff000000), - fontWeight: FontWeight.bold, - ), + textStyle: StreamChatTheme.of(context).textTheme.headlineBold, ), ), SizedBox( @@ -80,10 +77,7 @@ class _ChannelBottomSheetState extends State { .firstWhere((e) => e.user.id != userAsMember.user.id) .user .name, - style: TextStyle( - fontSize: 12.0, - fontWeight: FontWeight.w600, - ), + style: StreamChatTheme.of(context).textTheme.footnoteBold, maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -115,10 +109,9 @@ class _ChannelBottomSheetState extends State { ), Text( members[index].user.name, - style: TextStyle( - fontSize: 12.0, - fontWeight: FontWeight.w600, - ), + style: StreamChatTheme.of(context) + .textTheme + .footnoteBold, maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -133,7 +126,7 @@ class _ChannelBottomSheetState extends State { ), OptionListTile( leading: StreamSvgIcon.user( - color: Color(0xff7a7a7a), + color: StreamChatTheme.of(context).colorTheme.grey, ), title: 'View Info', onTap: widget.onViewInfoTap, @@ -141,7 +134,7 @@ class _ChannelBottomSheetState extends State { if (!channel.isDistinct) OptionListTile( leading: StreamSvgIcon.userRemove( - color: Color(0xff7a7a7a), + color: StreamChatTheme.of(context).colorTheme.grey, ), title: 'Leave Group', onTap: () async { @@ -151,17 +144,17 @@ class _ChannelBottomSheetState extends State { if (isOwner) OptionListTile( leading: StreamSvgIcon.delete( - color: Colors.red, + color: StreamChatTheme.of(context).colorTheme.accentRed, ), title: 'Delete Conversation', - titleColor: Colors.red, + titleColor: StreamChatTheme.of(context).colorTheme.accentRed, onTap: () async { _showDeleteDialog(); }, ), OptionListTile( leading: StreamSvgIcon.close_small( - color: Color(0xff7a7a7a), + color: StreamChatTheme.of(context).colorTheme.grey, ), title: 'Cancel', onTap: () { @@ -181,7 +174,7 @@ class _ChannelBottomSheetState extends State { question: 'Are you sure you want to delete this conversation?', cancelText: 'CANCEL', icon: StreamSvgIcon.delete( - color: Colors.red, + color: StreamChatTheme.of(context).colorTheme.accentRed, ), ); var channel = StreamChannel.of(context).channel; @@ -199,7 +192,7 @@ class _ChannelBottomSheetState extends State { question: 'Are you sure you want to leave this conversation?', cancelText: 'CANCEL', icon: StreamSvgIcon.userRemove( - color: Colors.red, + color: StreamChatTheme.of(context).colorTheme.accentRed, ), ); var channel = StreamChannel.of(context).channel; diff --git a/lib/src/option_list_tile.dart b/lib/src/option_list_tile.dart index 040b1a00..d551d33e 100644 --- a/lib/src/option_list_tile.dart +++ b/lib/src/option_list_tile.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; class OptionListTile extends StatelessWidget { @@ -21,13 +22,13 @@ class OptionListTile extends StatelessWidget { return Column( children: [ Container( - color: Color(0xffe6e6e6), - height: 2.0, + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, + height: 1.0, ), Material( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, child: Container( - height: 56.0, + height: 63.0, child: InkWell( onTap: onTap, child: Row( @@ -44,10 +45,14 @@ class OptionListTile extends StatelessWidget { flex: 4, child: Text( title, - style: TextStyle( - fontWeight: FontWeight.bold, - color: titleColor, - ), + style: titleColor == null + ? StreamChatTheme.of(context).textTheme.bodyBold + : StreamChatTheme.of(context) + .textTheme + .bodyBold + .copyWith( + color: titleColor, + ), ), ), Expanded( From 7c8d5a643f7ef41c0b62efeb5d9c9271256aa631 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 31 Dec 2020 20:27:51 +0530 Subject: [PATCH 8/8] feat: fixed overflow --- lib/src/channel_bottom_sheet.dart | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/lib/src/channel_bottom_sheet.dart b/lib/src/channel_bottom_sheet.dart index 130738d8..d2c7eb04 100644 --- a/lib/src/channel_bottom_sheet.dart +++ b/lib/src/channel_bottom_sheet.dart @@ -33,25 +33,29 @@ class _ChannelBottomSheetState extends State { topRight: Radius.circular(16.0), ), ), - child: Column( - mainAxisSize: MainAxisSize.min, + child: ListView( children: [ SizedBox( height: 24.0, ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: ChannelName( - textStyle: StreamChatTheme.of(context).textTheme.headlineBold, + Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: ChannelName( + textStyle: StreamChatTheme.of(context).textTheme.headlineBold, + ), ), ), SizedBox( height: 5.0, ), - ChannelInfo( - showTypingIndicator: false, - channel: StreamChannel.of(context).channel, - textStyle: StreamChatTheme.of(context).channelPreviewTheme.subtitle, + Center( + child: ChannelInfo( + showTypingIndicator: false, + channel: StreamChannel.of(context).channel, + textStyle: + StreamChatTheme.of(context).channelPreviewTheme.subtitle, + ), ), SizedBox( height: 17.0,