handleDiff function

dynamic handleDiff(
  1. dynamic state,
  2. dynamic animatedList,
  3. dynamic diff
)

Implementation

@visibleForTesting
ChatRoomState handleDiff(
  ChatRoomState state, // the current state
  AnimatedListState? animatedList, // the animated list connected to this state
  RoomMessageDiff diff, // the diff to apply
) {
  final action = diff.action();
  switch (action) {
    case 'Append':
      List<RoomMessage> 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.add(uniqueId);
      }
      final endLen = messageList.length;
      animatedList?.insertAllItems(startLen, endLen - startLen);
      return state.copyWith(
        messageList: messageList,
        messages: messages,
      );
    case 'Set': // used to update UnableToDecrypt message
      RoomMessage m = diff.value().expect('set diff must contain value');
      final index = diff.index().expect('set diff must contain 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(index);
      messageList.insert(index, 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':
      RoomMessage m = diff.value().expect('insert diff must contain value');
      final index = diff.index().expect('insert diff must contain index');
      return state.copyWithNewMessageAt(index, m, animatedList);
    case 'Remove':
      int index = diff.index().expect('remove diff must contain index');
      return state.copyWithRemovedMessageAt(index, animatedList);
    case 'PushBack':
      RoomMessage 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(
          state.messageList.length, m, animatedList,);
    case 'PushFront':
      RoomMessage m = diff.value().expect('push front diff must contain value');
      return state.copyWithNewMessageAt(0, m, animatedList);
    case 'PopBack':
      if (state.messageList.isEmpty) {
        return state;
      }
      return state.copyWithRemovedMessageAt(
        state.messageList.length - 1,
        animatedList,
      );
    case 'PopFront':
      return state.copyWithRemovedMessageAt(0, animatedList);
    case 'Clear':
      if (state.messageList.isNotEmpty && animatedList != null) {
        animatedList.removeAllItems((b, a) => const SizedBox.shrink());
      }
      return state.copyWith(messageList: [], messages: {});
    case 'Reset':
      List<RoomMessage> incoming =
          diff.values().expect('reset diff must contain values').toList();
      final (messageList, messages) = incoming
          .fold((List<String>.empty(growable: true), <String, RoomMessage>{}),
              (val, m) {
        final (list, map) = val;
        final uniqueId = m.uniqueId();
        list.add(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 (before, after) =
          state.messageList.fold((<String>[], <String>[]), (f, e) {
        final (before, after) = f;
        if (before.length >= index) {
          after.add(e);
        } else {
          before.add(e);
        }
        return (before, after);
      });
      if (after.isEmpty) {
        animatedList?.removeAllItems((a, b) => const SizedBox.shrink());
        return state.copyWith(
          messageList: before,
          messages: Map.fromEntries(
            state.messages.entries,
          ),
        );
      } else {
        if (animatedList != null) {
          for (var x = state.messageList.length; x >= after.length; x--) {
            // remove from the bottom up
            animatedList.removeItem(x - 1, (a, b) => const SizedBox.shrink());
          }
        }
        // we have to remove some
        final messages = Map.fromEntries(
          state.messages.entries.where((entry) => !after.contains(entry.key)),
        );
        return state.copyWith(messageList: before, messages: messages);
      }
    default:
      _log.severe('Unsupported action $action when diffing room messages');
      break;
  }
  return state;
}