feat: Added gridview for channel list view and changed implementation for image sharing

This commit is contained in:
Deven Joshi
2020-12-22 17:45:08 +05:30
parent b0093dc065
commit 4aa2a8946a
5 changed files with 192 additions and 92 deletions
+6
View File
@@ -52,6 +52,7 @@ class ChannelImage extends StatelessWidget {
this.onTap,
this.showOnlineStatus = true,
this.borderRadius,
this.selected = false,
}) : super(key: key);
final BorderRadius borderRadius;
@@ -67,6 +68,8 @@ class ChannelImage extends StatelessWidget {
final bool showOnlineStatus;
final bool selected;
@override
Widget build(BuildContext context) {
final streamChat = StreamChat.of(context);
@@ -99,6 +102,7 @@ class ChannelImage extends StatelessWidget {
onTap();
}
: null,
selected: selected,
);
});
} else {
@@ -111,12 +115,14 @@ class ChannelImage extends StatelessWidget {
.toList();
return GroupImage(
images: images,
borderRadius: borderRadius,
constraints: constraints ??
StreamChatTheme.of(context)
.channelPreviewTheme
.avatarTheme
.constraints,
onTap: onTap,
selected: selected,
);
}
+76 -15
View File
@@ -74,6 +74,8 @@ class ChannelListView extends StatefulWidget {
this.onStartChatPressed,
this.swipeToAction = false,
this.pullToRefresh = true,
this.crossAxisCount = 1,
this.selectedChannels = const [],
}) : super(key: key);
/// 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
final VoidCallback onStartChatPressed;
/// The number of children in the cross axis.
final int crossAxisCount;
final List<Channel> selectedChannels;
@override
_ChannelListViewState createState() => _ChannelListViewState();
}
@@ -259,22 +266,35 @@ class _ChannelListViewState extends State<ChannelListView>
}
if (channels.isNotEmpty) {
child = ListView.custom(
physics: AlwaysScrollableScrollPhysics(),
controller: _scrollController,
childrenDelegate: SliverChildBuilderDelegate(
(context, i) {
return _itemBuilder(context, i, channels);
if (widget.crossAxisCount > 1) {
child = GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: widget.crossAxisCount),
itemCount: channels.length,
physics: AlwaysScrollableScrollPhysics(),
controller: _scrollController,
itemBuilder: (context, index) {
return _gridItemBuilder(context, index, 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;
},
),
);
);
} else {
child = ListView.custom(
physics: AlwaysScrollableScrollPhysics(),
controller: _scrollController,
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(
context,
ChannelsBlocState channelsProvider,
+36 -5
View File
@@ -9,21 +9,33 @@ class GroupImage extends StatelessWidget {
@required this.images,
this.constraints,
this.onTap,
this.borderRadius,
this.selected = false,
this.selectionColor = const Color(0xFF006CFF),
this.selectionThickness = 4,
}) : super(key: key);
final List<String> images;
final BoxConstraints constraints;
final VoidCallback onTap;
final bool selected;
final BorderRadius borderRadius;
final Color selectionColor;
final double selectionThickness;
@override
Widget build(BuildContext context) {
return GestureDetector(
var avatar;
final streamChatTheme = StreamChatTheme.of(context);
avatar = GestureDetector(
onTap: onTap,
child: ClipRRect(
borderRadius: StreamChatTheme.of(context)
.ownMessageTheme
.avatarTheme
.borderRadius,
borderRadius: borderRadius ??
StreamChatTheme.of(context)
.ownMessageTheme
.avatarTheme
.borderRadius,
child: Container(
constraints: constraints ??
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
View File
@@ -68,7 +68,7 @@ class _ImageFooterState extends State<ImageFooter> {
String _userNameQuery;
bool _isSearchActive = false;
Set<User> _selectedUsers = {};
List<Channel> _selectedChannels = [];
bool _loading = false;
Timer _debounce;
@@ -278,78 +278,86 @@ class _ImageFooterState extends State<ImageFooter> {
),
clipBehavior: Clip.antiAlias,
child: Scaffold(
resizeToAvoidBottomInset: false,
body: UsersBloc(
child: Column(
children: [
_buildTextInputSection(modalSetState),
if (_userSearchMode)
SizedBox(
height: 22.0,
),
Expanded(
child: UserListView(
selectedUsers: _selectedUsers,
onUserTap: (user, _) {
_searchController.clear();
if (!_selectedUsers.contains(user)) {
modalSetState(() {
_selectedUsers.add(user);
});
} else {
modalSetState(() {
_selectedUsers.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,
child: ChannelsBloc(
child: ChannelListView(
selectedChannels: _selectedChannels,
onChannelTap: (user, _) {
_searchController.clear();
if (!_selectedChannels.contains(user)) {
modalSetState(() {
_selectedChannels.add(user);
});
} else {
modalSetState(() {
_selectedChannels.remove(user);
});
}
},
},
sort: [
SortOption(
'name',
direction: 1,
crossAxisCount: 4,
pagination: PaginationParams(
limit: 25,
),
],
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,
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...'),
],
Text(
'No user matches these keywords...'),
],
),
),
),
),
);
},
);
},
);
},
);
},
),
),
),
if (_selectedUsers.isNotEmpty)
if (_selectedChannels.isNotEmpty)
_buildShareTextInputSection(modalSetState),
if (!_userSearchMode && _selectedUsers.isEmpty)
if (!_userSearchMode && _selectedChannels.isEmpty)
Align(
alignment: Alignment.bottomCenter,
child: Container(
@@ -614,25 +622,16 @@ class _ImageFooterState extends State<ImageFooter> {
final client = StreamChat.of(context).client;
for (var user in _selectedUsers) {
var c = client.channel('messaging', extraData: {
'members': [
user.id,
StreamChat.of(context).user.id,
],
});
await c.watch();
for (var channel in _selectedChannels) {
final message = Message(
text: text,
attachments: [attachments[widget.currentPage]],
);
await c.sendMessage(message);
await channel.sendMessage(message);
}
_selectedUsers.clear();
_selectedChannels.clear();
Navigator.pop(context);
}
+3
View File
@@ -61,12 +61,15 @@ class UserAvatar extends StatelessWidget {
: streamChatTheme.defaultUserImage(context, user),
),
);
if (selected) {
avatar = ClipRRect(
borderRadius: (borderRadius ??
streamChatTheme.ownMessageTheme.avatarTheme.borderRadius) +
BorderRadius.circular(selectionThickness),
child: Container(
constraints: constraints ??
streamChatTheme.ownMessageTheme.avatarTheme.constraints,
color: selectionColor,
child: Padding(
padding: EdgeInsets.all(selectionThickness),