From f2a707bc45e13cf0f546002fb8d33ee179e7a8f5 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 14 Dec 2020 20:22:48 +0530 Subject: [PATCH 01/34] feat: Added group info screen, refactored components, added some functionality --- lib/src/channel_file_display_screen.dart | 36 ++ lib/src/channel_header.dart | 11 + lib/src/channel_media_display_screen.dart | 36 ++ lib/src/channel_preview.dart | 11 + lib/src/chat_info_screen.dart | 161 +------ lib/src/group_info_screen.dart | 502 ++++++++++++++++++++++ lib/src/option_list_tile.dart | 68 +++ 7 files changed, 681 insertions(+), 144 deletions(-) create mode 100644 lib/src/channel_file_display_screen.dart create mode 100644 lib/src/channel_media_display_screen.dart create mode 100644 lib/src/group_info_screen.dart create mode 100644 lib/src/option_list_tile.dart diff --git a/lib/src/channel_file_display_screen.dart b/lib/src/channel_file_display_screen.dart new file mode 100644 index 00000000..d34a4b87 --- /dev/null +++ b/lib/src/channel_file_display_screen.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +class ChannelFileDisplayScreen 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, + ), + ); + } +} diff --git a/lib/src/channel_header.dart b/lib/src/channel_header.dart index 65f63cc4..73e44b35 100644 --- a/lib/src/channel_header.dart +++ b/lib/src/channel_header.dart @@ -9,6 +9,7 @@ import '../stream_chat_flutter.dart'; import './channel_name.dart'; import 'channel_image.dart'; import 'chat_info_screen.dart'; +import 'group_info_screen.dart'; import 'stream_channel.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header.png) @@ -124,6 +125,16 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { Navigator.pop(context); } } + } else { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: GroupInfoScreen(), + ), + ), + ); } }, ), diff --git a/lib/src/channel_media_display_screen.dart b/lib/src/channel_media_display_screen.dart new file mode 100644 index 00000000..bcad35e2 --- /dev/null +++ b/lib/src/channel_media_display_screen.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +class ChannelMediaDisplayScreen 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, + ), + ); + } +} diff --git a/lib/src/channel_preview.dart b/lib/src/channel_preview.dart index 5f58e254..ff727cfa 100644 --- a/lib/src/channel_preview.dart +++ b/lib/src/channel_preview.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter/src/group_info_screen.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import '../stream_chat_flutter.dart'; @@ -82,6 +83,16 @@ class ChannelPreview extends StatelessWidget { ), ); } + } else { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: GroupInfoScreen(), + ), + ), + ); } }, ), diff --git a/lib/src/chat_info_screen.dart b/lib/src/chat_info_screen.dart index 0ec62c36..436ff675 100644 --- a/lib/src/chat_info_screen.dart +++ b/lib/src/chat_info_screen.dart @@ -4,6 +4,9 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import '../stream_chat_flutter.dart'; +import 'channel_file_display_screen.dart'; +import 'channel_media_display_screen.dart'; +import 'option_list_tile.dart'; /// Detail screen for a 1:1 chat correspondence class ChatInfoScreen extends StatefulWidget { @@ -74,7 +77,7 @@ class _ChatInfoScreenState extends State { SizedBox(height: 7.0), _buildConnectedTitleState(), SizedBox(height: 15.0), - _OptionListTile( + OptionListTile( title: '@${widget.user.id}', trailing: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), @@ -124,7 +127,7 @@ class _ChatInfoScreenState extends State { StreamBuilder( stream: StreamChannel.of(context).channel.isMutedStream, builder: (context, snapshot) { - return _OptionListTile( + return OptionListTile( title: 'Mute user', leading: StreamSvgIcon.mute( size: 23.0, @@ -163,7 +166,7 @@ class _ChatInfoScreenState extends State { // ), // onTap: () {}, // ), - _OptionListTile( + OptionListTile( title: 'Photos & Videos', leading: StreamSvgIcon.pictures( size: 32.0, @@ -171,11 +174,13 @@ class _ChatInfoScreenState extends State { ), trailing: StreamSvgIcon.right(), onTap: () { - Navigator.push(context, - MaterialPageRoute(builder: (context) => _MediaDisplayScreen())); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ChannelMediaDisplayScreen())); }, ), - _OptionListTile( + OptionListTile( title: 'Files', leading: StreamSvgIcon.files( size: 32.0, @@ -183,11 +188,13 @@ class _ChatInfoScreenState extends State { ), trailing: StreamSvgIcon.right(), onTap: () { - Navigator.push(context, - MaterialPageRoute(builder: (context) => _FileDisplayScreen())); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ChannelFileDisplayScreen())); }, ), - _OptionListTile( + OptionListTile( title: 'Shared groups', leading: StreamSvgIcon.Icon_group( size: 24.0, @@ -207,7 +214,7 @@ class _ChatInfoScreenState extends State { } Widget _buildDeleteListTile() { - return _OptionListTile( + return OptionListTile( title: 'Delete', leading: StreamSvgIcon.delete( color: Colors.red, @@ -283,72 +290,6 @@ class _ChatInfoScreenState extends State { } } -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(), - ), - ), - ), - ], - ), - ), - ), - ), - ], - ); - } -} - class _SharedGroupsScreen extends StatefulWidget { final User mainUser; final User otherUser; @@ -504,71 +445,3 @@ 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, - ), - ); - } -} diff --git a/lib/src/group_info_screen.dart b/lib/src/group_info_screen.dart new file mode 100644 index 00000000..ceb33dc8 --- /dev/null +++ b/lib/src/group_info_screen.dart @@ -0,0 +1,502 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:jiffy/jiffy.dart'; +import 'package:stream_chat_flutter/src/option_list_tile.dart'; +import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; + +import '../stream_chat_flutter.dart'; +import 'channel_file_display_screen.dart'; +import 'channel_media_display_screen.dart'; + +class GroupInfoScreen extends StatefulWidget { + @override + _GroupInfoScreenState createState() => _GroupInfoScreenState(); +} + +class _GroupInfoScreenState extends State { + Future _memberQueryFuture; + TextEditingController _nameController; + + bool _userSearchMode = false; + TextEditingController _searchController; + bool _loading = false; + String _userNameQuery = ''; + + Timer _debounce; + Function modalSetStateCallback; + + void _userNameListener() { + if (_debounce?.isActive ?? false) _debounce.cancel(); + _debounce = Timer(const Duration(milliseconds: 350), () { + if (mounted && modalSetStateCallback != null) { + modalSetStateCallback(() { + _userNameQuery = _searchController.text; + }); + } + }); + } + + @override + void initState() { + super.initState(); + var channel = StreamChannel.of(context); + + _memberQueryFuture = channel.channel.queryMembers( + filter: {}, + ); + _nameController = TextEditingController.fromValue( + TextEditingValue(text: channel.channel.extraData['name'])); + _searchController = TextEditingController()..addListener(_userNameListener); + } + + @override + Widget build(BuildContext context) { + var channel = StreamChannel.of(context); + + return Scaffold( + backgroundColor: Color(0xffdbdbdb), + appBar: AppBar( + elevation: 1.0, + toolbarHeight: 56.0, + backgroundColor: Colors.white, + leading: InkWell( + onTap: () { + Navigator.pop(context); + }, + child: Padding( + padding: const EdgeInsets.only(left: 12.0), + child: StreamSvgIcon.left(), + ), + ), + leadingWidth: 36.0, + title: Column( + children: [ + // TODO: Add member names instead of Demo (if name = null) + Text( + channel.channel.extraData['name'] ?? 'Demo', + style: TextStyle(color: Colors.black), + ), + SizedBox( + height: 3.0, + ), + FutureBuilder( + future: _memberQueryFuture, + builder: (context, snapshot) { + return Text( + '${channel.channel.memberCount} Members, ${snapshot?.data?.members?.where((e) => e.user.online)?.length ?? 0} Online', + style: TextStyle( + color: Colors.black.withOpacity(0.5), fontSize: 12.0), + ); + }), + ], + ), + centerTitle: true, + actions: [ + StreamNeumorphicButton( + child: InkWell( + onTap: () { + _buildAddUserModal(context); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: StreamSvgIcon.userAdd( + color: StreamChatTheme.of(context).accentColor), + ), + ), + ), + ], + ), + body: ListView( + children: [ + _buildMembers(), + Container( + height: 8.0, + color: Color(0xffdbdbdb), + ), + _buildNameTile(), + _buildOptionListTiles(), + ], + ), + ); + } + + Widget _buildMembers() { + var channel = StreamChannel.of(context); + + return FutureBuilder( + builder: (context, snapshot) { + if (snapshot.data == null) { + return Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: CircularProgressIndicator(), + ), + ); + } + + return ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: snapshot.data.members.length, + itemBuilder: (context, position) { + return Material( + child: Container( + height: 65.0, + child: Column( + children: [ + Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 12.0), + child: UserAvatar( + user: snapshot.data.members[position].user, + constraints: + BoxConstraints(maxHeight: 40.0, maxWidth: 40.0), + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + snapshot.data.members[position].user.name, + style: TextStyle(fontWeight: FontWeight.bold), + ), + SizedBox( + height: 1.0, + ), + Text( + _getLastSeen( + snapshot.data.members[position].user), + style: TextStyle( + color: Colors.black.withOpacity(0.5)), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + snapshot.data.members[position].user.id == + channel.channel.createdBy.id + ? 'Owner' + : '', + style: + TextStyle(color: Colors.black.withOpacity(0.5)), + ), + ), + ], + ), + Container( + height: 1.0, + color: Color(0xffdbdbdb), + ), + ], + ), + ), + color: Colors.white, + ); + }, + ); + }, + future: _memberQueryFuture, + ); + } + + Widget _buildNameTile() { + return Material( + color: Colors.white, + child: Container( + height: 56.0, + alignment: Alignment.center, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'NAME', + style: TextStyle( + color: Colors.black.withOpacity(0.5), fontSize: 12.0), + ), + ), + SizedBox( + width: 9.0, + ), + Expanded( + child: TextField( + controller: _nameController, + cursorColor: Colors.black, + decoration: InputDecoration.collapsed( + hintText: 'Add a group name', + hintStyle: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black.withOpacity(0.5), + ), + ), + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + ); + } + + 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 group', + 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: 'Photos & Videos', + leading: StreamSvgIcon.pictures( + size: 32.0, + color: Colors.black.withOpacity(0.5), + ), + trailing: StreamSvgIcon.right(), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ChannelMediaDisplayScreen())); + }, + ), + OptionListTile( + title: 'Files', + leading: StreamSvgIcon.files( + size: 32.0, + color: Colors.black.withOpacity(0.5), + ), + trailing: StreamSvgIcon.right(), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ChannelFileDisplayScreen())); + }, + ), + OptionListTile( + title: 'Leave Group', + leading: StreamSvgIcon.userRemove( + size: 24.0, + color: Colors.black.withOpacity(0.5), + ), + trailing: Container( + height: 24.0, + width: 24.0, + ), + onTap: () async { + await channel.channel + .removeMembers([StreamChat.of(context).user.id]); + Navigator.pop(context); + Navigator.pop(context); + }, + ), + ], + ); + } + + void _buildAddUserModal(context) { + var channel = StreamChannel.of(context).channel; + + showDialog( + context: context, + builder: (context) { + return StatefulBuilder(builder: (context, modalSetState) { + modalSetStateCallback = modalSetState; + return Padding( + padding: EdgeInsets.only(top: 16.0, left: 8.0, right: 8.0), + child: Material( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16.0), + topRight: Radius.circular(16.0), + ), + clipBehavior: Clip.antiAlias, + child: Scaffold( + body: UsersBloc( + child: Column( + children: [ + _buildTextInputSection(modalSetState), + Expanded( + child: UserListView( + selectedUsers: {}, + onUserTap: (user, _) async { + _searchController.clear(); + + await channel.addMembers([user.id]); + Navigator.pop(context); + _memberQueryFuture = channel.queryMembers( + filter: {}, + ); + setState(() {}); + }, + crossAxisCount: 4, + pagination: PaginationParams( + limit: 25, + ), + filter: { + if (_searchController.text.isNotEmpty) + 'name': { + r'$autocomplete': _userNameQuery, + }, + 'id': { + r'$ne': StreamChat.of(context).user.id, + }, + }, + sort: [ + SortOption( + 'name', + direction: 1, + ), + ], + emptyBuilder: (_) { + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Center( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(24), + child: StreamSvgIcon.search( + size: 96, + color: Colors.grey, + ), + ), + Text( + 'No user matches these keywords...'), + ], + ), + ), + ), + ); + }, + ); + }, + ), + ), + ], + ), + ), + ), + ), + ); + }); + }, + ); + } + + Widget _buildTextInputSection(modalSetState) { + return Column( + children: [ + SizedBox( + height: 16.0, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: 16.0, + ), + Expanded( + child: TextField( + controller: _searchController, + cursorColor: Colors.black, + autofocus: true, + decoration: InputDecoration( + isDense: true, + prefixIconConstraints: BoxConstraints.tight(Size(36.0, 44.0)), + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + vertical: 2.0, horizontal: 6.0), + child: StreamSvgIcon.search( + color: Colors.black, + ), + ), + hintText: 'Search', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(32.0), + borderSide: + BorderSide(color: Colors.black.withOpacity(0.08)), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(32.0), + borderSide: + BorderSide(color: Colors.black.withOpacity(0.08)), + ), + contentPadding: EdgeInsets.zero, + ), + ), + ), + SizedBox( + width: 8.0, + ), + IconButton( + icon: StreamSvgIcon.close_small( + color: Colors.black.withOpacity(0.5), + ), + onPressed: () { + Navigator.pop(context); + }, + ) + ], + ), + ], + ); + } + + String _getLastSeen(User user) { + if (user.online) { + return 'Online'; + } else { + return 'Last seen ${Jiffy(user.lastActive).fromNow()}'; + } + } +} 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(), + ), + ), + ), + ], + ), + ), + ), + ), + ], + ); + } +} From d2a115be6fe9584bd5e7a25cb0e5592088005eb3 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 15 Dec 2020 19:47:21 +0530 Subject: [PATCH 02/34] feat: Added change name, group name, etc --- lib/src/group_info_screen.dart | 85 ++++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 4 deletions(-) diff --git a/lib/src/group_info_screen.dart b/lib/src/group_info_screen.dart index ceb33dc8..7dd7ceaa 100644 --- a/lib/src/group_info_screen.dart +++ b/lib/src/group_info_screen.dart @@ -27,6 +27,11 @@ class _GroupInfoScreenState extends State { Timer _debounce; Function modalSetStateCallback; + final FocusNode _focusNode = FocusNode(); + + bool namedChanged = false; + String changedName = ''; + void _userNameListener() { if (_debounce?.isActive ?? false) _debounce.cancel(); _debounce = Timer(const Duration(milliseconds: 350), () { @@ -49,6 +54,10 @@ class _GroupInfoScreenState extends State { _nameController = TextEditingController.fromValue( TextEditingValue(text: channel.channel.extraData['name'])); _searchController = TextEditingController()..addListener(_userNameListener); + + _nameController.addListener(() { + setState(() {}); + }); } @override @@ -74,10 +83,19 @@ class _GroupInfoScreenState extends State { title: Column( children: [ // TODO: Add member names instead of Demo (if name = null) - Text( - channel.channel.extraData['name'] ?? 'Demo', - style: TextStyle(color: Colors.black), - ), + FutureBuilder( + future: _memberQueryFuture, + builder: (context, snapshot) { + return Text( + _getChannelName(MediaQuery.of(context).size.width, + members: snapshot.data?.members + ?.map((e) => e.user) + ?.toList()), + style: TextStyle(color: Colors.black), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + }), SizedBox( height: 3.0, ), @@ -208,6 +226,9 @@ class _GroupInfoScreenState extends State { } Widget _buildNameTile() { + var channel = StreamChannel.of(context).channel; + var channelName = channel.extraData['name']; + return Material( color: Colors.white, child: Container( @@ -228,6 +249,7 @@ class _GroupInfoScreenState extends State { ), Expanded( child: TextField( + focusNode: _focusNode, controller: _nameController, cursorColor: Colors.black, decoration: InputDecoration.collapsed( @@ -242,6 +264,48 @@ class _GroupInfoScreenState extends State { ), ), ), + if ((channelName == null) || + (channelName != _nameController.text.trim())) + Row( + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + child: StreamSvgIcon.close_small(), + onTap: () { + setState(() { + _nameController.text = + _getChannelName(MediaQuery.of(context).size.width); + _focusNode.unfocus(); + }); + }, + ), + Padding( + padding: const EdgeInsets.only(right: 16.0, left: 8.0), + child: InkWell( + child: StreamSvgIcon.check( + color: StreamChatTheme.of(context).accentColor, + size: 24.0, + ), + onTap: () { + StreamChannel.of(context).channel.update({ + 'name': _nameController.text.trim(), + }).then((value) { + setState(() { + _focusNode.unfocus(); + namedChanged = true; + changedName = _nameController.text.trim(); + }); + }).catchError((err) { + setState(() { + _nameController.text = channelName; + _focusNode.unfocus(); + }); + }); + }, + ), + ), + ], + ), ], ), ), @@ -492,6 +556,19 @@ class _GroupInfoScreenState extends State { ); } + String _getChannelName(double width, {List members}) { + if (namedChanged) { + return changedName; + } + + if (StreamChannel.of(context).channel.extraData['name'] == null && + members != null) { + return '${members[0].name} & ${members.length - 1} others'; + } + + return StreamChannel.of(context).channel.extraData['name'] ?? ''; + } + String _getLastSeen(User user) { if (user.online) { return 'Online'; From 7c24e7b202d00d6b7c74b8d2f05f61f22decfa5d Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 17 Dec 2020 19:42:07 +0530 Subject: [PATCH 03/34] feat: Added file and image screens, group info details --- lib/src/channel_file_display_screen.dart | 136 +++++++++++++++++++- lib/src/channel_media_display_screen.dart | 147 +++++++++++++++++++++- lib/src/chat_info_screen.dart | 46 ++++++- lib/src/group_info_screen.dart | 46 ++++++- lib/src/message_search_bloc.dart | 2 + pubspec.yaml | 2 +- 6 files changed, 364 insertions(+), 15 deletions(-) diff --git a/lib/src/channel_file_display_screen.dart b/lib/src/channel_file_display_screen.dart index d34a4b87..361aa808 100644 --- a/lib/src/channel_file_display_screen.dart +++ b/lib/src/channel_file_display_screen.dart @@ -1,7 +1,52 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -class ChannelFileDisplayScreen extends StatelessWidget { +class ChannelFileDisplayScreen extends StatefulWidget { + /// The sorting used for the channels matching the filters. + /// Sorting is based on field and direction, multiple sorting options can be provided. + /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. + /// Direction can be ascending or descending. + final List sortOptions; + + /// Pagination parameters + /// limit: the number of users to return (max is 30) + /// offset: the offset (max is 1000) + /// message_limit: how many messages should be included to each channel + final PaginationParams paginationParams; + + ChannelFileDisplayScreen({ + this.sortOptions, + this.paginationParams, + }); + + @override + _ChannelFileDisplayScreenState createState() => + _ChannelFileDisplayScreenState(); +} + +class _ChannelFileDisplayScreenState extends State { + @override + void initState() { + super.initState(); + final messageSearchBloc = MessageSearchBloc.of(context); + messageSearchBloc.search( + filter: { + 'cid': { + r'$in': ['messaging:${StreamChannel.of(context).channel.id}'] + } + }, + messageFilter: { + 'attachments.type': { + r'$in': ['file'], + }, + }, + sort: widget.sortOptions, + pagination: widget.paginationParams, + ); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -31,6 +76,95 @@ class ChannelFileDisplayScreen extends StatelessWidget { ), backgroundColor: StreamChatTheme.of(context).primaryColor, ), + body: _buildMediaGrid(), + ); + } + + Widget _buildMediaGrid() { + final messageSearchBloc = MessageSearchBloc.of(context); + + return StreamBuilder>( + builder: (context, snapshot) { + if (snapshot.data == null) { + return Center( + child: CircularProgressIndicator( + backgroundColor: StreamChatTheme.of(context).accentColor, + ), + ); + } + + if (snapshot.data.isEmpty) { + return Center( + child: Text('Nothing here...'), + ); + } + + Map media = {}; + + for (var item in snapshot.data) { + item.message.attachments + .where((e) => e.type == 'file') + .forEach((e) { + media[e] = item.message; + }); + } + + return LazyLoadScrollView( + onEndOfPage: () => messageSearchBloc.search( + filter: { + 'cid': { + r'$in': ['messaging:${StreamChannel.of(context).channel.id}'] + } + }, + messageFilter: { + 'attachments.type': { + r'$in': ['file'] + }, + }, + sort: widget.sortOptions, + pagination: widget.paginationParams.copyWith( + offset: messageSearchBloc.messageResponses?.length ?? 0, + ), + ), + child: ListView.builder( + itemBuilder: (context, position) { + var channel = StreamChannel.of(context).channel; + + return Padding( + padding: const EdgeInsets.all(1.0), + child: InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: FullScreenMedia( + mediaAttachments: [ + media.keys.toList()[position], + ], + message: media.values.toList()[position], + sentAt: media.values.toList()[position].createdAt, + userName: media.values.toList()[position].user.name, + ), + ), + ), + ); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: FileAttachment( + attachment: media.keys.toList()[position], + ), + ), + ), + ); + }, + itemCount: media.length, + ), + ); + }, + stream: messageSearchBloc.messagesStream, ); } } diff --git a/lib/src/channel_media_display_screen.dart b/lib/src/channel_media_display_screen.dart index bcad35e2..dd2534b0 100644 --- a/lib/src/channel_media_display_screen.dart +++ b/lib/src/channel_media_display_screen.dart @@ -1,7 +1,50 @@ +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:video_player/video_player.dart'; + +class ChannelMediaDisplayScreen extends StatefulWidget { + /// The sorting used for the channels matching the filters. + /// Sorting is based on field and direction, multiple sorting options can be provided. + /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. + /// Direction can be ascending or descending. + final List sortOptions; + + /// Pagination parameters + /// limit: the number of users to return (max is 30) + /// offset: the offset (max is 1000) + /// message_limit: how many messages should be included to each channel + final PaginationParams paginationParams; + + ChannelMediaDisplayScreen({this.sortOptions, this.paginationParams}); + + @override + _ChannelMediaDisplayScreenState createState() => + _ChannelMediaDisplayScreenState(); +} + +class _ChannelMediaDisplayScreenState extends State { + @override + void initState() { + super.initState(); + final messageSearchBloc = MessageSearchBloc.of(context); + messageSearchBloc.search( + filter: { + 'cid': { + r'$in': ['messaging:${StreamChannel.of(context).channel.id}'] + } + }, + messageFilter: { + 'attachments.type': { + r'$in': ['image', 'video'] + }, + }, + sort: widget.sortOptions, + pagination: widget.paginationParams, + ); + } -class ChannelMediaDisplayScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( @@ -31,6 +74,108 @@ class ChannelMediaDisplayScreen extends StatelessWidget { ), backgroundColor: StreamChatTheme.of(context).primaryColor, ), + body: _buildMediaGrid(), + ); + } + + Widget _buildMediaGrid() { + final messageSearchBloc = MessageSearchBloc.of(context); + + return StreamBuilder>( + builder: (context, snapshot) { + if (snapshot.data == null) { + return Center( + child: CircularProgressIndicator( + backgroundColor: StreamChatTheme.of(context).accentColor, + ), + ); + } + + if (snapshot.data.isEmpty) { + return Center( + child: Text('Nothing here...'), + ); + } + + List<_AssetPackage> media = []; + + for (var item in snapshot.data) { + item.message.attachments + .where((e) => e.type == 'image' || e.type == 'video') + .forEach((e) { + VideoPlayerController controller; + if(e.type == 'video') { + controller = VideoPlayerController.network(e.assetUrl); + controller.initialize(); + } + media.add(_AssetPackage(e, item.message, controller)); + }); + } + + return LazyLoadScrollView( + onEndOfPage: () => messageSearchBloc.search( + filter: { + 'cid': { + r'$in': ['messaging:${StreamChannel.of(context).channel.id}'] + } + }, + messageFilter: { + 'attachments.type': { + r'$in': ['image', 'video'] + }, + }, + sort: widget.sortOptions, + pagination: widget.paginationParams.copyWith( + offset: messageSearchBloc.messageResponses?.length ?? 0, + ), + ), + child: GridView.builder( + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), + itemBuilder: (context, position) { + var channel = StreamChannel.of(context).channel; + + return Padding( + padding: const EdgeInsets.all(1.0), + child: InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: FullScreenMedia( + mediaAttachments: [ + media[position].attachment, + ], + message: media[position].message, + sentAt: media[position].message.createdAt, + userName: media[position].message.user.name, + ), + ), + ), + ); + }, + child: media[position].attachment.type == 'image' ? CachedNetworkImage( + imageUrl: media[position].attachment.imageUrl, + fit: BoxFit.cover, + ) : VideoPlayer(media[position].videoPlayer), + ), + ); + }, + itemCount: media.length, + ), + ); + }, + stream: messageSearchBloc.messagesStream, ); } } + +class _AssetPackage { + Attachment attachment; + Message message; + VideoPlayerController videoPlayer; + + _AssetPackage(this.attachment, this.message, this.videoPlayer); +} diff --git a/lib/src/chat_info_screen.dart b/lib/src/chat_info_screen.dart index 436ff675..fd25bc97 100644 --- a/lib/src/chat_info_screen.dart +++ b/lib/src/chat_info_screen.dart @@ -174,10 +174,27 @@ class _ChatInfoScreenState extends State { ), trailing: StreamSvgIcon.right(), onTap: () { + final channel = StreamChannel.of(context).channel; + Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ChannelMediaDisplayScreen())); + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: MessageSearchBloc( + child: ChannelMediaDisplayScreen( + sortOptions: [ + SortOption( + 'created_at', + direction: SortOption.ASC, + ), + ], + paginationParams: PaginationParams(limit: 20), + ), + ), + ), + ), + ); }, ), OptionListTile( @@ -188,10 +205,27 @@ class _ChatInfoScreenState extends State { ), trailing: StreamSvgIcon.right(), onTap: () { + final channel = StreamChannel.of(context).channel; + Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ChannelFileDisplayScreen())); + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: MessageSearchBloc( + child: ChannelFileDisplayScreen( + sortOptions: [ + SortOption( + 'created_at', + direction: SortOption.ASC, + ), + ], + paginationParams: PaginationParams(limit: 20), + ), + ), + ), + ), + ); }, ), OptionListTile( diff --git a/lib/src/group_info_screen.dart b/lib/src/group_info_screen.dart index 7dd7ceaa..d097ed0e 100644 --- a/lib/src/group_info_screen.dart +++ b/lib/src/group_info_screen.dart @@ -361,10 +361,27 @@ class _GroupInfoScreenState extends State { ), trailing: StreamSvgIcon.right(), onTap: () { + var channel = StreamChannel.of(context).channel; + Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ChannelMediaDisplayScreen())); + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: MessageSearchBloc( + child: ChannelMediaDisplayScreen( + sortOptions: [ + SortOption( + 'created_at', + direction: SortOption.ASC, + ), + ], + paginationParams: PaginationParams(limit: 20), + ), + ), + ), + ), + ); }, ), OptionListTile( @@ -375,10 +392,27 @@ class _GroupInfoScreenState extends State { ), trailing: StreamSvgIcon.right(), onTap: () { + var channel = StreamChannel.of(context).channel; + Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ChannelFileDisplayScreen())); + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: MessageSearchBloc( + child: ChannelFileDisplayScreen( + sortOptions: [ + SortOption( + 'created_at', + direction: SortOption.ASC, + ), + ], + paginationParams: PaginationParams(limit: 20), + ), + ), + ), + ), + ); }, ), OptionListTile( diff --git a/lib/src/message_search_bloc.dart b/lib/src/message_search_bloc.dart index 0128bb22..ab277c4c 100644 --- a/lib/src/message_search_bloc.dart +++ b/lib/src/message_search_bloc.dart @@ -55,6 +55,7 @@ class MessageSearchBlocState extends State /// Calls [Client.search] updating [queryMessagesLoading] stream Future search({ Map filter, + Map messageFilter, List sort, String query, PaginationParams pagination, @@ -78,6 +79,7 @@ class MessageSearchBlocState extends State sort, query, pagination, + messageFilters: messageFilter, ); if (clear) { diff --git a/pubspec.yaml b/pubspec.yaml index b41f58c6..d5112694 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,7 +28,7 @@ dependencies: file_picker: ^2.0.12 image_picker: ^0.6.7+2 flutter_keyboard_visibility: ^3.3.0 - stream_chat: ^0.2.14 + stream_chat: ^0.2.19 mime: ^0.9.6+3 video_compress: ^2.1.1 visibility_detector: ^0.1.5 From 58cc196ecc331e445164a7d1745cf2cb4b428f27 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 18 Dec 2020 20:08:44 +0530 Subject: [PATCH 04/34] feat: group info screen details --- lib/src/group_info_screen.dart | 346 ++++++++++++++++++++++++++------- 1 file changed, 277 insertions(+), 69 deletions(-) diff --git a/lib/src/group_info_screen.dart b/lib/src/group_info_screen.dart index d097ed0e..642065dc 100644 --- a/lib/src/group_info_screen.dart +++ b/lib/src/group_info_screen.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; +import 'package:stream_chat_flutter/src/chat_info_screen.dart'; import 'package:stream_chat_flutter/src/option_list_tile.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; @@ -52,7 +53,7 @@ class _GroupInfoScreenState extends State { filter: {}, ); _nameController = TextEditingController.fromValue( - TextEditingValue(text: channel.channel.extraData['name'])); + TextEditingValue(text: channel.channel.extraData['name'] ?? '')); _searchController = TextEditingController()..addListener(_userNameListener); _nameController.addListener(() { @@ -160,60 +161,65 @@ class _GroupInfoScreenState extends State { itemCount: snapshot.data.members.length, itemBuilder: (context, position) { return Material( - child: Container( - height: 65.0, - child: Column( - children: [ - Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 12.0), - child: UserAvatar( - user: snapshot.data.members[position].user, - constraints: - BoxConstraints(maxHeight: 40.0, maxWidth: 40.0), + child: InkWell( + onTap: () { + var userMember = snapshot.data.members.firstWhere((e) => e.user.id == StreamChat.of(context).user.id); + _showUserInfoModal(snapshot.data.members[position].user, userMember.role == 'owner'); + }, + child: Container( + height: 65.0, + child: Column( + children: [ + Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 12.0), + child: UserAvatar( + user: snapshot.data.members[position].user, + constraints: BoxConstraints( + maxHeight: 40.0, maxWidth: 40.0), + ), ), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - snapshot.data.members[position].user.name, - style: TextStyle(fontWeight: FontWeight.bold), - ), - SizedBox( - height: 1.0, - ), - Text( - _getLastSeen( - snapshot.data.members[position].user), - style: TextStyle( - color: Colors.black.withOpacity(0.5)), - ), - ], + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + snapshot.data.members[position].user.name, + style: TextStyle(fontWeight: FontWeight.bold), + ), + SizedBox( + height: 1.0, + ), + Text( + _getLastSeen( + snapshot.data.members[position].user), + style: TextStyle( + color: Colors.black.withOpacity(0.5)), + ), + ], + ), ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - snapshot.data.members[position].user.id == - channel.channel.createdBy.id - ? 'Owner' - : '', - style: - TextStyle(color: Colors.black.withOpacity(0.5)), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + snapshot.data.members[position].role == 'owner' + ? 'Owner' + : '', + style: TextStyle( + color: Colors.black.withOpacity(0.5)), + ), ), - ), - ], - ), - Container( - height: 1.0, - color: Color(0xffdbdbdb), - ), - ], + ], + ), + Container( + height: 1.0, + color: Color(0xffdbdbdb), + ), + ], + ), ), ), color: Colors.white, @@ -227,7 +233,7 @@ class _GroupInfoScreenState extends State { Widget _buildNameTile() { var channel = StreamChannel.of(context).channel; - var channelName = channel.extraData['name']; + var channelName = channel.extraData['name'] ?? ''; return Material( color: Colors.white, @@ -415,23 +421,24 @@ class _GroupInfoScreenState extends State { ); }, ), - OptionListTile( - title: 'Leave Group', - leading: StreamSvgIcon.userRemove( - size: 24.0, - color: Colors.black.withOpacity(0.5), + if (!channel.channel.isDistinct) + OptionListTile( + title: 'Leave Group', + leading: StreamSvgIcon.userRemove( + size: 24.0, + color: Colors.black.withOpacity(0.5), + ), + trailing: Container( + height: 24.0, + width: 24.0, + ), + onTap: () async { + await channel.channel + .removeMembers([StreamChat.of(context).user.id]); + Navigator.pop(context); + Navigator.pop(context); + }, ), - trailing: Container( - height: 24.0, - width: 24.0, - ), - onTap: () async { - await channel.channel - .removeMembers([StreamChat.of(context).user.id]); - Navigator.pop(context); - Navigator.pop(context); - }, - ), ], ); } @@ -590,6 +597,207 @@ class _GroupInfoScreenState extends State { ); } + void _showUserInfoModal(User user, bool isUserAdmin) { + var channel = StreamChannel.of(context).channel; + + showModalBottomSheet( + context: context, + clipBehavior: Clip.antiAlias, + isScrollControlled: true, + builder: (context) { + return StreamChannel( + channel: channel, + child: Material( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 24.0, + ), + Center( + child: Text( + user.name, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), + ), + ), + SizedBox( + height: 5.0, + ), + _buildConnectedTitleState(user), + Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: UserAvatar( + user: user, + constraints: BoxConstraints( + maxHeight: 64.0, + minHeight: 64.0, + ), + borderRadius: BorderRadius.circular(32.0), + ), + ), + ), + if (StreamChat.of(context).user.id != user.id) + _buildModalListTile( + StreamSvgIcon.user( + color: Color(0xff7a7a7a), + size: 24.0, + ), + 'View info', () async { + var client = StreamChat.of(context).client; + + var c = client.channel('messaging', extraData: { + 'members': [ + user.id, + StreamChat.of(context).user.id, + ], + }); + + await c.watch(); + + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: c, + child: ChatInfoScreen( + user: user, + ), + ), + ), + ); + }), + _buildModalListTile( + StreamSvgIcon.message( + color: Color(0xff7a7a7a), + size: 24.0, + ), + 'Message', + () {}), + if (!channel.isDistinct && StreamChat.of(context).user.id != user.id && isUserAdmin) + _buildModalListTile( + StreamSvgIcon.userAdd( + color: Color(0xff7a7a7a), + size: 24.0, + ), + 'Make Owner', () { + // TODO: I have no clue how to make someone an owner + }), + if (!channel.isDistinct && StreamChat.of(context).user.id != user.id && isUserAdmin) + _buildModalListTile( + StreamSvgIcon.userRemove( + color: Colors.red, + size: 24.0, + ), + 'Remove From Group', () async { + await channel.removeMembers([user.id]); + Navigator.pop(context); + _memberQueryFuture = channel.queryMembers( + filter: {}, + ); + }, color: Colors.red), + _buildModalListTile( + StreamSvgIcon.close_small( + color: Color(0xff7a7a7a), + size: 24.0, + ), + 'Cancel', () { + Navigator.pop(context); + }), + ], + ), + ), + ); + }, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16.0), + topRight: Radius.circular(16.0), + ), + ), + ); + } + + void _showRemoveUserModal(User user) {} + + Widget _buildConnectedTitleState(User user) { + var alternativeWidget; + + final otherMember = user; + + if (otherMember != null) { + if (otherMember.online) { + alternativeWidget = Text( + 'Online', + style: TextStyle(color: Colors.black.withOpacity(0.5)), + ); + } else { + alternativeWidget = Text( + 'Last seen ${Jiffy(otherMember.lastActive).fromNow()}', + style: TextStyle(color: Colors.black.withOpacity(0.5)), + ); + } + } + + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (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, + ], + ); + } + + Widget _buildModalListTile(Widget leading, String title, VoidCallback onTap, + {Color color = Colors.black}) { + return Material( + child: InkWell( + onTap: onTap, + child: Column( + children: [ + Container( + height: 1.0, + color: Color(0xffdbdbdb), + ), + Container( + height: 64.0, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: leading, + ), + Expanded( + child: Text( + title, + style: TextStyle(color: color, fontWeight: FontWeight.bold), + )) + ], + ), + ), + ], + ), + )); + } + String _getChannelName(double width, {List members}) { if (namedChanged) { return changedName; From 0968bff3ae269db10b4670b19546ffb75f9dc8b5 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 21 Dec 2020 20:29:12 +0530 Subject: [PATCH 05/34] rfac: Moved pages to sample app --- .../src => example/lib}/chat_info_screen.dart | 5 +- example/lib/customize_channel_preview.dart | 1 - .../lib}/group_info_screen.dart | 46 ++++++++++++++----- example/lib/main.dart | 40 ++++++++++++++++ example/lib/routes/app_routes.dart | 16 +++++++ example/lib/routes/routes.dart | 2 + lib/src/channel_header.dart | 40 +--------------- lib/src/channel_preview.dart | 36 +-------------- lib/stream_chat_flutter.dart | 3 ++ 9 files changed, 99 insertions(+), 90 deletions(-) rename {lib/src => example/lib}/chat_info_screen.dart (98%) rename {lib/src => example/lib}/group_info_screen.dart (95%) diff --git a/lib/src/chat_info_screen.dart b/example/lib/chat_info_screen.dart similarity index 98% rename from lib/src/chat_info_screen.dart rename to example/lib/chat_info_screen.dart index fd25bc97..07e36301 100644 --- a/lib/src/chat_info_screen.dart +++ b/example/lib/chat_info_screen.dart @@ -3,10 +3,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; -import '../stream_chat_flutter.dart'; -import 'channel_file_display_screen.dart'; -import 'channel_media_display_screen.dart'; -import 'option_list_tile.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Detail screen for a 1:1 chat correspondence class ChatInfoScreen extends StatefulWidget { diff --git a/example/lib/customize_channel_preview.dart b/example/lib/customize_channel_preview.dart index 3809f780..3cd48d6e 100644 --- a/example/lib/customize_channel_preview.dart +++ b/example/lib/customize_channel_preview.dart @@ -84,7 +84,6 @@ class ChannelListPage extends StatelessWidget { channel: channel, ), title: ChannelName( - channel: channel, textStyle: StreamChatTheme.of(context).channelPreviewTheme.title.copyWith( color: Colors.black.withOpacity(opacity), diff --git a/lib/src/group_info_screen.dart b/example/lib/group_info_screen.dart similarity index 95% rename from lib/src/group_info_screen.dart rename to example/lib/group_info_screen.dart index 642065dc..5d1c2063 100644 --- a/lib/src/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -3,13 +3,12 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; -import 'package:stream_chat_flutter/src/chat_info_screen.dart'; +import 'file:///Users/deven9852/Stream/stream-chat-flutter/example/lib/chat_info_screen.dart'; import 'package:stream_chat_flutter/src/option_list_tile.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; -import '../stream_chat_flutter.dart'; -import 'channel_file_display_screen.dart'; -import 'channel_media_display_screen.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'main.dart'; class GroupInfoScreen extends StatefulWidget { @override @@ -83,7 +82,6 @@ class _GroupInfoScreenState extends State { leadingWidth: 36.0, title: Column( children: [ - // TODO: Add member names instead of Demo (if name = null) FutureBuilder( future: _memberQueryFuture, builder: (context, snapshot) { @@ -163,8 +161,10 @@ class _GroupInfoScreenState extends State { return Material( child: InkWell( onTap: () { - var userMember = snapshot.data.members.firstWhere((e) => e.user.id == StreamChat.of(context).user.id); - _showUserInfoModal(snapshot.data.members[position].user, userMember.role == 'owner'); + var userMember = snapshot.data.members.firstWhere( + (e) => e.user.id == StreamChat.of(context).user.id); + _showUserInfoModal(snapshot.data.members[position].user, + userMember.role == 'owner'); }, child: Container( height: 65.0, @@ -675,9 +675,31 @@ class _GroupInfoScreenState extends State { color: Color(0xff7a7a7a), size: 24.0, ), - 'Message', - () {}), - if (!channel.isDistinct && StreamChat.of(context).user.id != user.id && isUserAdmin) + 'Message', () async { + var client = StreamChat.of(context).client; + + var c = client.channel('messaging', extraData: { + 'members': [ + user.id, + StreamChat.of(context).user.id, + ], + }); + + await c.watch(); + + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: c, + child: ChannelPage(), + ), + ), + ); + }), + if (!channel.isDistinct && + StreamChat.of(context).user.id != user.id && + isUserAdmin) _buildModalListTile( StreamSvgIcon.userAdd( color: Color(0xff7a7a7a), @@ -686,7 +708,9 @@ class _GroupInfoScreenState extends State { 'Make Owner', () { // TODO: I have no clue how to make someone an owner }), - if (!channel.isDistinct && StreamChat.of(context).user.id != user.id && isUserAdmin) + if (!channel.isDistinct && + StreamChat.of(context).user.id != user.id && + isUserAdmin) _buildModalListTile( StreamSvgIcon.userRemove( color: Colors.red, diff --git a/example/lib/main.dart b/example/lib/main.dart index 4b529e23..8a67a7da 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,6 +1,8 @@ import 'dart:io'; import 'package:example/choose_user_page.dart'; +import 'package:example/chat_info_screen.dart'; +import 'package:example/group_info_screen.dart'; import 'package:example/new_chat_screen.dart'; import 'package:example/new_group_chat_screen.dart'; import 'package:flutter/cupertino.dart'; @@ -426,6 +428,44 @@ class ChannelPage extends StatelessWidget { backgroundColor: Color.fromRGBO(252, 252, 252, 1), appBar: ChannelHeader( showTypingIndicator: false, + onImageTap: () async { + var channel = StreamChannel.of(context).channel; + + 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) { + final pop = await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + child: ChatInfoScreen( + user: otherUser.user, + ), + channel: channel, + ), + ), + ); + + if (pop == true) { + Navigator.pop(context); + } + } + } else { + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + child: GroupInfoScreen(), + channel: channel, + ), + ), + ); + } + }, ), body: Column( children: [ diff --git a/example/lib/routes/app_routes.dart b/example/lib/routes/app_routes.dart index 6b0529e1..f3261f72 100644 --- a/example/lib/routes/app_routes.dart +++ b/example/lib/routes/app_routes.dart @@ -7,6 +7,8 @@ import '../main.dart'; import '../group_chat_details_screen.dart'; import '../new_group_chat_screen.dart'; import '../new_chat_screen.dart'; +import '../chat_info_screen.dart'; +import '../group_info_screen.dart'; class AppRoutes { /// Add entry for new route here @@ -59,6 +61,20 @@ class AppRoutes { selectedUsers: args, ); }); + case Routes.CHAT_INFO_SCREEN: + return MaterialPageRoute( + settings: const RouteSettings(name: Routes.CHAT_INFO_SCREEN), + builder: (_) { + return ChatInfoScreen( + user: args, + ); + }); + case Routes.GROUP_INFO_SCREEN: + return MaterialPageRoute( + settings: const RouteSettings(name: Routes.GROUP_INFO_SCREEN), + builder: (_) { + return GroupInfoScreen(); + }); } } } diff --git a/example/lib/routes/routes.dart b/example/lib/routes/routes.dart index 0e0b1e57..0d2f4e5f 100644 --- a/example/lib/routes/routes.dart +++ b/example/lib/routes/routes.dart @@ -7,4 +7,6 @@ class Routes { static const String NEW_CHAT = '/new_chat'; static const String NEW_GROUP_CHAT = '/new_group_chat'; static const String NEW_GROUP_CHAT_DETAILS = '/new_group_chat_details'; + static const String CHAT_INFO_SCREEN = '/chat_info_screen'; + static const String GROUP_INFO_SCREEN = '/group_info_screen'; } diff --git a/lib/src/channel_header.dart b/lib/src/channel_header.dart index 73e44b35..4517d7a0 100644 --- a/lib/src/channel_header.dart +++ b/lib/src/channel_header.dart @@ -8,8 +8,6 @@ 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 'group_info_screen.dart'; import 'stream_channel.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header.png) @@ -100,43 +98,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { padding: const EdgeInsets.only(right: 10.0), 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( - (element) => element.user.id != currentUser.id, - orElse: () => null, - ); - if (otherUser != null) { - final pop = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: channel, - child: ChatInfoScreen( - user: otherUser.user, - ), - ), - ), - ); - - if (pop == true) { - Navigator.pop(context); - } - } - } else { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: channel, - child: GroupInfoScreen(), - ), - ), - ); - } - }, + onTap: onImageTap, ), ), ), diff --git a/lib/src/channel_preview.dart b/lib/src/channel_preview.dart index ff727cfa..26f68812 100644 --- a/lib/src/channel_preview.dart +++ b/lib/src/channel_preview.dart @@ -2,13 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter/src/group_info_screen.dart'; 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) @@ -62,39 +60,7 @@ class ChannelPreview extends StatelessWidget { } }, leading: ChannelImage( - 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, - ), - ), - ), - ); - } - } else { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: channel, - child: GroupInfoScreen(), - ), - ), - ); - } - }, + onTap: onImageTap, ), title: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/stream_chat_flutter.dart b/lib/stream_chat_flutter.dart index 5dd46fa3..799af175 100644 --- a/lib/stream_chat_flutter.dart +++ b/lib/stream_chat_flutter.dart @@ -44,3 +44,6 @@ 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/option_list_tile.dart'; +export 'src/channel_file_display_screen.dart'; +export 'src/channel_media_display_screen.dart'; From f8cf3a7d29a600603a9132db9eb3cd2e084b1997 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 21 Dec 2020 20:50:50 +0530 Subject: [PATCH 06/34] fmt: dartfmt --- lib/src/channel_file_display_screen.dart | 5 +---- lib/src/channel_media_display_screen.dart | 14 ++++++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/src/channel_file_display_screen.dart b/lib/src/channel_file_display_screen.dart index 361aa808..8ea7daa9 100644 --- a/lib/src/channel_file_display_screen.dart +++ b/lib/src/channel_file_display_screen.dart @@ -1,4 +1,3 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -102,9 +101,7 @@ class _ChannelFileDisplayScreenState extends State { Map media = {}; for (var item in snapshot.data) { - item.message.attachments - .where((e) => e.type == 'file') - .forEach((e) { + item.message.attachments.where((e) => e.type == 'file').forEach((e) { media[e] = item.message; }); } diff --git a/lib/src/channel_media_display_screen.dart b/lib/src/channel_media_display_screen.dart index dd2534b0..d47709cd 100644 --- a/lib/src/channel_media_display_screen.dart +++ b/lib/src/channel_media_display_screen.dart @@ -103,8 +103,8 @@ class _ChannelMediaDisplayScreenState extends State { item.message.attachments .where((e) => e.type == 'image' || e.type == 'video') .forEach((e) { - VideoPlayerController controller; - if(e.type == 'video') { + VideoPlayerController controller; + if (e.type == 'video') { controller = VideoPlayerController.network(e.assetUrl); controller.initialize(); } @@ -156,10 +156,12 @@ class _ChannelMediaDisplayScreenState extends State { ), ); }, - child: media[position].attachment.type == 'image' ? CachedNetworkImage( - imageUrl: media[position].attachment.imageUrl, - fit: BoxFit.cover, - ) : VideoPlayer(media[position].videoPlayer), + child: media[position].attachment.type == 'image' + ? CachedNetworkImage( + imageUrl: media[position].attachment.imageUrl, + fit: BoxFit.cover, + ) + : VideoPlayer(media[position].videoPlayer), ), ); }, From 9c53a295ae8ea7aca5ff0a3c40ed1a50c68bddf4 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 29 Dec 2020 18:53:05 +0530 Subject: [PATCH 07/34] fixes for review --- example/lib/chat_info_screen.dart | 2 +- example/lib/group_info_screen.dart | 4 +-- lib/src/channel_file_display_screen.dart | 31 ++++++++++++++++- lib/src/channel_media_display_screen.dart | 41 ++++++++++++++++++++--- lib/src/stream_svg_icon.dart | 12 +++++++ lib/svgs/Icon_user_settings.svg | 5 +++ 6 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 lib/svgs/Icon_user_settings.svg diff --git a/example/lib/chat_info_screen.dart b/example/lib/chat_info_screen.dart index eb12bfee..5f7f7c63 100644 --- a/example/lib/chat_info_screen.dart +++ b/example/lib/chat_info_screen.dart @@ -246,7 +246,7 @@ class _ChatInfoScreenState extends State { Widget _buildDeleteListTile() { return OptionListTile( - title: 'Delete', + title: 'Delete Conversation', leading: StreamSvgIcon.delete( color: Colors.red, size: 24.0, diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index 5d1c2063..b4972165 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -3,9 +3,9 @@ import 'dart:async'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; -import 'file:///Users/deven9852/Stream/stream-chat-flutter/example/lib/chat_info_screen.dart'; import 'package:stream_chat_flutter/src/option_list_tile.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; +import 'chat_info_screen.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'main.dart'; @@ -701,7 +701,7 @@ class _GroupInfoScreenState extends State { StreamChat.of(context).user.id != user.id && isUserAdmin) _buildModalListTile( - StreamSvgIcon.userAdd( + StreamSvgIcon.Icon_user_settings( color: Color(0xff7a7a7a), size: 24.0, ), diff --git a/lib/src/channel_file_display_screen.dart b/lib/src/channel_file_display_screen.dart index 8ea7daa9..c8b34ea0 100644 --- a/lib/src/channel_file_display_screen.dart +++ b/lib/src/channel_file_display_screen.dart @@ -94,7 +94,36 @@ class _ChannelFileDisplayScreenState extends State { if (snapshot.data.isEmpty) { return Center( - child: Text('Nothing here...'), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + StreamSvgIcon.files( + size: 136.0, + color: Color(0xffdbdbdb), + ), + SizedBox( + height: 16.0, + ), + Text( + 'No Media', + style: TextStyle( + fontSize: 14.0, + color: Color(0xff000000), + ), + ), + SizedBox( + height: 8.0, + ), + Text( + 'Files sent in this chat will appear here', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14.0, + color: Color(0xff000000).withOpacity(0.5), + ), + ), + ], + ), ); } diff --git a/lib/src/channel_media_display_screen.dart b/lib/src/channel_media_display_screen.dart index d47709cd..41ec41b3 100644 --- a/lib/src/channel_media_display_screen.dart +++ b/lib/src/channel_media_display_screen.dart @@ -93,7 +93,36 @@ class _ChannelMediaDisplayScreenState extends State { if (snapshot.data.isEmpty) { return Center( - child: Text('Nothing here...'), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + StreamSvgIcon.pictures( + size: 136.0, + color: Color(0xffdbdbdb), + ), + SizedBox( + height: 16.0, + ), + Text( + 'No Media', + style: TextStyle( + fontSize: 14.0, + color: Color(0xff000000), + ), + ), + SizedBox( + height: 8.0, + ), + Text( + 'Photos or video sent in this chat will \nappear here', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14.0, + color: Color(0xff000000).withOpacity(0.5), + ), + ), + ], + ), ); } @@ -157,9 +186,13 @@ class _ChannelMediaDisplayScreenState extends State { ); }, child: media[position].attachment.type == 'image' - ? CachedNetworkImage( - imageUrl: media[position].attachment.imageUrl, - fit: BoxFit.cover, + ? ImageAttachment( + attachment: media[position].attachment, + message: media[position].message, + size: Size( + MediaQuery.of(context).size.width * 0.8, + MediaQuery.of(context).size.height * 0.3, + ), ) : VideoPlayer(media[position].videoPlayer), ), diff --git a/lib/src/stream_svg_icon.dart b/lib/src/stream_svg_icon.dart index 98ce0f8e..0d8e3a95 100644 --- a/lib/src/stream_svg_icon.dart +++ b/lib/src/stream_svg_icon.dart @@ -757,4 +757,16 @@ class StreamSvgIcon extends StatelessWidget { height: size, ); } + + factory StreamSvgIcon.Icon_user_settings({ + double size, + Color color, + }) { + return StreamSvgIcon( + assetName: 'Icon_user_settings.svg', + color: color, + width: size, + height: size, + ); + } } diff --git a/lib/svgs/Icon_user_settings.svg b/lib/svgs/Icon_user_settings.svg new file mode 100644 index 00000000..36ce6115 --- /dev/null +++ b/lib/svgs/Icon_user_settings.svg @@ -0,0 +1,5 @@ + + + + + From 27578c7a0a033aa69b0e2501d2533ed4d5c6a52d Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 30 Dec 2020 12:45:16 +0530 Subject: [PATCH 08/34] Minor ui fixes Signed-off-by: Sahil Kumar --- example/lib/chat_info_screen.dart | 33 +++++++++++++++- example/lib/group_info_screen.dart | 46 ++++++++++++----------- lib/src/channel_file_display_screen.dart | 21 ++++++----- lib/src/channel_media_display_screen.dart | 22 +++++++---- 4 files changed, 82 insertions(+), 40 deletions(-) diff --git a/example/lib/chat_info_screen.dart b/example/lib/chat_info_screen.dart index 5f7f7c63..53adebb7 100644 --- a/example/lib/chat_info_screen.dart +++ b/example/lib/chat_info_screen.dart @@ -381,12 +381,43 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { }, ), builder: (context, snapshot) { - if (snapshot.data == null) { + if (!snapshot.hasData) { return Center( child: CircularProgressIndicator(), ); } + if (snapshot.data.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + StreamSvgIcon.message( + size: 136.0, + color: Color(0xffdbdbdb), + ), + SizedBox(height: 16.0), + Text( + 'No Shared Groups', + style: TextStyle( + fontSize: 14.0, + color: Color(0xff000000), + ), + ), + SizedBox(height: 8.0), + Text( + 'Group shared with User will appear here.', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14.0, + color: Color(0xff000000).withOpacity(0.5), + ), + ), + ], + ), + ); + } + return ListView.builder( itemCount: snapshot.data.length, itemBuilder: (context, position) { diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index b4972165..5a2d4a7d 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -671,32 +671,34 @@ class _GroupInfoScreenState extends State { ); }), _buildModalListTile( - StreamSvgIcon.message( - color: Color(0xff7a7a7a), - size: 24.0, - ), - 'Message', () async { - var client = StreamChat.of(context).client; + StreamSvgIcon.message( + color: Color(0xff7a7a7a), + size: 24.0, + ), + 'Message', + () async { + var client = StreamChat.of(context).client; - var c = client.channel('messaging', extraData: { - 'members': [ - user.id, - StreamChat.of(context).user.id, - ], - }); + var c = client.channel('messaging', extraData: { + 'members': [ + user.id, + StreamChat.of(context).user.id, + ], + }); - await c.watch(); + await c.watch(); - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: c, - child: ChannelPage(), + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: c, + child: ChannelPage(), + ), ), - ), - ); - }), + ); + }, + ), if (!channel.isDistinct && StreamChat.of(context).user.id != user.id && isUserAdmin) diff --git a/lib/src/channel_file_display_screen.dart b/lib/src/channel_file_display_screen.dart index c8b34ea0..c319b524 100644 --- a/lib/src/channel_file_display_screen.dart +++ b/lib/src/channel_file_display_screen.dart @@ -15,9 +15,13 @@ class ChannelFileDisplayScreen extends StatefulWidget { /// message_limit: how many messages should be included to each channel final PaginationParams paginationParams; - ChannelFileDisplayScreen({ + /// The builder used when the file list is empty. + final WidgetBuilder emptyBuilder; + + const ChannelFileDisplayScreen({ this.sortOptions, this.paginationParams, + this.emptyBuilder, }); @override @@ -93,6 +97,9 @@ class _ChannelFileDisplayScreenState extends State { } if (snapshot.data.isEmpty) { + if (widget.emptyBuilder != null) { + return widget.emptyBuilder(context); + } return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -101,19 +108,15 @@ class _ChannelFileDisplayScreenState extends State { size: 136.0, color: Color(0xffdbdbdb), ), - SizedBox( - height: 16.0, - ), + SizedBox(height: 16.0), Text( - 'No Media', + 'No Files', style: TextStyle( fontSize: 14.0, color: Color(0xff000000), ), ), - SizedBox( - height: 8.0, - ), + SizedBox(height: 8.0), Text( 'Files sent in this chat will appear here', textAlign: TextAlign.center, @@ -127,7 +130,7 @@ class _ChannelFileDisplayScreenState extends State { ); } - Map media = {}; + final media = {}; for (var item in snapshot.data) { item.message.attachments.where((e) => e.type == 'file').forEach((e) { diff --git a/lib/src/channel_media_display_screen.dart b/lib/src/channel_media_display_screen.dart index 41ec41b3..21eeb611 100644 --- a/lib/src/channel_media_display_screen.dart +++ b/lib/src/channel_media_display_screen.dart @@ -17,7 +17,14 @@ class ChannelMediaDisplayScreen extends StatefulWidget { /// message_limit: how many messages should be included to each channel final PaginationParams paginationParams; - ChannelMediaDisplayScreen({this.sortOptions, this.paginationParams}); + /// The builder used when the file list is empty. + final WidgetBuilder emptyBuilder; + + const ChannelMediaDisplayScreen({ + this.sortOptions, + this.paginationParams, + this.emptyBuilder, + }); @override _ChannelMediaDisplayScreenState createState() => @@ -92,6 +99,9 @@ class _ChannelMediaDisplayScreenState extends State { } if (snapshot.data.isEmpty) { + if (widget.emptyBuilder != null) { + return widget.emptyBuilder(context); + } return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -100,9 +110,7 @@ class _ChannelMediaDisplayScreenState extends State { size: 136.0, color: Color(0xffdbdbdb), ), - SizedBox( - height: 16.0, - ), + SizedBox(height: 16.0), Text( 'No Media', style: TextStyle( @@ -110,9 +118,7 @@ class _ChannelMediaDisplayScreenState extends State { color: Color(0xff000000), ), ), - SizedBox( - height: 8.0, - ), + SizedBox(height: 8.0), Text( 'Photos or video sent in this chat will \nappear here', textAlign: TextAlign.center, @@ -126,7 +132,7 @@ class _ChannelMediaDisplayScreenState extends State { ); } - List<_AssetPackage> media = []; + final media = <_AssetPackage>[]; for (var item in snapshot.data) { item.message.attachments From 4e7fb6123c9e93d13f15007bec358bcfcfff3d7a Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 30 Dec 2020 14:50:13 +0530 Subject: [PATCH 09/34] fixes Signed-off-by: Sahil Kumar --- lib/src/channel_file_display_screen.dart | 4 +- lib/src/channel_list_view.dart | 11 +- lib/src/channel_media_display_screen.dart | 4 +- lib/src/image_footer.dart | 432 +++++++++++----------- 4 files changed, 225 insertions(+), 226 deletions(-) diff --git a/lib/src/channel_file_display_screen.dart b/lib/src/channel_file_display_screen.dart index c319b524..a6b1008e 100644 --- a/lib/src/channel_file_display_screen.dart +++ b/lib/src/channel_file_display_screen.dart @@ -90,9 +90,7 @@ class _ChannelFileDisplayScreenState extends State { builder: (context, snapshot) { if (snapshot.data == null) { return Center( - child: CircularProgressIndicator( - backgroundColor: StreamChatTheme.of(context).accentColor, - ), + child: const CircularProgressIndicator(), ); } diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 5efa1e29..126e6d0d 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -75,6 +75,7 @@ class ChannelListView extends StatefulWidget { this.swipeToAction = false, this.pullToRefresh = true, this.crossAxisCount = 1, + this.padding, this.selectedChannels = const [], }) : super(key: key); @@ -139,6 +140,9 @@ class ChannelListView extends StatefulWidget { /// The number of children in the cross axis. final int crossAxisCount; + /// The amount of space by which to inset the children. + final EdgeInsetsGeometry padding; + final List selectedChannels; @override @@ -268,6 +272,7 @@ class _ChannelListViewState extends State if (channels.isNotEmpty) { if (widget.crossAxisCount > 1) { child = GridView.builder( + padding: widget.padding, gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: widget.crossAxisCount), itemCount: channels.length, @@ -279,6 +284,7 @@ class _ChannelListViewState extends State ); } else { child = ListView.separated( + padding: widget.padding, physics: AlwaysScrollableScrollPhysics(), itemCount: channels.isNotEmpty ? channels.length + 1 : channels.length, @@ -306,6 +312,7 @@ class _ChannelListViewState extends State Widget _buildLoadingWidget() { return ListView( + padding: widget.padding, physics: AlwaysScrollableScrollPhysics(), children: List.generate( 25, @@ -331,9 +338,7 @@ class _ChannelListViewState extends State highlightColor: Color(0xffffffff), child: Column( children: [ - SizedBox( - height: 4.0, - ), + SizedBox(height: 4.0), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ diff --git a/lib/src/channel_media_display_screen.dart b/lib/src/channel_media_display_screen.dart index 21eeb611..ddf52c66 100644 --- a/lib/src/channel_media_display_screen.dart +++ b/lib/src/channel_media_display_screen.dart @@ -92,9 +92,7 @@ class _ChannelMediaDisplayScreenState extends State { builder: (context, snapshot) { if (snapshot.data == null) { return Center( - child: CircularProgressIndicator( - backgroundColor: StreamChatTheme.of(context).accentColor, - ), + child: const CircularProgressIndicator(), ); } diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 00b38a90..97e40cba 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -16,7 +16,7 @@ import 'package:stream_chat_flutter/src/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -class ImageFooter extends StatefulWidget { +class ImageFooter extends StatefulWidget implements PreferredSizeWidget { /// Callback to call when pressing the back button. /// By default it calls [Navigator.pop] final VoidCallback onBackPressed; @@ -61,12 +61,12 @@ class ImageFooter extends StatefulWidget { class _ImageFooterState extends State { bool _userSearchMode = false; TextEditingController _searchController; - TextEditingController _messageController = TextEditingController(); - FocusNode _messageFocusNode = FocusNode(); + final TextEditingController _messageController = TextEditingController(); + final FocusNode _messageFocusNode = FocusNode(); String _channelNameQuery; - List _selectedChannels = []; + final List _selectedChannels = []; bool _loading = false; Timer _debounce; @@ -103,52 +103,54 @@ class _ImageFooterState extends State { @override Widget build(BuildContext context) { - return SafeArea( - child: Container( - color: - StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, - padding: EdgeInsets.symmetric(vertical: 8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - icon: StreamSvgIcon.icon_SHARE( - size: 24.0, - color: Colors.black, + return SizedBox.fromSize( + size: widget.preferredSize, + child: MediaQuery.removePadding( + context: context, + removeTop: true, + child: BottomAppBar( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: StreamSvgIcon.icon_SHARE( + size: 24.0, + color: Colors.black, + ), + onPressed: () { + _buildShareModal(context); + }, ), - onPressed: () { - _buildShareModal(context); - }, - ), - InkWell( - onTap: widget.onTitleTap, - child: Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - '${widget.currentPage + 1} of ${widget.totalPages}', - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w700, - color: Colors.black, + InkWell( + onTap: widget.onTitleTap, + child: Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '${widget.currentPage + 1} of ${widget.totalPages}', + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w700, + color: Colors.black, + ), ), - ), - ], + ], + ), ), ), - ), - IconButton( - icon: StreamSvgIcon.Icon_grid( - color: Colors.black, + IconButton( + icon: StreamSvgIcon.Icon_grid( + color: Colors.black, + ), + onPressed: () { + _buildPhotosModal(context); + }, ), - onPressed: () { - _buildPhotosModal(context); - }, - ), - ], + ], + ), ), ), ); @@ -258,8 +260,8 @@ class _ImageFooterState extends State { ); } - void _buildShareModal(context) { - showDialog( + void _buildShareModal(context) async { + final result = await showDialog( context: context, builder: (context) { return StatefulBuilder(builder: (context, modalSetState) { @@ -282,10 +284,6 @@ class _ImageFooterState extends State { child: Column( children: [ _buildTextInputSection(modalSetState), - if (_userSearchMode) - SizedBox( - height: 22.0, - ), Expanded( child: ChannelsBloc( child: ChannelListView( @@ -302,6 +300,7 @@ class _ImageFooterState extends State { }); } }, + padding: const EdgeInsets.symmetric(vertical: 8), crossAxisCount: 4, pagination: PaginationParams( limit: 25, @@ -342,7 +341,8 @@ class _ImageFooterState extends State { ), ), Text( - 'No chat matches these keywords...'), + 'No chat matches these keywords...', + ), ], ), ), @@ -360,42 +360,30 @@ class _ImageFooterState extends State { Align( alignment: Alignment.bottomCenter, child: Container( - color: Colors.white, - height: 48.0, - child: Material( - child: InkWell( - onTap: () async { - var url = widget - .mediaAttachments[widget.currentPage] - .imageUrl ?? - widget - .mediaAttachments[widget.currentPage] - .assetUrl ?? - widget - .mediaAttachments[widget.currentPage] - .thumbUrl; - - if (widget - .mediaAttachments[widget.currentPage] - .type == - 'video') { + height: kToolbarHeight, + child: SizedBox.expand( + child: RaisedButton( + onPressed: () async { + final attachment = widget + .mediaAttachments[widget.currentPage]; + var url = attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl; + if (attachment.type == 'video') { await _saveVideo(url); - Navigator.pop(context); } else { await _saveImage(url); - Navigator.pop(context); } + Navigator.pop(context); }, - child: SizedBox.expand( - child: Center( - child: Text( - 'Save to Photos', - style: TextStyle( - color: StreamChatTheme.of(context) - .accentColor, - fontWeight: FontWeight.bold, - ), - ), + color: Colors.white, + elevation: 8, + child: Text( + 'Save to Photos', + style: TextStyle( + color: + StreamChatTheme.of(context).accentColor, + fontWeight: FontWeight.bold, ), ), ), @@ -411,62 +399,74 @@ class _ImageFooterState extends State { }); }, ); + if (result == null) { + _selectedChannels.clear(); + } } Widget _buildTextInputSection(modalSetState) { if (_userSearchMode) { return Column( children: [ - SizedBox( - height: 16.0, - ), + SizedBox(height: 16.0), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SizedBox( - width: 16.0, - ), + SizedBox(width: 8.0), Expanded( - child: TextField( - controller: _searchController, - cursorColor: Colors.black, - autofocus: true, - decoration: InputDecoration( - isDense: true, - prefixIconConstraints: - BoxConstraints.tight(Size(36.0, 44.0)), - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - vertical: 2.0, horizontal: 6.0), - child: StreamSvgIcon.search( - color: Colors.black, + child: Container( + height: 36, + margin: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 8, + ), + child: TextField( + controller: _searchController, + cursorColor: Colors.black, + autofocus: true, + decoration: InputDecoration( + prefixIconConstraints: BoxConstraints.tight(Size(40, 24)), + prefixIcon: Padding( + padding: const EdgeInsets.only(left: 8, right: 8), + child: StreamSvgIcon.search( + color: Colors.black, + size: 24, + ), ), + hintText: 'Search', + hintStyle: TextStyle( + color: Colors.black.withOpacity(0.5), + fontSize: 14, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.black.withOpacity(0.08), + ), + borderRadius: BorderRadius.circular(24), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(24.0), + borderSide: BorderSide( + color: Colors.black.withOpacity(0.08), + ), + ), + contentPadding: EdgeInsets.zero, ), - hintText: 'Search', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(32.0), - borderSide: - BorderSide(color: Colors.black.withOpacity(0.08)), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(32.0), - borderSide: - BorderSide(color: Colors.black.withOpacity(0.08)), - ), - contentPadding: EdgeInsets.zero, ), ), ), - SizedBox( - width: 8.0, - ), IconButton( icon: StreamSvgIcon.close_small( color: Colors.black.withOpacity(0.5), ), + splashRadius: 24, onPressed: () { modalSetState(() { - _userSearchMode = false; + if (_searchController.text.isNotEmpty) { + _searchController.text = ''; + } else { + _userSearchMode = false; + } }); setState(() {}); }, @@ -475,80 +475,80 @@ class _ImageFooterState extends State { ), ], ); - } else { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 6.0), - child: IconButton( - icon: StreamSvgIcon.search( - color: Colors.black, - ), - iconSize: 24.0, - onPressed: () { - modalSetState(() { - _userSearchMode = true; - }); - }, - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: Text( - 'Select a Chat to Share', - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w700, - color: Colors.black, - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 6.0), - child: IconButton( - icon: StreamSvgIcon.share_arrow( - color: Colors.black, - ), - onPressed: () async { - var url = - widget.mediaAttachments[widget.currentPage].imageUrl ?? - widget.mediaAttachments[widget.currentPage].assetUrl ?? - widget.mediaAttachments[widget.currentPage].thumbUrl; - var type = - widget.mediaAttachments[widget.currentPage].type == 'image' - ? 'jpg' - : url?.split('?')?.first?.split('.')?.last ?? 'jpg'; - var request = await HttpClient().getUrl(Uri.parse(url)); - var response = await request.close(); - var bytes = await consolidateHttpClientResponseBytes(response); - await Share.file('File', 'image.$type', bytes, 'image/$type'); - }, - ), - ), - ], - ); } + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 6.0), + child: IconButton( + icon: StreamSvgIcon.search( + color: Colors.black, + ), + iconSize: 24.0, + onPressed: () { + modalSetState(() { + _userSearchMode = true; + }); + }, + ), + ), + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'Select a Chat to Share', + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.w700, + color: Colors.black, + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 6.0), + child: IconButton( + icon: StreamSvgIcon.share_arrow( + color: Colors.black, + ), + onPressed: () async { + final attachment = widget.mediaAttachments[widget.currentPage]; + var url = attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl; + var type = attachment.type == 'image' + ? 'jpg' + : url?.split('?')?.first?.split('.')?.last ?? 'jpg'; + var request = await HttpClient().getUrl(Uri.parse(url)); + var response = await request.close(); + var bytes = await consolidateHttpClientResponseBytes(response); + await Share.file('File', 'image.$type', bytes, 'image/$type'); + }, + ), + ), + ], + ); } Widget _buildShareTextInputSection(modalSetState) { return Align( alignment: Alignment.bottomCenter, - child: Container( - color: Colors.white, - height: 56.0, - child: _loading - ? Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: CircularProgressIndicator(), - ), - ) - : Row( - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.only(left: 8.0), + child: BottomAppBar( + child: Container( + height: 40.0, + margin: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 8, + ), + child: _loading + ? Center( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: CircularProgressIndicator(), + ), + ) + : Row( + children: [ + Expanded( child: TextField( controller: _messageController, focusNode: _messageFocusNode, @@ -560,54 +560,52 @@ class _ImageFooterState extends State { setState(() {}); }, decoration: InputDecoration( - isDense: true, prefixText: ' ', hintText: 'Add a comment', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(32.0), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(24.0), borderSide: BorderSide( color: Colors.black.withOpacity(0.16), ), ), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(32.0), + borderRadius: BorderRadius.circular(24.0), borderSide: BorderSide( color: Colors.black.withOpacity(0.16), )), - contentPadding: EdgeInsets.symmetric(vertical: 12.0), + contentPadding: const EdgeInsets.all(0), ), ), ), - ), - IconTheme( - data: StreamChatTheme.of(context) - .channelTheme - .messageInputButtonIconTheme, - child: Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: InkWell( - onTap: () async { - modalSetState(() { - _loading = true; - }); - await sendMessage(); - modalSetState(() { - _loading = false; - }); - }, - child: Transform.rotate( - angle: -pi / 2, - child: StreamSvgIcon.Icon_send_message( - color: StreamChatTheme.of(context).accentColor, - ), + SizedBox(width: 8), + IconTheme( + data: StreamChatTheme.of(context) + .channelTheme + .messageInputButtonIconTheme, + child: IconButton( + onPressed: () async { + modalSetState(() => _loading = true); + await sendMessage(); + modalSetState(() => _loading = false); + }, + splashRadius: 24, + visualDensity: VisualDensity.compact, + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + padding: EdgeInsets.zero, + icon: Transform.rotate( + angle: -pi / 2, + child: StreamSvgIcon.Icon_send_message( + color: StreamChatTheme.of(context).accentColor, ), ), ), ), - ), - ], - ), + ], + ), + ), ), ); } From 7047f3d9a48d82e4d83bf7962d41aad97182a35b Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 30 Dec 2020 15:06:40 +0530 Subject: [PATCH 10/34] [Image Attachment] Add a property to hide/show image title Signed-off-by: Sahil Kumar --- lib/src/channel_media_display_screen.dart | 1 + lib/src/image_attachment.dart | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/src/channel_media_display_screen.dart b/lib/src/channel_media_display_screen.dart index ddf52c66..b65f27ab 100644 --- a/lib/src/channel_media_display_screen.dart +++ b/lib/src/channel_media_display_screen.dart @@ -193,6 +193,7 @@ class _ChannelMediaDisplayScreenState extends State { ? ImageAttachment( attachment: media[position].attachment, message: media[position].message, + showTitle: false, size: Size( MediaQuery.of(context).size.width * 0.8, MediaQuery.of(context).size.height * 0.3, diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart index 2345b2b8..3210949b 100644 --- a/lib/src/image_attachment.dart +++ b/lib/src/image_attachment.dart @@ -12,6 +12,7 @@ class ImageAttachment extends StatelessWidget { final Message message; final MessageTheme messageTheme; final Size size; + final bool showTitle; const ImageAttachment({ Key key, @@ -19,6 +20,7 @@ class ImageAttachment extends StatelessWidget { @required this.message, @required this.size, this.messageTheme, + this.showTitle = true, }) : super(key: key); @override @@ -83,7 +85,7 @@ class ImageAttachment extends StatelessWidget { ), ), ), - if (attachment.title != null) + if (showTitle && attachment.title != null) Material( color: messageTheme.messageBackgroundColor, child: AttachmentTitle( @@ -93,7 +95,8 @@ class ImageAttachment extends StatelessWidget { ), ], ), - if (attachment.titleLink != null || attachment.ogScrapeUrl != null) + if (showTitle && + (attachment.titleLink != null || attachment.ogScrapeUrl != null)) Positioned.fill( child: Material( color: Colors.transparent, From ce06b18c193dcb4b7bc7830edf09c7af020e656f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 30 Dec 2020 12:00:21 +0100 Subject: [PATCH 11/34] fix title style --- example/lib/group_info_screen.dart | 13 +++++++++---- example/pubspec.yaml | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index 5a2d4a7d..15ae0c10 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -5,9 +5,9 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/src/option_list_tile.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; -import 'chat_info_screen.dart'; - import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'chat_info_screen.dart'; import 'main.dart'; class GroupInfoScreen extends StatefulWidget { @@ -90,7 +90,10 @@ class _GroupInfoScreenState extends State { members: snapshot.data?.members ?.map((e) => e.user) ?.toList()), - style: TextStyle(color: Colors.black), + style: TextStyle( + color: Colors.black, + fontSize: 16, + ), maxLines: 1, overflow: TextOverflow.ellipsis, ); @@ -104,7 +107,9 @@ class _GroupInfoScreenState extends State { return Text( '${channel.channel.memberCount} Members, ${snapshot?.data?.members?.where((e) => e.user.online)?.length ?? 0} Online', style: TextStyle( - color: Colors.black.withOpacity(0.5), fontSize: 12.0), + color: Colors.black.withOpacity(0.5), + fontSize: 12.0, + ), ); }), ], diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 175cdaeb..4c5a590b 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.94+97 +version: 1.0.95+98 environment: sdk: ">=2.2.2 <3.0.0" From c7cc553e004581943a811a6445ac594a5267e87e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 30 Dec 2020 12:08:26 +0100 Subject: [PATCH 12/34] fix debouncing called on pop --- example/lib/group_info_screen.dart | 8 +++++++- example/pubspec.yaml | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index 15ae0c10..1c926106 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -33,6 +33,9 @@ class _GroupInfoScreenState extends State { String changedName = ''; void _userNameListener() { + if (_searchController.text == _userNameQuery) { + return; + } if (_debounce?.isActive ?? false) _debounce.cancel(); _debounce = Timer(const Duration(milliseconds: 350), () { if (mounted && modalSetStateCallback != null) { @@ -492,7 +495,10 @@ class _GroupInfoScreenState extends State { r'$autocomplete': _userNameQuery, }, 'id': { - r'$ne': StreamChat.of(context).user.id, + r'$nin': [ + StreamChat.of(context).user.id, + ...channel.state.members.map((e) => e.userId), + ], }, }, sort: [ diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 4c5a590b..b4cbfad9 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.95+98 +version: 1.0.96+99 environment: sdk: ">=2.2.2 <3.0.0" From f8ea85348619b4e8e0dfd81a4a06a4930c2fcd1c Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 31 Dec 2020 12:41:05 +0530 Subject: [PATCH 13/34] fix: fixed channel media issue --- lib/src/channel_media_display_screen.dart | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/src/channel_media_display_screen.dart b/lib/src/channel_media_display_screen.dart index b65f27ab..ea6dabff 100644 --- a/lib/src/channel_media_display_screen.dart +++ b/lib/src/channel_media_display_screen.dart @@ -167,6 +167,7 @@ class _ChannelMediaDisplayScreenState extends State { SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), itemBuilder: (context, position) { var channel = StreamChannel.of(context).channel; + print('NAV'); return Padding( padding: const EdgeInsets.all(1.0), @@ -178,9 +179,9 @@ class _ChannelMediaDisplayScreenState extends State { builder: (context) => StreamChannel( channel: channel, child: FullScreenMedia( - mediaAttachments: [ - media[position].attachment, - ], + mediaAttachments: + media.map((e) => e.attachment).toList(), + startIndex: position, message: media[position].message, sentAt: media[position].message.createdAt, userName: media[position].message.user.name, @@ -190,13 +191,15 @@ class _ChannelMediaDisplayScreenState extends State { ); }, child: media[position].attachment.type == 'image' - ? ImageAttachment( - attachment: media[position].attachment, - message: media[position].message, - showTitle: false, - size: Size( - MediaQuery.of(context).size.width * 0.8, - MediaQuery.of(context).size.height * 0.3, + ? IgnorePointer( + child: ImageAttachment( + attachment: media[position].attachment, + message: media[position].message, + showTitle: false, + size: Size( + MediaQuery.of(context).size.width * 0.8, + MediaQuery.of(context).size.height * 0.3, + ), ), ) : VideoPlayer(media[position].videoPlayer), From f02f8a27cbcfdcad16981d2803be9fe1ce8d6cb7 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 31 Dec 2020 14:19:03 +0530 Subject: [PATCH 14/34] fix: fixed group member list --- example/lib/group_info_screen.dart | 176 +++++++++++++++++++---------- lib/src/image_footer.dart | 5 +- 2 files changed, 121 insertions(+), 60 deletions(-) diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index 1c926106..3c469b9f 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -32,6 +32,8 @@ class _GroupInfoScreenState extends State { bool namedChanged = false; String changedName = ''; + int groupMemberListLength; + void _userNameListener() { if (_searchController.text == _userNameQuery) { return; @@ -161,78 +163,134 @@ class _GroupInfoScreenState extends State { ); } - return ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: snapshot.data.members.length, - itemBuilder: (context, position) { - return Material( - child: InkWell( - onTap: () { - var userMember = snapshot.data.members.firstWhere( - (e) => e.user.id == StreamChat.of(context).user.id); - _showUserInfoModal(snapshot.data.members[position].user, - userMember.role == 'owner'); - }, - child: Container( - height: 65.0, - child: Column( - children: [ - Row( + groupMemberListLength ??= + snapshot.data.members.length > 6 ? 6 : snapshot.data.members.length; + + return Column( + children: [ + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: groupMemberListLength, + itemBuilder: (context, position) { + return Material( + child: InkWell( + onTap: () { + var userMember = snapshot.data.members.firstWhere( + (e) => e.user.id == StreamChat.of(context).user.id); + _showUserInfoModal(snapshot.data.members[position].user, + userMember.role == 'owner'); + }, + child: Container( + height: 65.0, + child: Column( children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 12.0), - child: UserAvatar( - user: snapshot.data.members[position].user, - constraints: BoxConstraints( - maxHeight: 40.0, maxWidth: 40.0), - ), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - snapshot.data.members[position].user.name, - style: TextStyle(fontWeight: FontWeight.bold), + Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 12.0), + child: UserAvatar( + user: snapshot.data.members[position].user, + constraints: BoxConstraints( + maxHeight: 40.0, maxWidth: 40.0), ), - SizedBox( - height: 1.0, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + snapshot.data.members[position].user.name, + style: TextStyle( + fontWeight: FontWeight.bold), + ), + SizedBox( + height: 1.0, + ), + Text( + _getLastSeen( + snapshot.data.members[position].user), + style: TextStyle( + color: Colors.black.withOpacity(0.5)), + ), + ], ), - Text( - _getLastSeen( - snapshot.data.members[position].user), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + snapshot.data.members[position].role == + 'owner' + ? 'Owner' + : '', style: TextStyle( color: Colors.black.withOpacity(0.5)), ), - ], - ), + ), + ], ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - snapshot.data.members[position].role == 'owner' - ? 'Owner' - : '', - style: TextStyle( - color: Colors.black.withOpacity(0.5)), - ), + Container( + height: 1.0, + color: Color(0xffdbdbdb), ), ], ), - Container( - height: 1.0, - color: Color(0xffdbdbdb), - ), - ], + ), + ), + color: Colors.white, + ); + }, + ), + if (groupMemberListLength != snapshot.data.members.length) + InkWell( + onTap: () { + setState(() { + groupMemberListLength = snapshot.data.members.length; + }); + }, + child: Material( + child: Container( + height: 65.0, + child: Column( + children: [ + Expanded( + child: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 21.0, vertical: 12.0), + child: StreamSvgIcon.down( + color: Color(0xff7a7a7a), + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '${snapshot.data.members.length - groupMemberListLength} more', + style: + TextStyle(color: Color(0xff7a7a7a)), + ), + ], + ), + ), + ], + ), + ), + Container( + height: 1.0, + color: Color(0xffdbdbdb), + ), + ], + ), ), ), ), - color: Colors.white, - ); - }, + ], ); }, future: _memberQueryFuture, diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 97e40cba..8a3738ac 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -104,7 +104,10 @@ class _ImageFooterState extends State { @override Widget build(BuildContext context) { return SizedBox.fromSize( - size: widget.preferredSize, + size: Size( + MediaQuery.of(context).size.width, + MediaQuery.of(context).padding.bottom + widget.preferredSize.height, + ), child: MediaQuery.removePadding( context: context, removeTop: true, From 1a6d575e5aa8a1f2fd8508bdbb9bd74c22f4d892 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 31 Dec 2020 18:38:38 +0530 Subject: [PATCH 15/34] fix: fixes for review --- example/lib/group_info_screen.dart | 1 + lib/src/channel_file_display_screen.dart | 28 ++++-------------------- lib/src/option_list_tile.dart | 15 ++++++++----- 3 files changed, 14 insertions(+), 30 deletions(-) diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index 3c469b9f..31653db8 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -333,6 +333,7 @@ class _GroupInfoScreenState extends State { ), style: TextStyle( fontWeight: FontWeight.bold, + height: 0.82, ), ), ), diff --git a/lib/src/channel_file_display_screen.dart b/lib/src/channel_file_display_screen.dart index a6b1008e..0cd3d854 100644 --- a/lib/src/channel_file_display_screen.dart +++ b/lib/src/channel_file_display_screen.dart @@ -159,30 +159,10 @@ class _ChannelFileDisplayScreenState extends State { return Padding( padding: const EdgeInsets.all(1.0), - child: InkWell( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: channel, - child: FullScreenMedia( - mediaAttachments: [ - media.keys.toList()[position], - ], - message: media.values.toList()[position], - sentAt: media.values.toList()[position].createdAt, - userName: media.values.toList()[position].user.name, - ), - ), - ), - ); - }, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: FileAttachment( - attachment: media.keys.toList()[position], - ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: FileAttachment( + attachment: media.keys.toList()[position], ), ), ); diff --git a/lib/src/option_list_tile.dart b/lib/src/option_list_tile.dart index c1f48b71..382db945 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.w600, + color: titleColor, + ), + ), + ), Expanded( flex: 2, child: Padding( From 08d07e4425c293ca922789a55e44f7887d95e6d6 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 31 Dec 2020 20:19:59 +0530 Subject: [PATCH 16/34] feat: review fixes --- example/lib/group_info_screen.dart | 5 +++-- lib/src/channel_file_display_screen.dart | 2 +- lib/src/channel_media_display_screen.dart | 2 +- lib/src/image_footer.dart | 4 +++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index 31653db8..87064472 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -129,7 +129,7 @@ class _GroupInfoScreenState extends State { child: Padding( padding: const EdgeInsets.all(8.0), child: StreamSvgIcon.userAdd( - color: StreamChatTheme.of(context).accentColor), + color: StreamChatTheme.of(context).colorTheme.accentBlue), ), ), ), @@ -356,7 +356,8 @@ class _GroupInfoScreenState extends State { padding: const EdgeInsets.only(right: 16.0, left: 8.0), child: InkWell( child: StreamSvgIcon.check( - color: StreamChatTheme.of(context).accentColor, + color: + StreamChatTheme.of(context).colorTheme.accentBlue, size: 24.0, ), onTap: () { diff --git a/lib/src/channel_file_display_screen.dart b/lib/src/channel_file_display_screen.dart index 0cd3d854..79645f73 100644 --- a/lib/src/channel_file_display_screen.dart +++ b/lib/src/channel_file_display_screen.dart @@ -77,7 +77,7 @@ class _ChannelFileDisplayScreenState extends State { ), ), ), - backgroundColor: StreamChatTheme.of(context).primaryColor, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, ), body: _buildMediaGrid(), ); diff --git a/lib/src/channel_media_display_screen.dart b/lib/src/channel_media_display_screen.dart index ea6dabff..506a46b9 100644 --- a/lib/src/channel_media_display_screen.dart +++ b/lib/src/channel_media_display_screen.dart @@ -79,7 +79,7 @@ class _ChannelMediaDisplayScreenState extends State { ), ), ), - backgroundColor: StreamChatTheme.of(context).primaryColor, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, ), body: _buildMediaGrid(), ); diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 128acbf7..c17a5bcf 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -621,7 +621,9 @@ class _ImageFooterState extends State { icon: Transform.rotate( angle: -pi / 2, child: StreamSvgIcon.Icon_send_message( - color: StreamChatTheme.of(context).accentColor, + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, ), ), ), From fe38ba8a5614cef0f5861d6fbd159b0ae6466454 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 31 Dec 2020 21:14:11 +0530 Subject: [PATCH 17/34] fix: review fixes --- example/lib/chat_info_screen.dart | 15 +- example/lib/group_info_screen.dart | 250 +++++++++++++++++++---------- example/lib/main.dart | 2 +- lib/src/option_list_tile.dart | 8 +- 4 files changed, 178 insertions(+), 97 deletions(-) diff --git a/example/lib/chat_info_screen.dart b/example/lib/chat_info_screen.dart index 529550ca..125c510f 100644 --- a/example/lib/chat_info_screen.dart +++ b/example/lib/chat_info_screen.dart @@ -260,13 +260,13 @@ class _ChatInfoScreenState extends State { return OptionListTile( title: 'Delete Conversation', leading: StreamSvgIcon.delete( - color: Colors.red, + color: StreamChatTheme.of(context).colorTheme.accentRed, size: 24.0, ), onTap: () { _showDeleteDialog(); }, - titleColor: Colors.red, + titleColor: StreamChatTheme.of(context).colorTheme.accentRed, ); } @@ -278,7 +278,7 @@ class _ChatInfoScreenState 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; @@ -416,14 +416,14 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { children: [ StreamSvgIcon.message( size: 136.0, - color: Color(0xffdbdbdb), + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, ), SizedBox(height: 16.0), Text( 'No Shared Groups', style: TextStyle( fontSize: 14.0, - color: Color(0xff000000), + color: StreamChatTheme.of(context).colorTheme.black, ), ), SizedBox(height: 8.0), @@ -432,7 +432,10 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> { textAlign: TextAlign.center, style: TextStyle( fontSize: 14.0, - color: Color(0xff000000).withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), ), ), ], diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index 87064472..304619d6 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -70,11 +70,11 @@ class _GroupInfoScreenState extends State { var channel = StreamChannel.of(context); return Scaffold( - backgroundColor: Color(0xffdbdbdb), + backgroundColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, appBar: AppBar( elevation: 1.0, toolbarHeight: 56.0, - backgroundColor: Colors.white, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, leading: InkWell( onTap: () { Navigator.pop(context); @@ -96,7 +96,7 @@ class _GroupInfoScreenState extends State { ?.map((e) => e.user) ?.toList()), style: TextStyle( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, fontSize: 16, ), maxLines: 1, @@ -112,7 +112,10 @@ class _GroupInfoScreenState extends State { return Text( '${channel.channel.memberCount} Members, ${snapshot?.data?.members?.where((e) => e.user.online)?.length ?? 0} Online', style: TextStyle( - color: Colors.black.withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), fontSize: 12.0, ), ); @@ -140,7 +143,7 @@ class _GroupInfoScreenState extends State { _buildMembers(), Container( height: 8.0, - color: Color(0xffdbdbdb), + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, ), _buildNameTile(), _buildOptionListTiles(), @@ -213,7 +216,10 @@ class _GroupInfoScreenState extends State { _getLastSeen( snapshot.data.members[position].user), style: TextStyle( - color: Colors.black.withOpacity(0.5)), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), ), ], ), @@ -226,20 +232,25 @@ class _GroupInfoScreenState extends State { ? 'Owner' : '', style: TextStyle( - color: Colors.black.withOpacity(0.5)), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), ), ), ], ), Container( height: 1.0, - color: Color(0xffdbdbdb), + color: StreamChatTheme.of(context) + .colorTheme + .greyGainsboro, ), ], ), ), ), - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, ); }, ), @@ -262,7 +273,9 @@ class _GroupInfoScreenState extends State { padding: const EdgeInsets.symmetric( horizontal: 21.0, vertical: 12.0), child: StreamSvgIcon.down( - color: Color(0xff7a7a7a), + color: StreamChatTheme.of(context) + .colorTheme + .grey, ), ), Expanded( @@ -272,8 +285,10 @@ class _GroupInfoScreenState extends State { children: [ Text( '${snapshot.data.members.length - groupMemberListLength} more', - style: - TextStyle(color: Color(0xff7a7a7a)), + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .grey), ), ], ), @@ -283,7 +298,9 @@ class _GroupInfoScreenState extends State { ), Container( height: 1.0, - color: Color(0xffdbdbdb), + color: StreamChatTheme.of(context) + .colorTheme + .greyGainsboro, ), ], ), @@ -302,7 +319,7 @@ class _GroupInfoScreenState extends State { var channelName = channel.extraData['name'] ?? ''; return Material( - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, child: Container( height: 56.0, alignment: Alignment.center, @@ -313,7 +330,11 @@ class _GroupInfoScreenState extends State { child: Text( 'NAME', style: TextStyle( - color: Colors.black.withOpacity(0.5), fontSize: 12.0), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + fontSize: 12.0), ), ), SizedBox( @@ -323,12 +344,15 @@ class _GroupInfoScreenState extends State { child: TextField( focusNode: _focusNode, controller: _nameController, - cursorColor: Colors.black, + cursorColor: StreamChatTheme.of(context).colorTheme.black, decoration: InputDecoration.collapsed( hintText: 'Add a group name', hintStyle: TextStyle( fontWeight: FontWeight.bold, - color: Colors.black.withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), ), ), style: TextStyle( @@ -395,7 +419,7 @@ class _GroupInfoScreenState extends State { // title: 'Notifications', // leading: StreamSvgIcon.Icon_notification( // size: 24.0, - // color: Colors.black.withOpacity(0.5), + // color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), // ), // trailing: CupertinoSwitch( // value: true, @@ -407,10 +431,16 @@ class _GroupInfoScreenState extends State { stream: StreamChannel.of(context).channel.isMutedStream, builder: (context, snapshot) { return OptionListTile( + tileColor: StreamChatTheme.of(context).colorTheme.white, + separatorColor: + StreamChatTheme.of(context).colorTheme.greyGainsboro, title: 'Mute group', leading: StreamSvgIcon.mute( size: 23.0, - color: Colors.black.withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), ), trailing: snapshot.data == null ? CircularProgressIndicator() @@ -428,10 +458,13 @@ class _GroupInfoScreenState extends State { ); }), OptionListTile( + tileColor: StreamChatTheme.of(context).colorTheme.white, + separatorColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, title: 'Photos & Videos', leading: StreamSvgIcon.pictures( size: 32.0, - color: Colors.black.withOpacity(0.5), + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), ), trailing: StreamSvgIcon.right(), onTap: () { @@ -459,10 +492,13 @@ class _GroupInfoScreenState extends State { }, ), OptionListTile( + tileColor: StreamChatTheme.of(context).colorTheme.white, + separatorColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, title: 'Files', leading: StreamSvgIcon.files( size: 32.0, - color: Colors.black.withOpacity(0.5), + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), ), trailing: StreamSvgIcon.right(), onTap: () { @@ -491,10 +527,14 @@ class _GroupInfoScreenState extends State { ), if (!channel.channel.isDistinct) OptionListTile( + tileColor: StreamChatTheme.of(context).colorTheme.white, + separatorColor: + StreamChatTheme.of(context).colorTheme.greyGainsboro, title: 'Leave Group', leading: StreamSvgIcon.userRemove( size: 24.0, - color: Colors.black.withOpacity(0.5), + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), ), trailing: Container( height: 24.0, @@ -583,7 +623,9 @@ class _GroupInfoScreenState extends State { padding: const EdgeInsets.all(24), child: StreamSvgIcon.search( size: 96, - color: Colors.grey, + color: StreamChatTheme.of(context) + .colorTheme + .grey, ), ), Text( @@ -624,7 +666,7 @@ class _GroupInfoScreenState extends State { Expanded( child: TextField( controller: _searchController, - cursorColor: Colors.black, + cursorColor: StreamChatTheme.of(context).colorTheme.black, autofocus: true, decoration: InputDecoration( isDense: true, @@ -633,19 +675,25 @@ class _GroupInfoScreenState extends State { padding: const EdgeInsets.symmetric( vertical: 2.0, horizontal: 6.0), child: StreamSvgIcon.search( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), ), hintText: 'Search', border: OutlineInputBorder( borderRadius: BorderRadius.circular(32.0), - borderSide: - BorderSide(color: Colors.black.withOpacity(0.08)), + borderSide: BorderSide( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.08)), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(32.0), - borderSide: - BorderSide(color: Colors.black.withOpacity(0.08)), + borderSide: BorderSide( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.08)), ), contentPadding: EdgeInsets.zero, ), @@ -656,7 +704,10 @@ class _GroupInfoScreenState extends State { ), IconButton( icon: StreamSvgIcon.close_small( - color: Colors.black.withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), ), onPressed: () { Navigator.pop(context); @@ -679,6 +730,7 @@ class _GroupInfoScreenState extends State { return StreamChannel( channel: channel, child: Material( + color: StreamChatTheme.of(context).colorTheme.white, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -713,37 +765,41 @@ class _GroupInfoScreenState extends State { ), if (StreamChat.of(context).user.id != user.id) _buildModalListTile( - StreamSvgIcon.user( - color: Color(0xff7a7a7a), - size: 24.0, - ), - 'View info', () async { - var client = StreamChat.of(context).client; + context, + StreamSvgIcon.user( + color: StreamChatTheme.of(context).colorTheme.grey, + size: 24.0, + ), + 'View info', + () async { + var client = StreamChat.of(context).client; - var c = client.channel('messaging', extraData: { - 'members': [ - user.id, - StreamChat.of(context).user.id, - ], - }); + var c = client.channel('messaging', extraData: { + 'members': [ + user.id, + StreamChat.of(context).user.id, + ], + }); - await c.watch(); + await c.watch(); - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: c, - child: ChatInfoScreen( - user: user, + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: c, + child: ChatInfoScreen( + user: user, + ), ), ), - ), - ); - }), + ); + }, + ), _buildModalListTile( + context, StreamSvgIcon.message( - color: Color(0xff7a7a7a), + color: StreamChatTheme.of(context).colorTheme.grey, size: 24.0, ), 'Message', @@ -774,8 +830,9 @@ class _GroupInfoScreenState extends State { StreamChat.of(context).user.id != user.id && isUserAdmin) _buildModalListTile( + context, StreamSvgIcon.Icon_user_settings( - color: Color(0xff7a7a7a), + color: StreamChatTheme.of(context).colorTheme.grey, size: 24.0, ), 'Make Owner', () { @@ -785,8 +842,9 @@ class _GroupInfoScreenState extends State { StreamChat.of(context).user.id != user.id && isUserAdmin) _buildModalListTile( + context, StreamSvgIcon.userRemove( - color: Colors.red, + color: StreamChatTheme.of(context).colorTheme.accentRed, size: 24.0, ), 'Remove From Group', () async { @@ -795,10 +853,11 @@ class _GroupInfoScreenState extends State { _memberQueryFuture = channel.queryMembers( filter: {}, ); - }, color: Colors.red), + }, color: StreamChatTheme.of(context).colorTheme.accentRed), _buildModalListTile( + context, StreamSvgIcon.close_small( - color: Color(0xff7a7a7a), + color: StreamChatTheme.of(context).colorTheme.grey, size: 24.0, ), 'Cancel', () { @@ -829,12 +888,20 @@ class _GroupInfoScreenState extends State { if (otherMember.online) { alternativeWidget = Text( 'Online', - style: TextStyle(color: Colors.black.withOpacity(0.5)), + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), ); } else { alternativeWidget = Text( 'Last seen ${Jiffy(otherMember.lastActive).fromNow()}', - style: TextStyle(color: Colors.black.withOpacity(0.5)), + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), ); } } @@ -856,43 +923,50 @@ class _GroupInfoScreenState extends State { color: Color(0xff20E070), ), ), - color: Colors.white, + color: StreamChatTheme.of(context).colorTheme.white, ), alternativeWidget, ], ); } - Widget _buildModalListTile(Widget leading, String title, VoidCallback onTap, - {Color color = Colors.black}) { + Widget _buildModalListTile( + BuildContext context, Widget leading, String title, VoidCallback onTap, + {Color color}) { + color ??= StreamChatTheme.of(context).colorTheme.black; + return Material( - child: InkWell( - onTap: onTap, - child: Column( - children: [ - Container( - height: 1.0, - color: Color(0xffdbdbdb), - ), - Container( - height: 64.0, - child: Row( - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: leading, - ), - Expanded( - child: Text( - title, - style: TextStyle(color: color, fontWeight: FontWeight.bold), - )) - ], + color: StreamChatTheme.of(context).colorTheme.white, + child: InkWell( + onTap: onTap, + child: Column( + children: [ + Container( + height: 1.0, + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, ), - ), - ], + Container( + height: 64.0, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: leading, + ), + Expanded( + child: Text( + title, + style: + TextStyle(color: color, fontWeight: FontWeight.bold), + ), + ) + ], + ), + ), + ], + ), ), - )); + ); } String _getChannelName(double width, {List members}) { diff --git a/example/lib/main.dart b/example/lib/main.dart index 57f50305..c2bb1a74 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -63,7 +63,7 @@ class MyApp extends StatelessWidget { debugShowCheckedModeBanner: false, theme: ThemeData.light(), darkTheme: ThemeData.dark(), - themeMode: ThemeMode.system, + themeMode: ThemeMode.dark, onGenerateRoute: AppRoutes.generateRoute, initialRoute: client.state.user == null ? Routes.CHOOSE_USER : Routes.HOME, diff --git a/lib/src/option_list_tile.dart b/lib/src/option_list_tile.dart index 382db945..c8aff92e 100644 --- a/lib/src/option_list_tile.dart +++ b/lib/src/option_list_tile.dart @@ -7,6 +7,8 @@ class OptionListTile extends StatelessWidget { final Widget trailing; final VoidCallback onTap; final Color titleColor; + final Color tileColor; + final Color separatorColor; OptionListTile({ this.title, @@ -14,6 +16,8 @@ class OptionListTile extends StatelessWidget { this.trailing, this.onTap, this.titleColor, + this.tileColor, + this.separatorColor, }); @override @@ -21,11 +25,11 @@ class OptionListTile extends StatelessWidget { return Column( children: [ Container( - color: Color(0xffe6e6e6), + color: separatorColor ?? Color(0xffe6e6e6), height: 2.0, ), Material( - color: Colors.white, + color: tileColor ?? Colors.white, child: Container( height: 56.0, child: InkWell( From 0ff6b437dd1cfb5352692aff49e64f2be5e622e5 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 31 Dec 2020 21:23:00 +0530 Subject: [PATCH 18/34] feat: Converted completely to dark theme --- example/lib/group_info_screen.dart | 4 +++- lib/src/channel_file_display_screen.dart | 17 +++++++++++------ lib/src/channel_media_display_screen.dart | 18 ++++++++++++------ 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index 304619d6..33102de9 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -81,7 +81,9 @@ class _GroupInfoScreenState extends State { }, child: Padding( padding: const EdgeInsets.only(left: 12.0), - child: StreamSvgIcon.left(), + child: StreamSvgIcon.left( + color: StreamChatTheme.of(context).colorTheme.black, + ), ), ), leadingWidth: 36.0, diff --git a/lib/src/channel_file_display_screen.dart b/lib/src/channel_file_display_screen.dart index 79645f73..6feac224 100644 --- a/lib/src/channel_file_display_screen.dart +++ b/lib/src/channel_file_display_screen.dart @@ -53,14 +53,16 @@ class _ChannelFileDisplayScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.white, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, appBar: AppBar( brightness: Theme.of(context).brightness, elevation: 1, centerTitle: true, title: Text( 'Files', - style: TextStyle(color: Colors.black, fontSize: 16.0), + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.black, + fontSize: 16.0), ), leading: Center( child: InkWell( @@ -69,7 +71,7 @@ class _ChannelFileDisplayScreenState extends State { }, child: Container( child: StreamSvgIcon.left( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, size: 24.0, ), width: 24.0, @@ -104,14 +106,14 @@ class _ChannelFileDisplayScreenState extends State { children: [ StreamSvgIcon.files( size: 136.0, - color: Color(0xffdbdbdb), + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, ), SizedBox(height: 16.0), Text( 'No Files', style: TextStyle( fontSize: 14.0, - color: Color(0xff000000), + color: StreamChatTheme.of(context).colorTheme.black, ), ), SizedBox(height: 8.0), @@ -120,7 +122,10 @@ class _ChannelFileDisplayScreenState extends State { textAlign: TextAlign.center, style: TextStyle( fontSize: 14.0, - color: Color(0xff000000).withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), ), ), ], diff --git a/lib/src/channel_media_display_screen.dart b/lib/src/channel_media_display_screen.dart index 506a46b9..09d2a8a8 100644 --- a/lib/src/channel_media_display_screen.dart +++ b/lib/src/channel_media_display_screen.dart @@ -55,14 +55,17 @@ class _ChannelMediaDisplayScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Colors.white, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, appBar: AppBar( brightness: Theme.of(context).brightness, elevation: 1, centerTitle: true, title: Text( 'Photos & Videos', - style: TextStyle(color: Colors.black, fontSize: 16.0), + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.black, + fontSize: 16.0, + ), ), leading: Center( child: InkWell( @@ -71,7 +74,7 @@ class _ChannelMediaDisplayScreenState extends State { }, child: Container( child: StreamSvgIcon.left( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, size: 24.0, ), width: 24.0, @@ -106,14 +109,14 @@ class _ChannelMediaDisplayScreenState extends State { children: [ StreamSvgIcon.pictures( size: 136.0, - color: Color(0xffdbdbdb), + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, ), SizedBox(height: 16.0), Text( 'No Media', style: TextStyle( fontSize: 14.0, - color: Color(0xff000000), + color: StreamChatTheme.of(context).colorTheme.black, ), ), SizedBox(height: 8.0), @@ -122,7 +125,10 @@ class _ChannelMediaDisplayScreenState extends State { textAlign: TextAlign.center, style: TextStyle( fontSize: 14.0, - color: Color(0xff000000).withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), ), ), ], From dc643c0f71d5d76472a12050ab75f4e67f8c58f2 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 1 Jan 2021 14:04:33 +0530 Subject: [PATCH 19/34] feat: Fixed conflicts, added group info screen --- example/lib/main.dart | 28 ++++++++++++++++++++++++++++ lib/src/channel_list_view.dart | 23 ++++++----------------- lib/src/option_list_tile.dart | 12 +++--------- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index c2bb1a74..c958efb8 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -398,6 +398,34 @@ class _ChannelListPageState extends State { limit: 20, ), channelWidget: ChannelPage(), + onViewInfoTap: (channel) { + if (channel.memberCount == 2 && + channel.isDistinct) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: ChatInfoScreen( + user: channel.state.members.first.user, + ), + ), + ), + ); + } else { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: GroupInfoScreen(), + ), + ), + ); + } + + // TODO: Add group screen + }, ), ), ), diff --git a/lib/src/channel_list_view.dart b/lib/src/channel_list_view.dart index 831696f7..6c4e967e 100644 --- a/lib/src/channel_list_view.dart +++ b/lib/src/channel_list_view.dart @@ -22,6 +22,8 @@ typedef ChannelTapCallback = void Function(Channel, Widget); /// Builder used to create a custom [ChannelPreview] from a [Channel] typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel); +typedef ViewInfoCallback = void Function(Channel); + /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view_paint.png) /// @@ -77,6 +79,7 @@ class ChannelListView extends StatefulWidget { this.crossAxisCount = 1, this.padding, this.selectedChannels = const [], + this.onViewInfoTap, }) : super(key: key); /// The builder that will be used in case of error @@ -145,6 +148,8 @@ class ChannelListView extends StatefulWidget { final List selectedChannels; + final ViewInfoCallback onViewInfoTap; + @override _ChannelListViewState createState() => _ChannelListViewState(); } @@ -579,23 +584,7 @@ class _ChannelListViewState extends State return StreamChannel( 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 + widget.onViewInfoTap(channel); }, ), channel: channel, diff --git a/lib/src/option_list_tile.dart b/lib/src/option_list_tile.dart index 5a9641f7..a18c2ab1 100644 --- a/lib/src/option_list_tile.dart +++ b/lib/src/option_list_tile.dart @@ -26,18 +26,12 @@ class OptionListTile extends StatelessWidget { return Column( children: [ Container( - color: separatorColor ?? Color(0xffe6e6e6), + color: separatorColor ?? + StreamChatTheme.of(context).colorTheme.greyGainsboro, height: 2.0, ), Material( - color: tileColor ?? Colors.white, - child: Container( - height: 56.0, - color: StreamChatTheme.of(context).colorTheme.greyGainsboro, - height: 1.0, - ), - Material( - color: StreamChatTheme.of(context).colorTheme.white, + color: tileColor ?? StreamChatTheme.of(context).colorTheme.white, child: Container( height: 63.0, child: InkWell( From b11543ea7f8d05491edaf18d59c71cf1107ed763 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 1 Jan 2021 15:11:22 +0530 Subject: [PATCH 20/34] rfac: Changed over group info screen to use streams --- example/lib/group_info_screen.dart | 489 ++++++++++++++--------------- example/lib/main.dart | 2 - 2 files changed, 237 insertions(+), 254 deletions(-) diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index 33102de9..fd866578 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -16,12 +16,9 @@ class GroupInfoScreen extends StatefulWidget { } class _GroupInfoScreenState extends State { - Future _memberQueryFuture; TextEditingController _nameController; - bool _userSearchMode = false; TextEditingController _searchController; - bool _loading = false; String _userNameQuery = ''; Timer _debounce; @@ -29,9 +26,6 @@ class _GroupInfoScreenState extends State { final FocusNode _focusNode = FocusNode(); - bool namedChanged = false; - String changedName = ''; - int groupMemberListLength; void _userNameListener() { @@ -52,10 +46,6 @@ class _GroupInfoScreenState extends State { void initState() { super.initState(); var channel = StreamChannel.of(context); - - _memberQueryFuture = channel.channel.queryMembers( - filter: {}, - ); _nameController = TextEditingController.fromValue( TextEditingValue(text: channel.channel.extraData['name'] ?? '')); _searchController = TextEditingController()..addListener(_userNameListener); @@ -69,50 +59,59 @@ class _GroupInfoScreenState extends State { Widget build(BuildContext context) { var channel = StreamChannel.of(context); - return Scaffold( - backgroundColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, - appBar: AppBar( - elevation: 1.0, - toolbarHeight: 56.0, - backgroundColor: StreamChatTheme.of(context).colorTheme.white, - leading: InkWell( - onTap: () { - Navigator.pop(context); - }, - child: Padding( - padding: const EdgeInsets.only(left: 12.0), - child: StreamSvgIcon.left( - color: StreamChatTheme.of(context).colorTheme.black, - ), - ), - ), - leadingWidth: 36.0, - title: Column( - children: [ - FutureBuilder( - future: _memberQueryFuture, - builder: (context, snapshot) { - return Text( - _getChannelName(MediaQuery.of(context).size.width, - members: snapshot.data?.members - ?.map((e) => e.user) - ?.toList()), - style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.black, - fontSize: 16, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ); - }), - SizedBox( - height: 3.0, - ), - FutureBuilder( - future: _memberQueryFuture, - builder: (context, snapshot) { - return Text( - '${channel.channel.memberCount} Members, ${snapshot?.data?.members?.where((e) => e.user.online)?.length ?? 0} Online', + return StreamBuilder>( + stream: channel.channel.state.membersStream, + builder: (context, snapshot) { + if (!snapshot.hasData) { + return Container( + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, + child: Center(child: CircularProgressIndicator()), + ); + } + + return Scaffold( + backgroundColor: + StreamChatTheme.of(context).colorTheme.greyGainsboro, + appBar: AppBar( + elevation: 1.0, + toolbarHeight: 56.0, + backgroundColor: StreamChatTheme.of(context).colorTheme.white, + leading: InkWell( + onTap: () { + Navigator.pop(context); + }, + child: Padding( + padding: const EdgeInsets.only(left: 12.0), + child: StreamSvgIcon.left( + color: StreamChatTheme.of(context).colorTheme.black, + ), + ), + ), + leadingWidth: 36.0, + title: Column( + children: [ + StreamBuilder( + stream: channel.channelStateStream, + builder: (context, state) { + return Text( + _getChannelName( + 2 * MediaQuery.of(context).size.width / 3, + members: snapshot.data, + extraData: state.data.channel.extraData, + maxFontSize: 16.0), + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.black, + fontSize: 16, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); + }), + SizedBox( + height: 3.0, + ), + Text( + '${channel.channel.memberCount} Members, ${snapshot?.data?.where((e) => e.user.online)?.length ?? 0} Online', style: TextStyle( color: StreamChatTheme.of(context) .colorTheme @@ -120,199 +119,178 @@ class _GroupInfoScreenState extends State { .withOpacity(0.5), fontSize: 12.0, ), - ); - }), - ], - ), - centerTitle: true, - actions: [ - StreamNeumorphicButton( - child: InkWell( - onTap: () { - _buildAddUserModal(context); - }, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: StreamSvgIcon.userAdd( - color: StreamChatTheme.of(context).colorTheme.accentBlue), + ), + ], ), - ), - ), - ], - ), - body: ListView( - children: [ - _buildMembers(), - Container( - height: 8.0, - color: StreamChatTheme.of(context).colorTheme.greyGainsboro, - ), - _buildNameTile(), - _buildOptionListTiles(), - ], - ), - ); - } - - Widget _buildMembers() { - var channel = StreamChannel.of(context); - - return FutureBuilder( - builder: (context, snapshot) { - if (snapshot.data == null) { - return Center( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: CircularProgressIndicator(), - ), - ); - } - - groupMemberListLength ??= - snapshot.data.members.length > 6 ? 6 : snapshot.data.members.length; - - return Column( - children: [ - ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: groupMemberListLength, - itemBuilder: (context, position) { - return Material( + centerTitle: true, + actions: [ + StreamNeumorphicButton( child: InkWell( onTap: () { - var userMember = snapshot.data.members.firstWhere( - (e) => e.user.id == StreamChat.of(context).user.id); - _showUserInfoModal(snapshot.data.members[position].user, - userMember.role == 'owner'); + _buildAddUserModal(context); }, - child: Container( - height: 65.0, - child: Column( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: StreamSvgIcon.userAdd( + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue), + ), + ), + ), + ], + ), + body: ListView( + children: [ + _buildMembers(snapshot.data), + Container( + height: 8.0, + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, + ), + _buildNameTile(), + _buildOptionListTiles(), + ], + ), + ); + }); + } + + Widget _buildMembers(List members) { + groupMemberListLength ??= members.length > 6 ? 6 : members.length; + + return Column( + children: [ + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: groupMemberListLength, + itemBuilder: (context, position) { + return Material( + child: InkWell( + onTap: () { + var userMember = members.firstWhere( + (e) => e.user.id == StreamChat.of(context).user.id); + _showUserInfoModal( + members[position].user, userMember.role == 'owner'); + }, + child: Container( + height: 65.0, + child: Column( + children: [ + Row( children: [ - Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 12.0), - child: UserAvatar( - user: snapshot.data.members[position].user, - constraints: BoxConstraints( - maxHeight: 40.0, maxWidth: 40.0), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 12.0), + child: UserAvatar( + user: members[position].user, + constraints: BoxConstraints( + maxHeight: 40.0, maxWidth: 40.0), + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + members[position].user.name, + style: TextStyle(fontWeight: FontWeight.bold), ), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - snapshot.data.members[position].user.name, - style: TextStyle( - fontWeight: FontWeight.bold), - ), - SizedBox( - height: 1.0, - ), - Text( - _getLastSeen( - snapshot.data.members[position].user), - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5)), - ), - ], + SizedBox( + height: 1.0, ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - snapshot.data.members[position].role == - 'owner' - ? 'Owner' - : '', + Text( + _getLastSeen(members[position].user), style: TextStyle( color: StreamChatTheme.of(context) .colorTheme .black .withOpacity(0.5)), ), - ), - ], + ], + ), ), - Container( - height: 1.0, - color: StreamChatTheme.of(context) - .colorTheme - .greyGainsboro, + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + members[position].role == 'owner' ? 'Owner' : '', + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5)), + ), + ), + ], + ), + Container( + height: 1.0, + color: StreamChatTheme.of(context) + .colorTheme + .greyGainsboro, + ), + ], + ), + ), + ), + color: StreamChatTheme.of(context).colorTheme.whiteSnow, + ); + }, + ), + if (groupMemberListLength != members.length) + InkWell( + onTap: () { + setState(() { + groupMemberListLength = members.length; + }); + }, + child: Material( + color: StreamChatTheme.of(context).colorTheme.whiteSnow, + child: Container( + height: 65.0, + child: Column( + children: [ + Expanded( + child: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 21.0, vertical: 12.0), + child: StreamSvgIcon.down( + color: + StreamChatTheme.of(context).colorTheme.white, + ), + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + '${members.length - groupMemberListLength} more', + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .grey), + ), + ], + ), ), ], ), ), - ), - color: StreamChatTheme.of(context).colorTheme.white, - ); - }, - ), - if (groupMemberListLength != snapshot.data.members.length) - InkWell( - onTap: () { - setState(() { - groupMemberListLength = snapshot.data.members.length; - }); - }, - child: Material( - child: Container( - height: 65.0, - child: Column( - children: [ - Expanded( - child: Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 21.0, vertical: 12.0), - child: StreamSvgIcon.down( - color: StreamChatTheme.of(context) - .colorTheme - .grey, - ), - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - '${snapshot.data.members.length - groupMemberListLength} more', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .grey), - ), - ], - ), - ), - ], - ), - ), - Container( - height: 1.0, - color: StreamChatTheme.of(context) - .colorTheme - .greyGainsboro, - ), - ], + Container( + height: 1.0, + color: + StreamChatTheme.of(context).colorTheme.greyGainsboro, ), - ), + ], ), ), - ], - ); - }, - future: _memberQueryFuture, + ), + ), + ], ); } @@ -321,7 +299,7 @@ class _GroupInfoScreenState extends State { var channelName = channel.extraData['name'] ?? ''; return Material( - color: StreamChatTheme.of(context).colorTheme.white, + color: StreamChatTheme.of(context).colorTheme.whiteSnow, child: Container( height: 56.0, alignment: Alignment.center, @@ -389,12 +367,6 @@ class _GroupInfoScreenState extends State { onTap: () { StreamChannel.of(context).channel.update({ 'name': _nameController.text.trim(), - }).then((value) { - setState(() { - _focusNode.unfocus(); - namedChanged = true; - changedName = _nameController.text.trim(); - }); }).catchError((err) { setState(() { _nameController.text = channelName; @@ -433,7 +405,7 @@ class _GroupInfoScreenState extends State { stream: StreamChannel.of(context).channel.isMutedStream, builder: (context, snapshot) { return OptionListTile( - tileColor: StreamChatTheme.of(context).colorTheme.white, + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, separatorColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, title: 'Mute group', @@ -460,7 +432,7 @@ class _GroupInfoScreenState extends State { ); }), OptionListTile( - tileColor: StreamChatTheme.of(context).colorTheme.white, + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, separatorColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, title: 'Photos & Videos', leading: StreamSvgIcon.pictures( @@ -494,7 +466,7 @@ class _GroupInfoScreenState extends State { }, ), OptionListTile( - tileColor: StreamChatTheme.of(context).colorTheme.white, + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, separatorColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, title: 'Files', leading: StreamSvgIcon.files( @@ -529,7 +501,7 @@ class _GroupInfoScreenState extends State { ), if (!channel.channel.isDistinct) OptionListTile( - tileColor: StreamChatTheme.of(context).colorTheme.white, + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, separatorColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, title: 'Leave Group', @@ -582,9 +554,6 @@ class _GroupInfoScreenState extends State { await channel.addMembers([user.id]); Navigator.pop(context); - _memberQueryFuture = channel.queryMembers( - filter: {}, - ); setState(() {}); }, crossAxisCount: 4, @@ -852,9 +821,6 @@ class _GroupInfoScreenState extends State { 'Remove From Group', () async { await channel.removeMembers([user.id]); Navigator.pop(context); - _memberQueryFuture = channel.queryMembers( - filter: {}, - ); }, color: StreamChatTheme.of(context).colorTheme.accentRed), _buildModalListTile( context, @@ -971,17 +937,36 @@ class _GroupInfoScreenState extends State { ); } - String _getChannelName(double width, {List members}) { - if (namedChanged) { - return changedName; - } + String _getChannelName(double width, + {List members, Map extraData, double maxFontSize}) { + String title; + var client = StreamChat.of(context); + if (extraData['name'] == null) { + final otherMembers = + members.where((member) => member.user.id != client.user.id); + if (otherMembers.isNotEmpty) { + final maxWidth = width; + final maxChars = maxWidth / maxFontSize; + var currentChars = 0; + final currentMembers = []; + otherMembers.forEach((element) { + final newLength = currentChars + element.user.name.length; + if (newLength < maxChars) { + currentChars = newLength; + currentMembers.add(element); + } + }); - if (StreamChannel.of(context).channel.extraData['name'] == null && - members != null) { - return '${members[0].name} & ${members.length - 1} others'; + 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 StreamChannel.of(context).channel.extraData['name'] ?? ''; + return title; } String _getLastSeen(User user) { diff --git a/example/lib/main.dart b/example/lib/main.dart index c958efb8..69300312 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -423,8 +423,6 @@ class _ChannelListPageState extends State { ), ); } - - // TODO: Add group screen }, ), ), From e837b9b0e567f21e63b0b0de945551b14e607d31 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 1 Jan 2021 18:35:25 +0530 Subject: [PATCH 21/34] feat: Fixed issues with alignment in group info screen and dark theme --- example/lib/chat_info_screen.dart | 78 ++++++++++----- example/lib/group_info_screen.dart | 147 ++++++++++++++++------------- lib/src/channel_bottom_sheet.dart | 28 ++++-- lib/src/image_footer.dart | 42 +++++---- lib/src/option_list_tile.dart | 26 ++--- 5 files changed, 198 insertions(+), 123 deletions(-) diff --git a/example/lib/chat_info_screen.dart b/example/lib/chat_info_screen.dart index 125c510f..d992e73b 100644 --- a/example/lib/chat_info_screen.dart +++ b/example/lib/chat_info_screen.dart @@ -76,6 +76,7 @@ class _ChatInfoScreenState extends State { SizedBox(height: 15.0), OptionListTile( title: '@${widget.user.id}', + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, trailing: Padding( padding: const EdgeInsets.symmetric(horizontal: 8.0), child: Text( @@ -131,13 +132,18 @@ class _ChatInfoScreenState extends State { stream: StreamChannel.of(context).channel.isMutedStream, builder: (context, snapshot) { return OptionListTile( + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, title: 'Mute user', - leading: StreamSvgIcon.mute( - size: 23.0, - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: StreamSvgIcon.mute( + size: 24.0, + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + ), ), trailing: snapshot.data == null ? CircularProgressIndicator() @@ -174,10 +180,15 @@ class _ChatInfoScreenState extends State { // ), OptionListTile( title: 'Photos & Videos', - leading: StreamSvgIcon.pictures( - size: 32.0, - color: - StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: StreamSvgIcon.pictures( + size: 32.0, + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + ), ), trailing: StreamSvgIcon.right(), onTap: () { @@ -206,12 +217,19 @@ class _ChatInfoScreenState extends State { ), OptionListTile( title: 'Files', - leading: StreamSvgIcon.files( - size: 32.0, - color: - StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: StreamSvgIcon.files( + size: 32.0, + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + ), + ), + trailing: StreamSvgIcon.right( + color: StreamChatTheme.of(context).colorTheme.grey, ), - trailing: StreamSvgIcon.right(), onTap: () { final channel = StreamChannel.of(context).channel; @@ -238,12 +256,19 @@ class _ChatInfoScreenState extends State { ), OptionListTile( title: 'Shared groups', - leading: StreamSvgIcon.Icon_group( - size: 24.0, - color: - StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: StreamSvgIcon.Icon_group( + size: 24.0, + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + ), + ), + trailing: StreamSvgIcon.right( + color: StreamChatTheme.of(context).colorTheme.grey, ), - trailing: StreamSvgIcon.right(), onTap: () { Navigator.push( context, @@ -259,9 +284,16 @@ class _ChatInfoScreenState extends State { Widget _buildDeleteListTile() { return OptionListTile( title: 'Delete Conversation', - leading: StreamSvgIcon.delete( - color: StreamChatTheme.of(context).colorTheme.accentRed, - size: 24.0, + tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + titleTextStyle: StreamChatTheme.of(context).textTheme.body.copyWith( + color: StreamChatTheme.of(context).colorTheme.accentRed, + ), + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: StreamSvgIcon.delete( + color: StreamChatTheme.of(context).colorTheme.accentRed, + size: 24.0, + ), ), onTap: () { _showDeleteDialog(); diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index fd866578..c044a23d 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -70,8 +70,7 @@ class _GroupInfoScreenState extends State { } return Scaffold( - backgroundColor: - StreamChatTheme.of(context).colorTheme.greyGainsboro, + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, appBar: AppBar( elevation: 1.0, toolbarHeight: 56.0, @@ -259,7 +258,7 @@ class _GroupInfoScreenState extends State { horizontal: 21.0, vertical: 12.0), child: StreamSvgIcon.down( color: - StreamChatTheme.of(context).colorTheme.white, + StreamChatTheme.of(context).colorTheme.grey, ), ), Expanded( @@ -306,19 +305,18 @@ class _GroupInfoScreenState extends State { child: Row( children: [ Padding( - padding: const EdgeInsets.all(8.0), + padding: const EdgeInsets.all(7.0), child: Text( 'NAME', - style: TextStyle( + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( color: StreamChatTheme.of(context) .colorTheme .black - .withOpacity(0.5), - fontSize: 12.0), + .withOpacity(0.5)), ), ), SizedBox( - width: 9.0, + width: 7.0, ), Expanded( child: TextField( @@ -326,15 +324,15 @@ class _GroupInfoScreenState extends State { controller: _nameController, cursorColor: StreamChatTheme.of(context).colorTheme.black, decoration: InputDecoration.collapsed( - hintText: 'Add a group name', - hintStyle: TextStyle( - fontWeight: FontWeight.bold, - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), - ), - ), + hintText: 'Add a group name', + hintStyle: StreamChatTheme.of(context) + .textTheme + .bodyBold + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5))), style: TextStyle( fontWeight: FontWeight.bold, height: 0.82, @@ -409,12 +407,16 @@ class _GroupInfoScreenState extends State { separatorColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, title: 'Mute group', - leading: StreamSvgIcon.mute( - size: 23.0, - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: StreamSvgIcon.mute( + size: 24.0, + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + ), ), trailing: snapshot.data == null ? CircularProgressIndicator() @@ -435,12 +437,18 @@ class _GroupInfoScreenState extends State { tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, separatorColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, title: 'Photos & Videos', - leading: StreamSvgIcon.pictures( - size: 32.0, - color: - StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: StreamSvgIcon.pictures( + size: 32.0, + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + ), + ), + trailing: StreamSvgIcon.right( + color: StreamChatTheme.of(context).colorTheme.grey, ), - trailing: StreamSvgIcon.right(), onTap: () { var channel = StreamChannel.of(context).channel; @@ -469,12 +477,18 @@ class _GroupInfoScreenState extends State { tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, separatorColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, title: 'Files', - leading: StreamSvgIcon.files( - size: 32.0, - color: - StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: StreamSvgIcon.files( + size: 32.0, + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + ), + ), + trailing: StreamSvgIcon.right( + color: StreamChatTheme.of(context).colorTheme.grey, ), - trailing: StreamSvgIcon.right(), onTap: () { var channel = StreamChannel.of(context).channel; @@ -505,10 +519,16 @@ class _GroupInfoScreenState extends State { separatorColor: StreamChatTheme.of(context).colorTheme.greyGainsboro, title: 'Leave Group', - leading: StreamSvgIcon.userRemove( - size: 24.0, - color: - StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), + titleTextStyle: StreamChatTheme.of(context).textTheme.body, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: StreamSvgIcon.userRemove( + size: 24.0, + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), + ), ), trailing: Container( height: 24.0, @@ -767,36 +787,37 @@ class _GroupInfoScreenState extends State { ); }, ), - _buildModalListTile( - context, - StreamSvgIcon.message( - color: StreamChatTheme.of(context).colorTheme.grey, - size: 24.0, - ), - 'Message', - () async { - var client = StreamChat.of(context).client; + if (StreamChat.of(context).user.id != user.id) + _buildModalListTile( + context, + StreamSvgIcon.message( + color: StreamChatTheme.of(context).colorTheme.grey, + size: 24.0, + ), + 'Message', + () async { + var client = StreamChat.of(context).client; - var c = client.channel('messaging', extraData: { - 'members': [ - user.id, - StreamChat.of(context).user.id, - ], - }); + var c = client.channel('messaging', extraData: { + 'members': [ + user.id, + StreamChat.of(context).user.id, + ], + }); - await c.watch(); + await c.watch(); - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: c, - child: ChannelPage(), + await Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: c, + child: ChannelPage(), + ), ), - ), - ); - }, - ), + ); + }, + ), if (!channel.isDistinct && StreamChat.of(context).user.id != user.id && isUserAdmin) diff --git a/lib/src/channel_bottom_sheet.dart b/lib/src/channel_bottom_sheet.dart index d2c7eb04..3e9f6ec3 100644 --- a/lib/src/channel_bottom_sheet.dart +++ b/lib/src/channel_bottom_sheet.dart @@ -129,16 +129,22 @@ class _ChannelBottomSheetState extends State { height: 24.0, ), OptionListTile( - leading: StreamSvgIcon.user( - color: StreamChatTheme.of(context).colorTheme.grey, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: StreamSvgIcon.user( + color: StreamChatTheme.of(context).colorTheme.grey, + ), ), title: 'View Info', onTap: widget.onViewInfoTap, ), if (!channel.isDistinct) OptionListTile( - leading: StreamSvgIcon.userRemove( - color: StreamChatTheme.of(context).colorTheme.grey, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: StreamSvgIcon.userRemove( + color: StreamChatTheme.of(context).colorTheme.grey, + ), ), title: 'Leave Group', onTap: () async { @@ -147,8 +153,11 @@ class _ChannelBottomSheetState extends State { ), if (isOwner) OptionListTile( - leading: StreamSvgIcon.delete( - color: StreamChatTheme.of(context).colorTheme.accentRed, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: StreamSvgIcon.delete( + color: StreamChatTheme.of(context).colorTheme.accentRed, + ), ), title: 'Delete Conversation', titleColor: StreamChatTheme.of(context).colorTheme.accentRed, @@ -157,8 +166,11 @@ class _ChannelBottomSheetState extends State { }, ), OptionListTile( - leading: StreamSvgIcon.close_small( - color: StreamChatTheme.of(context).colorTheme.grey, + leading: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: StreamSvgIcon.close_small( + color: StreamChatTheme.of(context).colorTheme.grey, + ), ), title: 'Cancel', onTap: () { diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index c17a5bcf..11e62302 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -112,13 +112,14 @@ class _ImageFooterState extends State { context: context, removeTop: true, child: BottomAppBar( + color: StreamChatTheme.of(context).colorTheme.white, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: StreamSvgIcon.icon_SHARE( size: 24.0, - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), onPressed: () { _buildShareModal(context); @@ -134,11 +135,8 @@ class _ImageFooterState extends State { children: [ Text( '${widget.currentPage + 1} of ${widget.totalPages}', - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w700, - color: Colors.black, - ), + style: + StreamChatTheme.of(context).textTheme.headlineBold, ), ], ), @@ -146,7 +144,7 @@ class _ImageFooterState extends State { ), IconButton( icon: StreamSvgIcon.Icon_grid( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), onPressed: () { _buildPhotosModal(context); @@ -337,7 +335,10 @@ class _ImageFooterState extends State { padding: const EdgeInsets.all(24), child: StreamSvgIcon.search( size: 96, - color: Colors.grey, + color: + StreamChatTheme.of(context) + .colorTheme + .grey, ), ), Text( @@ -436,32 +437,41 @@ class _ImageFooterState extends State { ), child: TextField( controller: _searchController, - cursorColor: Colors.black, + cursorColor: StreamChatTheme.of(context).colorTheme.black, autofocus: true, decoration: InputDecoration( prefixIconConstraints: BoxConstraints.tight(Size(40, 24)), prefixIcon: Padding( padding: const EdgeInsets.only(left: 8, right: 8), child: StreamSvgIcon.search( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, size: 24, ), ), hintText: 'Search', hintStyle: TextStyle( - color: Colors.black.withOpacity(0.5), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.5), fontSize: 14, ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( - color: Colors.black.withOpacity(0.08), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.08), ), borderRadius: BorderRadius.circular(24), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(24.0), borderSide: BorderSide( - color: Colors.black.withOpacity(0.08), + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(0.08), ), ), contentPadding: EdgeInsets.zero, @@ -500,7 +510,7 @@ class _ImageFooterState extends State { padding: const EdgeInsets.symmetric(horizontal: 6.0), child: IconButton( icon: StreamSvgIcon.search( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), iconSize: 24.0, onPressed: () { @@ -517,7 +527,7 @@ class _ImageFooterState extends State { style: TextStyle( fontSize: 16.0, fontWeight: FontWeight.w700, - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), ), ), @@ -525,7 +535,7 @@ class _ImageFooterState extends State { padding: const EdgeInsets.symmetric(horizontal: 6.0), child: IconButton( icon: StreamSvgIcon.share_arrow( - color: Colors.black, + color: StreamChatTheme.of(context).colorTheme.black, ), onPressed: () async { final attachment = widget.mediaAttachments[widget.currentPage]; diff --git a/lib/src/option_list_tile.dart b/lib/src/option_list_tile.dart index a18c2ab1..eb6e0ab7 100644 --- a/lib/src/option_list_tile.dart +++ b/lib/src/option_list_tile.dart @@ -4,12 +4,13 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; class OptionListTile extends StatelessWidget { final String title; - final StreamSvgIcon leading; + final Widget leading; final Widget trailing; final VoidCallback onTap; final Color titleColor; final Color tileColor; final Color separatorColor; + final TextStyle titleTextStyle; OptionListTile({ this.title, @@ -19,6 +20,7 @@ class OptionListTile extends StatelessWidget { this.titleColor, this.tileColor, this.separatorColor, + this.titleTextStyle, }); @override @@ -38,10 +40,7 @@ class OptionListTile extends StatelessWidget { onTap: onTap, child: Row( children: [ - if (leading != null) - Expanded( - child: Center(child: leading), - ), + if (leading != null) Center(child: leading), if (leading == null) SizedBox( width: 16.0, @@ -50,14 +49,15 @@ class OptionListTile extends StatelessWidget { flex: 4, child: Text( title, - style: titleColor == null - ? StreamChatTheme.of(context).textTheme.bodyBold - : StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: titleColor, - ), + style: titleTextStyle ?? + (titleColor == null + ? StreamChatTheme.of(context).textTheme.bodyBold + : StreamChatTheme.of(context) + .textTheme + .bodyBold + .copyWith( + color: titleColor, + )), ), ), Expanded( From 4e265f4a8de0ed9b6c5cdbff9b28827e2c7d35bb Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 1 Jan 2021 18:49:36 +0530 Subject: [PATCH 22/34] fix: Fixed remove user bug --- example/lib/group_info_screen.dart | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index c044a23d..84aa9c3a 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -26,7 +26,7 @@ class _GroupInfoScreenState extends State { final FocusNode _focusNode = FocusNode(); - int groupMemberListLength; + bool listExpanded = false; void _userNameListener() { if (_searchController.text == _userNameQuery) { @@ -155,7 +155,13 @@ class _GroupInfoScreenState extends State { } Widget _buildMembers(List members) { - groupMemberListLength ??= members.length > 6 ? 6 : members.length; + int groupMemberListLength; + + if (listExpanded) { + groupMemberListLength = members.length; + } else { + groupMemberListLength = members.length > 6 ? 6 : members.length; + } return Column( children: [ @@ -241,7 +247,7 @@ class _GroupInfoScreenState extends State { InkWell( onTap: () { setState(() { - groupMemberListLength = members.length; + listExpanded = true; }); }, child: Material( From 00e211f9fe1967f1c271ea701f330b575e33f0cc Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 1 Jan 2021 18:59:38 +0530 Subject: [PATCH 23/34] fix: Fixed separator width --- lib/src/option_list_tile.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/option_list_tile.dart b/lib/src/option_list_tile.dart index eb6e0ab7..1b87c9d7 100644 --- a/lib/src/option_list_tile.dart +++ b/lib/src/option_list_tile.dart @@ -30,7 +30,7 @@ class OptionListTile extends StatelessWidget { Container( color: separatorColor ?? StreamChatTheme.of(context).colorTheme.greyGainsboro, - height: 2.0, + height: 1.0, ), Material( color: tileColor ?? StreamChatTheme.of(context).colorTheme.white, From 7cf0e00b15b629c69eb375ff7165b35d87e864b9 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Sat, 2 Jan 2021 14:14:04 +0530 Subject: [PATCH 24/34] fix: added todo --- example/lib/group_info_screen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index 84aa9c3a..056f17c6 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -834,7 +834,7 @@ class _GroupInfoScreenState extends State { size: 24.0, ), 'Make Owner', () { - // TODO: I have no clue how to make someone an owner + // TODO: Add make owner implementation (Remaining from backend) }), if (!channel.isDistinct && StreamChat.of(context).user.id != user.id && From 94555a756560cde9bce8644237aff5b6c48b415d Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 4 Jan 2021 14:17:57 +0530 Subject: [PATCH 25/34] del: Removed share media modal --- lib/src/image_footer.dart | 315 ++------------------------------------ 1 file changed, 14 insertions(+), 301 deletions(-) diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 11e62302..3a6cfbcc 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -12,7 +12,6 @@ import 'package:flutter/material.dart'; import 'package:image_gallery_saver/image_gallery_saver.dart'; import 'package:path_provider/path_provider.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter/src/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -121,8 +120,20 @@ class _ImageFooterState extends State { size: 24.0, color: StreamChatTheme.of(context).colorTheme.black, ), - onPressed: () { - _buildShareModal(context); + onPressed: () async { + final attachment = + widget.mediaAttachments[widget.currentPage]; + var url = attachment.imageUrl ?? + attachment.assetUrl ?? + attachment.thumbUrl; + var type = attachment.type == 'image' + ? 'jpg' + : url?.split('?')?.first?.split('.')?.last ?? 'jpg'; + var request = await HttpClient().getUrl(Uri.parse(url)); + var response = await request.close(); + var bytes = + await consolidateHttpClientResponseBytes(response); + await Share.file('File', 'image.$type', bytes, 'image/$type'); }, ), InkWell( @@ -258,304 +269,6 @@ class _ImageFooterState extends State { ); } - void _buildShareModal(context) async { - final result = await showDialog( - context: context, - builder: (context) { - return StatefulBuilder(builder: (context, modalSetState) { - modalSetStateCallback = modalSetState; - return Padding( - padding: EdgeInsets.only( - top: _userSearchMode || _messageFocusNode.hasFocus - ? 16.0 - : MediaQuery.of(context).size.height / 2, - left: 8.0, - right: 8.0), - child: Material( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16.0), - topRight: Radius.circular(16.0), - ), - clipBehavior: Clip.antiAlias, - child: Scaffold( - body: UsersBloc( - child: Column( - children: [ - _buildTextInputSection(modalSetState), - Expanded( - child: ChannelsBloc( - child: ChannelListView( - selectedChannels: _selectedChannels, - onChannelTap: (channel, _) { - _searchController.clear(); - if (!_selectedChannels.contains(channel)) { - modalSetState(() { - _selectedChannels.add(channel); - }); - } else { - modalSetState(() { - _selectedChannels.remove(channel); - }); - } - }, - padding: const EdgeInsets.symmetric(vertical: 8), - crossAxisCount: 4, - pagination: PaginationParams( - limit: 25, - ), - filter: { - if (_channelNameQuery?.trim()?.isNotEmpty == true) - 'name': { - r'$autocomplete': _channelNameQuery, - }, - 'id': { - r'$ne': StreamChat.of(context).user.id, - }, - }, - sort: [ - SortOption( - 'name', - direction: SortOption.ASC, - ), - ], - emptyBuilder: (_) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: - viewportConstraints.maxHeight, - ), - child: Center( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(24), - child: StreamSvgIcon.search( - size: 96, - color: - StreamChatTheme.of(context) - .colorTheme - .grey, - ), - ), - Text( - 'No chat matches these keywords...', - ), - ], - ), - ), - ), - ); - }, - ); - }, - ), - ), - ), - if (_selectedChannels.isNotEmpty) - _buildShareTextInputSection(modalSetState), - if (!_userSearchMode && _selectedChannels.isEmpty) - Align( - alignment: Alignment.bottomCenter, - child: Container( - color: StreamChatTheme.of(context).colorTheme.white, - height: 48.0, - child: Material( - color: - StreamChatTheme.of(context).colorTheme.white, - child: InkWell( - onTap: () async { - var url = widget - .mediaAttachments[widget.currentPage] - .imageUrl ?? - widget - .mediaAttachments[widget.currentPage] - .assetUrl ?? - widget - .mediaAttachments[widget.currentPage] - .thumbUrl; - - if (widget - .mediaAttachments[widget.currentPage] - .type == - 'video') { - await _saveVideo(url); - } else { - await _saveImage(url); - } - Navigator.pop(context); - }, - child: SizedBox.expand( - child: Center( - child: Text( - 'Save to Photos', - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - fontWeight: FontWeight.bold, - ), - ), - ), - ), - ), - ), - ), - ), - ], - ), - ), - ), - ), - ); - }); - }, - ); - if (result == null) { - _selectedChannels.clear(); - } - } - - Widget _buildTextInputSection(modalSetState) { - if (_userSearchMode) { - return Column( - children: [ - SizedBox(height: 16.0), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(width: 8.0), - Expanded( - child: Container( - height: 36, - margin: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 8, - ), - child: TextField( - controller: _searchController, - cursorColor: StreamChatTheme.of(context).colorTheme.black, - autofocus: true, - decoration: InputDecoration( - prefixIconConstraints: BoxConstraints.tight(Size(40, 24)), - prefixIcon: Padding( - padding: const EdgeInsets.only(left: 8, right: 8), - child: StreamSvgIcon.search( - color: StreamChatTheme.of(context).colorTheme.black, - size: 24, - ), - ), - hintText: 'Search', - hintStyle: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), - fontSize: 14, - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.08), - ), - borderRadius: BorderRadius.circular(24), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(24.0), - borderSide: BorderSide( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.08), - ), - ), - contentPadding: EdgeInsets.zero, - ), - ), - ), - ), - IconButton( - icon: StreamSvgIcon.close_small( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(0.5), - ), - splashRadius: 24, - onPressed: () { - modalSetState(() { - if (_searchController.text.isNotEmpty) { - _searchController.text = ''; - } else { - _userSearchMode = false; - } - }); - setState(() {}); - }, - ) - ], - ), - ], - ); - } - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 6.0), - child: IconButton( - icon: StreamSvgIcon.search( - color: StreamChatTheme.of(context).colorTheme.black, - ), - iconSize: 24.0, - onPressed: () { - modalSetState(() { - _userSearchMode = true; - }); - }, - ), - ), - Padding( - padding: const EdgeInsets.all(16.0), - child: Text( - 'Select a Chat to Share', - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.w700, - color: StreamChatTheme.of(context).colorTheme.black, - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 6.0), - child: IconButton( - icon: StreamSvgIcon.share_arrow( - color: StreamChatTheme.of(context).colorTheme.black, - ), - onPressed: () async { - final attachment = widget.mediaAttachments[widget.currentPage]; - var url = attachment.imageUrl ?? - attachment.assetUrl ?? - attachment.thumbUrl; - var type = attachment.type == 'image' - ? 'jpg' - : url?.split('?')?.first?.split('.')?.last ?? 'jpg'; - var request = await HttpClient().getUrl(Uri.parse(url)); - var response = await request.close(); - var bytes = await consolidateHttpClientResponseBytes(response); - await Share.file('File', 'image.$type', bytes, 'image/$type'); - }, - ), - ), - ], - ); - } - Widget _buildShareTextInputSection(modalSetState) { return Align( alignment: Alignment.bottomCenter, From 54efd6f3de3de6ae9c88853d234195170a41db0a Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 4 Jan 2021 14:29:18 +0530 Subject: [PATCH 26/34] fix: fix channel null error --- example/lib/group_info_screen.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index 056f17c6..1cba6fa7 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -92,6 +92,10 @@ class _GroupInfoScreenState extends State { StreamBuilder( stream: channel.channelStateStream, builder: (context, state) { + if (!state.hasData) { + return CircularProgressIndicator(); + } + return Text( _getChannelName( 2 * MediaQuery.of(context).size.width / 3, From 43446713c8200a2086d6120ae6fa53b05304dffb Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 4 Jan 2021 14:29:55 +0530 Subject: [PATCH 27/34] fix: fixed null error --- example/lib/group_info_screen.dart | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index 1cba6fa7..0aa0cfb0 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -93,7 +93,16 @@ class _GroupInfoScreenState extends State { stream: channel.channelStateStream, builder: (context, state) { if (!state.hasData) { - return CircularProgressIndicator(); + return Text( + 'Loading...', + style: TextStyle( + color: + StreamChatTheme.of(context).colorTheme.black, + fontSize: 16, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ); } return Text( From 95d91f3959f25865011bd41c746cbde7c5ab269a Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 4 Jan 2021 16:00:20 +0530 Subject: [PATCH 28/34] feat: Added show in chat --- example/lib/chat_info_screen.dart | 31 ++++++++++++++++++++++- example/lib/group_info_screen.dart | 25 ++++++++++++++++++ example/lib/main.dart | 21 +++++++++++++++ lib/src/channel_media_display_screen.dart | 4 +++ lib/src/full_screen_media.dart | 9 +++++++ lib/src/giphy_attachment.dart | 3 +++ lib/src/image_actions_modal.dart | 14 ++++++---- lib/src/image_attachment.dart | 3 +++ lib/src/image_header.dart | 5 ++++ lib/src/message_list_view.dart | 5 ++++ lib/src/message_widget.dart | 6 +++++ lib/src/video_attachment.dart | 3 +++ 12 files changed, 123 insertions(+), 6 deletions(-) diff --git a/example/lib/chat_info_screen.dart b/example/lib/chat_info_screen.dart index d992e73b..535c999b 100644 --- a/example/lib/chat_info_screen.dart +++ b/example/lib/chat_info_screen.dart @@ -5,6 +5,9 @@ import 'package:jiffy/jiffy.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'main.dart'; +import 'routes/routes.dart'; + /// Detail screen for a 1:1 chat correspondence class ChatInfoScreen extends StatefulWidget { /// User in consideration @@ -190,7 +193,9 @@ class _ChatInfoScreenState extends State { StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), ), ), - trailing: StreamSvgIcon.right(), + trailing: StreamSvgIcon.right( + color: StreamChatTheme.of(context).colorTheme.grey, + ), onTap: () { final channel = StreamChannel.of(context).channel; @@ -208,6 +213,30 @@ class _ChatInfoScreenState extends State { ), ], paginationParams: PaginationParams(limit: 20), + onShowMessage: (m, c) async { + final client = StreamChat.of(context).client; + final message = m; + final channel = client.channel( + c.type, + id: c.id, + ); + if (channel.state == null) { + await channel.watch(); + } + Navigator.pop(context); + Navigator.pop(context); + Navigator.pop(context); + Navigator.pop(context); + Navigator.pop(context); + Navigator.pushNamed( + context, + Routes.CHANNEL_PAGE, + arguments: ChannelPageArgs( + channel: channel, + initialMessage: message, + ), + ); + }, ), ), ), diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index 0aa0cfb0..a8fb52e4 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -9,6 +9,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'chat_info_screen.dart'; import 'main.dart'; +import 'routes/routes.dart'; class GroupInfoScreen extends StatefulWidget { @override @@ -485,6 +486,30 @@ class _GroupInfoScreenState extends State { ), ], paginationParams: PaginationParams(limit: 20), + onShowMessage: (m, c) async { + final client = StreamChat.of(context).client; + final message = m; + final channel = client.channel( + c.type, + id: c.id, + ); + if (channel.state == null) { + await channel.watch(); + } + Navigator.pop(context); + Navigator.pop(context); + Navigator.pop(context); + Navigator.pop(context); + Navigator.pop(context); + Navigator.pushNamed( + context, + Routes.CHANNEL_PAGE, + arguments: ChannelPageArgs( + channel: channel, + initialMessage: message, + ), + ); + }, ), ), ), diff --git a/example/lib/main.dart b/example/lib/main.dart index 69300312..cc01cec4 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -517,6 +517,27 @@ class ChannelPage extends StatelessWidget { parent: parentMessage, ); }, + onShowMessage: (m, c) async { + final client = StreamChat.of(context).client; + final message = m; + final channel = client.channel( + c.type, + id: c.id, + ); + if (channel.state == null) { + await channel.watch(); + } + Navigator.pop(context); + Navigator.pop(context); + Navigator.pushReplacementNamed( + context, + Routes.CHANNEL_PAGE, + arguments: ChannelPageArgs( + channel: channel, + initialMessage: message, + ), + ); + }, ), Positioned( bottom: 0, diff --git a/lib/src/channel_media_display_screen.dart b/lib/src/channel_media_display_screen.dart index 09d2a8a8..1f8e9257 100644 --- a/lib/src/channel_media_display_screen.dart +++ b/lib/src/channel_media_display_screen.dart @@ -20,10 +20,13 @@ class ChannelMediaDisplayScreen extends StatefulWidget { /// The builder used when the file list is empty. final WidgetBuilder emptyBuilder; + final ShowMessageCallback onShowMessage; + const ChannelMediaDisplayScreen({ this.sortOptions, this.paginationParams, this.emptyBuilder, + this.onShowMessage, }); @override @@ -191,6 +194,7 @@ class _ChannelMediaDisplayScreenState extends State { message: media[position].message, sentAt: media[position].message.createdAt, userName: media[position].message.user.name, + onShowMessage: widget.onShowMessage, ), ), ), diff --git a/lib/src/full_screen_media.dart b/lib/src/full_screen_media.dart index 7e3f3d7d..e164eec8 100644 --- a/lib/src/full_screen_media.dart +++ b/lib/src/full_screen_media.dart @@ -6,9 +6,12 @@ import 'package:photo_view/photo_view.dart'; import 'package:stream_chat_flutter/src/image_footer.dart'; import 'package:stream_chat_flutter/src/image_header.dart'; import 'package:video_player/video_player.dart'; +import 'stream_channel.dart'; import '../stream_chat_flutter.dart'; +typedef ShowMessageCallback = void Function(Message message, Channel channel); + /// A full screen image widget class FullScreenMedia extends StatefulWidget { /// The url of the image @@ -18,6 +21,7 @@ class FullScreenMedia extends StatefulWidget { final int startIndex; final String userName; final DateTime sentAt; + final ShowMessageCallback onShowMessage; /// Instantiate a new FullScreenImage const FullScreenMedia({ @@ -27,6 +31,7 @@ class FullScreenMedia extends StatefulWidget { this.startIndex = 0, this.userName = '', this.sentAt, + this.onShowMessage, }) : super(key: key); @override @@ -167,6 +172,10 @@ class _FullScreenMediaState extends State message: widget.message, urls: widget.mediaAttachments, currentIndex: _currentPage, + onShowMessage: () { + widget.onShowMessage( + widget.message, StreamChannel.of(context).channel); + }, ), ImageFooter( currentPage: _currentPage, diff --git a/lib/src/giphy_attachment.dart b/lib/src/giphy_attachment.dart index cc1a319f..379fe182 100644 --- a/lib/src/giphy_attachment.dart +++ b/lib/src/giphy_attachment.dart @@ -12,6 +12,7 @@ class GiphyAttachment extends StatelessWidget { final MessageTheme messageTheme; final Message message; final Size size; + final ShowMessageCallback onShowMessage; const GiphyAttachment({ Key key, @@ -19,6 +20,7 @@ class GiphyAttachment extends StatelessWidget { this.messageTheme, this.message, this.size, + this.onShowMessage, }) : super(key: key); @override @@ -75,6 +77,7 @@ class GiphyAttachment extends StatelessWidget { userName: message.user.name, sentAt: message.createdAt, message: message, + onShowMessage: onShowMessage, ), ); })); diff --git a/lib/src/image_actions_modal.dart b/lib/src/image_actions_modal.dart index 168581c0..0001df6f 100644 --- a/lib/src/image_actions_modal.dart +++ b/lib/src/image_actions_modal.dart @@ -15,9 +15,15 @@ class ImageActionsModal extends StatelessWidget { final String sentAt; final List urls; final currentIndex; + final VoidCallback onShowMessage; ImageActionsModal( - {this.message, this.userName, this.sentAt, this.urls, this.currentIndex}); + {this.message, + this.userName, + this.sentAt, + this.urls, + this.currentIndex, + this.onShowMessage}); @override Widget build(BuildContext context) { @@ -134,10 +140,8 @@ class ImageActionsModal extends StatelessWidget { size: 24.0, color: StreamChatTheme.of(context).colorTheme.black, - ), () { - Navigator.pop(context); - Navigator.pop(context); - }), + ), + onShowMessage), _buildButton( context, 'Save ${urls[currentIndex].type == 'video' ? 'Video' : 'Image'}', diff --git a/lib/src/image_attachment.dart b/lib/src/image_attachment.dart index 3210949b..f9e3290b 100644 --- a/lib/src/image_attachment.dart +++ b/lib/src/image_attachment.dart @@ -13,6 +13,7 @@ class ImageAttachment extends StatelessWidget { final MessageTheme messageTheme; final Size size; final bool showTitle; + final ShowMessageCallback onShowMessage; const ImageAttachment({ Key key, @@ -21,6 +22,7 @@ class ImageAttachment extends StatelessWidget { @required this.size, this.messageTheme, this.showTitle = true, + this.onShowMessage, }) : super(key: key); @override @@ -56,6 +58,7 @@ class ImageAttachment extends StatelessWidget { userName: message.user.name, sentAt: message.createdAt, message: message, + onShowMessage: onShowMessage, ), ); }, diff --git a/lib/src/image_header.dart b/lib/src/image_header.dart index a9f1173f..6139bc7c 100644 --- a/lib/src/image_header.dart +++ b/lib/src/image_header.dart @@ -13,6 +13,9 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { /// By default it calls [Navigator.pop] final VoidCallback onBackPressed; + /// Callback to call when pressing the show message button. + final VoidCallback onShowMessage; + /// Callback to call when the header is tapped. final VoidCallback onTitleTap; @@ -35,6 +38,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { this.currentIndex, this.showBackButton = true, this.onBackPressed, + this.onShowMessage, this.onTitleTap, this.onImageTap, this.userName = '', @@ -111,6 +115,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget { message: message, urls: urls, currentIndex: currentIndex, + onShowMessage: onShowMessage, ), ); }); diff --git a/lib/src/message_list_view.dart b/lib/src/message_list_view.dart index 40680991..1bcce91f 100644 --- a/lib/src/message_list_view.dart +++ b/lib/src/message_list_view.dart @@ -116,6 +116,7 @@ class MessageListView extends StatefulWidget { this.scrollController, this.itemPositionListener, this.highlightInitialMessage = false, + this.onShowMessage, }) : super(key: key); /// Function used to build a custom message widget @@ -162,6 +163,8 @@ class MessageListView extends StatefulWidget { /// Also See [StreamChannel] final bool highlightInitialMessage; + final ShowMessageCallback onShowMessage; + @override _MessageListViewState createState() => _MessageListViewState(); } @@ -711,6 +714,7 @@ class _MessageListViewState extends State { messageTheme: isMyMessage ? StreamChatTheme.of(context).ownMessageTheme : StreamChatTheme.of(context).otherMessageTheme, + onShowMessage: widget.onShowMessage, ); } @@ -788,6 +792,7 @@ class _MessageListViewState extends State { : StreamChatTheme.of(context).otherMessageTheme, readList: readList, allRead: allRead, + onShowMessage: widget.onShowMessage, ); if (!initialMessageHighlightComplete && diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 5c175512..474c3bcf 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -119,6 +119,8 @@ class MessageWidget extends StatefulWidget { final List readList; + final ShowMessageCallback onShowMessage; + /// If true show the users username next to the timestamp of the message final bool showUsername; final bool showTimestamp; @@ -157,6 +159,7 @@ class MessageWidget extends StatefulWidget { this.onUserAvatarTap, this.onLinkTap, this.onMessageActions, + this.onShowMessage, this.editMessageInputBuilder, this.textBuilder, Map customAttachmentBuilders, @@ -179,6 +182,7 @@ class MessageWidget extends StatefulWidget { MediaQuery.of(context).size.width * 0.8, MediaQuery.of(context).size.height * 0.3, ), + onShowMessage: onShowMessage, ); }, 'video': (context, message, attachment) { @@ -190,6 +194,7 @@ class MessageWidget extends StatefulWidget { MediaQuery.of(context).size.height * 0.3, ), message: message, + onShowMessage: onShowMessage, ); }, 'giphy': (context, message, attachment) { @@ -201,6 +206,7 @@ class MessageWidget extends StatefulWidget { MediaQuery.of(context).size.width * 0.8, MediaQuery.of(context).size.height * 0.3, ), + onShowMessage: onShowMessage, ); }, 'file': (context, message, attachment) { diff --git a/lib/src/video_attachment.dart b/lib/src/video_attachment.dart index 21ede87a..83763aee 100644 --- a/lib/src/video_attachment.dart +++ b/lib/src/video_attachment.dart @@ -14,6 +14,7 @@ class VideoAttachment extends StatefulWidget { final MessageTheme messageTheme; final Size size; final Message message; + final ShowMessageCallback onShowMessage; VideoAttachment({ Key key, @@ -21,6 +22,7 @@ class VideoAttachment extends StatefulWidget { @required this.messageTheme, this.message, this.size, + this.onShowMessage, }) : super(key: key); @override @@ -95,6 +97,7 @@ class _VideoAttachmentState extends State { userName: widget.message.user.name, sentAt: widget.message.createdAt, message: widget.message, + onShowMessage: widget.onShowMessage, ), ), ), From 74cff7576b104fed196f38e6b5ddb64795b1bf9f Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 4 Jan 2021 16:22:08 +0530 Subject: [PATCH 29/34] fix: fixed onShowMessage not showing up for some aspects --- example/lib/chat_info_screen.dart | 13 ++++++++----- example/lib/group_info_screen.dart | 15 +++++++++------ lib/src/giphy_attachment.dart | 1 + lib/src/image_group.dart | 3 +++ lib/src/message_widget.dart | 1 + 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/example/lib/chat_info_screen.dart b/example/lib/chat_info_screen.dart index 535c999b..c96d982f 100644 --- a/example/lib/chat_info_screen.dart +++ b/example/lib/chat_info_screen.dart @@ -223,11 +223,14 @@ class _ChatInfoScreenState extends State { if (channel.state == null) { await channel.watch(); } - Navigator.pop(context); - Navigator.pop(context); - Navigator.pop(context); - Navigator.pop(context); - Navigator.pop(context); + await Navigator.pop(context); + await Future.delayed(Duration(milliseconds: 200)); + await Navigator.pop(context); + await Future.delayed(Duration(milliseconds: 200)); + await Navigator.pop(context); + await Future.delayed(Duration(milliseconds: 200)); + await Navigator.pop(context); + await Navigator.pop(context); Navigator.pushNamed( context, Routes.CHANNEL_PAGE, diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index a8fb52e4..ace7e2c8 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -496,12 +496,15 @@ class _GroupInfoScreenState extends State { if (channel.state == null) { await channel.watch(); } - Navigator.pop(context); - Navigator.pop(context); - Navigator.pop(context); - Navigator.pop(context); - Navigator.pop(context); - Navigator.pushNamed( + await Navigator.pop(context); + await Future.delayed(Duration(milliseconds: 200)); + await Navigator.pop(context); + await Future.delayed(Duration(milliseconds: 200)); + await Navigator.pop(context); + await Future.delayed(Duration(milliseconds: 200)); + await Navigator.pop(context); + await Navigator.pop(context); + await Navigator.pushNamed( context, Routes.CHANNEL_PAGE, arguments: ChannelPageArgs( diff --git a/lib/src/giphy_attachment.dart b/lib/src/giphy_attachment.dart index 379fe182..c879cd5b 100644 --- a/lib/src/giphy_attachment.dart +++ b/lib/src/giphy_attachment.dart @@ -330,6 +330,7 @@ class GiphyAttachment extends StatelessWidget { userName: message.user.name, sentAt: message.createdAt, message: message, + onShowMessage: onShowMessage, ), ); })); diff --git a/lib/src/image_group.dart b/lib/src/image_group.dart index e9b52f88..bb577a4b 100644 --- a/lib/src/image_group.dart +++ b/lib/src/image_group.dart @@ -10,11 +10,13 @@ class ImageGroup extends StatelessWidget { @required this.images, @required this.message, @required this.size, + this.onShowMessage, }) : super(key: key); final List images; final Message message; final Size size; + final ShowMessageCallback onShowMessage; @override Widget build(BuildContext context) { @@ -119,6 +121,7 @@ class ImageGroup extends StatelessWidget { userName: message.user.name, sentAt: message.createdAt, message: message, + onShowMessage: onShowMessage, ), ), ), diff --git a/lib/src/message_widget.dart b/lib/src/message_widget.dart index 474c3bcf..b19a8840 100644 --- a/lib/src/message_widget.dart +++ b/lib/src/message_widget.dart @@ -736,6 +736,7 @@ class _MessageWidgetState extends State { ), images: images, message: widget.message, + onShowMessage: widget.onShowMessage, ), ), ), From 523790b0274c6ce95f425d942e55480ffbbd2ddf Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 4 Jan 2021 16:45:17 +0530 Subject: [PATCH 30/34] fix: temp fix for navigation issues --- example/lib/chat_info_screen.dart | 8 -------- example/lib/group_info_screen.dart | 8 -------- example/lib/main.dart | 2 -- 3 files changed, 18 deletions(-) diff --git a/example/lib/chat_info_screen.dart b/example/lib/chat_info_screen.dart index c96d982f..50cb16f8 100644 --- a/example/lib/chat_info_screen.dart +++ b/example/lib/chat_info_screen.dart @@ -223,14 +223,6 @@ class _ChatInfoScreenState extends State { if (channel.state == null) { await channel.watch(); } - await Navigator.pop(context); - await Future.delayed(Duration(milliseconds: 200)); - await Navigator.pop(context); - await Future.delayed(Duration(milliseconds: 200)); - await Navigator.pop(context); - await Future.delayed(Duration(milliseconds: 200)); - await Navigator.pop(context); - await Navigator.pop(context); Navigator.pushNamed( context, Routes.CHANNEL_PAGE, diff --git a/example/lib/group_info_screen.dart b/example/lib/group_info_screen.dart index ace7e2c8..ffab48df 100644 --- a/example/lib/group_info_screen.dart +++ b/example/lib/group_info_screen.dart @@ -496,14 +496,6 @@ class _GroupInfoScreenState extends State { if (channel.state == null) { await channel.watch(); } - await Navigator.pop(context); - await Future.delayed(Duration(milliseconds: 200)); - await Navigator.pop(context); - await Future.delayed(Duration(milliseconds: 200)); - await Navigator.pop(context); - await Future.delayed(Duration(milliseconds: 200)); - await Navigator.pop(context); - await Navigator.pop(context); await Navigator.pushNamed( context, Routes.CHANNEL_PAGE, diff --git a/example/lib/main.dart b/example/lib/main.dart index cc01cec4..b7c30dce 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -527,8 +527,6 @@ class ChannelPage extends StatelessWidget { if (channel.state == null) { await channel.watch(); } - Navigator.pop(context); - Navigator.pop(context); Navigator.pushReplacementNamed( context, Routes.CHANNEL_PAGE, From eaa7d03bb426cd42d0a62a0e2ffa329ddfc55d3d Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 4 Jan 2021 16:59:13 +0530 Subject: [PATCH 31/34] fix: added back system theme --- example/lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index b7c30dce..74a6d808 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -63,7 +63,7 @@ class MyApp extends StatelessWidget { debugShowCheckedModeBanner: false, theme: ThemeData.light(), darkTheme: ThemeData.dark(), - themeMode: ThemeMode.dark, + themeMode: ThemeMode.system, onGenerateRoute: AppRoutes.generateRoute, initialRoute: client.state.user == null ? Routes.CHOOSE_USER : Routes.HOME, From 4ef7516b52b06efe4a90d16ef928f020e4d45124 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 4 Jan 2021 17:22:14 +0530 Subject: [PATCH 32/34] fix: chat_info_screen.dart --- example/lib/chat_info_screen.dart | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/example/lib/chat_info_screen.dart b/example/lib/chat_info_screen.dart index 50cb16f8..5eb0df7d 100644 --- a/example/lib/chat_info_screen.dart +++ b/example/lib/chat_info_screen.dart @@ -28,12 +28,14 @@ class _ChatInfoScreenState extends State { body: ListView( children: [ _buildUserHeader(), - SizedBox( + Container( height: 8.0, + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, ), _buildOptionListTiles(), - SizedBox( + Container( height: 8.0, + color: StreamChatTheme.of(context).colorTheme.greyGainsboro, ), if ([ 'admin', @@ -139,7 +141,7 @@ class _ChatInfoScreenState extends State { title: 'Mute user', titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.symmetric(horizontal: 22.0), child: StreamSvgIcon.mute( size: 24.0, color: StreamChatTheme.of(context) @@ -186,9 +188,9 @@ class _ChatInfoScreenState extends State { tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), + padding: const EdgeInsets.symmetric(horizontal: 16.0), child: StreamSvgIcon.pictures( - size: 32.0, + size: 36.0, color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.5), ), @@ -244,7 +246,7 @@ class _ChatInfoScreenState extends State { tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12.0), + padding: const EdgeInsets.symmetric(horizontal: 18.0), child: StreamSvgIcon.files( size: 32.0, color: @@ -283,7 +285,7 @@ class _ChatInfoScreenState extends State { tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow, titleTextStyle: StreamChatTheme.of(context).textTheme.body, leading: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.symmetric(horizontal: 22.0), child: StreamSvgIcon.Icon_group( size: 24.0, color: @@ -313,7 +315,7 @@ class _ChatInfoScreenState extends State { color: StreamChatTheme.of(context).colorTheme.accentRed, ), leading: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), + padding: const EdgeInsets.symmetric(horizontal: 22.0), child: StreamSvgIcon.delete( color: StreamChatTheme.of(context).colorTheme.accentRed, size: 24.0, From a298794e66470428b5f696dc4decb4f12527b033 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 4 Jan 2021 13:27:39 +0100 Subject: [PATCH 33/34] remove log --- lib/src/channel_media_display_screen.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/src/channel_media_display_screen.dart b/lib/src/channel_media_display_screen.dart index 1f8e9257..cd5238bc 100644 --- a/lib/src/channel_media_display_screen.dart +++ b/lib/src/channel_media_display_screen.dart @@ -1,4 +1,3 @@ -import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -176,8 +175,6 @@ class _ChannelMediaDisplayScreenState extends State { SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), itemBuilder: (context, position) { var channel = StreamChannel.of(context).channel; - print('NAV'); - return Padding( padding: const EdgeInsets.all(1.0), child: InkWell( From 7f7b33bfd2e1687d504d835aad2ad6f4b2f791c5 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 4 Jan 2021 13:44:35 +0100 Subject: [PATCH 34/34] merge feature/new-ui --- example/lib/main.dart | 4 +--- example/pubspec.yaml | 2 +- lib/src/image_footer.dart | 8 ++++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 74a6d808..b004271b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,11 +1,9 @@ import 'dart:async'; import 'dart:io'; -import 'package:example/choose_user_page.dart'; import 'package:example/chat_info_screen.dart'; +import 'package:example/choose_user_page.dart'; import 'package:example/group_info_screen.dart'; -import 'package:example/new_chat_screen.dart'; -import 'package:example/new_group_chat_screen.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 4f76c695..67ff213d 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,6 +1,6 @@ name: example description: A new Flutter project. -version: 1.0.106+109 +version: 1.0.107+110 environment: sdk: ">=2.2.2 <3.0.0" diff --git a/lib/src/image_footer.dart b/lib/src/image_footer.dart index 46821ce2..3a6cfbcc 100644 --- a/lib/src/image_footer.dart +++ b/lib/src/image_footer.dart @@ -47,10 +47,14 @@ class ImageFooter extends StatefulWidget implements PreferredSizeWidget { this.message, this.videoPackages, this.mediaSelectedCallBack, - }) : super(key: key); + }) : preferredSize = Size.fromHeight(kToolbarHeight), + super(key: key); @override _ImageFooterState createState() => _ImageFooterState(); + + @override + final Size preferredSize; } class _ImageFooterState extends State { @@ -164,7 +168,7 @@ class _ImageFooterState extends State { ); } - void _buildPhotosModal(context) { + Widget _buildPhotosModal(context) { var videoAttachments = widget.mediaAttachments .where((element) => element.type == 'video') .toList();