tanutapi / dart_meteor

A Meteor DDP library for Dart/Flutter developers.
Other
37 stars 15 forks source link

How can we display photo ? #54

Closed Saeeed-B closed 2 years ago

Saeeed-B commented 2 years ago

@tanutapi How can we use the file package and display a photo or something? https://github.com/veliovgroup/Meteor-Files

This package works with file collections. To get the link of a file, the link method must be called a document. like this : https://github.com/VeliovGroup/Meteor-Files#stream-files.

But this method was not detected in your package.

tanutapi commented 2 years ago

I implemented some parts of it in my mobile application. My implementation is not a full implementation. It can do a simple upload, download, and display an uploaded image.

Here is my implementation ostrio_file_collection.dart

import 'dart:convert';
import 'dart:io';
import 'package:open_file/open_file.dart';
import 'package:path/path.dart' as p;
import 'package:http/http.dart' as http;
import 'package:dart_meteor/dart_meteor.dart';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:telemedicine_consumer_app/helpers/meteor_random.dart';

final mimeMapping = {
  'jpg': 'image/jpeg',
  'png': 'image/png',
};

class OstrioFileCollection {
  final String collectionName;
  final MeteorClient meteor;

  OstrioFileCollection(
      {this.collectionName = 'MeteorUploadFiles', required this.meteor});

  Map<String, String> get _methodName => {
        '_Abort': '_FilesCollectionAbort_$collectionName',
        '_Write': '_FilesCollectionWrite_$collectionName',
        '_Start': '_FilesCollectionStart_$collectionName',
        '_Remove': '_FilesCollectionRemove_$collectionName',
      };

  Future<dynamic> insert({
    required XFile file,
    String? fileId,
    Map<String, dynamic> meta = const {},
    int chunkSize = -1,
    Function(dynamic)? onUploaded,
    Function()? onStart,
    Function(MeteorError)? onError,
    Function(double)? onProgress,
    Function(dynamic)? onBeforeUpload,
    bool autoStart = true,
  }) async {
    if (await file.length() <= 0) {
      throw Exception('Can\'t upload empty file');
    }

    final fileSize = await file.length();
    final fileContent = await file.readAsBytes();
    final base64FileContent = base64Encode(fileContent);
    final base64ContentSize = base64FileContent.length;

    if (chunkSize == -1) {
      // dynamic
      chunkSize = (base64ContentSize / 1000).ceil();
      if (chunkSize < 327680) {
        chunkSize = 327680;
      } else if (chunkSize > 1048576) {
        chunkSize = 1048576;
      }
    }

    // Base64 chunk size
    chunkSize = (chunkSize / 4).floor() * 4;

    fileId ??= MeteorRandom.id();
    var totalChunk = (base64ContentSize / chunkSize).ceil();
    var fileExtension =
        file.name.substring(file.name.lastIndexOf('.') + 1).trim();
    var fileType = mimeMapping[fileExtension] ?? '';

    final fileData = {
      'size': fileSize,
      'type': fileType,
      'name': file.name,
      'meta': meta,
    };

    final optsStart = {
      'file': fileData,
      'fileId': fileId,
      'chunkSize': (chunkSize / 4 * 3).toInt(),
      'fileLength': totalChunk,
    };

    if (onBeforeUpload != null) {
      onBeforeUpload(fileData);
    }

    try {
      if (onStart != null) {
        onStart();
      }
      if (onProgress != null) {
        onProgress(0.0);
      }

      await meteor.call(_methodName['_Start']!, args: [optsStart]);

      for (var i = 0; i < totalChunk; i++) {
        var start = i * chunkSize;
        var end = (i + 1) * chunkSize;
        if (end > base64FileContent.length) {
          end = base64FileContent.length;
        }
        var chunkData = base64FileContent.substring(start, end);
        var padding = chunkData.length % 4;
        while (padding > 0) {
          chunkData += '=';
          padding--;
        }
        final optsWrite = {
          'fileId': fileId,
          'chunkId': i + 1,
          'binData': chunkData,
        };
        await meteor.call(_methodName['_Write']!, args: [optsWrite]);
        if (onProgress != null) {
          onProgress((i + 1) / totalChunk);
        }
      }

      final optsEof = {'eof': true, 'fileId': fileId};

      final uploadResult = await meteor.call(
        _methodName['_Write']!,
        args: [optsEof],
      );

      if (onProgress != null) {
        onProgress(1.0);
      }

      if (onUploaded != null) {
        onUploaded(uploadResult);
      }

      return uploadResult;
    } on MeteorError catch (error) {
      if (onError != null) {
        onError(error);
      }
      rethrow;
    }
  }
}

class MeteorTokenHttpClient extends http.BaseClient {
  final String sessionId;
  final http.Client _inner;

  MeteorTokenHttpClient(this.sessionId, this._inner);

  @override
  Future<http.StreamedResponse> send(http.BaseRequest request) {
    request.headers['cookie'] = 'x_mtok=$sessionId';
    return _inner.send(request);
  }
}

Future<bool> presentDownloadableFile({
  required MeteorClient meteor,
  required String uriString,
  Function(double)? progress,
}) async {
  final filename = p.basename(uriString);
  final uri = Uri.parse(uriString);
  final client = MeteorTokenHttpClient(
    meteor.connection.sessionId!,
    http.Client(),
  );
  final request = http.Request('GET', uri);
  final response = await client.send(request);
  var received = 0;

  if (response.statusCode == 200) {
    final tempDir = await getTemporaryDirectory();
    final outputFile = File('${tempDir.path}/$filename');
    final ioSink = outputFile.openWrite();
    await response.stream.map((chunk) {
      received += chunk.length;
      var prog = received / response.contentLength!;
      if (progress != null) {
        progress(prog);
      }
      return chunk;
    }).pipe(ioSink);
    await OpenFile.open(outputFile.path);
    return true;
  }
  return false;
}
tanutapi commented 2 years ago

Here is an example of sendFile function when user tap on the UI.

  void sendFile() async {
    var source = await showImageSourceDialog(context);
    var imagePicker = ImagePicker();
    if (source != null) {
      XFile? file = await imagePicker.pickImage(source: source);
      if (file != null) {
        final fileCollection = OstrioFileCollection(
          collectionName: 'chatUploadedFiles',
          meteor: meteor,
        );
        fileCollection.insert(
          file: file,
          meta: {
            'visitId': widget.visit!.id,
          },
        );
      }
    }
  }