diff --git a/packages/app-mobile/components/screens/Note.tsx b/packages/app-mobile/components/screens/Note.tsx index ca7d705c2..4662f0f69 100644 --- a/packages/app-mobile/components/screens/Note.tsx +++ b/packages/app-mobile/components/screens/Note.tsx @@ -6,13 +6,15 @@ import UndoRedoService from '@joplin/lib/services/UndoRedoService'; import NoteBodyViewer from '../NoteBodyViewer/NoteBodyViewer'; import checkPermissions from '../../utils/checkPermissions'; import NoteEditor from '../NoteEditor/NoteEditor'; - +import { Size } from '@joplin/utils/types'; const FileViewer = require('react-native-file-viewer').default; const React = require('react'); -const { Keyboard, View, TextInput, StyleSheet, Linking, Image, Share } = require('react-native'); +import { Keyboard, View, TextInput, StyleSheet, Linking, Image, Share } from 'react-native'; import { Platform, PermissionsAndroid } from 'react-native'; const { connect } = require('react-redux'); // const { MarkdownEditor } = require('@joplin/lib/../MarkdownEditor/index.js'); +import DocumentPicker, { DocumentPickerResponse } from 'react-native-document-picker'; +import { openDocument } from '@joplin/react-native-saf-x'; import Note from '@joplin/lib/models/Note'; import BaseItem from '@joplin/lib/models/BaseItem'; import Resource from '@joplin/lib/models/Resource'; @@ -38,7 +40,7 @@ const { dialogs } = require('../../utils/dialogs.js'); const DialogBox = require('react-native-dialogbox').default; import ImageResizer from '@bam.tech/react-native-image-resizer'; import shared from '@joplin/lib/components/shared/note-screen-shared'; -import { ImagePickerResponse, launchImageLibrary } from 'react-native-image-picker'; +import { Asset, ImagePickerResponse, launchImageLibrary } from 'react-native-image-picker'; import SelectDateTimeDialog from '../SelectDateTimeDialog'; import ShareExtension from '../../utils/ShareExtension.js'; import CameraView from '../CameraView'; @@ -54,12 +56,64 @@ import { ChangeEvent as EditorChangeEvent, UndoRedoDepthChangeEvent } from '@jop import { join } from 'path'; const urlUtils = require('@joplin/lib/urlUtils'); -// import Vosk from 'react-native-vosk'; - const emptyArray: any[] = []; const logger = Logger.create('screens/Note'); +interface SelectedDocument { + type: string; + mime: string; + uri: string; + fileName: string; +} + +const pickDocument = async (multiple: boolean): Promise => { + let result: SelectedDocument[] = []; + try { + if (shim.fsDriver().isUsingAndroidSAF()) { + const openDocResult = await openDocument({ multiple }); + if (!openDocResult) { + throw new Error('User canceled document picker'); + } + result = openDocResult.map(r => { + const converted: SelectedDocument = { + type: r.mime, + fileName: r.name, + mime: r.mime, + uri: r.uri, + }; + + return converted; + }); + } else { + let docPickerResult: DocumentPickerResponse[] = []; + if (multiple) { + docPickerResult = await DocumentPicker.pick({ allowMultiSelection: true }); + } else { + docPickerResult = [await DocumentPicker.pickSingle()]; + } + + result = docPickerResult.map(r => { + return { + mime: '', + type: r.type, + uri: r.uri, + fileName: r.name, + }; + }); + } + } catch (error) { + if (DocumentPicker.isCancel(error) || error?.message?.includes('cancel')) { + logger.info('pickDocuments: user has cancelled'); + return []; + } else { + throw error; + } + } + + return result; +}; + class NoteScreenComponent extends BaseScreenComponent { // This isn't in this.state because we don't want changing scroll to trigger // a re-render. @@ -215,7 +269,7 @@ class NoteScreenComponent extends BaseScreenComponent { if (msg.indexOf('file://') === 0) { throw new Error(_('Links with protocol "%s" are not supported', 'file://')); } else { - Linking.openURL(msg); + await Linking.openURL(msg); } } } catch (error) { @@ -574,15 +628,11 @@ class NoteScreenComponent extends BaseScreenComponent { } private async pickDocuments() { - const result = await shim.fsDriver().pickDocument({ multiple: true }); - if (!result) { - // eslint-disable-next-line no-console - console.info('pickDocuments: user has cancelled'); - } + const result = await pickDocument(true); return result; } - public async imageDimensions(uri: string) { + public async imageDimensions(uri: string): Promise { return new Promise((resolve, reject) => { Image.getSize( uri, @@ -598,7 +648,7 @@ class NoteScreenComponent extends BaseScreenComponent { public async resizeImage(localFilePath: string, targetPath: string, mimeType: string) { const maxSize = Resource.IMAGE_MAX_DIMENSION; - const dimensions: any = await this.imageDimensions(localFilePath); + const dimensions = await this.imageDimensions(localFilePath); reg.logger().info('Original dimensions ', dimensions); const saveOriginalImage = async () => { @@ -657,7 +707,7 @@ class NoteScreenComponent extends BaseScreenComponent { return await saveOriginalImage(); } - public async attachFile(pickerResponse: any, fileType: string): Promise { + public async attachFile(pickerResponse: Asset, fileType: string): Promise { if (!pickerResponse) { // User has cancelled return null; @@ -690,8 +740,8 @@ class NoteScreenComponent extends BaseScreenComponent { let resource: ResourceEntity = Resource.new(); resource.id = uuid.create(); resource.mime = mimeType; - resource.title = pickerResponse.name ? pickerResponse.name : ''; - resource.file_extension = safeFileExtension(fileExtension(pickerResponse.name ? pickerResponse.name : localFilePath)); + resource.title = pickerResponse.fileName ? pickerResponse.fileName : ''; + resource.file_extension = safeFileExtension(fileExtension(pickerResponse.fileName ? pickerResponse.fileName : localFilePath)); if (!resource.mime) resource.mime = 'application/octet-stream'; @@ -810,7 +860,7 @@ class NoteScreenComponent extends BaseScreenComponent { return await this.attachFile({ uri: filePath, - name: _('Drawing'), + fileName: _('Drawing'), }, 'image'); } @@ -935,7 +985,7 @@ class NoteScreenComponent extends BaseScreenComponent { const note = await Note.load(this.state.note.id); try { const url = Note.geolocationUrl(note); - Linking.openURL(url); + await Linking.openURL(url); } catch (error) { this.props.dispatch({ type: 'SIDE_MENU_CLOSE' }); await dialogs.error(this, error.message); @@ -947,7 +997,7 @@ class NoteScreenComponent extends BaseScreenComponent { const note = await Note.load(this.state.note.id); try { - Linking.openURL(note.source_url); + await Linking.openURL(note.source_url); } catch (error) { await dialogs.error(this, error.message); } @@ -1376,7 +1426,8 @@ class NoteScreenComponent extends BaseScreenComponent { placeholderTextColor={theme.colorFaded} // need some extra padding for iOS so that the keyboard won't cover last line of the note // see https://github.com/laurent22/joplin/issues/3607 - paddingBottom={ Platform.OS === 'ios' ? 40 : 0} + // Property is gone as of RN 0.72? + // paddingBottom={ (Platform.OS === 'ios' ? 40 : 0) as any} /> ); } else { diff --git a/packages/app-mobile/package.json b/packages/app-mobile/package.json index c6547753c..92a4c1d11 100644 --- a/packages/app-mobile/package.json +++ b/packages/app-mobile/package.json @@ -55,7 +55,7 @@ "react-native-fingerprint-scanner": "6.0.0", "react-native-fs": "2.20.0", "react-native-get-random-values": "1.9.0", - "react-native-image-picker": "5.7.0", + "react-native-image-picker": "7.0.0", "react-native-localize": "3.0.3", "react-native-modal-datetime-picker": "17.1.0", "react-native-paper": "5.11.1", diff --git a/packages/app-mobile/utils/fs-driver/fs-driver-rn.ts b/packages/app-mobile/utils/fs-driver/fs-driver-rn.ts index e261d0353..b8fdc6494 100644 --- a/packages/app-mobile/utils/fs-driver/fs-driver-rn.ts +++ b/packages/app-mobile/utils/fs-driver/fs-driver-rn.ts @@ -1,8 +1,6 @@ import FsDriverBase, { ReadDirStatsOptions } from '@joplin/lib/fs-driver-base'; const RNFetchBlob = require('rn-fetch-blob').default; import * as RNFS from 'react-native-fs'; -const DocumentPicker = require('react-native-document-picker').default; -import { openDocument } from '@joplin/react-native-saf-x'; import RNSAF, { DocumentFileDetail, openDocumentTree } from '@joplin/react-native-saf-x'; import { Platform } from 'react-native'; import * as tar from 'tar-stream'; @@ -379,40 +377,4 @@ export default class FsDriverRN extends FsDriverBase { return Platform.OS === 'android' && Platform.Version > 28; } - /** always returns an array */ - public async pickDocument(options: { multiple: false }) { - const { multiple = false } = options || {}; - let result; - try { - if (this.isUsingAndroidSAF()) { - result = await openDocument({ multiple }); - if (!result) { - // to catch the error down below using the 'cancel' keyword - throw new Error('User canceled document picker'); - } - result = result.map(r => { - (r.type as string) = r.mime; - ((r as any).fileCopyUri as string) = r.uri; - return r; - }); - } else { - // the result is an array - if (multiple) { - result = await DocumentPicker.pick({ allowMultiSelection: true }); - } else { - result = [await DocumentPicker.pick()]; - } - } - } catch (error) { - if (DocumentPicker.isCancel(error) || error?.message?.includes('cancel')) { - // eslint-disable-next-line no-console - console.info('pickDocuments: user has cancelled'); - return null; - } else { - throw error; - } - } - - return result; - } } diff --git a/yarn.lock b/yarn.lock index e8c404fd5..d28f24f41 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6556,7 +6556,7 @@ __metadata: react-native-fingerprint-scanner: 6.0.0 react-native-fs: 2.20.0 react-native-get-random-values: 1.9.0 - react-native-image-picker: 5.7.0 + react-native-image-picker: 7.0.0 react-native-localize: 3.0.3 react-native-modal-datetime-picker: 17.1.0 react-native-paper: 5.11.1 @@ -35027,13 +35027,13 @@ __metadata: languageName: node linkType: hard -"react-native-image-picker@npm:5.7.0": - version: 5.7.0 - resolution: "react-native-image-picker@npm:5.7.0" +"react-native-image-picker@npm:7.0.0": + version: 7.0.0 + resolution: "react-native-image-picker@npm:7.0.0" peerDependencies: react: "*" react-native: "*" - checksum: 170cbda1c405b49ca8434bbe364a54406be3dbcc953ca6e4b40565556004cbddeca34a1091a1dbad60cd1dc06c8777fdd25ab2489447199902c39ec09aa169e1 + checksum: 3985c1741089c12c056f94dcf088aee8f6e2eeb6e3c7a86867c0f865f1f8cdc5b9e1ad072967dc0a031bcd1f5556ae501136d90eea7fc5a179ab2b03aebe456c languageName: node linkType: hard