diff --git a/Assets/macOs.iconset/icon_512x512@2x.png b/Assets/macOs.iconset/icon_512x512@2x.png index 91a6977703..b5df3b3055 100644 Binary files a/Assets/macOs.iconset/icon_512x512@2x.png and b/Assets/macOs.iconset/icon_512x512@2x.png differ diff --git a/ReactNativeClient/images/StartUpIcon.png b/ReactNativeClient/images/StartUpIcon.png new file mode 100644 index 0000000000..f8aaac5541 Binary files /dev/null and b/ReactNativeClient/images/StartUpIcon.png differ diff --git a/ReactNativeClient/images/StartUpIcon@2x.png b/ReactNativeClient/images/StartUpIcon@2x.png new file mode 100644 index 0000000000..fc1df0176d Binary files /dev/null and b/ReactNativeClient/images/StartUpIcon@2x.png differ diff --git a/ReactNativeClient/images/StartUpIcon@3x.png b/ReactNativeClient/images/StartUpIcon@3x.png new file mode 100644 index 0000000000..e7b23d6daf Binary files /dev/null and b/ReactNativeClient/images/StartUpIcon@3x.png differ diff --git a/ReactNativeClient/lib/joplin-database.js b/ReactNativeClient/lib/joplin-database.js index ab0a2897a9..0609c2c1ce 100644 --- a/ReactNativeClient/lib/joplin-database.js +++ b/ReactNativeClient/lib/joplin-database.js @@ -3,6 +3,7 @@ const { Database } = require('lib/database.js'); const { sprintf } = require('sprintf-js'); const Resource = require('lib/models/Resource'); const { shim } = require('lib/shim.js'); +const EventEmitter = require('events'); const structureSql = ` CREATE TABLE folders ( @@ -125,6 +126,11 @@ class JoplinDatabase extends Database { this.tableFields_ = null; this.version_ = null; this.tableFieldNames_ = {}; + this.eventEmitter_ = new EventEmitter(); + } + + eventEmitter() { + return this.eventEmitter_; } initialized() { @@ -350,6 +356,8 @@ class JoplinDatabase extends Database { let queries = []; + this.eventEmitter_.emit('startMigration', { version: targetVersion }); + if (targetVersion == 1) { queries = this.wrapQueries(this.sqlStringToLines(structureSql)); } diff --git a/ReactNativeClient/root.js b/ReactNativeClient/root.js index 478e6a2780..6b65553931 100644 --- a/ReactNativeClient/root.js +++ b/ReactNativeClient/root.js @@ -2,7 +2,7 @@ import setUpQuickActions from './setUpQuickActions'; import PluginAssetsLoader from './PluginAssetsLoader'; const React = require('react'); -const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar } = require('react-native'); +const { AppState, Keyboard, NativeModules, BackHandler, Animated, View, StatusBar, Text, Image } = require('react-native'); const SafeAreaView = require('lib/components/SafeAreaView'); const { connect, Provider } = require('react-redux'); const { BackButtonService } = require('lib/services/back-button.js'); @@ -376,7 +376,7 @@ function decryptionWorker_resourceMetadataButNotBlobDecrypted() { ResourceFetcher.instance().scheduleAutoAddResources(); } -async function initialize(dispatch) { +async function initialize(dispatch, messageHandler) { shimInit(); Setting.setConstant('env', __DEV__ ? 'dev' : 'prod'); @@ -414,8 +414,13 @@ async function initialize(dispatch) { dbLogger.setLevel(Logger.LEVEL_INFO); } + const db_startUpgrade = (event) => { + messageHandler(`Upgrading database to v${event.version}...`); + }; + const db = new JoplinDatabase(new DatabaseDriverReactNative()); db.setLogger(dbLogger); + db.eventEmitter().on('startMigration', db_startUpgrade); reg.setDb(db); reg.dispatch = dispatch; @@ -452,9 +457,13 @@ async function initialize(dispatch) { // await db.clearForTesting(); } + db.eventEmitter().removeListener('startMigration', db_startUpgrade); + reg.logger().info('Database is ready.'); reg.logger().info('Loading settings...'); + messageHandler('Initialising application...'); + await loadKeychainServiceAndSettings(KeychainServiceDriverMobile); if (!Setting.value('clientId')) Setting.setValue('clientId', uuid.create()); @@ -601,6 +610,7 @@ class AppComponent extends React.Component { this.state = { sideMenuContentOpacity: new Animated.Value(0), + initMessage: '', }; this.lastSyncStarted_ = defaultState.syncStarted; @@ -614,47 +624,52 @@ class AppComponent extends React.Component { }; } - async componentDidMount() { - if (this.props.appState == 'starting') { + componentDidMount() { + setTimeout(async () => { + // We run initialization code with a small delay to give time + // to the view to render "please wait" messages. + this.props.dispatch({ type: 'APP_STATE_SET', state: 'initializing', }); - await initialize(this.props.dispatch); + await initialize(this.props.dispatch, (message) => { + this.setState({ initMessage: message }); + }); + + BackButtonService.initialize(this.backButtonHandler_); + + AlarmService.setInAppNotificationHandler(async (alarmId) => { + const alarm = await Alarm.load(alarmId); + const notification = await Alarm.makeNotification(alarm); + this.dropdownAlert_.alertWithType('info', notification.title, notification.body ? notification.body : ''); + }); + + AppState.addEventListener('change', this.onAppStateChange_); + + const sharedData = await ShareExtension.data(); + if (sharedData) { + reg.logger().info('Received shared data'); + if (this.props.selectedFolderId) { + handleShared(sharedData, this.props.selectedFolderId, this.props.dispatch); + } else { + reg.logger.info('Cannot handle share - default folder id is not set'); + } + } this.props.dispatch({ type: 'APP_STATE_SET', state: 'ready', }); - } - - BackButtonService.initialize(this.backButtonHandler_); - - AlarmService.setInAppNotificationHandler(async (alarmId) => { - const alarm = await Alarm.load(alarmId); - const notification = await Alarm.makeNotification(alarm); - this.dropdownAlert_.alertWithType('info', notification.title, notification.body ? notification.body : ''); - }); - - AppState.addEventListener('change', this.onAppStateChange_); - - const sharedData = await ShareExtension.data(); - if (sharedData) { - reg.logger().info('Received shared data'); - if (this.props.selectedFolderId) { - handleShared(sharedData, this.props.selectedFolderId, this.props.dispatch); - } else { - reg.logger.info('Cannot handle share - default folder id is not set'); - } - } + }, 100); } componentWillUnmount() { AppState.removeEventListener('change', this.onAppStateChange_); } - componentDidUpdate(prevProps) { + async componentDidUpdate(prevProps) { if (this.props.showSideMenu !== prevProps.showSideMenu) { Animated.timing(this.state.sideMenuContentOpacity, { toValue: this.props.showSideMenu ? 0.5 : 0, @@ -699,8 +714,22 @@ class AppComponent extends React.Component { }); } + renderStartupScreen() { + return ( + + + + {this.state.initMessage} + + + ); + } + render() { - if (this.props.appState != 'ready') return null; + if (this.props.appState != 'ready') { + return this.renderStartupScreen(); + } + const theme = themeStyle(this.props.theme); let sideMenuContent = null; diff --git a/Tools/generate-images.js b/Tools/generate-images.js index 93f13ea3ba..e7e871b53f 100644 --- a/Tools/generate-images.js +++ b/Tools/generate-images.js @@ -281,6 +281,29 @@ const operations = [ iconWidth: 46, iconHeight: 46, }, + + // ============================================================================ + // Mobile startup icon + // ============================================================================ + + { + source: 7, + dest: 'ReactNativeClient/images/StartUpIcon.png', + width: 64, + height: 64, + }, + { + source: 7, + dest: 'ReactNativeClient/images/StartUpIcon@2x.png', + width: 128, + height: 128, + }, + { + source: 7, + dest: 'ReactNativeClient/images/StartUpIcon@3x.png', + width: 192, + height: 192, + }, ]; async function main() { diff --git a/joplin.code-workspace b/joplin.code-workspace index a432d5a333..800b05c552 100644 --- a/joplin.code-workspace +++ b/joplin.code-workspace @@ -3,11 +3,7 @@ { "name": ".", "path": "." - }, - { - "name": "D:/Web/www/nextcloud/apps/joplin", - "path": "D:/Web/www/nextcloud/apps/joplin" - }, + } ], "settings": { "files.exclude": {