handleDiff function
dynamic
handleDiff( - dynamic state,
- dynamic animatedList,
- dynamic diff
)
Implementation
@visibleForTesting
ChatRoomState handleDiff(
ChatRoomState state, // the current state
AnimatedListState? animatedList, // the animated list connected to this state
TimelineItemDiff diff, // the diff to apply
) {
final action = diff.action();
// the diff is applied in reverse order as the animated list is provided to UI in reverse .i.e. for improved scrolling
switch (action) {
case 'Append':
List<TimelineItem> incoming =
diff.values().expect('append diff must contain values').toList();
final messageList = state.messageList.toList();
final startLen = messageList.length;
final messages = Map.fromEntries(state.messages.entries);
for (final m in incoming) {
final uniqueId = m.uniqueId();
messages[uniqueId] = m;
messageList.insert(0, uniqueId);
}
final endLen = messageList.length;
animatedList?.insertAllItems(0, endLen - startLen);
return state.copyWith(messageList: messageList, messages: messages);
case 'Set': // used to update UnableToDecrypt message
TimelineItem m = diff.value().expect('set diff must contain value');
final index = diff.index().expect('set diff must contain index');
final reversedIndex =
state.messageList.isEmpty ? 0 : state.messageList.length - 1 - index;
final uniqueId = m.uniqueId();
if (state.messageList.isEmpty) {
animatedList?.insertItem(0);
return state.copyWith(messageList: [uniqueId], messages: {uniqueId: m});
}
final messageList = state.messageList.toList();
final removedItem = messageList.removeAt(reversedIndex);
messageList.insert(reversedIndex, uniqueId);
final messages = Map.fromEntries(
state.messages.entries.where((entry) => entry.key != removedItem),
);
messages[uniqueId] = m;
return state.copyWith(messageList: messageList, messages: messages);
case 'Insert':
TimelineItem m = diff.value().expect('insert diff must contain value');
final index = diff.index().expect('insert diff must contain index');
final reversedIndex =
state.messageList.isEmpty ? 0 : state.messageList.length - index;
return state.copyWithNewMessageAt(reversedIndex, m, animatedList);
case 'Remove':
int index = diff.index().expect('remove diff must contain index');
final reversedIndex =
state.messageList.isEmpty ? 0 : state.messageList.length - 1 - index;
return state.copyWithRemovedMessageAt(reversedIndex, animatedList);
case 'PushBack':
TimelineItem m = diff.value().expect('push back diff must contain value');
if (state.messageList.isEmpty) {
final uniqueId = m.uniqueId();
animatedList?.insertItem(0);
return state.copyWith(messageList: [uniqueId], messages: {uniqueId: m});
}
return state.copyWithNewMessageAt(0, m, animatedList);
case 'PushFront':
TimelineItem m = diff.value().expect(
'push front diff must contain value',
);
return state.copyWithNewMessageAt(
state.messageList.length,
m,
animatedList,
);
case 'PopBack':
if (state.messageList.isEmpty) {
return state;
}
return state.copyWithRemovedMessageAt(0, animatedList);
case 'PopFront':
return state.copyWithRemovedMessageAt(
state.messageList.length - 1,
animatedList,
);
case 'Clear':
if (state.messageList.isNotEmpty && animatedList != null) {
animatedList.removeAllItems((b, a) => const SizedBox.shrink());
}
return state.copyWith(messageList: [], messages: {});
case 'Reset':
List<TimelineItem> incoming =
diff.values().expect('reset diff must contain values').toList();
final (messageList, messages) = incoming.fold(
(List<String>.empty(growable: true), <String, TimelineItem>{}),
(val, m) {
final (list, map) = val;
final uniqueId = m.uniqueId();
list.insert(0, uniqueId);
map[uniqueId] = m;
return (list, map);
},
);
if (animatedList != null) {
animatedList.removeAllItems((b, a) => const SizedBox.shrink());
animatedList.insertAllItems(0, messageList.length);
}
return state.copyWith(messageList: messageList, messages: messages);
case 'Truncate':
if (state.messageList.isEmpty) {
return state;
}
final index = diff.index().expect('truncate diff must contain index');
final reversedIndex = state.messageList.length - index;
final messageList = state.messageList.toList();
final keptMessages = messageList.take(reversedIndex).toList();
final removedMessages = messageList.skip(reversedIndex).toList();
if (removedMessages.isEmpty) {
return state;
} else {
if (animatedList != null) {
for (var i = 0; i < removedMessages.length; i++) {
animatedList.removeItem(
reversedIndex + i,
(a, b) => const SizedBox.shrink(),
);
}
}
final messages = Map.fromEntries(
state.messages.entries.where(
(entry) => !removedMessages.contains(entry.key),
),
);
return state.copyWith(messageList: keptMessages, messages: messages);
}
default:
_log.severe('Unsupported action $action when diffing room messages');
break;
}
return state;
}