@@ -1685,8 +1685,7 @@ class ChannelClientState {
|
|||||||
...messages!,
|
...messages!,
|
||||||
];
|
];
|
||||||
|
|
||||||
newThreads[parentId]!
|
newThreads[parentId]!.sort(_sortByCreatedAt);
|
||||||
.sort(_sortByCreatedAt as int Function(Message, Message)?);
|
|
||||||
} else {
|
} else {
|
||||||
newThreads[parentId] = messages;
|
newThreads[parentId] = messages;
|
||||||
}
|
}
|
||||||
@@ -1713,7 +1712,7 @@ class ChannelClientState {
|
|||||||
.any((newMessage) => newMessage.id == m.id) !=
|
.any((newMessage) => newMessage.id == m.id) !=
|
||||||
true)
|
true)
|
||||||
.toList(),
|
.toList(),
|
||||||
]..sort(_sortByCreatedAt as int Function(Message, Message)?);
|
]..sort(_sortByCreatedAt);
|
||||||
|
|
||||||
final newWatchers = <User>[
|
final newWatchers = <User>[
|
||||||
...updatedState.watchers,
|
...updatedState.watchers,
|
||||||
@@ -1752,17 +1751,8 @@ class ChannelClientState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
int? _sortByCreatedAt(a, b) {
|
int _sortByCreatedAt(Message a, Message b) =>
|
||||||
if (a.createdAt == null) {
|
a.createdAt.compareTo(b.createdAt);
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b.createdAt == null) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return a.createdAt.compareTo(b.createdAt);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The channel state related to this client
|
/// The channel state related to this client
|
||||||
ChannelState get _channelState => _channelStateController.value!;
|
ChannelState get _channelState => _channelStateController.value!;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import 'package:stream_chat/src/models/message.dart';
|
|||||||
import 'package:stream_chat/src/models/reaction.dart';
|
import 'package:stream_chat/src/models/reaction.dart';
|
||||||
import 'package:stream_chat/src/models/read.dart';
|
import 'package:stream_chat/src/models/read.dart';
|
||||||
import 'package:stream_chat/src/models/user.dart';
|
import 'package:stream_chat/src/models/user.dart';
|
||||||
|
import 'package:stream_chat/src/extensions/iterable_extension.dart';
|
||||||
|
|
||||||
/// A simple client used for persisting chat data locally.
|
/// A simple client used for persisting chat data locally.
|
||||||
abstract class ChatPersistenceClient {
|
abstract class ChatPersistenceClient {
|
||||||
@@ -78,7 +79,7 @@ abstract class ChatPersistenceClient {
|
|||||||
return ChannelState(
|
return ChannelState(
|
||||||
members: data[0] as List<Member>,
|
members: data[0] as List<Member>,
|
||||||
read: data[1] as List<Read>,
|
read: data[1] as List<Read>,
|
||||||
channel: data[2] as ChannelModel,
|
channel: data[2] as ChannelModel?,
|
||||||
messages: data[3] as List<Message>,
|
messages: data[3] as List<Message>,
|
||||||
pinnedMessages: data[4] as List<Message>,
|
pinnedMessages: data[4] as List<Message>,
|
||||||
);
|
);
|
||||||
@@ -192,17 +193,17 @@ abstract class ChatPersistenceClient {
|
|||||||
deleteMembers,
|
deleteMembers,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
final channels = cleanedChannelStates
|
final channels = cleanedChannelStates.map((it) => it.channel).withNullifyer;
|
||||||
.map((it) => it.channel)
|
|
||||||
.where((it) => it != null) as Iterable<ChannelModel>;
|
|
||||||
|
|
||||||
final reactions =
|
final reactions = cleanedChannelStates
|
||||||
cleanedChannelStates.expand((it) => it.messages).expand((it) => [
|
.expand((it) => it.messages)
|
||||||
|
.expand((it) => [
|
||||||
if (it.ownReactions != null)
|
if (it.ownReactions != null)
|
||||||
...it.ownReactions!.where((r) => r.userId != null),
|
...it.ownReactions!.where((r) => r.userId != null),
|
||||||
if (it.latestReactions != null)
|
if (it.latestReactions != null)
|
||||||
...it.latestReactions!.where((r) => r.userId != null),
|
...it.latestReactions!.where((r) => r.userId != null),
|
||||||
]);
|
])
|
||||||
|
.withNullifyer;
|
||||||
|
|
||||||
final users = cleanedChannelStates
|
final users = cleanedChannelStates
|
||||||
.map((cs) => [
|
.map((cs) => [
|
||||||
@@ -220,7 +221,7 @@ abstract class ChatPersistenceClient {
|
|||||||
...cs.members.map((m) => m.user),
|
...cs.members.map((m) => m.user),
|
||||||
])
|
])
|
||||||
.expand((it) => it)
|
.expand((it) => it)
|
||||||
.where((it) => it != null) as Iterable<User>;
|
.withNullifyer;
|
||||||
|
|
||||||
final updateMessagesFuture = cleanedChannelStates.map((it) {
|
final updateMessagesFuture = cleanedChannelStates.map((it) {
|
||||||
final cid = it.channel!.cid;
|
final cid = it.channel!.cid;
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
/// Useful extension functions for [Iterable]
|
||||||
|
extension IterableX<T> on Iterable<T?> {
|
||||||
|
/// Removes all the null values
|
||||||
|
/// and converts `Iterable<T?>` into `Iterable<T>`
|
||||||
|
Iterable<T> get withNullifyer => [
|
||||||
|
for (final item in this)
|
||||||
|
if (item != null) item
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -50,15 +50,16 @@ Future<void> main() async {
|
|||||||
|
|
||||||
/// Example using Stream's Low Level Dart client.
|
/// Example using Stream's Low Level Dart client.
|
||||||
class StreamExample extends StatelessWidget {
|
class StreamExample extends StatelessWidget {
|
||||||
/// To initialize this example, an instance of [client] and [channel] is required.
|
/// To initialize this example, an instance of
|
||||||
|
/// [client] and [channel] is required.
|
||||||
const StreamExample({
|
const StreamExample({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.client,
|
required this.client,
|
||||||
@required this.channel,
|
required this.channel,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
/// Instance of [StreamChatClient] we created earlier. This contains information about
|
/// Instance of [StreamChatClient] we created earlier.
|
||||||
/// our application and connection state.
|
/// This contains information about our application and connection state.
|
||||||
final StreamChatClient client;
|
final StreamChatClient client;
|
||||||
|
|
||||||
/// The channel we'd like to observe and participate.
|
/// The channel we'd like to observe and participate.
|
||||||
@@ -77,28 +78,31 @@ class StreamExample extends StatelessWidget {
|
|||||||
/// containing the channel name and a [MessageView] displaying recent messages.
|
/// containing the channel name and a [MessageView] displaying recent messages.
|
||||||
class HomeScreen extends StatelessWidget {
|
class HomeScreen extends StatelessWidget {
|
||||||
/// [HomeScreen] is constructed using the [Channel] we defined earlier.
|
/// [HomeScreen] is constructed using the [Channel] we defined earlier.
|
||||||
const HomeScreen({Key key, @required this.channel}) : super(key: key);
|
const HomeScreen({
|
||||||
|
Key? key,
|
||||||
|
required this.channel,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
/// Channel object containing the [Channel.id] we'd like to observe.
|
/// Channel object containing the [Channel.id] we'd like to observe.
|
||||||
final Channel channel;
|
final Channel channel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final messages = channel.state.channelStateStream;
|
final messages = channel.state!.channelStateStream;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text('Channel: ${channel.id}'),
|
title: Text('Channel: ${channel.id}'),
|
||||||
),
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: StreamBuilder<ChannelState>(
|
child: StreamBuilder<ChannelState?>(
|
||||||
stream: messages,
|
stream: messages,
|
||||||
builder: (
|
builder: (
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
AsyncSnapshot<ChannelState> snapshot,
|
AsyncSnapshot<ChannelState?> snapshot,
|
||||||
) {
|
) {
|
||||||
if (snapshot.hasData && snapshot.data != null) {
|
if (snapshot.hasData && snapshot.data != null) {
|
||||||
return MessageView(
|
return MessageView(
|
||||||
messages: snapshot.data.messages.reversed.toList(),
|
messages: snapshot.data!.messages.reversed.toList(),
|
||||||
channel: channel,
|
channel: channel,
|
||||||
);
|
);
|
||||||
} else if (snapshot.hasError) {
|
} else if (snapshot.hasError) {
|
||||||
@@ -110,8 +114,8 @@ class HomeScreen extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
return const Center(
|
return const Center(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 100.0,
|
width: 100,
|
||||||
height: 100.0,
|
height: 100,
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -127,9 +131,9 @@ class HomeScreen extends StatelessWidget {
|
|||||||
class MessageView extends StatefulWidget {
|
class MessageView extends StatefulWidget {
|
||||||
/// Message takes the latest list of messages and the current channel.
|
/// Message takes the latest list of messages and the current channel.
|
||||||
const MessageView({
|
const MessageView({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.messages,
|
required this.messages,
|
||||||
@required this.channel,
|
required this.channel,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
/// List of messages sent in the given channel.
|
/// List of messages sent in the given channel.
|
||||||
@@ -143,8 +147,8 @@ class MessageView extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MessageViewState extends State<MessageView> {
|
class _MessageViewState extends State<MessageView> {
|
||||||
TextEditingController _controller;
|
late final TextEditingController _controller;
|
||||||
ScrollController _scrollController;
|
late final ScrollController _scrollController;
|
||||||
|
|
||||||
List<Message> get _messages => widget.messages;
|
List<Message> get _messages => widget.messages;
|
||||||
|
|
||||||
@@ -182,20 +186,20 @@ class _MessageViewState extends State<MessageView> {
|
|||||||
reverse: true,
|
reverse: true,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
final item = _messages[index];
|
final item = _messages[index];
|
||||||
if (item.user.id == widget.channel.client.uid) {
|
if (item.user?.id == widget.channel.client.uid) {
|
||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Text(item.text),
|
child: Text(item.text ?? ''),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Text(item.text),
|
child: Text(item.text ?? ''),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -203,7 +207,7 @@ class _MessageViewState extends State<MessageView> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -251,7 +255,8 @@ class _MessageViewState extends State<MessageView> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper extension for quickly retrieving the current user id from a [StreamChatClient].
|
/// Helper extension for quickly retrieving
|
||||||
|
/// the current user id from a [StreamChatClient].
|
||||||
extension on StreamChatClient {
|
extension on StreamChatClient {
|
||||||
String get uid => state.user.id;
|
String get uid => state.user!.id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ publish_to: 'none'
|
|||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.7.0 <3.0.0"
|
sdk: ">=2.12.0 <3.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
|
|||||||
Reference in New Issue
Block a user