Process chat messages only when necessary

parent 037eb23d
......@@ -96,21 +96,31 @@ class Matrix {
}
}
void _processUser(MyUser user) {
void _processUser(MyUser user, {MyUser delta, bool wasTimelineLoad = false}) {
_user = user;
_chats = Map.fromEntries(
_user.rooms.where((r) => !r.isUpgraded).map(
(r) => MapEntry(
r.id,
r.toChat(myId: _user.id),
r.toChat(
myId: _user.id,
delta: delta != null ? delta.rooms[r.id] : null,
wasTimelineLoad: wasTimelineLoad,
),
),
),
);
}
void _processUpdate(Update update) {
_processUser(update.user);
final requestType = update is RequestUpdate ? update.type : null;
_processUser(
update.user,
delta: update.delta,
wasTimelineLoad: requestType == RequestType.loadRoomEvents,
);
_chatOrderBloc.add(
UpdateChatOrder(
......
......@@ -37,6 +37,13 @@ class Chat {
final ChatMessage latestMessage;
final ChatMessage latestMessageForSorting;
final List<ChatMessage> messages;
final List<ChatMessage> newMessages;
final bool endReached;
/// Whether this Chat instance was created because of a Timeline load.
final bool wasTimelineLoad;
final bool isJustMe;
final ChatMember directMember;
......@@ -53,6 +60,10 @@ class Chat {
@required this.room,
this.latestMessage,
this.latestMessageForSorting,
this.messages,
this.newMessages,
this.endReached,
this.wasTimelineLoad,
this.isJustMe = false,
this.directMember,
}) : name = room.name ??
......@@ -71,7 +82,11 @@ class Chat {
}
extension RoomToChat on Room {
Chat toChat({@required UserId myId}) {
Chat toChat({
@required UserId myId,
@required Room delta,
@required bool wasTimelineLoad,
}) {
// We should always have at least 30 items in the timeline, so don't load
var latestEvent = timeline.firstWhere(
(event) => !ignoredEvents.contains(event.runtimeType),
......@@ -101,6 +116,80 @@ extension RoomToChat on Room {
latestEventForSorting = latestEvent;
}
final messages = <ChatMessage>[];
RoomEvent event;
for (event in timeline) {
var shouldIgnore = false;
// In direct chats, don't show the invite event between this user
// and the direct user.
//
// Also in direct chats, don't show the join events between this user
// and the direct user.
if (isDirect) {
if (event is InviteEvent) {
final iInvitedYou =
event.senderId == me && event.subjectId == directUserId;
final youInvitedMe =
event.senderId == directUserId && event.subjectId == me.id;
shouldIgnore = iInvitedYou || youInvitedMe;
} else if (event is JoinEvent) {
final subject = event.subjectId;
shouldIgnore = subject == me || subject == directUserId;
}
}
shouldIgnore |= event is JoinEvent &&
event is! DisplayNameChangeEvent &&
creatorId == event.subjectId;
// Don't show creation events in rooms that are replacements
shouldIgnore |= event is RoomCreationEvent && isReplacement;
if (ignoredEvents.contains(event.runtimeType) || shouldIgnore) {
continue;
}
ChatMessage inReplyTo;
if (event is MessageEvent && event.content?.inReplyToId != null) {
// TODO: Might not be loaded?
final inReplyToEvent = timeline[event.content.inReplyToId];
if (inReplyToEvent != null) {
inReplyTo = ChatMessage(
this,
inReplyToEvent,
isMe: (id) => id == myId,
);
}
}
messages.add(
ChatMessage(
this,
event,
inReplyTo: inReplyTo,
isMe: (id) => id == myId,
),
);
}
var endReached = false;
if (event is RoomCreationEvent) {
endReached = true;
}
// Get messages new in update
final newMessages = delta?.timeline != null
? messages
.where(
(msg) => delta.timeline.any((event) => event.id == msg.event.id),
)
.toList()
: <ChatMessage>[];
return Chat(
room: this,
isJustMe: summary.joinedMembersCount == 1,
......@@ -118,6 +207,10 @@ extension RoomToChat on Room {
isMe: (id) => id == myId,
)
: null,
messages: messages,
newMessages: newMessages,
endReached: endReached,
wasTimelineLoad: wasTimelineLoad,
directMember: isDirect
? ChatMember.fromRoomAndUserId(
this,
......
......@@ -23,10 +23,8 @@ import 'package:pedantic/pedantic.dart';
import 'package:meta/meta.dart';
import '../../../models/chat.dart';
import '../../../models/chat_message.dart';
import '../../../matrix.dart';
import '../../../util/room.dart';
import '../../../notifications/bloc.dart';
......@@ -54,25 +52,14 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
) : _chat = _matrix.chats[roomId] {
_syncSub = _matrix.updatesFor(roomId).listen((update) {
_chat = update.chat;
print('${update.type}');
add(
RefreshChat(
chat: _chat,
delta: update.delta,
isBecauseOfTimelineRequest: update.type == RequestType.loadRoomEvents,
),
);
add(RefreshChat(chat: _chat));
});
}
MyUser get me => _matrix.user;
@override
ChatState get initialState => _loadMessages(
chat: _chat,
delta: _room.delta(),
becauseOfTimelineLoad: false,
);
ChatState get initialState => _processChat(chat: _chat);
@override
Stream<ChatState> mapEventToState(ChatEvent event) async* {
......@@ -82,11 +69,7 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
}
if (event is RefreshChat) {
yield _loadMessages(
chat: event.chat,
delta: event.delta,
becauseOfTimelineLoad: event.isBecauseOfTimelineRequest,
);
yield _processChat(chat: event.chat);
}
if (event is MarkAsRead) {
......@@ -94,96 +77,16 @@ class ChatBloc extends Bloc<ChatEvent, ChatState> {
}
}
ChatState _loadMessages({
ChatState _processChat({
@required Chat chat,
@required Room delta,
@required bool becauseOfTimelineLoad,
}) {
final room = chat.room;
final messages = <ChatMessage>[];
RoomEvent event;
for (event in room.timeline) {
var shouldIgnore = false;
// In direct chats, don't show the invite event between this user
// and the direct user.
//
// Also in direct chats, don't show the join events between this user
// and the direct user.
if (room.isDirect) {
if (event is InviteEvent) {
final iInvitedYou =
event.senderId == me && event.subjectId == room.directUserId;
final youInvitedMe =
event.senderId == room.directUserId && event.subjectId == me.id;
shouldIgnore = iInvitedYou || youInvitedMe;
} else if (event is JoinEvent) {
final subject = event.subjectId;
shouldIgnore = subject == me || subject == room.directUserId;
}
}
shouldIgnore |= event is JoinEvent &&
event is! DisplayNameChangeEvent &&
room.creatorId == event.subjectId;
// Don't show creation events in rooms that are replacements
shouldIgnore |= event is RoomCreationEvent && room.isReplacement;
if (room.ignoredEvents.contains(event.runtimeType) || shouldIgnore) {
continue;
}
ChatMessage inReplyTo;
if (event is MessageEvent && event.content?.inReplyToId != null) {
// TODO: Might not be loaded?
final inReplyToEvent = room.timeline[event.content.inReplyToId];
if (inReplyToEvent != null) {
inReplyTo = ChatMessage(
room,
inReplyToEvent,
isMe: (id) => id == _matrix.user.id,
);
}
}
messages.add(
ChatMessage(
room,
event,
inReplyTo: inReplyTo,
isMe: (id) => id == _matrix.user.id,
),
);
}
var endReached = false;
if (event is RoomCreationEvent) {
endReached = true;
}
// Get messages new in update
final newMessages = delta.timeline != null
? messages
.where(
(msg) => delta.timeline.any((event) => event.id == msg.event.id),
)
.toList()
: <ChatMessage>[];
if (messages.length + newMessages.length < _pageSize) {
if (!chat.endReached &&
chat.messages.length + chat.newMessages.length < _pageSize) {
add(LoadMoreFromTimeline());
}
return ChatState(
chat: chat,
messages: messages,
newMessages: newMessages,
endReached: endReached,
wasTimelineLoad: becauseOfTimelineLoad,
);
}
......
......@@ -15,7 +15,6 @@
// You should have received a copy of the GNU Affero General Public License
// along with Pattle. If not, see <https://www.gnu.org/licenses/>.
import 'package:matrix_sdk/matrix_sdk.dart';
import 'package:meta/meta.dart';
import 'package:equatable/equatable.dart';
......@@ -28,18 +27,13 @@ abstract class ChatEvent extends Equatable {
class RefreshChat extends ChatEvent {
final Chat chat;
final Room delta;
final bool isBecauseOfTimelineRequest;
RefreshChat({
@required this.chat,
@required this.delta,
@required this.isBecauseOfTimelineRequest,
});
@override
List<Object> get props => [chat, delta];
List<Object> get props => [chat];
}
class LoadMoreFromTimeline extends ChatEvent {}
......
......@@ -154,7 +154,7 @@ class _ChatPageWithBlocState extends State<_ChatPageWithBloc> {
}
void _onStateChange(BuildContext context, ChatState state) {
if (!state.wasTimelineLoad) {
if (!state.chat.wasTimelineLoad) {
context.bloc<ChatBloc>().add(MarkAsRead());
}
}
......@@ -274,7 +274,7 @@ class _MessageListState extends State<_MessageList> {
final state = bloc.state;
if (maxScroll - currentScroll <= _scrollThreshold &&
!state.endReached &&
!state.chat.endReached &&
!_requestingMore) {
_requestingMore = true;
bloc.add(LoadMoreFromTimeline());
......@@ -291,22 +291,22 @@ class _MessageListState extends State<_MessageList> {
return BlocConsumer<ChatBloc, ChatState>(
listener: (context, state) => _onStateChange(state),
builder: (context, state) {
var messages = state.messages;
if (state.wasTimelineLoad) {
messages = [...state.newMessages, ...messages];
final chat = state.chat;
var messages = chat.messages;
if (chat.wasTimelineLoad) {
messages = [...chat.newMessages, ...messages];
} else {
messages = [...messages, ...state.newMessages];
messages = [...messages, ...chat.newMessages];
}
return ListView.builder(
controller: _scrollController,
reverse: true,
padding: EdgeInsets.symmetric(horizontal: 16),
itemCount: state.endReached
? state.messages.length
: state.messages.length + 1,
itemCount:
chat.endReached ? chat.messages.length : chat.messages.length + 1,
itemBuilder: (context, index) {
if (index >= state.messages.length) {
if (index >= chat.messages.length) {
return Center(
child: Padding(
padding: EdgeInsets.all(8),
......@@ -315,7 +315,7 @@ class _MessageListState extends State<_MessageList> {
);
}
final message = state.messages[index];
final message = chat.messages[index];
final event = message.event;
......@@ -323,12 +323,12 @@ class _MessageListState extends State<_MessageList> {
// Note: Because the items are reversed in the
// ListView.builder, the 'previous' event is actually the next
// one in the list.
if (index != state.messages.length - 1) {
previousMessage = state.messages[index + 1];
if (index != chat.messages.length - 1) {
previousMessage = chat.messages[index + 1];
}
if (index != 0) {
nextMessage = state.messages[index - 1];
nextMessage = chat.messages[index - 1];
}
Widget bubble;
......
......@@ -19,23 +19,14 @@ import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
import '../../../models/chat.dart';
import '../../../models/chat_message.dart';
class ChatState extends Equatable {
final Chat chat;
final List<ChatMessage> messages;
final List<ChatMessage> newMessages;
final bool endReached;
final bool wasTimelineLoad;
ChatState({
@required this.chat,
@required this.messages,
@required this.newMessages,
@required this.endReached,
@required this.wasTimelineLoad,
});
@override
List<Object> get props => [chat, messages, endReached, wasTimelineLoad];
List<Object> get props => [chat];
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment