From 96fb7c2087a971bb74a73a624c089733073358e4 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Tue, 27 Mar 2018 00:55:44 +0100 Subject: [PATCH] Getting Dropbox to work in mobile app --- ElectronClient/app/gui/DropboxLoginScreen.jsx | 69 +++-------------- .../lib/components/screens/dropbox-login.js | 57 ++++++++++++++ .../components/shared/dropbox-login-shared.js | 74 +++++++++++++++++++ ReactNativeClient/lib/dialogs.js | 5 ++ .../lib/file-api-driver-dropbox.js | 2 +- ReactNativeClient/lib/shim-init-node.js | 5 ++ ReactNativeClient/lib/shim-init-react.js | 5 ++ ReactNativeClient/lib/shim.js | 1 + ReactNativeClient/root.js | 14 +--- 9 files changed, 162 insertions(+), 70 deletions(-) create mode 100644 ReactNativeClient/lib/components/screens/dropbox-login.js create mode 100644 ReactNativeClient/lib/components/shared/dropbox-login-shared.js diff --git a/ElectronClient/app/gui/DropboxLoginScreen.jsx b/ElectronClient/app/gui/DropboxLoginScreen.jsx index ab4a8e3da..66cd6c6bb 100644 --- a/ElectronClient/app/gui/DropboxLoginScreen.jsx +++ b/ElectronClient/app/gui/DropboxLoginScreen.jsx @@ -6,71 +6,22 @@ const { Header } = require('./Header.min.js'); const { themeStyle } = require('../theme.js'); const SyncTargetRegistry = require('lib/SyncTargetRegistry'); const { _ } = require('lib/locale.js'); +const Shared = require('lib/components/shared/dropbox-login-shared'); class DropboxLoginScreenComponent extends React.Component { constructor() { super(); - this.dropboxApi_ = null; - - this.state = { - loginUrl: '', - authCode: '', - checkingAuthToken: false, - }; - - this.loginUrl_click = () => { - if (!this.state.loginUrl) return; - bridge().openExternal(this.state.loginUrl) - } - - this.authCodeInput_change = (event) => { - this.setState({ - authCode: event.target.value - }); - } - - this.submit_click = async () => { - this.setState({ checkingAuthToken: true }); - - const api = await this.dropboxApi(); - try { - const response = await api.execAuthToken(this.state.authCode); - Setting.setValue('sync.' + this.syncTargetId() + '.auth', response.access_token); - api.setAuthToken(response.access_token); - bridge().showInfoMessageBox(_('The application has been authorised!')); - this.props.dispatch({ type: 'NAV_BACK' }); - } catch (error) { - bridge().showErrorMessageBox(_('Could not authorise application:\n\n%s\n\nPlease try again.', error.message)); - } finally { - this.setState({ checkingAuthToken: false }); - } - } + this.shared_ = new Shared( + this, + (msg) => bridge().showInfoMessageBox(msg), + (msg) => bridge().showErrorMessageBox(msg) + ); } componentWillMount() { - this.refreshUrl(); - } - - syncTargetId() { - return SyncTargetRegistry.nameToId('dropbox'); - } - - async dropboxApi() { - if (this.dropboxApi_) return this.dropboxApi_; - - const syncTarget = reg.syncTarget(this.syncTargetId()); - this.dropboxApi_ = await syncTarget.api(); - return this.dropboxApi_; - } - - async refreshUrl() { - const api = await this.dropboxApi(); - - this.setState({ - loginUrl: api.loginUrl(), - }); + this.shared_.refreshUrl(); } render() { @@ -89,10 +40,10 @@ class DropboxLoginScreenComponent extends React.Component {

{_('To allow Joplin to synchronise with Dropbox, please follow the steps below:')}

{_('Step 1: Open this URL in your browser to authorise the application:')}

- {this.state.loginUrl} + {this.state.loginUrl}

{_('Step 2: Enter the code provided by Dropbox:')}

-

- +

+
); diff --git a/ReactNativeClient/lib/components/screens/dropbox-login.js b/ReactNativeClient/lib/components/screens/dropbox-login.js new file mode 100644 index 000000000..4d3dbdf82 --- /dev/null +++ b/ReactNativeClient/lib/components/screens/dropbox-login.js @@ -0,0 +1,57 @@ +const React = require('react'); const Component = React.Component; +const { View, Button, Text, TextInput, TouchableOpacity } = require('react-native'); +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 Shared = require('lib/components/shared/dropbox-login-shared'); + +class DropboxLoginScreenComponent extends BaseScreenComponent { + + constructor() { + super(); + + this.shared_ = new Shared( + this, + (msg) => dialogs.info(this, msg), + (msg) => dialogs.error(this, msg) + ); + } + + componentWillMount() { + this.shared_.refreshUrl(); + } + + render() { + return ( + + + + {_('To allow Joplin to synchronise with Dropbox, please follow the steps below:')} + {_('Step 1: Open this URL in your browser to authorise the application:')} + + + {this.state.loginUrl} + + + {_('Step 2: Enter the code provided by Dropbox:')} + + + + + { this.dialogbox = dialogbox }}/> + + ); + } + +} + +const DropboxLoginScreen = connect( + (state) => { + return {}; + } +)(DropboxLoginScreenComponent) + +module.exports = { DropboxLoginScreen }; \ No newline at end of file diff --git a/ReactNativeClient/lib/components/shared/dropbox-login-shared.js b/ReactNativeClient/lib/components/shared/dropbox-login-shared.js new file mode 100644 index 000000000..d8cc66e53 --- /dev/null +++ b/ReactNativeClient/lib/components/shared/dropbox-login-shared.js @@ -0,0 +1,74 @@ +const { shim } = require('lib/shim'); +const SyncTargetRegistry = require('lib/SyncTargetRegistry'); +const { reg } = require('lib/registry.js'); +const { _ } = require('lib/locale.js'); +const Setting = require('lib/models/Setting'); + +class Shared { + + constructor(comp, showInfoMessageBox, showErrorMessageBox) { + this.comp_ = comp; + + this.dropboxApi_ = null; + + this.comp_.state = { + loginUrl: '', + authCode: '', + checkingAuthToken: false, + }; + + this.loginUrl_click = () => { + if (!this.comp_.state.loginUrl) return; + shim.openUrl(this.comp_.state.loginUrl); + } + + this.authCodeInput_change = (event) => { + this.comp_.setState({ + authCode: typeof event === 'object' ? event.target.value : event + }); + } + + this.submit_click = async () => { + this.comp_.setState({ checkingAuthToken: true }); + + const api = await this.dropboxApi(); + try { + const response = await api.execAuthToken(this.comp_.state.authCode); + + Setting.setValue('sync.' + this.syncTargetId() + '.auth', response.access_token); + api.setAuthToken(response.access_token); + await showInfoMessageBox(_('The application has been authorised!')); + this.comp_.props.dispatch({ type: 'NAV_BACK' }); + reg.scheduleSync(); + } catch (error) { + console.error(error); + await showErrorMessageBox(_('Could not authorise application:\n\n%s\n\nPlease try again.', error.message)); + } finally { + this.comp_.setState({ checkingAuthToken: false }); + } + } + } + + syncTargetId() { + return SyncTargetRegistry.nameToId('dropbox'); + } + + async dropboxApi() { + if (this.dropboxApi_) return this.dropboxApi_; + + const syncTarget = reg.syncTarget(this.syncTargetId()); + this.dropboxApi_ = await syncTarget.api(); + return this.dropboxApi_; + } + + async refreshUrl() { + const api = await this.dropboxApi(); + + this.comp_.setState({ + loginUrl: api.loginUrl(), + }); + } + +} + +module.exports = Shared; \ No newline at end of file diff --git a/ReactNativeClient/lib/dialogs.js b/ReactNativeClient/lib/dialogs.js index f9ea0fca5..f611f0fdd 100644 --- a/ReactNativeClient/lib/dialogs.js +++ b/ReactNativeClient/lib/dialogs.js @@ -67,6 +67,11 @@ dialogs.error = (parentComponent, message) => { return parentComponent.dialogbox.alert(message); } +dialogs.info = (parentComponent, message) => { + Keyboard.dismiss(); + return parentComponent.dialogbox.alert(message); +} + dialogs.DialogBox = DialogBox module.exports = { dialogs }; \ No newline at end of file diff --git a/ReactNativeClient/lib/file-api-driver-dropbox.js b/ReactNativeClient/lib/file-api-driver-dropbox.js index 69797b28a..6d5c308da 100644 --- a/ReactNativeClient/lib/file-api-driver-dropbox.js +++ b/ReactNativeClient/lib/file-api-driver-dropbox.js @@ -150,7 +150,7 @@ class FileApiDriverDropbox { async put(path, content, options = null) { // See https://github.com/facebook/react-native/issues/14445#issuecomment-352965210 - if (typeof content === 'string') content = Buffer.from(content, 'utf8') + if (typeof content === 'string') content = shim.Buffer.from(content, 'utf8') await this.api().exec('POST', 'files/upload', content, { 'Dropbox-API-Arg': JSON.stringify({ diff --git a/ReactNativeClient/lib/shim-init-node.js b/ReactNativeClient/lib/shim-init-node.js index bf0065c09..d8ade679a 100644 --- a/ReactNativeClient/lib/shim-init-node.js +++ b/ReactNativeClient/lib/shim-init-node.js @@ -183,6 +183,11 @@ function shimInit() { shim.Buffer = Buffer; + shim.openUrl = (url) => { + const { bridge } = require('electron').remote.require('./bridge'); + bridge().openExternal(url) + } + } module.exports = { shimInit }; \ No newline at end of file diff --git a/ReactNativeClient/lib/shim-init-react.js b/ReactNativeClient/lib/shim-init-react.js index c8edf6e4e..19e9c1712 100644 --- a/ReactNativeClient/lib/shim-init-react.js +++ b/ReactNativeClient/lib/shim-init-react.js @@ -6,6 +6,7 @@ const { generateSecureRandom } = require('react-native-securerandom'); const FsDriverRN = require('lib/fs-driver-rn.js').FsDriverRN; const urlValidator = require('valid-url'); const { Buffer } = require('buffer'); +const { Linking } = require('react-native'); function shimInit() { shim.Geolocation = GeolocationReact; @@ -118,6 +119,10 @@ function shimInit() { } shim.Buffer = Buffer; + + shim.openUrl = (url) => { + Linking.openURL(url); + } } module.exports = { shimInit }; \ No newline at end of file diff --git a/ReactNativeClient/lib/shim.js b/ReactNativeClient/lib/shim.js index db8ab1efb..4c7d5d5c7 100644 --- a/ReactNativeClient/lib/shim.js +++ b/ReactNativeClient/lib/shim.js @@ -130,5 +130,6 @@ shim.stringByteLength = function(string) { throw new Error('Not implemented'); } shim.detectAndSetLocale = null; shim.attachFileToNote = async (note, filePath) => {} shim.Buffer = null; +shim.openUrl = () => { throw new Error('Not implemented'); } module.exports = { shim }; \ No newline at end of file diff --git a/ReactNativeClient/root.js b/ReactNativeClient/root.js index 89abe22ad..64d5caa27 100644 --- a/ReactNativeClient/root.js +++ b/ReactNativeClient/root.js @@ -36,6 +36,7 @@ const { WelcomeScreen } = require('lib/components/screens/welcome.js'); const { SearchScreen } = require('lib/components/screens/search.js'); const { OneDriveLoginScreen } = require('lib/components/screens/onedrive-login.js'); const { EncryptionConfigScreen } = require('lib/components/screens/encryption-config.js'); +const { DropboxLoginScreen } = require('lib/components/screens/dropbox-login.js'); const Setting = require('lib/models/Setting.js'); const { MenuContext } = require('react-native-popup-menu'); const { SideMenu } = require('lib/components/side-menu.js'); @@ -55,10 +56,12 @@ const SyncTargetFilesystem = require('lib/SyncTargetFilesystem.js'); const SyncTargetOneDriveDev = require('lib/SyncTargetOneDriveDev.js'); const SyncTargetNextcloud = require('lib/SyncTargetNextcloud.js'); const SyncTargetWebDAV = require('lib/SyncTargetWebDAV.js'); +const SyncTargetDropbox = require('lib/SyncTargetDropbox.js'); SyncTargetRegistry.addClass(SyncTargetOneDrive); SyncTargetRegistry.addClass(SyncTargetOneDriveDev); SyncTargetRegistry.addClass(SyncTargetNextcloud); SyncTargetRegistry.addClass(SyncTargetWebDAV); +SyncTargetRegistry.addClass(SyncTargetDropbox); // Disabled because not fully working //SyncTargetRegistry.addClass(SyncTargetFilesystem); @@ -365,16 +368,6 @@ async function initialize(dispatch) { await db.open({ name: 'joplin.sqlite' }) } else { await db.open({ name: 'joplin-68.sqlite' }) - //await db.open({ name: 'joplin-67.sqlite' }) - - // await db.exec('DELETE FROM notes'); - // await db.exec('DELETE FROM folders'); - // await db.exec('DELETE FROM tags'); - // await db.exec('DELETE FROM note_tags'); - // await db.exec('DELETE FROM resources'); - // await db.exec('DELETE FROM deleted_items'); - - // await db.exec('UPDATE notes SET is_conflict = 1 where id like "546f%"'); } reg.logger().info('Database is ready.'); @@ -559,6 +552,7 @@ class AppComponent extends React.Component { Note: { screen: NoteScreen }, Folder: { screen: FolderScreen }, OneDriveLogin: { screen: OneDriveLoginScreen }, + DropboxLogin: { screen: DropboxLoginScreen }, EncryptionConfig: { screen: EncryptionConfigScreen }, Log: { screen: LogScreen }, Status: { screen: StatusScreen },