harmony_flutter 支持分享文本、图片、视频和文件

203 阅读4分钟

HarmonyOS next之harmony_flutter 支持分享文本、图片、视频和文件

ohos端适配
import Log from '@ohos/flutter_ohos/src/main/ets/util/Log';
import UIAbility from '@ohos.app.ability.UIAbility';
import List from '@ohos.util.List';
import fs from '@ohos.file.fs';
import { fileUri } from '@kit.CoreFileKit';
import { MethodResult } from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodChannel';
import systemShare from '@hms.collaboration.systemShare';
import uniformTypeDescriptor from '@ohos.data.uniformTypeDescriptor';
​
const TAG = 'FlutterShare';
​
export default class Share {
  private  ability?: UIAbility | undefined = undefined;
​
  constructor() {
  }
​
  public setAbility(ability: UIAbility | undefined): void {
    this.ability = ability;
  }
​
  /**
   * 分享文本
   *
   * @param text 待分享文本
   * @param withResult 是否需要dismiss回调
   * @param result result
   */
  public shareText(text: string, withResult?: boolean, result?: MethodResult): void {
    if (!this.ability) {
      Log.w(TAG, "share ability not exist");
      return
    }
    Log.i(TAG, "share");
    let record: systemShare.SharedRecord = {
      utd: uniformTypeDescriptor.getUniformDataTypeByMIMEType('text/plain'),
      content: text || ''
    }
    let data = new systemShare.SharedData(record);
    this.startShare(data, withResult, result);
  }
​
  /**
   * 分享文件
   *
   * @param paths 待分享文件路径集合
   * @param subject 分享标题
   * @param withResult 是否需要dismiss回调
   * @param result result
   * @returns await
   */
  public async shareFiles(paths: List<string>, subject: string | null, withResult?: boolean, result?: MethodResult): Promise<void> {
    if (!this.ability) {
      Log.w(TAG, "shareFiles ability not exist");
      return
    }
    Log.i(TAG, "shareFiles");
    await this.clearShareCache()
    let fileUris = await this.getUrisForPaths(paths)
    Log.d(TAG, "shareFiles: path->" + JSON.stringify(fileUris));
    if (!fileUris || fileUris.length == 0) {
      Log.w(TAG, "shareFiles copy to cache failed");
      return
    }
    let data: systemShare.SharedData | null = null;
    fileUris.forEach((uri: string, index: number) => {
      let record: systemShare.SharedRecord = {
        utd: this.getUniformTypeDescriptor(uri),
        uri: uri,
        title: subject || ''
      }
      if (data) {
        data.addRecord(record);
      } else {
        data = new systemShare.SharedData(record);
      }
    })
    this.startShare(data, withResult, result);
  }
​
  private startShare(data: systemShare.SharedData | null, withResult?: boolean, result?: MethodResult): void {
    if (!this.ability || !data) {
      Log.w(TAG, "startShare ability not exist or data empty");
      return
    }
    Log.i(TAG, "startShare");
    try {
      let controller = new systemShare.ShareController(data);
      if (withResult) {
        controller.on('dismiss', () => {
          result?.success('');
        });
      }
      controller.show(this.ability!.context, {
        previewMode: systemShare.SharePreviewMode.DETAIL,
        selectionMode: systemShare.SelectionMode.BATCH
      });
    } catch (err) {
      Log.e(TAG, 'startShare err:' + JSON.stringify(err));
    }
  }
​
  private getUniformTypeDescriptor(fileUri: string): string {
    const dotIndex = fileUri.lastIndexOf('.');
    if (dotIndex != -1) {
      const fileExtension = '.' + fileUri.substring(dotIndex + 1);
      return uniformTypeDescriptor.getUniformDataTypeByFilenameExtension(fileExtension) || 'general.file';
    }
    return 'general.file';
  }
​
  /**
   * 分享路径
   *
   * @returns 分享路径
   */
  private shareCacheFolder(): string {
    if (!this.ability) {
      Log.w(TAG, "shareCacheFolder ability not exist");
      return ""
    }
    return this.ability.context.cacheDir + '/share_extend' || "";
  }
​
  /**
   * 获取复制到cache后的路径集合
   *
   * @param paths paths
   * @returns cache的路径集合
   */
  private async getUrisForPaths(paths: List<string>): Promise<Array<string>> {
    Log.i(TAG, "getUrisForPaths");
    let uris = new Array<string>();
    try {
      paths.forEach(async path => {
        let file: string = await this.copyToShareCache(path);
        if (file) {
          uris.push(fileUri.getUriFromPath(file));
        }
      })
    } catch (err) {
      Log.e(TAG, 'getUrisForPaths err:' + JSON.stringify(err));
    }
    return uris
  }
​
  /**
   * 清空cache缓存
   *
   * @returns 清空cache缓存
   */
  private async clearShareCache(): Promise<void> {
    Log.i(TAG, "clearShareCacheFolder");
    try {
      let folder: string = this.shareCacheFolder();
      if (folder && fs.accessSync(folder)) {
        let files = fs.listFileSync(folder);
        if (files.length > 0) {
          files.forEach(item => {
            fs.unlinkSync(folder + '/' + item);
          })
          fs.rmdirSync(folder);
        }
      }
    } catch (err) {
      Log.e(TAG, 'clearShareCacheFolder err:' + JSON.stringify(err));
    }
  }
​
  /**
   * 复制到缓存目录
   *
   * @param file 待复制文件
   * @returns 缓存路径
   */
  private async copyToShareCache(file: string): Promise<string> {
    let folder = this.shareCacheFolder();
    if (!folder) {
      return ""
    }
    Log.i(TAG, "copyToShareCache");
    if (!fs.accessSync(folder)) {
      fs.mkdirSync(folder);
    }
    let oldFile: fs.File = fs.openSync(file);
    let newFile = folder + '/' + oldFile.name;
    fs.copyFileSync(oldFile.fd, newFile);
    return newFile
  }
}
import {
  FlutterPlugin,
  FlutterPluginBinding
} from '@ohos/flutter_ohos/src/main/ets/embedding/engine/plugins/FlutterPlugin';
import MethodChannel from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodChannel';
import { AbilityAware, AbilityPluginBinding, Log } from '@ohos/flutter_ohos';
import MethodChannelHandlerImpl from './MethodChannelHandlerImpl';
import Share from './Share';
​
const TAG = "ShareExtendPlugin";
​
export default class ShareExtendPlugin implements FlutterPlugin, AbilityAware {
  private channel: MethodChannel | null = null;
  private methodChannelHandlerImpl: MethodChannelHandlerImpl | null = null;
  private share: Share | null = nullconstructor() {
  }
​
  getUniqueClassName(): string {
    return "ShareExtendPlugin"
  }
​
  onAttachedToAbility(binding: AbilityPluginBinding) {
    Log.i(TAG, "onAttachedToAbility");
    this.share?.setAbility(binding.getAbility());
  }
​
  onDetachedFromAbility() {
    Log.i(TAG, "onDetachedFromAbility");
    this.share?.setAbility(undefined);
  }
​
  onAttachedToEngine(binding: FlutterPluginBinding): void {
    this.share = new Share();
    this.methodChannelHandlerImpl = new MethodChannelHandlerImpl(this.share);
    this.channel = new MethodChannel(binding.getBinaryMessenger(), "com.zt.shareextend/share_extend");
    this.channel.setMethodCallHandler(this.methodChannelHandlerImpl);
  }
​
  onDetachedFromEngine(binding: FlutterPluginBinding): void {
    if (this.channel != null) {
      this.channel.setMethodCallHandler(null)
      this.channel = null;
    }
  }
}
import Log from '@ohos/flutter_ohos/src/main/ets/util/Log';
import { MethodCall } from '@ohos/flutter_ohos';
import { MethodCallHandler, MethodResult } from '@ohos/flutter_ohos/src/main/ets/plugin/common/MethodChannel';
import Share from './Share';
import { List } from '@kit.ArkTS';
​
const TAG = 'MethodCallHandlerImpl';
​
export default class MethodCallHandlerImpl implements MethodCallHandler {
  private share: Share | null = null;
​
  constructor(share: Share) {
    this.share = share;
  }
​
  onMethodCall(call: MethodCall, result: MethodResult): void {
    let method: string = call.method;
    Log.i(TAG, "onMethodCall: " + method);
    let type: string = call.argument("type");
    let list: List<string> = call.argument("list");
    let subject: string = call.argument('subject');
    try {
      switch (method) {
        case 'share':
          if ("text" === type) {
            this.share?.shareText(list[0]);
          } else {
            this.share?.shareFiles(list, subject);
          }
          break;
        default:
          result.notImplemented();
          break;
      }
    } catch (err) {
      Log.e(TAG, 'err:' + JSON.stringify(err));
    }
  }
}
import 'dart:async';
import 'package:flutter/services.dart';
import 'dart:ui';
​
class ShareExtend {
  static const MethodChannel _channel =
      const MethodChannel('com.zt.shareextend/share_extend');
​
  static Future<void> shareMultiple(List<String> list, String type,
      {Rect? sharePositionOrigin,
      String? sharePanelTitle,
      String subject = "",
      List<String>? extraTexts}) {
    assert(list.isNotEmpty);
    return _shareInner(list, type,
        sharePositionOrigin: sharePositionOrigin,
        subject: subject,
        sharePanelTitle: sharePanelTitle,
        extraTexts: extraTexts);
  }
​
  /// method to share with system ui
  ///  It uses the ACTION_SEND Intent on Android and UIActivityViewController
  /// on iOS.
  /// [list] can be text or path list
  /// [type]  "text", "image", "audio", "video" or "file"
  /// [sharePositionOrigin] only supports iPad os
  /// [sharePanelTitle] only supports android (some devices may not support)
  /// [subject] Intent.EXTRA_SUBJECT on Android and "subject" on iOS.
  /// [extraText] only supports android for Intent.EXTRA_TEXT when sharing image or file.
  ///
  static Future<void> share(String text, String type,
      {Rect? sharePositionOrigin,
      String? sharePanelTitle,
      String subject = "",
      String extraText = ""}) {
    assert(text.isNotEmpty);
    List<String> list = [text];
    return _shareInner(
      list,
      type,
      sharePositionOrigin: sharePositionOrigin,
      sharePanelTitle: sharePanelTitle,
      subject: subject,
      extraTexts: [extraText],
    );
  }
​
  static Future<void> _shareInner(List<String> list, String type,
      {Rect? sharePositionOrigin,
      String? sharePanelTitle,
      String? subject,
      List<String>? extraTexts}) {
    assert(list.isNotEmpty);
    final Map<String, dynamic> params = <String, dynamic>{
      'list': list,
      'type': type,
      'sharePanelTitle': sharePanelTitle,
      'subject': subject,
      'extraTexts': extraTexts
    };
    if (sharePositionOrigin != null) {
      params['originX'] = sharePositionOrigin.left;
      params['originY'] = sharePositionOrigin.top;
      params['originWidth'] = sharePositionOrigin.width;
      params['originHeight'] = sharePositionOrigin.height;
    }
    return _channel.invokeMethod('share', params);
  }
}
flutter端代分享类代码封装
import 'dart:async';
import 'package:flutter/services.dart';
import 'dart:ui';
​
/// Plugin for summoning a platform share sheet.
class ShareExtend {
  /// [MethodChannel] used to communicate with the platform side.
  static const MethodChannel _channel =
      const MethodChannel('com.zt.shareextend/share_extend');
​
  /// method to share with system ui
  ///  It uses the ACTION_SEND Intent on Android and UIActivityViewController
  /// on iOS.
  /// [list] can be text or path list
  /// [type]  "text", "image", "audio", "video" or "file"
  /// [sharePositionOrigin] only supports iPad os
  /// [sharePanelTitle] only supports android (some devices may not support)
  /// [subject] Intent.EXTRA_SUBJECT on Android and "subject" on iOS.
  /// [extraText] only supports android for Intent.EXTRA_TEXT when sharing image or file.
  ///
  static Future<void> shareMultiple(List<String> list, String type,
      {Rect? sharePositionOrigin,
      String? sharePanelTitle,
      String subject = "",
      List<String>? extraTexts}) {
    assert(list.isNotEmpty);
    return _shareInner(list, type,
        sharePositionOrigin: sharePositionOrigin,
        subject: subject,
        sharePanelTitle: sharePanelTitle,
        extraTexts: extraTexts);
  }
​
  /// method to share with system ui
  ///  It uses the ACTION_SEND Intent on Android and UIActivityViewController
  /// on iOS.
  /// [list] can be text or path list
  /// [type]  "text", "image", "audio", "video" or "file"
  /// [sharePositionOrigin] only supports iPad os
  /// [sharePanelTitle] only supports android (some devices may not support)
  /// [subject] Intent.EXTRA_SUBJECT on Android and "subject" on iOS.
  /// [extraText] only supports android for Intent.EXTRA_TEXT when sharing image or file.
  ///
  static Future<void> share(String text, String type,
      {Rect? sharePositionOrigin,
      String? sharePanelTitle,
      String subject = "",
      String extraText = ""}) {
    assert(text.isNotEmpty);
    List<String> list = [text];
    return _shareInner(
      list,
      type,
      sharePositionOrigin: sharePositionOrigin,
      sharePanelTitle: sharePanelTitle,
      subject: subject,
      extraTexts: [extraText],
    );
  }
​
  static Future<void> _shareInner(List<String> list, String type,
      {Rect? sharePositionOrigin,
      String? sharePanelTitle,
      String? subject,
      List<String>? extraTexts}) {
    assert(list.isNotEmpty);
    final Map<String, dynamic> params = <String, dynamic>{
      'list': list,
      'type': type,
      'sharePanelTitle': sharePanelTitle,
      'subject': subject,
      'extraTexts': extraTexts
    };
    if (sharePositionOrigin != null) {
      params['originX'] = sharePositionOrigin.left;
      params['originY'] = sharePositionOrigin.top;
      params['originWidth'] = sharePositionOrigin.width;
      params['originHeight'] = sharePositionOrigin.height;
    }
    return _channel.invokeMethod('share', params);
  }
}

这样就能通过调用ShareExtend中对应的方法来实现分享功能.