uploadFile static method

Uploads a file to the Encatch server for a specific form question.

Accepts either a local file path (UploadFileRequest.filePath) or base64 content (UploadFileRequest.fileData) forwarded from the WebView form engine. UploadFileRequest.fileData may be raw base64 or a data URL.

Returns the permanent UploadFileResponse.fileUrl on success.

Implementation

static Future<UploadFileResponse> uploadFile(UploadFileRequest params) async {
  if (!_initialized) throw StateError('Encatch SDK is not initialized');

  final url = Uri.parse('$_apiBaseUrl/${_endpoints['upload']!}');
  final headers = _buildHeaders();

  late final String name;
  late final http.MultipartFile multipartFile;

  if (params.filePath != null) {
    final file = File(params.filePath!);
    name = params.fileName ?? file.uri.pathSegments.last;
    multipartFile = await http.MultipartFile.fromPath(
      'file',
      params.filePath!,
      filename: name,
    );
  } else {
    // base64 path — decode and build from bytes
    name = params.fileName ?? 'upload';
    final Uint8List bytes = base64Decode(
      _extractBase64Payload(params.fileData!),
    );
    multipartFile = http.MultipartFile.fromBytes(
      'file',
      bytes,
      filename: name,
      contentType: _uploadMediaType(params.mimeType),
    );
  }

  var lastProgressPercent = -1;
  void emitProgress(int percent) {
    final clampedPercent = percent.clamp(0, 100).toInt();
    if (clampedPercent == lastProgressPercent) return;
    lastProgressPercent = clampedPercent;
    params.onProgress?.call(clampedPercent);
  }

  final request =
      _ProgressMultipartRequest(
          'POST',
          url,
          onProgress: (bytesSent, totalBytes) {
            if (totalBytes <= 0) return;
            final percent = ((bytesSent / totalBytes) * 100).floor();
            final bucketedPercent = (percent ~/ 5) * 5;
            emitProgress(bucketedPercent);
          },
        )
        ..headers.addAll(headers)
        ..fields['formId'] = params.feedbackConfigurationId
        ..fields['questionId'] = params.questionId
        ..files.add(multipartFile);

  _logger.debug('Uploading file: $name');

  final streamedResponse = await _httpClient.send(request);
  final response = await http.Response.fromStream(streamedResponse);

  if (response.statusCode < 200 || response.statusCode >= 300) {
    throw Exception(
      'Encatch upload error: status ${response.statusCode} — ${response.body}',
    );
  }

  final decoded = jsonDecode(response.body) as Map<String, dynamic>;
  final result = UploadFileResponse.fromJson(decoded);
  _logger.debug('Upload complete: ${result.fileUrl}');

  emitProgress(100);
  return result;
}