@react-native-oh-tpl/react-native-file-viewer
Version:
Native file viewer for react-native
218 lines (189 loc) • 7.55 kB
text/typescript
/*
* Copyright (c) 2024 Huawei Device Co., Ltd. All rights reserved
* Use of this source code is governed by a MIT license that can be
* found in the LICENSE file.
*/
import { TurboModule, TurboModuleContext } from '@rnoh/react-native-openharmony/ts';
import { TM } from '@rnoh/react-native-openharmony/generated/ts';
import { BusinessError } from '@kit.BasicServicesKit';
import { filePreview } from '@kit.PreviewKit';
import fileUri from '@ohos.file.fileuri';
import { Context, common, Want } from '@kit.AbilityKit';
import wantConstant from '@ohos.app.ability.wantConstant';
import fs from '@ohos.file.fs';
import mime from 'mime';
import EventEmitter from './EventMitter';
import Hilog from './Logger';
import type { ISubscribe } from './EventMitter';
import { fileShare } from '@kit.CoreFileKit';
type EmitID = ISubscribe['id'];
interface RNFileViewerOptions {
displayName?: string;
showAppsSuggestions?: boolean;
showOpenWithDialog?: boolean;
}
// 派发事件名称
const OPEN_EVENT: string = 'RNFileViewerDidOpen';
const DISMISS_EVENT: string = 'RNFileViewerDidDismiss';
const eventEmitter = new EventEmitter();
export class RNFileViewerTurboModule extends TurboModule implements TM.FileViewerNativeModule.Spec {
constructor(protected ctx: TurboModuleContext) {
super(ctx);
this.ctx = ctx;
}
open(filepath: string, currentId: number, options: RNFileViewerOptions | string = {}): Promise<void> {
const _options = typeof options === 'string' ? { displayName: options } : options;
return new Promise((resolve, reject) => {
const openSubscription = eventEmitter.$on(
OPEN_EVENT,
(id: EmitID, error: string) => {
if (id === openSubscription.id) {
openSubscription.$off();
this.ctx.rnInstance.emitDeviceEvent(OPEN_EVENT, { id: currentId });
return error ? reject(new Error(error)) : resolve();
}
},
);
const dismissSubscription = eventEmitter.$on(
DISMISS_EVENT,
(id: EmitID) => {
if (id === dismissSubscription.id) {
dismissSubscription.$off();
this.ctx.rnInstance.emitDeviceEvent(DISMISS_EVENT, { id: currentId });
}
},
);
// 判断文件是否存在
fs.stat(filepath)
.then((stat: fs.Stat) => {
this.OpenFile(filepath, this.ctx, _options);
resolve();
})
.catch((err: BusinessError) => {
Hilog.error('get file info failed with error message: ' + err.message + ', error code: ' + err.code);
reject('get file info failed with error code: ' + err.code);
throw new Error('err.code:' + err.code);
});
});
}
private async OpenFile(uri: string, ctx: TurboModuleContext, options: RNFileViewerOptions): Promise<void> {
const { showOpenWithDialog, showAppsSuggestions, displayName } = options;
const uiContext: Context = ctx.uiAbilityContext;
// 获取前提信息
const fUri: fileUri.FileUri = new fileUri.FileUri(uri);
const filename: string = fUri.name;
const fileExt: string = filename.substring(filename.lastIndexOf('.'));
const fileMimeType: string = mime.getType(fileExt);
const context = uiContext as common.UIAbilityContext; // UIAbilityContext
// 是否支持直接预览
const canPreviewFlag: boolean = await filePreview.canPreview(
uiContext,
uri,
);
// 非多种预览方式,直接进行预览
if (canPreviewFlag && !showOpenWithDialog) {
const showFileName = displayName ? displayName : filename.split('.')[0];
// action: 'ohos.want.action.viewData'
this.OpenByFilePreview(ctx, uri, fileMimeType, showFileName);
return;
}
let want: Want = {
type: fileMimeType,
uri: 'file://' + uri,
flags: wantConstant.Flags.FLAG_AUTH_READ_URI_PERMISSION,
};
// 跳转应用商店
if (showAppsSuggestions) {
want = {
uri: `store://appgallery.huawei.com/app`,
};
}
try {
context.startAbilityForResult(
want,
(err: BusinessError, result: common.AbilityResult) => {
if (err.code) {
// 处理业务逻辑错误
Hilog.error(`startAbilityForResult failed, code is ${err.code}`);
throw new Error('err.code:' + err.code);
}
eventEmitter.$emit(OPEN_EVENT);
// 关闭ability
if (result.resultCode === -1) {
eventEmitter.$emit(DISMISS_EVENT);
}
},
);
} catch (error) {
Hilog.error(`Failed to startAbility. message: ${error}`);
eventEmitter.$emit(OPEN_EVENT, error);
}
}
// filePreview能够展示自定义name,无关闭回调
private OpenByFilePreview(
ctx: TurboModuleContext,
uri: string,
fileMimeType: string,
filename: string,
): void {
const uiContext: Context = ctx.uiAbilityContext;
// 计算打开窗口大小及居中位置
const screenInfo = ctx.getDisplayMetrics().screenPhysicalPixels;
const screenWidth = screenInfo.width / screenInfo.scale;
const screenHeight = screenInfo.height / screenInfo.scale;
const offsetX =
screenInfo.width > screenWidth ? (screenInfo.width - screenWidth) / 2 : 0;
const offsetY =
screenInfo.height > screenHeight
? (screenInfo.height - screenHeight) / 2
: 0;
if (!canIUse('SystemCapability.FileManagement.AppFileService.FolderAuthorization')) {
console.error('this api is not supported on this device');
return;
}
let policyInfo: fileShare.PolicyInfo = {
uri: fileUri.getUriFromPath(uri),
operationMode: fileShare.OperationMode.READ_MODE,
};
let policies: Array<fileShare.PolicyInfo> = [policyInfo];
fileShare.persistPermission(policies).then(() => {
console.info("persistPermission successfully");
// 调用OS filePreview 进行预览
const displayInfo: filePreview.DisplayInfo = {
x: offsetX,
y: offsetY,
width: screenWidth,
height: screenHeight,
};
const fileInfo: filePreview.PreviewInfo = {
title: filename,
uri: fileUri.getUriFromPath(uri),
mimeType: fileMimeType,
};
// 打开预览窗口
filePreview
.openPreview(uiContext, fileInfo, displayInfo)
.then(() => {
eventEmitter.$emit(OPEN_EVENT);
})
.catch((err: BusinessError) => {
Hilog.error(`Failed to openPreview, err.code = ${err.code}`);
eventEmitter.$emit(OPEN_EVENT, err);
throw new Error('err.code:' + err.code);
});
}).catch((err: BusinessError<Array<fileShare.PolicyErrorResult>>) => {
console.error("persistPermission failed with error message: " + err.message + ", error code: " + err.code);
if (err.code == 13900001 && err.data) {
for (let i = 0; i < err.data.length; i++) {
console.error("error code : " + JSON.stringify(err.data[i].code));
console.error("error uri : " + JSON.stringify(err.data[i].uri));
console.error("error reason : " + JSON.stringify(err.data[i].message));
}
}
});
}
public addListener(eventType: string): void {
}
public removeListeners(count: number): void {
}
}