sendMediaMessage method

Future<String> sendMediaMessage({
  1. required String filePath,
  2. required MediaType mediaType,
  3. required String targetId,
  4. String? caption,
})

Sends a media message (image, video, audio, or file).

The file is:

  1. Validated (size check against kMaxMediaFileSizeBytes).
  2. Copied to internal Airpass storage.
  3. SHA-256 hashed for integrity verification.
  4. Thumbnailed for instant preview (images only in v1).
  5. Saved as a message with metadata + embedded thumbnail.

The actual binary propagates via FILE payloads on-demand (Phase 3). The metadata + thumbnail propagate through epidemic routing immediately.

filePath — absolute path to the source file on the device. mediaType — the type of media (image, video, audio, file). targetId — the target (group ID, node UUID, or '*' for broadcast). caption — optional text caption alongside the media.

Throws MediaFileTooLargeException if the file exceeds the size limit. Throws FileSystemException if the file doesn't exist.

Returns the generated message ID.

Implementation

Future<String> sendMediaMessage({
  required String filePath,
  required MediaType mediaType,
  required String targetId,
  String? caption,
}) async {
  final file = File(filePath);

  // 1. Validate file exists
  if (!await file.exists()) {
    throw FileSystemException('File not found', filePath);
  }

  // 2. Validate file size
  final fileSize = await file.length();
  if (fileSize > kMaxMediaFileSizeBytes) {
    throw MediaFileTooLargeException(
      fileSize: fileSize,
      maxSize: kMaxMediaFileSizeBytes,
      fileName: file.uri.pathSegments.last,
    );
  }

  final messageId = _uuid.v4();
  final fileName = file.uri.pathSegments.last;

  // 3. Copy to internal storage
  final localPath = await _mediaStorage.saveMediaFromPath(
    messageId: messageId,
    sourcePath: filePath,
    fileName: fileName,
  );

  // 4. Compute SHA-256 hash
  final hash = await _mediaStorage.computeHash(localPath);

  // 5. Generate thumbnail (images only in v1)
  Uint8List? thumbnail;
  if (mediaType == MediaType.image) {
    final imageBytes = await file.readAsBytes();
    thumbnail = await _mediaStorage.generateThumbnail(imageBytes);
  }

  // 6. Determine MIME type from extension
  final mimeType = _inferMimeType(fileName, mediaType);

  // 7. Encode caption (or empty string) as the message payload
  final payloadBytes = utf8.encode(caption ?? '');

  // 8. Save to database
  await _db.createMessage(
    messageId: messageId,
    senderId: _localNodeId,
    targetId: targetId,
    payload: payloadBytes,
    ttl: kMediaDefaultMaxHops,
    mediaType: mediaType,
    mediaFileName: fileName,
    mediaMimeType: mimeType,
    mediaFileSize: fileSize,
    mediaHash: hash,
    mediaLocalPath: localPath,
    mediaAvailability: MediaAvailability.available, // We have it locally
    mediaThumbnail: thumbnail,
  );

  _log(
    'Saved outgoing media message $messageId '
    '(${mediaType.name}, $fileSize bytes) for target "$targetId"',
  );

  // Wake up the background service to broadcast metadata immediately
  triggerImmediateSync();

  return messageId;
}