feat: Added gridview for channel list view and changed implementation for image sharing
This commit is contained in:
@@ -52,6 +52,7 @@ class ChannelImage extends StatelessWidget {
|
|||||||
this.onTap,
|
this.onTap,
|
||||||
this.showOnlineStatus = true,
|
this.showOnlineStatus = true,
|
||||||
this.borderRadius,
|
this.borderRadius,
|
||||||
|
this.selected = false,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final BorderRadius borderRadius;
|
final BorderRadius borderRadius;
|
||||||
@@ -67,6 +68,8 @@ class ChannelImage extends StatelessWidget {
|
|||||||
|
|
||||||
final bool showOnlineStatus;
|
final bool showOnlineStatus;
|
||||||
|
|
||||||
|
final bool selected;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final streamChat = StreamChat.of(context);
|
final streamChat = StreamChat.of(context);
|
||||||
@@ -99,6 +102,7 @@ class ChannelImage extends StatelessWidget {
|
|||||||
onTap();
|
onTap();
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
|
selected: selected,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -111,12 +115,14 @@ class ChannelImage extends StatelessWidget {
|
|||||||
.toList();
|
.toList();
|
||||||
return GroupImage(
|
return GroupImage(
|
||||||
images: images,
|
images: images,
|
||||||
|
borderRadius: borderRadius,
|
||||||
constraints: constraints ??
|
constraints: constraints ??
|
||||||
StreamChatTheme.of(context)
|
StreamChatTheme.of(context)
|
||||||
.channelPreviewTheme
|
.channelPreviewTheme
|
||||||
.avatarTheme
|
.avatarTheme
|
||||||
.constraints,
|
.constraints,
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
selected: selected,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,6 +74,8 @@ class ChannelListView extends StatefulWidget {
|
|||||||
this.onStartChatPressed,
|
this.onStartChatPressed,
|
||||||
this.swipeToAction = false,
|
this.swipeToAction = false,
|
||||||
this.pullToRefresh = true,
|
this.pullToRefresh = true,
|
||||||
|
this.crossAxisCount = 1,
|
||||||
|
this.selectedChannels = const [],
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
/// The builder that will be used in case of error
|
/// The builder that will be used in case of error
|
||||||
@@ -134,6 +136,11 @@ class ChannelListView extends StatefulWidget {
|
|||||||
/// Callback used in the default empty list widget
|
/// Callback used in the default empty list widget
|
||||||
final VoidCallback onStartChatPressed;
|
final VoidCallback onStartChatPressed;
|
||||||
|
|
||||||
|
/// The number of children in the cross axis.
|
||||||
|
final int crossAxisCount;
|
||||||
|
|
||||||
|
final List<Channel> selectedChannels;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ChannelListViewState createState() => _ChannelListViewState();
|
_ChannelListViewState createState() => _ChannelListViewState();
|
||||||
}
|
}
|
||||||
@@ -259,22 +266,35 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (channels.isNotEmpty) {
|
if (channels.isNotEmpty) {
|
||||||
child = ListView.custom(
|
if (widget.crossAxisCount > 1) {
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
child = GridView.builder(
|
||||||
controller: _scrollController,
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
childrenDelegate: SliverChildBuilderDelegate(
|
crossAxisCount: widget.crossAxisCount),
|
||||||
(context, i) {
|
itemCount: channels.length,
|
||||||
return _itemBuilder(context, i, channels);
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
|
controller: _scrollController,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return _gridItemBuilder(context, index, channels);
|
||||||
},
|
},
|
||||||
childCount: (channels.length * 2) + 1,
|
);
|
||||||
findChildIndexCallback: (key) {
|
} else {
|
||||||
final ValueKey<String> valueKey = key;
|
child = ListView.custom(
|
||||||
final index = channels.indexWhere(
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
(channel) => 'CHANNEL-${channel.id}' == valueKey.value);
|
controller: _scrollController,
|
||||||
return index != -1 ? (index * 2) : null;
|
childrenDelegate: SliverChildBuilderDelegate(
|
||||||
},
|
(context, i) {
|
||||||
),
|
return _itemBuilder(context, i, channels);
|
||||||
);
|
},
|
||||||
|
childCount: (channels.length * 2) + 1,
|
||||||
|
findChildIndexCallback: (key) {
|
||||||
|
final ValueKey<String> valueKey = key;
|
||||||
|
final index = channels.indexWhere(
|
||||||
|
(channel) => 'CHANNEL-${channel.id}' == valueKey.value);
|
||||||
|
return index != -1 ? (index * 2) : null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -581,6 +601,47 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _gridItemBuilder(BuildContext context, int i, List<Channel> channels) {
|
||||||
|
var channel = channels[i];
|
||||||
|
|
||||||
|
var selected = widget.selectedChannels.contains(channel);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
key: ValueKey<String>('CHANNEL-${channel.id}'),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
ChannelImage(
|
||||||
|
channel: channel,
|
||||||
|
borderRadius: BorderRadius.circular(32),
|
||||||
|
selected: selected,
|
||||||
|
constraints: BoxConstraints.tightFor(
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
),
|
||||||
|
onTap: () {
|
||||||
|
widget.onChannelTap(channel, null);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: 7),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||||
|
child: StreamChannel(
|
||||||
|
child: ChannelName(
|
||||||
|
textStyle: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
channel: channel,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _buildQueryProgressIndicator(
|
Widget _buildQueryProgressIndicator(
|
||||||
context,
|
context,
|
||||||
ChannelsBlocState channelsProvider,
|
ChannelsBlocState channelsProvider,
|
||||||
|
|||||||
@@ -9,21 +9,33 @@ class GroupImage extends StatelessWidget {
|
|||||||
@required this.images,
|
@required this.images,
|
||||||
this.constraints,
|
this.constraints,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
|
this.borderRadius,
|
||||||
|
this.selected = false,
|
||||||
|
this.selectionColor = const Color(0xFF006CFF),
|
||||||
|
this.selectionThickness = 4,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final List<String> images;
|
final List<String> images;
|
||||||
final BoxConstraints constraints;
|
final BoxConstraints constraints;
|
||||||
final VoidCallback onTap;
|
final VoidCallback onTap;
|
||||||
|
final bool selected;
|
||||||
|
final BorderRadius borderRadius;
|
||||||
|
final Color selectionColor;
|
||||||
|
final double selectionThickness;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return GestureDetector(
|
var avatar;
|
||||||
|
final streamChatTheme = StreamChatTheme.of(context);
|
||||||
|
|
||||||
|
avatar = GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: ClipRRect(
|
child: ClipRRect(
|
||||||
borderRadius: StreamChatTheme.of(context)
|
borderRadius: borderRadius ??
|
||||||
.ownMessageTheme
|
StreamChatTheme.of(context)
|
||||||
.avatarTheme
|
.ownMessageTheme
|
||||||
.borderRadius,
|
.avatarTheme
|
||||||
|
.borderRadius,
|
||||||
child: Container(
|
child: Container(
|
||||||
constraints: constraints ??
|
constraints: constraints ??
|
||||||
StreamChatTheme.of(context)
|
StreamChatTheme.of(context)
|
||||||
@@ -91,5 +103,24 @@ class GroupImage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
avatar = ClipRRect(
|
||||||
|
borderRadius: (borderRadius ??
|
||||||
|
streamChatTheme.ownMessageTheme.avatarTheme.borderRadius) +
|
||||||
|
BorderRadius.circular(selectionThickness),
|
||||||
|
child: Container(
|
||||||
|
color: selectionColor,
|
||||||
|
height: 64.0,
|
||||||
|
width: 64.0,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(selectionThickness),
|
||||||
|
child: avatar,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return avatar;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+71
-72
@@ -68,7 +68,7 @@ class _ImageFooterState extends State<ImageFooter> {
|
|||||||
String _userNameQuery;
|
String _userNameQuery;
|
||||||
bool _isSearchActive = false;
|
bool _isSearchActive = false;
|
||||||
|
|
||||||
Set<User> _selectedUsers = {};
|
List<Channel> _selectedChannels = [];
|
||||||
bool _loading = false;
|
bool _loading = false;
|
||||||
|
|
||||||
Timer _debounce;
|
Timer _debounce;
|
||||||
@@ -278,78 +278,86 @@ class _ImageFooterState extends State<ImageFooter> {
|
|||||||
),
|
),
|
||||||
clipBehavior: Clip.antiAlias,
|
clipBehavior: Clip.antiAlias,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
body: UsersBloc(
|
body: UsersBloc(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
_buildTextInputSection(modalSetState),
|
_buildTextInputSection(modalSetState),
|
||||||
|
if (_userSearchMode)
|
||||||
|
SizedBox(
|
||||||
|
height: 22.0,
|
||||||
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: UserListView(
|
child: ChannelsBloc(
|
||||||
selectedUsers: _selectedUsers,
|
child: ChannelListView(
|
||||||
onUserTap: (user, _) {
|
selectedChannels: _selectedChannels,
|
||||||
_searchController.clear();
|
onChannelTap: (user, _) {
|
||||||
if (!_selectedUsers.contains(user)) {
|
_searchController.clear();
|
||||||
modalSetState(() {
|
if (!_selectedChannels.contains(user)) {
|
||||||
_selectedUsers.add(user);
|
modalSetState(() {
|
||||||
});
|
_selectedChannels.add(user);
|
||||||
} else {
|
});
|
||||||
modalSetState(() {
|
} else {
|
||||||
_selectedUsers.remove(user);
|
modalSetState(() {
|
||||||
});
|
_selectedChannels.remove(user);
|
||||||
}
|
});
|
||||||
},
|
}
|
||||||
crossAxisCount: 4,
|
|
||||||
pagination: PaginationParams(
|
|
||||||
limit: 25,
|
|
||||||
),
|
|
||||||
filter: {
|
|
||||||
if (_searchController.text.isNotEmpty)
|
|
||||||
'name': {
|
|
||||||
r'$autocomplete': _userNameQuery,
|
|
||||||
},
|
|
||||||
'id': {
|
|
||||||
r'$ne': StreamChat.of(context).user.id,
|
|
||||||
},
|
},
|
||||||
},
|
crossAxisCount: 4,
|
||||||
sort: [
|
pagination: PaginationParams(
|
||||||
SortOption(
|
limit: 25,
|
||||||
'name',
|
|
||||||
direction: 1,
|
|
||||||
),
|
),
|
||||||
],
|
filter: {
|
||||||
emptyBuilder: (_) {
|
if (_searchController.text.isNotEmpty)
|
||||||
return LayoutBuilder(
|
'name': {
|
||||||
builder: (context, viewportConstraints) {
|
r'$autocomplete': _userNameQuery,
|
||||||
return SingleChildScrollView(
|
},
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
'id': {
|
||||||
child: ConstrainedBox(
|
r'$ne': StreamChat.of(context).user.id,
|
||||||
constraints: BoxConstraints(
|
},
|
||||||
minHeight: viewportConstraints.maxHeight,
|
},
|
||||||
),
|
sort: [
|
||||||
child: Center(
|
SortOption(
|
||||||
child: Column(
|
'name',
|
||||||
children: [
|
direction: 1,
|
||||||
Padding(
|
),
|
||||||
padding: const EdgeInsets.all(24),
|
],
|
||||||
child: StreamSvgIcon.search(
|
emptyBuilder: (_) {
|
||||||
size: 96,
|
return LayoutBuilder(
|
||||||
color: Colors.grey,
|
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(
|
||||||
Text(
|
'No user matches these keywords...'),
|
||||||
'No user matches these keywords...'),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
},
|
||||||
},
|
);
|
||||||
);
|
},
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_selectedUsers.isNotEmpty)
|
if (_selectedChannels.isNotEmpty)
|
||||||
_buildShareTextInputSection(modalSetState),
|
_buildShareTextInputSection(modalSetState),
|
||||||
if (!_userSearchMode && _selectedUsers.isEmpty)
|
if (!_userSearchMode && _selectedChannels.isEmpty)
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -614,25 +622,16 @@ class _ImageFooterState extends State<ImageFooter> {
|
|||||||
|
|
||||||
final client = StreamChat.of(context).client;
|
final client = StreamChat.of(context).client;
|
||||||
|
|
||||||
for (var user in _selectedUsers) {
|
for (var channel in _selectedChannels) {
|
||||||
var c = client.channel('messaging', extraData: {
|
|
||||||
'members': [
|
|
||||||
user.id,
|
|
||||||
StreamChat.of(context).user.id,
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
await c.watch();
|
|
||||||
|
|
||||||
final message = Message(
|
final message = Message(
|
||||||
text: text,
|
text: text,
|
||||||
attachments: [attachments[widget.currentPage]],
|
attachments: [attachments[widget.currentPage]],
|
||||||
);
|
);
|
||||||
|
|
||||||
await c.sendMessage(message);
|
await channel.sendMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
_selectedUsers.clear();
|
_selectedChannels.clear();
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,12 +61,15 @@ class UserAvatar extends StatelessWidget {
|
|||||||
: streamChatTheme.defaultUserImage(context, user),
|
: streamChatTheme.defaultUserImage(context, user),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (selected) {
|
if (selected) {
|
||||||
avatar = ClipRRect(
|
avatar = ClipRRect(
|
||||||
borderRadius: (borderRadius ??
|
borderRadius: (borderRadius ??
|
||||||
streamChatTheme.ownMessageTheme.avatarTheme.borderRadius) +
|
streamChatTheme.ownMessageTheme.avatarTheme.borderRadius) +
|
||||||
BorderRadius.circular(selectionThickness),
|
BorderRadius.circular(selectionThickness),
|
||||||
child: Container(
|
child: Container(
|
||||||
|
constraints: constraints ??
|
||||||
|
streamChatTheme.ownMessageTheme.avatarTheme.constraints,
|
||||||
color: selectionColor,
|
color: selectionColor,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(selectionThickness),
|
padding: EdgeInsets.all(selectionThickness),
|
||||||
|
|||||||
Reference in New Issue
Block a user