From f4327343388d09872fb67caed61cd55afaa8e4e0 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Wed, 10 Jun 2020 22:12:18 +0100 Subject: [PATCH] Mobile: Refactored and made dialog boxes more reliable --- .eslintignore | 1 + .gitignore | 1 + ReactNativeClient/lib/components/dialogs.tsx | 103 ++++++++++++++++++ .../lib/components/screen-header.js | 12 +- .../lib/components/screens/dropbox-login.js | 11 +- .../components/screens/encryption-config.js | 14 +-- .../lib/components/screens/folder.js | 9 +- .../lib/components/screens/note.js | 27 ++--- .../lib/components/screens/notes.js | 21 ++-- .../lib/components/screens/search.js | 6 - ReactNativeClient/lib/dialogs.js | 76 ------------- ReactNativeClient/package-lock.json | 68 +++++++++++- ReactNativeClient/package.json | 2 +- ReactNativeClient/root.js | 6 + tsconfig.json | 2 + 15 files changed, 206 insertions(+), 153 deletions(-) create mode 100644 ReactNativeClient/lib/components/dialogs.tsx delete mode 100644 ReactNativeClient/lib/dialogs.js diff --git a/.eslintignore b/.eslintignore index 88ed59d2e..b03cad12e 100644 --- a/.eslintignore +++ b/.eslintignore @@ -100,6 +100,7 @@ ElectronClient/gui/ResourceScreen.js ElectronClient/gui/ShareNoteDialog.js ReactNativeClient/lib/AsyncActionQueue.js ReactNativeClient/lib/checkPermissions.js +ReactNativeClient/lib/components/dialogs.js ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js ReactNativeClient/lib/hooks/usePrevious.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js diff --git a/.gitignore b/.gitignore index 04d7fd640..293ad87e9 100644 --- a/.gitignore +++ b/.gitignore @@ -90,6 +90,7 @@ ElectronClient/gui/ResourceScreen.js ElectronClient/gui/ShareNoteDialog.js ReactNativeClient/lib/AsyncActionQueue.js ReactNativeClient/lib/checkPermissions.js +ReactNativeClient/lib/components/dialogs.js ReactNativeClient/lib/hooks/useImperativeHandlerDebugger.js ReactNativeClient/lib/hooks/usePrevious.js ReactNativeClient/lib/joplin-renderer/MdToHtml/rules/checkbox.js diff --git a/ReactNativeClient/lib/components/dialogs.tsx b/ReactNativeClient/lib/components/dialogs.tsx new file mode 100644 index 000000000..433654162 --- /dev/null +++ b/ReactNativeClient/lib/components/dialogs.tsx @@ -0,0 +1,103 @@ +const React = require('react'); +const { forwardRef,useState, useImperativeHandle } = require('react'); +const RnDialog = require('react-native-dialog').default; +const { View } = require('react-native'); +const { _ } = require('lib/locale'); + +let dialogRef_:any = null; + +export function initializeDialogs(ref:any) { + dialogRef_ = ref; +} + +export const Dialog = forwardRef(function(_props:any, ref:any) { + const [visible, setVisible] = useState(false); + const [type, setType] = useState(''); + const [message, setMessage] = useState(''); + const [buttons, setButtons] = useState([]); + const [resultPromise, setResultPromise] = useState({}); + + function dialogTitle(type:string) { + if (type === 'info') return _('Information'); + if (type === 'error') return _('Error'); + return ''; + } + + useImperativeHandle(ref, () => { + return { + show: async function(type:string, message:string, buttons:any[] = null) { + // Shouldn't happen but just to be sure throw an error in this case + if (visible) throw new Error('Only one dialog can be visible at a time'); + + setVisible(true); + setType(type); + setMessage(message); + setButtons(buttons); + + return new Promise((resolve, reject) => { + setResultPromise({ + resolve: resolve, + reject: reject, + }); + }); + }, + }; + }); + + function onPress(event:any) { + setVisible(false); + resultPromise.resolve(event ? event.value : true); + } + + function renderTitle() { + const title = dialogTitle(type); + if (!title) return null; + return {title}; + } + + function renderButtons() { + const output = []; + + if (type === 'confirm') { + output.push( onPress({ value: true })} />); + output.push( onPress({ value: false })} />); + } + + if (type === 'info' || type === 'error') { + output.push(); + } + + if (type === 'pop') { + for (const button of buttons) { + output.push( onPress({ value: button.id })} />); + } + } + + return output; + } + + return ( + + + {renderTitle()} + {message} + {renderButtons()} + + + ); +}); + +export default { + confirm: async function(message:string) { + return dialogRef_.current.show('confirm', message); + }, + pop: async function(message:string, buttons:any[]) { + return dialogRef_.current.show('pop', message, buttons); + }, + error: async function(message:string) { + return dialogRef_.current.show('error', message); + }, + info: async function(message:string) { + return dialogRef_.current.show('info', message); + }, +}; diff --git a/ReactNativeClient/lib/components/screen-header.js b/ReactNativeClient/lib/components/screen-header.js index 0a3053573..151243b38 100644 --- a/ReactNativeClient/lib/components/screen-header.js +++ b/ReactNativeClient/lib/components/screen-header.js @@ -12,8 +12,7 @@ const Note = require('lib/models/Note.js'); const Folder = require('lib/models/Folder.js'); const { themeStyle } = require('lib/components/global-style.js'); const { Dropdown } = require('lib/components/Dropdown.js'); -const { dialogs } = require('lib/dialogs.js'); -const DialogBox = require('react-native-dialogbox').default; +const dialogs = require('lib/components/dialogs.js').default; Icon.loadFont(); @@ -182,7 +181,7 @@ class ScreenHeaderComponent extends React.PureComponent { async deleteButton_press() { // Dialog needs to be displayed as a child of the parent component, otherwise // it won't be visible within the header component. - const ok = await dialogs.confirm(this.props.parentComponent, _('Delete these notes?')); + const ok = await dialogs.confirm(_('Delete these notes?')); if (!ok) return; const noteIds = this.props.selectedNoteIds; @@ -400,7 +399,7 @@ class ScreenHeaderComponent extends React.PureComponent { const folder = await Folder.load(folderId); - const ok = noteIds.length > 1 ? await dialogs.confirm(this.props.parentComponent, _('Move %d notes to notebook "%s"?', noteIds.length, folder.title)) : true; + const ok = noteIds.length > 1 ? await dialogs.confirm(_('Move %d notes to notebook "%s"?', noteIds.length, folder.title)) : true; if (!ok) return; this.props.dispatch({ type: 'NOTE_SELECTION_END' }); @@ -480,11 +479,6 @@ class ScreenHeaderComponent extends React.PureComponent { {menuComp} {warningComps} - { - this.dialogbox = dialogbox; - }} - /> ); } diff --git a/ReactNativeClient/lib/components/screens/dropbox-login.js b/ReactNativeClient/lib/components/screens/dropbox-login.js index 548480a43..142e516cf 100644 --- a/ReactNativeClient/lib/components/screens/dropbox-login.js +++ b/ReactNativeClient/lib/components/screens/dropbox-login.js @@ -5,8 +5,7 @@ const { connect } = require('react-redux'); const { ScreenHeader } = require('lib/components/screen-header.js'); const { _ } = require('lib/locale.js'); const { BaseScreenComponent } = require('lib/components/base-screen.js'); -const DialogBox = require('react-native-dialogbox').default; -const { dialogs } = require('lib/dialogs.js'); +const dialogs = require('lib/components/dialogs.js').default; const Shared = require('lib/components/shared/dropbox-login-shared'); const { themeStyle } = require('lib/components/global-style.js'); @@ -16,7 +15,7 @@ class DropboxLoginScreenComponent extends BaseScreenComponent { this.styles_ = {}; - this.shared_ = new Shared(this, msg => dialogs.info(this, msg), msg => dialogs.error(this, msg)); + this.shared_ = new Shared(this, msg => dialogs.info(msg), msg => dialogs.error(msg)); } UNSAFE_componentWillMount() { @@ -66,12 +65,6 @@ class DropboxLoginScreenComponent extends BaseScreenComponent { {/* Add this extra padding to make sure the view is scrollable when the keyboard is visible on small screens (iPhone SE) */} - - { - this.dialogbox = dialogbox; - }} - /> ); } diff --git a/ReactNativeClient/lib/components/screens/encryption-config.js b/ReactNativeClient/lib/components/screens/encryption-config.js index e21bfea15..4a218685b 100644 --- a/ReactNativeClient/lib/components/screens/encryption-config.js +++ b/ReactNativeClient/lib/components/screens/encryption-config.js @@ -9,8 +9,7 @@ const { BaseScreenComponent } = require('lib/components/base-screen.js'); const { themeStyle } = require('lib/components/global-style.js'); const { time } = require('lib/time-utils.js'); const shared = require('lib/components/shared/encryption-config-shared.js'); -const { dialogs } = require('lib/dialogs.js'); -const DialogBox = require('react-native-dialogbox').default; +const dialogs = require('lib/components/dialogs.js').default; class EncryptionConfigScreenComponent extends BaseScreenComponent { static navigationOptions() { @@ -141,7 +140,7 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent { await EncryptionService.instance().generateMasterKeyAndEnableEncryption(password); this.setState({ passwordPromptShow: false }); } catch (error) { - await dialogs.error(this, error.message); + await dialogs.error(error.message); } }; @@ -212,13 +211,13 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent { const onToggleButtonClick = async () => { if (this.props.encryptionEnabled) { - const ok = await dialogs.confirm(this, _('Disabling encryption means *all* your notes and attachments are going to be re-synchronised and sent unencrypted to the sync target. Do you wish to continue?')); + const ok = await dialogs.confirm(_('Disabling encryption means *all* your notes and attachments are going to be re-synchronised and sent unencrypted to the sync target. Do you wish to continue?')); if (!ok) return; try { await EncryptionService.instance().disableEncryption(); } catch (error) { - await dialogs.error(this, error.message); + await dialogs.error(error.message); } } else { this.setState({ @@ -285,11 +284,6 @@ class EncryptionConfigScreenComponent extends BaseScreenComponent { {nonExistingMasterKeySection} - { - this.dialogbox = dialogbox; - }} - /> ); } diff --git a/ReactNativeClient/lib/components/screens/folder.js b/ReactNativeClient/lib/components/screens/folder.js index 4e5ef58eb..7ae8e62a4 100644 --- a/ReactNativeClient/lib/components/screens/folder.js +++ b/ReactNativeClient/lib/components/screens/folder.js @@ -6,7 +6,7 @@ const Folder = require('lib/models/Folder.js'); const BaseModel = require('lib/BaseModel.js'); const { ScreenHeader } = require('lib/components/screen-header.js'); const { BaseScreenComponent } = require('lib/components/base-screen.js'); -const { dialogs } = require('lib/dialogs.js'); +const dialogs = require('lib/components/dialogs.js').default; const { themeStyle } = require('lib/components/global-style.js'); const { _ } = require('lib/locale.js'); @@ -84,7 +84,7 @@ class FolderScreenComponent extends BaseScreenComponent { try { folder = await Folder.save(folder, { userSideValidation: true }); } catch (error) { - dialogs.error(this, _('The notebook could not be saved: %s', error.message)); + dialogs.error(_('The notebook could not be saved: %s', error.message)); return; } @@ -108,11 +108,6 @@ class FolderScreenComponent extends BaseScreenComponent { this.saveFolderButton_press()} showSideMenuButton={false} showSearchButton={false} /> this.title_changeText(text)} /> - { - this.dialogbox = dialogbox; - }} - /> ); } diff --git a/ReactNativeClient/lib/components/screens/note.js b/ReactNativeClient/lib/components/screens/note.js index 334523f28..afd9cab1e 100644 --- a/ReactNativeClient/lib/components/screens/note.js +++ b/ReactNativeClient/lib/components/screens/note.js @@ -29,8 +29,7 @@ const { shim } = require('lib/shim.js'); const ResourceFetcher = require('lib/services/ResourceFetcher'); const { BaseScreenComponent } = require('lib/components/base-screen.js'); const { themeStyle, editorFont } = require('lib/components/global-style.js'); -const { dialogs } = require('lib/dialogs.js'); -const DialogBox = require('react-native-dialogbox').default; +const dialogs = require('lib/components/dialogs.js').default; const { NoteBodyViewer } = require('lib/components/note-body-viewer.js'); const { DocumentPicker, DocumentPickerUtil } = require('react-native-document-picker'); const ImageResizer = require('react-native-image-resizer').default; @@ -90,7 +89,7 @@ class NoteScreenComponent extends BaseScreenComponent { const saveDialog = async () => { if (this.isModified()) { - const buttonId = await dialogs.pop(this, _('This note has been modified:'), [{ text: _('Save changes'), id: 'save' }, { text: _('Discard changes'), id: 'discard' }, { text: _('Cancel'), id: 'cancel' }]); + const buttonId = await dialogs.pop(_('This note has been modified:'), [{ text: _('Save changes'), id: 'save' }, { text: _('Discard changes'), id: 'discard' }, { text: _('Cancel'), id: 'cancel' }]); if (buttonId == 'cancel') return true; if (buttonId == 'save') await this.saveNoteButton_press(); @@ -185,7 +184,7 @@ class NoteScreenComponent extends BaseScreenComponent { } } } catch (error) { - dialogs.error(this, error.message); + dialogs.error(error.message); } }; @@ -398,7 +397,7 @@ class NoteScreenComponent extends BaseScreenComponent { const note = this.state.note; if (!note.id) return; - const ok = await dialogs.confirm(this, _('Delete note?')); + const ok = await dialogs.confirm(_('Delete note?')); if (!ok) return; const folderId = note.parent_id; @@ -460,7 +459,7 @@ class NoteScreenComponent extends BaseScreenComponent { let mustResize = dimensions.width > maxSize || dimensions.height > maxSize; if (mustResize) { - const buttonId = await dialogs.pop(this, _('You are about to attach a large image (%dx%d pixels). Would you like to resize it down to %d pixels before attaching it?', dimensions.width, dimensions.height, maxSize), [ + const buttonId = await dialogs.pop(_('You are about to attach a large image (%dx%d pixels). Would you like to resize it down to %d pixels before attaching it?', dimensions.width, dimensions.height, maxSize), [ { text: _('Yes'), id: 'yes' }, { text: _('No'), id: 'no' }, { text: _('Cancel'), id: 'cancel' }, @@ -555,7 +554,7 @@ class NoteScreenComponent extends BaseScreenComponent { if (!done) return; } else { if (fileType === 'image') { - dialogs.error(this, _('Unsupported image type: %s', mimeType)); + dialogs.error(_('Unsupported image type: %s', mimeType)); return; } else { await shim.fsDriver().copy(localFilePath, targetPath); @@ -569,7 +568,7 @@ class NoteScreenComponent extends BaseScreenComponent { } } catch (error) { reg.logger().warn('Could not attach file:', error); - await dialogs.error(this, error.message); + await dialogs.error(error.message); return; } @@ -681,7 +680,7 @@ class NoteScreenComponent extends BaseScreenComponent { Linking.openURL(url); } catch (error) { this.props.dispatch({ type: 'SIDE_MENU_CLOSE' }); - await dialogs.error(this, error.message); + await dialogs.error(error.message); } } @@ -692,7 +691,7 @@ class NoteScreenComponent extends BaseScreenComponent { try { Linking.openURL(note.source_url); } catch (error) { - await dialogs.error(this, error.message); + await dialogs.error(error.message); } } @@ -752,7 +751,7 @@ class NoteScreenComponent extends BaseScreenComponent { output.push({ title: _('Attach...'), onPress: async () => { - const buttonId = await dialogs.pop(this, _('Choose an option'), [{ text: _('Take photo'), id: 'takePhoto' }, { text: _('Attach photo'), id: 'attachPhoto' }, { text: _('Attach any file'), id: 'attachFile' }]); + const buttonId = await dialogs.pop(_('Choose an option'), [{ text: _('Take photo'), id: 'takePhoto' }, { text: _('Attach photo'), id: 'attachPhoto' }, { text: _('Attach any file'), id: 'attachFile' }]); if (buttonId === 'takePhoto') this.takePhoto_onPress(); if (buttonId === 'attachPhoto') this.attachPhoto_onPress(); @@ -1082,12 +1081,6 @@ class NoteScreenComponent extends BaseScreenComponent { {!Setting.value('editor.beta') && actionButtonComp} - - { - this.dialogbox = dialogbox; - }} - /> {noteTagDialog} ); diff --git a/ReactNativeClient/lib/components/screens/notes.js b/ReactNativeClient/lib/components/screens/notes.js index ef39b043e..5636ef03f 100644 --- a/ReactNativeClient/lib/components/screens/notes.js +++ b/ReactNativeClient/lib/components/screens/notes.js @@ -12,8 +12,7 @@ const { themeStyle } = require('lib/components/global-style.js'); const { ScreenHeader } = require('lib/components/screen-header.js'); const { _ } = require('lib/locale.js'); const { ActionButton } = require('lib/components/action-button.js'); -const { dialogs } = require('lib/dialogs.js'); -const DialogBox = require('react-native-dialogbox').default; +const dialogs = require('lib/components/dialogs.js').default; const { BaseScreenComponent } = require('lib/components/base-screen.js'); const { BackButtonService } = require('lib/services/back-button.js'); @@ -64,17 +63,17 @@ class NotesScreenComponent extends BaseScreenComponent { id: { name: 'showCompletedTodos', value: !Setting.value('showCompletedTodos') }, }); - const r = await dialogs.pop(this, Setting.settingMetadata('notes.sortOrder.field').label(), buttons); + const r = await dialogs.pop(Setting.settingMetadata('notes.sortOrder.field').label(), buttons); if (!r) return; Setting.setValue(r.name, r.value); }; this.backHandler = () => { - if (this.dialogbox.state.isVisible) { - this.dialogbox.close(); - return true; - } + // if (this.dialogbox.state.isVisible) { + // this.dialogbox.close(); + // return true; + // } return false; }; } @@ -151,7 +150,7 @@ class NotesScreenComponent extends BaseScreenComponent { } deleteFolder_onPress(folderId) { - dialogs.confirm(this, _('Delete notebook? All notes and sub-notebooks within this notebook will also be deleted.')).then(ok => { + dialogs.confirm(_('Delete notebook? All notes and sub-notebooks within this notebook will also be deleted.')).then(ok => { if (!ok) return; Folder.delete(folderId) @@ -236,11 +235,7 @@ class NotesScreenComponent extends BaseScreenComponent { {actionButtonComp} - { - this.dialogbox = dialogbox; - }} - /> + ); } diff --git a/ReactNativeClient/lib/components/screens/search.js b/ReactNativeClient/lib/components/screens/search.js index 789df7f66..e3518195c 100644 --- a/ReactNativeClient/lib/components/screens/search.js +++ b/ReactNativeClient/lib/components/screens/search.js @@ -10,7 +10,6 @@ const { NoteItem } = require('lib/components/note-item.js'); const { BaseScreenComponent } = require('lib/components/base-screen.js'); const { themeStyle } = require('lib/components/global-style.js'); const SearchEngineUtils = require('lib/services/SearchEngineUtils'); -const DialogBox = require('react-native-dialogbox').default; Icon.loadFont(); @@ -190,11 +189,6 @@ class SearchScreenComponent extends BaseScreenComponent { item.id} renderItem={event => } /> - { - this.dialogbox = dialogbox; - }} - /> ); } diff --git a/ReactNativeClient/lib/dialogs.js b/ReactNativeClient/lib/dialogs.js deleted file mode 100644 index 48bfe0f77..000000000 --- a/ReactNativeClient/lib/dialogs.js +++ /dev/null @@ -1,76 +0,0 @@ -const DialogBox = require('react-native-dialogbox').default; -const { Keyboard } = require('react-native'); - -// Add this at the bottom of the component: -// -// { this.dialogbox = dialogbox }}/> - -const dialogs = {}; - -dialogs.confirm = (parentComponent, message) => { - if (!parentComponent) throw new Error('parentComponent is required'); - if (!('dialogbox' in parentComponent)) throw new Error('A "dialogbox" component must be defined on the parent component!'); - - return new Promise((resolve) => { - Keyboard.dismiss(); - - parentComponent.dialogbox.confirm({ - content: message, - - ok: { - callback: () => { - resolve(true); - }, - }, - - cancel: { - callback: () => { - resolve(false); - }, - }, - }); - }); -}; - -dialogs.pop = (parentComponent, message, buttons, options = null) => { - if (!parentComponent) throw new Error('parentComponent is required'); - if (!('dialogbox' in parentComponent)) throw new Error('A "dialogbox" component must be defined on the parent component!'); - - if (!options) options = {}; - if (!('buttonFlow' in options)) options.buttonFlow = 'auto'; - - return new Promise((resolve) => { - Keyboard.dismiss(); - - const btns = []; - for (let i = 0; i < buttons.length; i++) { - btns.push({ - text: buttons[i].text, - callback: () => { - parentComponent.dialogbox.close(); - resolve(buttons[i].id); - }, - }); - } - - parentComponent.dialogbox.pop({ - content: message, - btns: btns, - buttonFlow: options.buttonFlow, - }); - }); -}; - -dialogs.error = (parentComponent, message) => { - Keyboard.dismiss(); - return parentComponent.dialogbox.alert(message); -}; - -dialogs.info = (parentComponent, message) => { - Keyboard.dismiss(); - return parentComponent.dialogbox.alert(message); -}; - -dialogs.DialogBox = DialogBox; - -module.exports = { dialogs }; diff --git a/ReactNativeClient/package-lock.json b/ReactNativeClient/package-lock.json index 36e23123a..3d7ce820a 100644 --- a/ReactNativeClient/package-lock.json +++ b/ReactNativeClient/package-lock.json @@ -8739,6 +8739,34 @@ "prop-types": "^15.5.10" } }, + "react-native-animatable": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/react-native-animatable/-/react-native-animatable-1.3.3.tgz", + "integrity": "sha512-2ckIxZQAsvWn25Ho+DK3d1mXIgj7tITkrS4pYDvx96WyOttSvzzFeQnM2od0+FUMzILbdHDsDEqZvnz1DYNQ1w==", + "requires": { + "prop-types": "^15.7.2" + }, + "dependencies": { + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + } + } + }, "react-native-camera": { "version": "2.10.2", "resolved": "https://registry.npmjs.org/react-native-camera/-/react-native-camera-2.10.2.tgz", @@ -8780,12 +8808,13 @@ "resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-5.5.1.tgz", "integrity": "sha512-ceaGnxOAULRzVpx03bPjbayuHD5g21CcdmKKvLK5mV72sbguNZbTHNmnH3//Bx9Hfy1XOm6zHhM5Yfb7Glqplw==" }, - "react-native-dialogbox": { - "version": "0.6.10", - "resolved": "https://registry.npmjs.org/react-native-dialogbox/-/react-native-dialogbox-0.6.10.tgz", - "integrity": "sha512-RlDiFjqpH44Nfd+5ok1Xsf4QkUpzURhsDCq2UDUqpBWBzPO/2GVVIFGhJlLzATZsfxf+yVXUWrgW2qcaxNuoNg==", + "react-native-dialog": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/react-native-dialog/-/react-native-dialog-5.6.0.tgz", + "integrity": "sha512-pUTxHJHzErMY+JaDRSMKiCbJTEdy2Ik4hcNOwasOlxpj6S6tT5SonLsrLPGBCO0XpTOySE0qVzuikmKgUDZfig==", "requires": { - "prop-types": "^15.6.2" + "prop-types": "^15.7.2", + "react-native-modal": "^9.0.0" }, "dependencies": { "loose-envify": { @@ -8900,6 +8929,35 @@ "prop-types": "^15.5.9" } }, + "react-native-modal": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/react-native-modal/-/react-native-modal-9.0.0.tgz", + "integrity": "sha512-j4xeIK9noHU/ksp2Ndc8NI1qJvjApToqGvqLEu2wtYeaISanbhtd0S3V4hZkSlCa3DZtegl6aaMZBLeH1q6xfA==", + "requires": { + "prop-types": "^15.6.2", + "react-native-animatable": "^1.2.4" + }, + "dependencies": { + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + } + } + }, "react-native-popup-dialog": { "version": "0.9.38", "resolved": "https://registry.npmjs.org/react-native-popup-dialog/-/react-native-popup-dialog-0.9.38.tgz", diff --git a/ReactNativeClient/package.json b/ReactNativeClient/package.json index 07708c511..dc8653fcf 100644 --- a/ReactNativeClient/package.json +++ b/ReactNativeClient/package.json @@ -59,7 +59,7 @@ "react-native-camera": "^2.10.2", "react-native-datepicker": "^1.6.0", "react-native-device-info": "^5.5.1", - "react-native-dialogbox": "^0.6.10", + "react-native-dialog": "^5.6.0", "react-native-document-picker": "^2.3.0", "react-native-dropdownalert": "^3.1.2", "react-native-file-viewer": "^1.0.15", diff --git a/ReactNativeClient/root.js b/ReactNativeClient/root.js index 062100478..837a9e32c 100644 --- a/ReactNativeClient/root.js +++ b/ReactNativeClient/root.js @@ -31,6 +31,7 @@ const BaseService = require('lib/services/BaseService.js'); const ResourceService = require('lib/services/ResourceService'); const RevisionService = require('lib/services/RevisionService'); const KvStore = require('lib/services/KvStore'); +const { Dialog, initializeDialogs } = require('lib/components/dialogs.js'); const { JoplinDatabase } = require('lib/joplin-database.js'); const { Database } = require('lib/database.js'); const { NotesScreen } = require('lib/components/screens/notes.js'); @@ -607,6 +608,10 @@ class AppComponent extends React.Component { this.onAppStateChange_ = () => { PoorManIntervals.update(); }; + + this.dialogRef = React.createRef(); + + initializeDialogs(this.dialogRef); } async componentDidMount() { @@ -749,6 +754,7 @@ class AppComponent extends React.Component { + ); } diff --git a/tsconfig.json b/tsconfig.json index fb3bf0186..08890456d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,8 @@ "strictFunctionTypes": true, "sourceMap": true, "jsx": "react", + // This is needed because otherwise we get errors in node_modules for React + // but ideally it should be made to work. "skipLibCheck": true, }, "include": [