update single_conversation example

This commit is contained in:
Salvatore Giordano
2020-02-24 16:20:48 +01:00
parent 9f22241f89
commit d3b6a7e5e5
13 changed files with 714 additions and 260 deletions
+25
View File
@@ -0,0 +1,25 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
class ChannelImage extends StatelessWidget {
const ChannelImage({
Key key,
@required this.channel,
}) : super(key: key);
final Channel channel;
@override
Widget build(BuildContext context) {
return CircleAvatar(
radius: 20,
backgroundImage: channel.extraData.containsKey('image')
? CachedNetworkImageProvider(channel.extraData['image'] as String)
: null,
child: channel.extraData.containsKey('image')
? null
: Text(channel.config.name[0]),
);
}
}
+177
View File
@@ -0,0 +1,177 @@
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
import 'channel_preview.dart';
import 'stream_channel.dart';
import 'stream_chat.dart';
typedef ChannelPreviewBuilder = Widget Function(BuildContext, ChannelState);
typedef ChannelTapCallback = void Function(ChannelClient);
class ChannelListView extends StatefulWidget {
ChannelListView({
Key key,
ChannelPreviewBuilder channelPreviewBuilder,
this.filter,
this.options,
this.sort,
this.pagination,
this.onChannelTap,
}) : _channelPreviewBuilder = channelPreviewBuilder,
super(key: key);
final Map<String, dynamic> filter;
final Map<String, dynamic> options;
final List<SortOption> sort;
final PaginationParams pagination;
final ScrollController _scrollController = ScrollController();
final ChannelTapCallback onChannelTap;
final ChannelPreviewBuilder _channelPreviewBuilder;
@override
_ChannelListViewState createState() => _ChannelListViewState();
}
class _ChannelListViewState extends State<ChannelListView> {
@override
Widget build(BuildContext context) {
final streamChat = StreamChat.of(context);
return RefreshIndicator(
onRefresh: () async {
streamChat.clearChannels();
return streamChat.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination,
options: widget.options,
);
},
child: StreamBuilder<List<ChannelState>>(
stream: streamChat.channelsStream,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
print((snapshot.error as Error).stackTrace);
return Center(
child: Text(snapshot.error.toString()),
);
}
final channelsStates = snapshot.data;
return ListView.custom(
physics: AlwaysScrollableScrollPhysics(),
controller: widget._scrollController,
childrenDelegate: SliverChildBuilderDelegate(
(context, i) {
return _itemBuilder(context, i, channelsStates);
},
childCount: (channelsStates.length * 2) + 1,
findChildIndexCallback: (key) {
final ValueKey<String> valueKey = key;
final index = channelsStates.indexWhere(
(cs) => 'CHANNEL-${cs.channel.id}' == valueKey.value);
return index != -1 ? (index * 2) : null;
},
),
);
}),
);
}
Widget _itemBuilder(context, int i, List<ChannelState> channelsStates) {
if (i % 2 != 0) {
return _separatorBuilder(context, i);
}
i = i ~/ 2;
final streamChat = StreamChat.of(context);
if (i < channelsStates.length) {
final channelState = channelsStates[i];
final channelClient =
streamChat.client.channelClients[channelState.channel.id];
Widget child;
if (widget._channelPreviewBuilder != null) {
child = widget._channelPreviewBuilder(context, channelState);
} else {
child = ChannelPreview(
onTap: widget?.onChannelTap != null
? () {
widget?.onChannelTap(channelClient);
}
: null,
);
}
return StreamChannel(
key: ValueKey<String>('CHANNEL-${channelClient.id}'),
child: child,
channelClient: channelClient,
);
} else {
return _buildQueryProgressIndicator(context, streamChat);
}
}
Widget _buildQueryProgressIndicator(context, StreamChat streamChat) {
return StreamBuilder<bool>(
stream: streamChat.queryChannelsLoading,
initialData: false,
builder: (context, snapshot) {
return Container(
height: 100,
padding: EdgeInsets.all(32),
child: Center(
child: snapshot.data ? CircularProgressIndicator() : Container(),
),
);
});
}
Widget _separatorBuilder(context, i) {
return Container(
height: 1,
color: Colors.black.withOpacity(0.1),
margin: EdgeInsets.symmetric(horizontal: 16),
);
}
void _listenChannelPagination(StreamChat streamChat) {
if (widget._scrollController.position.maxScrollExtent ==
widget._scrollController.position.pixels) {
streamChat.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination.copyWith(
offset: streamChat.channels.length,
),
options: widget.options,
);
}
}
@override
void initState() {
super.initState();
final streamChat = StreamChat.of(context);
streamChat.queryChannels(
filter: widget.filter,
sortOptions: widget.sort,
paginationParams: widget.pagination,
options: widget.options,
);
widget._scrollController.addListener(() {
_listenChannelPagination(streamChat);
});
}
}
+19
View File
@@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import 'package:stream_chat/stream_chat.dart';
class ChannelNameText extends StatelessWidget {
const ChannelNameText({
Key key,
this.channel,
}) : super(key: key);
final Channel channel;
@override
Widget build(BuildContext context) {
return Text(
channel.extraData['name'] as String ?? channel.config.name,
style: Theme.of(context).textTheme.body2,
);
}
}
+148
View File
@@ -0,0 +1,148 @@
import 'package:date_format/date_format.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:stream_chat/stream_chat.dart';
import 'channel_image.dart';
import 'channel_name_text.dart';
import 'stream_channel.dart';
import 'stream_chat.dart';
class ChannelPreview extends StatelessWidget {
final VoidCallback onTap;
const ChannelPreview({
Key key,
@required this.onTap,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final streamChannel = StreamChannel.of(context);
return StreamBuilder<ChannelState>(
stream: streamChannel.channelClient.state.channelStateStream,
initialData: streamChannel.channelState,
builder: (context, snapshot) {
final channelState = snapshot.data;
return _buildChannelPreview(
context,
channelState,
streamChannel,
);
});
}
StreamChannel _buildChannelPreview(
BuildContext context,
ChannelState channelState,
StreamChannelState streamChannel,
) {
return StreamChannel(
channelClient:
StreamChat.of(context).client.channelClients[channelState.channel.id],
child: ListTile(
onTap: () {
if (onTap != null) {
onTap();
}
},
leading: ChannelImage(
channel: channelState.channel,
),
title: ChannelNameText(
channel: channelState.channel,
),
subtitle: _buildSubtitle(
streamChannel,
),
trailing: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
_buildDate(context, channelState.channel.lastMessageAt),
],
),
),
);
}
Text _buildDate(BuildContext context, DateTime lastMessageAt) {
String stringDate;
final now = DateTime.now();
if (now.year != lastMessageAt.year ||
now.month != lastMessageAt.month ||
now.day != lastMessageAt.day) {
stringDate =
'${lastMessageAt.day}/${lastMessageAt.month}/${lastMessageAt.year}';
stringDate = formatDate(lastMessageAt, [dd, '/', mm, '/', yyyy]);
} else {
stringDate = '${lastMessageAt.hour}:${lastMessageAt.minute}';
stringDate = formatDate(lastMessageAt, [HH, ':', nn]);
}
return Text(
stringDate,
style: Theme.of(context).textTheme.caption,
);
}
Widget _buildSubtitle(
StreamChannelState streamChannel,
) {
return StreamBuilder<List<User>>(
stream: streamChannel.channelClient.state.typingEventsStream,
initialData: [],
builder: (context, snapshot) {
final typings = snapshot.data;
final opacity =
streamChannel.channelClient.state.unreadCount > .0 ? 1.0 : 0.5;
return typings.isNotEmpty
? _buildTypings(typings, context, opacity)
: _buildLastMessage(context, streamChannel, opacity);
});
}
Widget _buildLastMessage(
BuildContext context, StreamChannelState streamChannel, double opacity) {
final lastMessage = streamChannel.channelState.messages.isNotEmpty
? streamChannel.channelState.messages.last
: null;
if (lastMessage == null) {
return SizedBox.fromSize(
size: Size.zero,
);
}
final prefix = lastMessage.attachments
.map((e) {
if (e.type == 'image') {
return '📷';
} else if (e.type == 'video') {
return '🎬';
}
return null;
})
.where((e) => e != null)
.join(' ');
return Text(
'$prefix ${lastMessage.text ?? ''}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.caption.copyWith(
color: Colors.black.withOpacity(opacity),
),
);
}
Text _buildTypings(List<User> typings, BuildContext context, double opacity) {
return Text(
'${typings.map((u) => u.extraData.containsKey('name') ? u.extraData['name'] : u.id).join(',')} ${typings.length == 1 ? 'is' : 'are'} typing...',
maxLines: 1,
style: Theme.of(context).textTheme.caption.copyWith(
color: Colors.black.withOpacity(opacity),
),
);
}
}
+1 -1
View File
@@ -256,7 +256,7 @@ class _MessageListViewState extends State<MessageListView> {
_messages = newMessages;
});
} else if (newMessages.first.user.id ==
streamChannel.channelClient.client.user.id) {
streamChannel.channelClient.client.state.user.id) {
_scrollController.jumpTo(0);
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
+16 -19
View File
@@ -30,18 +30,18 @@ class StreamChannel extends StatefulWidget {
}
@override
StreamChannelState createState() => StreamChannelState(channelClient);
StreamChannelState createState() => StreamChannelState();
}
class StreamChannelState extends State<StreamChannel> {
final ChannelClient channelClient;
StreamChannelState();
StreamChannelState(this.channelClient);
ChannelClient get channelClient => widget.channelClient;
ChannelState get channelState => channelClient.state.channelState;
ChannelState get channelState => widget.channelClient.state.channelState;
Stream<ChannelState> get channelStateStream =>
channelClient.state.channelStateStream;
widget.channelClient.state.channelStateStream;
final BehaviorSubject<bool> _queryMessageController = BehaviorSubject();
@@ -55,7 +55,7 @@ class StreamChannelState extends State<StreamChannel> {
firstId = channelState.messages.first.id;
}
channelClient
widget.channelClient
.query(
messagesPagination: PaginationParams(
lessThan: firstId,
@@ -73,11 +73,11 @@ class StreamChannelState extends State<StreamChannel> {
_queryMessageController.add(true);
String firstId;
if (channelClient.state.threads.containsKey(parentId)) {
firstId = channelClient.state.threads[parentId].first.id;
if (widget.channelClient.state.threads.containsKey(parentId)) {
firstId = widget.channelClient.state.threads[parentId].first.id;
}
return channelClient
return widget.channelClient
.getReplies(
parentId,
PaginationParams(
@@ -95,29 +95,26 @@ class StreamChannelState extends State<StreamChannel> {
@override
void dispose() {
_queryMessageController.close();
channelClient.dispose();
widget.channelClient.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
if (channelClient == null) {
if (widget.channelClient == null) {
return Center(
child: CircularProgressIndicator(),
);
}
return FutureBuilder<bool>(
future: channelClient.initialized,
future: widget.channelClient.initialized,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
} else if (snapshot.hasError) {
return Center(
+2 -19
View File
@@ -27,25 +27,9 @@ class StreamChat extends InheritedWidget {
}));
}
User get user => _userController.value;
User get user => client.state.user;
Stream<User> get userStream => _userController.stream;
final BehaviorSubject<User> _userController = BehaviorSubject();
Future<void> setUser(User newUser, [String token]) async {
_userController.sink.add(null);
try {
if (token != null) {
await client.setUser(newUser, token);
} else {
await client.setUserWithProvider(newUser);
}
_userController.sink.add(newUser);
} catch (e, stack) {
_userController.sink.addError(e, stack);
}
}
Stream<User> get userStream => client.state.userStream;
Stream<List<ChannelState>> get channelsStream => _channelsController.stream;
final BehaviorSubject<List<ChannelState>> _channelsController =
@@ -91,7 +75,6 @@ class StreamChat extends InheritedWidget {
void dispose() {
client.dispose();
_subscriptions.forEach((s) => s.cancel());
_userController.close();
_queryChannelsLoadingController.close();
_channelsController.close();
}