postChatMessage method

Future<ChatItem> postChatMessage({
  1. ChatItemId? existingId,
  2. PreciseDateTime? existingDateTime,
  3. ChatMessageText? text,
  4. List<Attachment>? attachments,
  5. List<ChatItem> repliesTo = const [],
})

Posts a new ChatMessage to the specified Chat by the authenticated MyUser.

For the posted ChatMessage to be meaningful, at least one of text or attachments arguments must be specified and non-empty.

Specify repliesTo argument if the posted ChatMessage is going to be a reply to some other ChatItem.

Implementation

Future<ChatItem> postChatMessage({
  ChatItemId? existingId,
  PreciseDateTime? existingDateTime,
  ChatMessageText? text,
  List<Attachment>? attachments,
  List<ChatItem> repliesTo = const [],
}) async {
  Log.debug(
    'postChatMessage($existingId, $existingDateTime, $text, $attachments, $repliesTo)',
    '$runtimeType($id)',
  );

  DtoChatMessage message = DtoChatMessage.sending(
    chatId: chat.value.id,
    me: me!,
    text: text,
    repliesTo: repliesTo.map((e) => ChatItemQuote.from(e)).toList(),
    attachments: attachments ?? [],
    existingId: existingId,
    existingDateTime: existingDateTime,
  );

  // Storing the already stored [ChatMessage] is meaningless as it creates
  // lag spikes, so update it's reactive value directly.
  if (existingId != null) {
    final Rx<ChatItem>? existing = messages.firstWhereOrNull(
      (e) => e.value.id == existingId,
    );
    existing?.value = message.value;
  } else {
    put(message);
  }

  // If the [ChatMessage] being posted is local, then no remote queries should
  // be performed, so return the constructed item right away.
  if (id.isLocal) {
    return message.value;
  }

  _pending.add(message.value);

  try {
    if (attachments != null) {
      final List<Future> uploads =
          attachments
              .mapIndexed((i, e) {
                if (e is LocalAttachment) {
                  return e.upload.value?.future.then(
                    (a) {
                      attachments[i] = a;

                      // Frequent writes of byte data freezes the Web page.
                      if (!PlatformUtils.isWeb) {
                        put(message);
                      }
                    },
                    onError: (_) {
                      // No-op, as failed upload attempts are handled below.
                    },
                  );
                }
              })
              .nonNulls
              .toList();

      if (existingId == null) {
        final List<Future> reads =
            attachments
                .whereType<LocalAttachment>()
                .map((e) => e.read.value?.future)
                .nonNulls
                .toList();
        if (reads.isNotEmpty) {
          await Future.wait(reads);
          put(message);
        }
      }

      await Future.wait(uploads);
    }

    if (attachments?.whereType<LocalAttachment>().isNotEmpty == true) {
      throw const ConnectionException(
        PostChatMessageException(PostChatMessageErrorCode.unknownAttachment),
      );
    }

    final response = await _chatRepository.postChatMessage(
      id,
      text: text,
      attachments: attachments?.map((e) => e.id).toList(),
      repliesTo: repliesTo.map((e) => e.id).toList(),
    );

    final event =
        response?.events
                .map((e) => _chatRepository.chatEvent(e))
                .firstWhereOrNull((e) => e is EventChatItemPosted)
            as EventChatItemPosted?;

    if (event != null && event.item is DtoChatMessage) {
      remove(message.value.id);
      _pending.remove(message.value);
      message = event.item as DtoChatMessage;
    }
  } catch (e) {
    message.value.status.value = SendingStatus.error;
    _pending.remove(message.value);
    rethrow;
  } finally {
    put(message);
  }

  return message.value;
}