update lists

This commit is contained in:
Salvatore Giordano
2022-04-28 14:53:00 +02:00
parent ad398090f6
commit 18e69cd4bd
6 changed files with 317 additions and 263 deletions
@@ -264,6 +264,7 @@
"${BUILT_PRODUCTS_DIR}/sqlite3/sqlite3.framework", "${BUILT_PRODUCTS_DIR}/sqlite3/sqlite3.framework",
"${BUILT_PRODUCTS_DIR}/sqlite3_flutter_libs/sqlite3_flutter_libs.framework", "${BUILT_PRODUCTS_DIR}/sqlite3_flutter_libs/sqlite3_flutter_libs.framework",
"${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework", "${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework",
"${BUILT_PRODUCTS_DIR}/video_compress/video_compress.framework",
"${BUILT_PRODUCTS_DIR}/video_player_avfoundation/video_player_avfoundation.framework", "${BUILT_PRODUCTS_DIR}/video_player_avfoundation/video_player_avfoundation.framework",
"${BUILT_PRODUCTS_DIR}/video_thumbnail/video_thumbnail.framework", "${BUILT_PRODUCTS_DIR}/video_thumbnail/video_thumbnail.framework",
"${BUILT_PRODUCTS_DIR}/wakelock/wakelock.framework", "${BUILT_PRODUCTS_DIR}/wakelock/wakelock.framework",
@@ -292,6 +293,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqlite3.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqlite3.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqlite3_flutter_libs.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqlite3_flutter_libs.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_compress.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_player_avfoundation.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_player_avfoundation.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_thumbnail.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/video_thumbnail.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/wakelock.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/wakelock.framework",
+50 -36
View File
@@ -20,9 +20,21 @@ class ChannelList extends StatefulWidget {
class _ChannelList extends State<ChannelList> { class _ChannelList extends State<ChannelList> {
ScrollController _scrollController = ScrollController(); ScrollController _scrollController = ScrollController();
TextEditingController? _controller; late StreamMessageSearchListController _messageSearchListController =
StreamMessageSearchListController(
client: StreamChat.of(context).client,
filter: Filter.in_('members', [StreamChat.of(context).currentUser!.id]),
limit: 5,
searchQuery: '',
sort: [
SortOption(
'created_at',
direction: SortOption.ASC,
),
],
);
String _channelQuery = ''; TextEditingController? _controller;
bool _isSearchActive = false; bool _isSearchActive = false;
@@ -32,10 +44,11 @@ class _ChannelList extends State<ChannelList> {
if (_debounce?.isActive ?? false) _debounce!.cancel(); if (_debounce?.isActive ?? false) _debounce!.cancel();
_debounce = Timer(const Duration(milliseconds: 350), () { _debounce = Timer(const Duration(milliseconds: 350), () {
if (mounted) { if (mounted) {
_messageSearchListController.searchQuery = _controller!.text;
setState(() { setState(() {
_channelQuery = _controller!.text; _isSearchActive = _controller!.text.isNotEmpty;
_isSearchActive = _channelQuery.isNotEmpty;
}); });
if (_isSearchActive) _messageSearchListController.doInitialLoad();
} }
}); });
} }
@@ -61,12 +74,12 @@ class _ChannelList extends State<ChannelList> {
_controller?.removeListener(_channelQueryListener); _controller?.removeListener(_channelQueryListener);
_controller?.dispose(); _controller?.dispose();
_scrollController.dispose(); _scrollController.dispose();
_channelListController.dispose();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final user = StreamChat.of(context).currentUser;
return WillPopScope( return WillPopScope(
onWillPop: () async { onWillPop: () async {
if (_isSearchActive) { if (_isSearchActive) {
@@ -98,17 +111,7 @@ class _ChannelList extends State<ChannelList> {
], ],
body: _isSearchActive body: _isSearchActive
? StreamMessageSearchListView( ? StreamMessageSearchListView(
showErrorTile: true, controller: _messageSearchListController,
messageQuery: _channelQuery,
filters: Filter.in_('members', [user!.id]),
sortOptions: [
SortOption(
'created_at',
direction: SortOption.ASC,
),
],
pullToRefresh: false,
limit: 30,
emptyBuilder: (_) { emptyBuilder: (_) {
return LayoutBuilder( return LayoutBuilder(
builder: (context, viewportConstraints) { builder: (context, viewportConstraints) {
@@ -139,24 +142,34 @@ class _ChannelList extends State<ChannelList> {
}, },
); );
}, },
onItemTap: (messageResponse) async { itemBuilder: (
FocusScope.of(context).requestFocus(FocusNode()); context,
final client = StreamChat.of(context).client; messageResponses,
final message = messageResponse.message; index,
final channel = client.channel( defaultWidget,
messageResponse.channel!.type, ) {
id: messageResponse.channel!.id, return defaultWidget.copyWith(
); onTap: () async {
if (channel.state == null) { final messageResponse = messageResponses[index];
await channel.watch(); FocusScope.of(context).requestFocus(FocusNode());
} final client = StreamChat.of(context).client;
Navigator.pushNamed( final message = messageResponse.message;
context, final channel = client.channel(
Routes.CHANNEL_PAGE, messageResponse.channel!.type,
arguments: ChannelPageArgs( id: messageResponse.channel!.id,
channel: channel, );
initialMessage: message, if (channel.state == null) {
), await channel.watch();
}
Navigator.pushNamed(
context,
Routes.CHANNEL_PAGE,
arguments: ChannelPageArgs(
channel: channel,
initialMessage: message,
),
);
},
); );
}, },
) )
@@ -166,9 +179,10 @@ class _ChannelList extends State<ChannelList> {
onRefresh: _channelListController.refresh, onRefresh: _channelListController.refresh,
child: StreamChannelListView( child: StreamChannelListView(
controller: _channelListController, controller: _channelListController,
itemBuilder: (context, channel, tile) { itemBuilder: (context, channels, index, defaultWidget) {
final chatTheme = StreamChatTheme.of(context); final chatTheme = StreamChatTheme.of(context);
final backgroundColor = chatTheme.colorTheme.inputBg; final backgroundColor = chatTheme.colorTheme.inputBg;
final channel = channels[index];
final canDeleteChannel = channel.ownCapabilities final canDeleteChannel = channel.ownCapabilities
.contains(PermissionType.deleteChannel); .contains(PermissionType.deleteChannel);
return Slidable( return Slidable(
@@ -249,7 +263,7 @@ class _ChannelList extends State<ChannelList> {
), ),
], ],
), ),
child: tile, child: defaultWidget,
); );
}, },
onChannelTap: (channel) { onChannelTap: (channel) {
@@ -704,6 +704,28 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
void _buildAddUserModal(context) { void _buildAddUserModal(context) {
var channel = StreamChannel.of(context).channel; var channel = StreamChannel.of(context).channel;
final userListController = StreamUserListController(
client: channel.client,
limit: 25,
filter: Filter.and(
[
if (_searchController!.text.isNotEmpty)
Filter.autoComplete('name', _userNameQuery),
Filter.notIn('id', [
StreamChat.of(context).currentUser!.id,
...channel.state!.members
.map<String?>(((e) => e.userId))
.whereType<String>(),
]),
],
),
sort: [
SortOption(
'name',
direction: 1,
),
],
);
showDialog( showDialog(
useRootNavigator: false, useRootNavigator: false,
@@ -721,85 +743,63 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
), ),
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
child: Scaffold( child: Scaffold(
body: UsersBloc( body: Column(
child: Column( children: [
children: [ Padding(
Padding( padding: const EdgeInsets.all(16),
padding: const EdgeInsets.all(16), child: _buildTextInputSection(modalSetState),
child: _buildTextInputSection(modalSetState), ),
), Expanded(
Expanded( child: StreamUserListView(
child: StreamUserListView( controller: userListController,
selectedUsers: {}, onUserTap: (user) async {
onUserTap: (user, _) async { _searchController!.clear();
_searchController!.clear();
await channel.addMembers([user.id]); await channel.addMembers([user.id]);
Navigator.pop(context); Navigator.pop(context);
setState(() {}); setState(() {});
}, },
crossAxisCount: 4, emptyBuilder: (_) {
limit: 25, return LayoutBuilder(
filter: Filter.and( builder: (context, viewportConstraints) {
[ return SingleChildScrollView(
if (_searchController!.text.isNotEmpty) physics: AlwaysScrollableScrollPhysics(),
Filter.autoComplete('name', _userNameQuery), child: ConstrainedBox(
Filter.notIn('id', [ constraints: BoxConstraints(
StreamChat.of(context).currentUser!.id, minHeight: viewportConstraints.maxHeight,
...channel.state!.members ),
.map<String?>(((e) => e.userId)) child: Center(
.whereType<String>(), child: Column(
]), children: [
], Padding(
), padding: const EdgeInsets.all(24),
sort: [ child: StreamSvgIcon.search(
SortOption( size: 96,
'name', color: StreamChatTheme.of(context)
direction: 1, .colorTheme
), .textLowEmphasis,
],
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
.textLowEmphasis,
),
), ),
Text(AppLocalizations.of(context) ),
.noUserMatchesTheseKeywords), Text(AppLocalizations.of(context)
], .noUserMatchesTheseKeywords),
), ],
), ),
), ),
); ),
}, );
); },
}, );
), },
), ),
], ),
), ],
), ),
), ),
), ),
); );
}); });
}, },
); ).then((_) => userListController.dispose());
} }
Widget _buildTextInputSection(modalSetState) { Widget _buildTextInputSection(modalSetState) {
@@ -19,6 +19,20 @@ class _NewChatScreenState extends State<NewChatScreen> {
late TextEditingController _controller; late TextEditingController _controller;
late final userListController = StreamUserListController(
client: StreamChat.of(context).client,
limit: 25,
filter: Filter.and([
Filter.notEqual('id', StreamChat.of(context).currentUser!.id),
]),
sort: [
SortOption(
'name',
direction: 1,
),
],
);
ChipInputTextFieldState? get _chipInputTextFieldState => ChipInputTextFieldState? get _chipInputTextFieldState =>
_chipInputTextFieldStateKey.currentState; _chipInputTextFieldStateKey.currentState;
@@ -45,6 +59,12 @@ class _NewChatScreenState extends State<NewChatScreen> {
_userNameQuery = _controller.text; _userNameQuery = _controller.text;
_isSearchActive = _userNameQuery.isNotEmpty; _isSearchActive = _userNameQuery.isNotEmpty;
}); });
userListController.filter = Filter.and([
if (_userNameQuery.isNotEmpty)
Filter.autoComplete('name', _userNameQuery),
Filter.notEqual('id', StreamChat.of(context).currentUser!.id),
]);
userListController.doInitialLoad();
}); });
} }
@@ -112,6 +132,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
_controller.clear(); _controller.clear();
_controller.removeListener(_userNameListener); _controller.removeListener(_userNameListener);
_controller.dispose(); _controller.dispose();
userListController.dispose();
super.dispose(); super.dispose();
} }
@@ -289,10 +310,10 @@ class _NewChatScreenState extends State<NewChatScreen> {
onPanDown: (_) => FocusScope.of(context).unfocus(), onPanDown: (_) => FocusScope.of(context).unfocus(),
child: UsersBloc( child: UsersBloc(
child: StreamUserListView( child: StreamUserListView(
selectedUsers: _selectedUsers, controller: userListController,
groupAlphabetically: // groupAlphabetically:
_isSearchActive ? false : true, // _isSearchActive ? false : true,
onUserTap: (user, _) { onUserTap: (user) {
_controller.clear(); _controller.clear();
if (!_selectedUsers.contains(user)) { if (!_selectedUsers.contains(user)) {
_chipInputTextFieldState _chipInputTextFieldState
@@ -302,19 +323,17 @@ class _NewChatScreenState extends State<NewChatScreen> {
_chipInputTextFieldState!.removeItem(user); _chipInputTextFieldState!.removeItem(user);
} }
}, },
limit: 25, itemBuilder: (
filter: Filter.and([ context,
if (_userNameQuery.isNotEmpty) users,
Filter.autoComplete('name', _userNameQuery), index,
Filter.notEqual('id', defaultWidget,
StreamChat.of(context).currentUser!.id), ) {
]), return defaultWidget.copyWith(
sort: [ selected:
SortOption( _selectedUsers.contains(users[index]),
'name', );
direction: 1, },
),
],
emptyBuilder: (_) { emptyBuilder: (_) {
return LayoutBuilder( return LayoutBuilder(
builder: (context, viewportConstraints) { builder: (context, viewportConstraints) {
@@ -23,6 +23,20 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
Timer? _debounce; Timer? _debounce;
late final userListController = StreamUserListController(
client: StreamChat.of(context).client,
sort: [
SortOption(
'name',
direction: 1,
),
],
limit: 25,
filter: Filter.and([
Filter.notEqual('id', StreamChat.of(context).currentUser!.id),
]),
);
void _userNameListener() { void _userNameListener() {
if (_debounce?.isActive ?? false) _debounce!.cancel(); if (_debounce?.isActive ?? false) _debounce!.cancel();
_debounce = Timer(const Duration(milliseconds: 350), () { _debounce = Timer(const Duration(milliseconds: 350), () {
@@ -31,6 +45,12 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
_userNameQuery = _controller!.text; _userNameQuery = _controller!.text;
_isSearchActive = _userNameQuery.isNotEmpty; _isSearchActive = _userNameQuery.isNotEmpty;
}); });
userListController.filter = Filter.and([
if (_userNameQuery.isNotEmpty)
Filter.autoComplete('name', _userNameQuery),
Filter.notEqual('id', StreamChat.of(context).currentUser!.id),
]);
userListController.doInitialLoad();
} }
}); });
} }
@@ -46,6 +66,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
_controller?.clear(); _controller?.clear();
_controller?.removeListener(_userNameListener); _controller?.removeListener(_userNameListener);
_controller?.dispose(); _controller?.dispose();
userListController.dispose();
super.dispose(); super.dispose();
} }
@@ -228,77 +249,66 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
body: GestureDetector( body: GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
onPanDown: (_) => FocusScope.of(context).unfocus(), onPanDown: (_) => FocusScope.of(context).unfocus(),
child: UsersBloc( child: StreamUserListView(
child: StreamUserListView( controller: userListController,
selectedUsers: _selectedUsers, // groupAlphabetically: _isSearchActive ? false : true,
pullToRefresh: false, itemBuilder: (context, items, index, defaultWidget) {
groupAlphabetically: _isSearchActive ? false : true, return defaultWidget.copyWith(
onUserTap: (user, _) { selected: _selectedUsers.contains(items[index]),
if (!_selectedUsers.contains(user)) { );
setState(() { },
_selectedUsers.add(user); onUserTap: (user) {
}); if (!_selectedUsers.contains(user)) {
} else { setState(() {
setState(() { _selectedUsers.add(user);
_selectedUsers.remove(user); });
}); } else {
} setState(() {
}, _selectedUsers.remove(user);
limit: 25, });
filter: Filter.and([ }
if (_userNameQuery.isNotEmpty) },
Filter.autoComplete('name', _userNameQuery), emptyBuilder: (_) {
Filter.notEqual( return LayoutBuilder(
'id', StreamChat.of(context).currentUser!.id), builder: (context, viewportConstraints) {
]), return SingleChildScrollView(
sort: [ physics: AlwaysScrollableScrollPhysics(),
SortOption( child: ConstrainedBox(
'name', constraints: BoxConstraints(
direction: 1, minHeight: viewportConstraints.maxHeight,
), ),
], child: Center(
emptyBuilder: (_) { child: Column(
return LayoutBuilder( children: [
builder: (context, viewportConstraints) { Padding(
return SingleChildScrollView( padding: const EdgeInsets.all(24),
physics: AlwaysScrollableScrollPhysics(), child: StreamSvgIcon.search(
child: ConstrainedBox( size: 96,
constraints: BoxConstraints( color: StreamChatTheme.of(context)
minHeight: viewportConstraints.maxHeight, .colorTheme
), .textLowEmphasis,
child: Center(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(24),
child: StreamSvgIcon.search(
size: 96,
color: StreamChatTheme.of(context)
.colorTheme
.textLowEmphasis,
),
), ),
Text( ),
AppLocalizations.of(context) Text(
.noUserMatchesTheseKeywords, AppLocalizations.of(context)
style: StreamChatTheme.of(context) .noUserMatchesTheseKeywords,
.textTheme style: StreamChatTheme.of(context)
.footnote .textTheme
.copyWith( .footnote
color: StreamChatTheme.of(context) .copyWith(
.colorTheme color: StreamChatTheme.of(context)
.textLowEmphasis, .colorTheme
), .textLowEmphasis,
), ),
], ),
), ],
), ),
), ),
); ),
}, );
); },
}, );
), },
), ),
), ),
), ),
@@ -5,85 +5,94 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'channel_page.dart'; import 'channel_page.dart';
class UserMentionsPage extends StatelessWidget { class UserMentionsPage extends StatefulWidget {
@override
State<UserMentionsPage> createState() => _UserMentionsPageState();
}
class _UserMentionsPageState extends State<UserMentionsPage> {
late final controller = StreamMessageSearchListController(
client: StreamChat.of(context).client,
filter: Filter.in_('members', [StreamChat.of(context).currentUser!.id]),
messageFilter: Filter.custom(
operator: r'$contains',
key: 'mentioned_users.id',
value: StreamChat.of(context).currentUser!.id,
),
sort: [
SortOption(
'created_at',
direction: SortOption.ASC,
),
],
limit: 20,
);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final user = StreamChat.of(context).currentUser!; return StreamMessageSearchListView(
return MessageSearchBloc( controller: controller,
child: StreamMessageSearchListView( emptyBuilder: (_) {
filters: Filter.in_('members', [user.id]), return LayoutBuilder(
messageFilters: Filter.custom( builder: (context, viewportConstraints) {
operator: r'$contains', return SingleChildScrollView(
key: 'mentioned_users.id', physics: AlwaysScrollableScrollPhysics(),
value: user.id, child: ConstrainedBox(
), constraints: BoxConstraints(
sortOptions: [ minHeight: viewportConstraints.maxHeight,
SortOption( ),
'created_at', child: Center(
direction: SortOption.ASC, child: Column(
), children: [
], Padding(
limit: 20, padding: const EdgeInsets.all(24),
showResultCount: false, child: StreamSvgIcon.mentions(
emptyBuilder: (_) { size: 96,
return LayoutBuilder( color:
builder: (context, viewportConstraints) { StreamChatTheme.of(context).colorTheme.disabled,
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(24),
child: StreamSvgIcon.mentions(
size: 96,
color:
StreamChatTheme.of(context).colorTheme.disabled,
),
), ),
Text( ),
AppLocalizations.of(context).noMentionsExistYet, Text(
style: StreamChatTheme.of(context) AppLocalizations.of(context).noMentionsExistYet,
.textTheme style:
.body StreamChatTheme.of(context).textTheme.body.copyWith(
.copyWith( color: StreamChatTheme.of(context)
color: StreamChatTheme.of(context) .colorTheme
.colorTheme .textLowEmphasis,
.textLowEmphasis, ),
), ),
), ],
],
),
), ),
), ),
); ),
}, );
); },
}, );
onItemTap: (messageResponse) async { },
final client = StreamChat.of(context).client; onMessageTap: (messageResponse) async {
final message = messageResponse.message; final client = StreamChat.of(context).client;
final channel = client.channel( final message = messageResponse.message;
messageResponse.channel!.type, final channel = client.channel(
id: messageResponse.channel!.id, messageResponse.channel!.type,
); id: messageResponse.channel!.id,
if (channel.state == null) { );
await channel.watch(); if (channel.state == null) {
} await channel.watch();
Navigator.pushNamed( }
context, Navigator.pushNamed(
Routes.CHANNEL_PAGE, context,
arguments: ChannelPageArgs( Routes.CHANNEL_PAGE,
channel: channel, arguments: ChannelPageArgs(
initialMessage: message, channel: channel,
), initialMessage: message,
); ),
}, );
), },
); );
} }
@override
void dispose() {
controller.dispose();
super.dispose();
}
} }