2020-12-30 13:51:43 +02:00
const React = require ( 'react' ) ;
import shim from '@joplin/lib/shim' ;
shim . setReact ( React ) ;
2021-05-19 23:26:42 +02:00
import setupQuickActions from './setupQuickActions' ;
2020-06-02 22:13:15 +02:00
import PluginAssetsLoader from './PluginAssetsLoader' ;
2020-12-30 12:54:00 +02:00
import AlarmService from '@joplin/lib/services/AlarmService' ;
import Alarm from '@joplin/lib/models/Alarm' ;
import time from '@joplin/lib/time' ;
2023-07-27 17:05:56 +02:00
import Logger , { TargetType } from '@joplin/utils/Logger' ;
2020-12-30 12:54:00 +02:00
import BaseModel from '@joplin/lib/BaseModel' ;
import BaseService from '@joplin/lib/services/BaseService' ;
import ResourceService from '@joplin/lib/services/ResourceService' ;
import KvStore from '@joplin/lib/services/KvStore' ;
import NoteScreen from './components/screens/Note' ;
import UpgradeSyncTargetScreen from './components/screens/UpgradeSyncTargetScreen' ;
2024-06-25 15:01:39 +02:00
import Setting , { AppType , Env } from '@joplin/lib/models/Setting' ;
2020-12-30 12:54:00 +02:00
import PoorManIntervals from '@joplin/lib/PoorManIntervals' ;
2023-09-24 21:21:58 +02:00
import reducer , { NotesParent , parseNotesParent , serializeNotesParent } from '@joplin/lib/reducer' ;
2020-12-30 12:54:00 +02:00
import ShareExtension from './utils/ShareExtension' ;
import handleShared from './utils/shareHandler' ;
import uuid from '@joplin/lib/uuid' ;
import { loadKeychainServiceAndSettings } from '@joplin/lib/services/SettingUtils' ;
2023-09-11 21:44:15 +02:00
import { _ , setLocale } from '@joplin/lib/locale' ;
2020-12-30 12:54:00 +02:00
import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer' ;
2021-06-03 17:12:07 +02:00
import SyncTargetJoplinCloud from '@joplin/lib/SyncTargetJoplinCloud' ;
2021-01-20 17:49:02 +02:00
import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive' ;
2023-01-10 14:08:13 +02:00
import initProfile from '@joplin/lib/services/profileConfig/initProfile' ;
2021-06-17 19:19:32 +02:00
const VersionInfo = require ( 'react-native-version-info' ) . default ;
2024-08-02 15:51:49 +02:00
const { Keyboard , BackHandler , Animated , StatusBar , Platform , Dimensions } = require ( 'react-native' ) ;
2024-09-16 23:17:12 +02:00
import { AppState as RNAppState , EmitterSubscription , View , Text , Linking , NativeEventSubscription , Appearance , ActivityIndicator } from 'react-native' ;
2022-08-25 17:59:38 +02:00
import getResponsiveValue from './components/getResponsiveValue' ;
2021-03-29 10:35:39 +02:00
import NetInfo from '@react-native-community/netinfo' ;
2020-12-30 12:54:00 +02:00
const DropdownAlert = require ( 'react-native-dropdownalert' ) . default ;
2021-01-27 19:42:58 +02:00
const AlarmServiceDriver = require ( './services/AlarmServiceDriver' ) . default ;
2020-11-05 18:58:23 +02:00
const SafeAreaView = require ( './components/SafeAreaView' ) ;
2018-03-09 22:59:12 +02:00
const { connect , Provider } = require ( 'react-redux' ) ;
2024-11-09 14:54:09 +02:00
import fastDeepEqual = require ( 'fast-deep-equal' ) ;
2023-01-10 14:08:13 +02:00
import { Provider as PaperProvider , MD3DarkTheme , MD3LightTheme } from 'react-native-paper' ;
2024-09-21 19:28:41 +02:00
import BackButtonService from './services/BackButtonService' ;
2021-01-23 17:51:19 +02:00
import NavService from '@joplin/lib/services/NavService' ;
2024-07-01 17:21:17 +02:00
import { createStore , applyMiddleware , Dispatch } from 'redux' ;
2024-01-03 20:02:05 +02:00
import reduxSharedMiddleware from '@joplin/lib/components/shared/reduxSharedMiddleware' ;
2024-07-16 20:23:03 +02:00
import shimInit from './utils/shim-init-react' ;
2020-11-05 18:58:23 +02:00
const { AppNav } = require ( './components/app-nav.js' ) ;
2021-01-22 19:41:11 +02:00
import Note from '@joplin/lib/models/Note' ;
import Folder from '@joplin/lib/models/Folder' ;
2021-03-30 23:58:47 +02:00
import BaseSyncTarget from '@joplin/lib/BaseSyncTarget' ;
2021-01-22 19:41:11 +02:00
import Resource from '@joplin/lib/models/Resource' ;
import Tag from '@joplin/lib/models/Tag' ;
import NoteTag from '@joplin/lib/models/NoteTag' ;
import BaseItem from '@joplin/lib/models/BaseItem' ;
import MasterKey from '@joplin/lib/models/MasterKey' ;
import Revision from '@joplin/lib/models/Revision' ;
2021-01-23 17:51:19 +02:00
import RevisionService from '@joplin/lib/services/RevisionService' ;
2021-01-29 20:45:11 +02:00
import JoplinDatabase from '@joplin/lib/JoplinDatabase' ;
import Database from '@joplin/lib/database' ;
2023-02-18 17:05:48 +02:00
import NotesScreen from './components/screens/Notes' ;
2024-11-20 13:37:09 +02:00
import TagsScreen from './components/screens/tags' ;
2023-07-18 15:58:06 +02:00
import ConfigScreen from './components/screens/ConfigScreen/ConfigScreen' ;
2020-11-05 18:58:23 +02:00
const { FolderScreen } = require ( './components/screens/folder.js' ) ;
2023-10-02 20:14:08 +02:00
import LogScreen from './components/screens/LogScreen' ;
2024-04-08 13:35:57 +02:00
import StatusScreen from './components/screens/status' ;
2024-09-24 16:12:02 +02:00
import SearchScreen from './components/screens/SearchScreen' ;
2020-11-05 18:58:23 +02:00
const { OneDriveLoginScreen } = require ( './components/screens/onedrive-login.js' ) ;
2021-08-07 13:22:37 +02:00
import EncryptionConfigScreen from './components/screens/encryption-config' ;
2024-11-16 23:09:50 +02:00
import DropboxLoginScreen from './components/screens/dropbox-login.js' ;
2023-11-17 13:43:01 +02:00
import { MenuProvider } from 'react-native-popup-menu' ;
2024-09-16 23:17:12 +02:00
import SideMenu , { SideMenuPosition } from './components/SideMenu' ;
2022-10-11 13:31:09 +02:00
import SideMenuContent from './components/side-menu-content' ;
2024-09-21 13:57:08 +02:00
import SideMenuContentNote from './components/SideMenuContentNote' ;
2021-01-29 20:45:11 +02:00
import { reg } from '@joplin/lib/registry' ;
2020-11-07 17:59:37 +02:00
const { defaultState } = require ( '@joplin/lib/reducer' ) ;
2024-04-25 14:31:48 +02:00
import FileApiDriverLocal from '@joplin/lib/file-api-driver-local' ;
2021-01-23 17:51:19 +02:00
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher' ;
2024-01-05 16:15:47 +02:00
import SearchEngine from '@joplin/lib/services/search/SearchEngine' ;
2023-05-10 13:18:59 +02:00
import WelcomeUtils from '@joplin/lib/WelcomeUtils' ;
2024-03-09 13:15:13 +02:00
import { themeStyle } from './components/global-style' ;
2021-08-16 16:20:14 +02:00
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry' ;
2024-04-25 14:31:48 +02:00
import SyncTargetFilesystem from '@joplin/lib/SyncTargetFilesystem' ;
2020-11-07 17:59:37 +02:00
const SyncTargetNextcloud = require ( '@joplin/lib/SyncTargetNextcloud.js' ) ;
const SyncTargetWebDAV = require ( '@joplin/lib/SyncTargetWebDAV.js' ) ;
const SyncTargetDropbox = require ( '@joplin/lib/SyncTargetDropbox.js' ) ;
const SyncTargetAmazonS3 = require ( '@joplin/lib/SyncTargetAmazonS3.js' ) ;
2023-01-04 22:18:51 +02:00
import BiometricPopup from './components/biometrics/BiometricPopup' ;
2023-08-04 11:57:54 +02:00
import initLib from '@joplin/lib/initLib' ;
2024-06-15 10:58:23 +02:00
import { isCallbackUrl , parseCallbackUrl , CallbackUrlCommand } from '@joplin/lib/callbackUrlUtils' ;
2024-03-11 17:17:23 +02:00
import JoplinCloudLoginScreen from './components/screens/JoplinCloudLoginScreen' ;
2020-02-08 13:59:19 +02:00
2024-08-02 15:51:49 +02:00
import SyncTargetNone from '@joplin/lib/SyncTargetNone' ;
2024-11-09 14:54:09 +02:00
2021-08-16 19:05:22 +02:00
SyncTargetRegistry . addClass ( SyncTargetNone ) ;
2017-11-24 21:21:30 +02:00
SyncTargetRegistry . addClass ( SyncTargetOneDrive ) ;
2018-01-25 21:01:14 +02:00
SyncTargetRegistry . addClass ( SyncTargetNextcloud ) ;
2018-02-02 01:40:05 +02:00
SyncTargetRegistry . addClass ( SyncTargetWebDAV ) ;
2018-03-27 01:55:44 +02:00
SyncTargetRegistry . addClass ( SyncTargetDropbox ) ;
2018-05-02 11:27:37 +02:00
SyncTargetRegistry . addClass ( SyncTargetFilesystem ) ;
2020-07-29 00:47:24 +02:00
SyncTargetRegistry . addClass ( SyncTargetAmazonS3 ) ;
2020-12-30 12:54:00 +02:00
SyncTargetRegistry . addClass ( SyncTargetJoplinServer ) ;
2021-06-03 17:12:07 +02:00
SyncTargetRegistry . addClass ( SyncTargetJoplinCloud ) ;
2018-01-17 23:01:41 +02:00
2023-10-22 12:51:31 +02:00
import FsDriverRN from './utils/fs-driver/fs-driver-rn' ;
2021-01-23 17:51:19 +02:00
import DecryptionWorker from '@joplin/lib/services/DecryptionWorker' ;
2021-08-23 19:47:07 +02:00
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService' ;
2021-01-23 17:51:19 +02:00
import MigrationService from '@joplin/lib/services/MigrationService' ;
2021-04-07 19:41:54 +02:00
import { clearSharedFilesCache } from './utils/ShareUtils' ;
2021-04-25 10:50:52 +02:00
import setIgnoreTlsErrors from './utils/TlsUtils' ;
2021-05-13 18:57:37 +02:00
import ShareService from '@joplin/lib/services/share/ShareService' ;
2021-05-19 23:26:42 +02:00
import setupNotifications from './utils/setupNotifications' ;
2021-08-30 15:15:35 +02:00
import { loadMasterKeysFromSettings , migrateMasterPassword } from '@joplin/lib/services/e2ee/utils' ;
2021-10-03 17:00:49 +02:00
import { setRSA } from '@joplin/lib/services/e2ee/ppk' ;
import RSA from './services/e2ee/RSA.react-native' ;
2023-10-22 12:51:31 +02:00
import { runIntegrationTests as runRsaIntegrationTests } from '@joplin/lib/services/e2ee/ppkTestUtils' ;
2024-10-26 22:15:10 +02:00
import { runIntegrationTests as runCryptoIntegrationTests } from '@joplin/lib/services/e2ee/cryptoTestUtils' ;
2023-01-08 14:22:41 +02:00
import { Theme , ThemeAppearance } from '@joplin/lib/themes/type' ;
2023-01-10 14:08:13 +02:00
import ProfileSwitcher from './components/ProfileSwitcher/ProfileSwitcher' ;
import ProfileEditor from './components/ProfileSwitcher/ProfileEditor' ;
2023-06-08 17:23:48 +02:00
import sensorInfo , { SensorInfo } from './components/biometrics/sensorInfo' ;
2023-01-10 14:08:13 +02:00
import { getCurrentProfile } from '@joplin/lib/services/profileConfig' ;
2024-04-04 13:10:59 +02:00
import { getDatabaseName , getPluginDataDir , getProfilesRootDir , getResourceDir , setDispatch } from './services/profiles' ;
2023-09-10 12:30:18 +02:00
import userFetcher , { initializeUserFetcher } from '@joplin/lib/utils/userFetcher' ;
2023-09-11 09:53:53 +02:00
import { ReactNode } from 'react' ;
2023-07-16 18:42:42 +02:00
import { parseShareCache } from '@joplin/lib/services/share/reducer' ;
2023-07-18 15:46:11 +02:00
import autodetectTheme , { onSystemColorSchemeChange } from './utils/autodetectTheme' ;
2023-10-22 12:51:31 +02:00
import runOnDeviceFsDriverTests from './utils/fs-driver/runOnDeviceTests' ;
2024-06-25 14:59:59 +02:00
import PluginRunnerWebView from './components/plugins/PluginRunnerWebView' ;
2024-03-02 16:25:27 +02:00
import { refreshFolders , scheduleRefreshFolders } from '@joplin/lib/folders-screen-utils' ;
2024-03-11 17:02:15 +02:00
import KeymapService from '@joplin/lib/services/KeymapService' ;
import PluginService from '@joplin/lib/services/plugins/PluginService' ;
import initializeCommandService from './utils/initializeCommandService' ;
2024-06-25 14:59:59 +02:00
import PlatformImplementation from './services/plugins/PlatformImplementation' ;
2024-04-15 19:17:34 +02:00
import ShareManager from './components/screens/ShareManager' ;
import appDefaultState , { DEFAULT_ROUTE } from './utils/appDefaultState' ;
2024-06-20 15:01:13 +02:00
import { setDateFormat , setTimeFormat , setTimeLocale } from '@joplin/utils/time' ;
2024-08-02 15:51:49 +02:00
import DatabaseDriverReactNative from './utils/database-driver-react-native' ;
import DialogManager from './components/DialogManager' ;
import lockToSingleInstance from './utils/lockToSingleInstance' ;
2024-07-26 13:35:50 +02:00
import { AppState } from './utils/types' ;
import { getDisplayParentId } from '@joplin/lib/services/trash' ;
2023-07-06 20:18:43 +02:00
2023-02-19 21:00:17 +02:00
const logger = Logger . create ( 'root' ) ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2024-03-02 16:25:27 +02:00
let storeDispatch : any = function ( _action : any ) { } ;
2018-01-02 21:17:14 +02:00
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-12-30 12:54:00 +02:00
const logReducerAction = function ( action : any ) {
2018-04-16 15:15:29 +02:00
if ( [ 'SIDE_MENU_OPEN_PERCENT' , 'SYNC_REPORT_UPDATE' ] . indexOf ( action . type ) >= 0 ) return ;
2020-03-14 01:46:14 +02:00
const msg = [ action . type ] ;
2018-04-16 15:15:29 +02:00
if ( action . routeName ) msg . push ( action . routeName ) ;
2019-05-28 23:05:11 +02:00
// reg.logger().debug('Reducer action', msg.join(', '));
2019-07-30 09:35:42 +02:00
} ;
2018-04-16 15:15:29 +02:00
2023-06-08 17:23:48 +02:00
const biometricsEnabled = ( sensorInfo : SensorInfo ) : boolean = > {
return ! ! sensorInfo && sensorInfo . enabled ;
} ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-12-30 12:54:00 +02:00
const generalMiddleware = ( store : any ) = > ( next : any ) = > async ( action : any ) = > {
2018-04-16 15:15:29 +02:00
logReducerAction ( action ) ;
2017-07-25 20:41:53 +02:00
PoorManIntervals . update ( ) ; // This function needs to be called regularly so put it here
const result = next ( action ) ;
2024-07-26 13:35:50 +02:00
const newState : AppState = store . getState ( ) ;
2024-03-02 16:25:27 +02:00
let doRefreshFolders = false ;
2017-07-25 20:41:53 +02:00
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2024-01-03 20:02:05 +02:00
await reduxSharedMiddleware ( store , next , action , storeDispatch as any ) ;
2018-05-09 13:39:17 +02:00
2022-07-23 09:31:32 +02:00
if ( action . type === 'NAV_GO' ) Keyboard . dismiss ( ) ;
2017-07-25 20:41:53 +02:00
2019-07-30 09:35:42 +02:00
if ( [ 'NOTE_UPDATE_ONE' , 'NOTE_DELETE' , 'FOLDER_UPDATE_ONE' , 'FOLDER_DELETE' ] . indexOf ( action . type ) >= 0 ) {
2023-05-11 15:58:19 +02:00
if ( ! await reg . syncTarget ( ) . syncStarted ( ) ) void reg . scheduleSync ( 1000 , { syncSteps : [ 'update_remote' , 'delete_remote' ] } , true ) ;
2019-01-18 20:31:07 +02:00
SearchEngine . instance ( ) . scheduleSyncTables ( ) ;
2017-07-26 19:49:01 +02:00
}
2024-03-02 16:25:27 +02:00
if ( [ 'FOLDER_UPDATE_ONE' ] . indexOf ( action . type ) >= 0 ) {
doRefreshFolders = true ;
}
2018-03-09 22:59:12 +02:00
if ( [ 'EVENT_NOTE_ALARM_FIELD_CHANGE' , 'NOTE_DELETE' ] . indexOf ( action . type ) >= 0 ) {
await AlarmService . updateNoteNotification ( action . id , action . type === 'NOTE_DELETE' ) ;
2017-11-28 20:58:04 +02:00
}
2024-07-26 13:35:50 +02:00
if ( action . type === 'NOTE_DELETE' && newState . route ? . routeName === 'Note' && newState . route . noteId === action . id ) {
const parentItem = action . originalItem ? . parent_id ? await Folder . load ( action . originalItem ? . parent_id ) : null ;
const parentId = getDisplayParentId ( action . originalItem , parentItem ) ;
await NavService . go ( 'Notes' , { folderId : parentId } ) ;
}
2022-07-23 09:31:32 +02:00
if ( action . type === 'SETTING_UPDATE_ONE' && action . key === 'sync.interval' || action . type === 'SETTING_UPDATE_ALL' ) {
2017-07-26 19:49:01 +02:00
reg . setupRecurrentSync ( ) ;
}
2022-07-23 09:31:32 +02:00
if ( ( action . type === 'SETTING_UPDATE_ONE' && ( action . key === 'dateFormat' || action . key === 'timeFormat' ) ) || ( action . type === 'SETTING_UPDATE_ALL' ) ) {
2018-03-09 22:59:12 +02:00
time . setDateFormat ( Setting . value ( 'dateFormat' ) ) ;
time . setTimeFormat ( Setting . value ( 'timeFormat' ) ) ;
2024-06-20 15:01:13 +02:00
setDateFormat ( Setting . value ( 'dateFormat' ) ) ;
setTimeFormat ( Setting . value ( 'timeFormat' ) ) ;
2017-11-28 20:02:54 +02:00
}
2022-07-23 09:31:32 +02:00
if ( action . type === 'SETTING_UPDATE_ONE' && action . key === 'locale' || action . type === 'SETTING_UPDATE_ALL' ) {
2018-03-09 22:59:12 +02:00
setLocale ( Setting . value ( 'locale' ) ) ;
2024-06-20 15:01:13 +02:00
setTimeLocale ( Setting . value ( 'locale' ) ) ;
2018-01-02 21:17:14 +02:00
}
2024-09-07 12:56:13 +02:00
// Like the desktop and CLI apps, we run this whenever the sync target properties change.
// Previously, this only ran when encryption was enabled/disabled. However, after fetching
// a new key, this needs to run and so we run it when the sync target info changes.
if (
( action . type === 'SETTING_UPDATE_ONE' && ( action . key === 'syncInfoCache' || action . key . startsWith ( 'encryption.' ) ) )
|| action . type === 'SETTING_UPDATE_ALL'
) {
2021-08-12 17:54:10 +02:00
await loadMasterKeysFromSettings ( EncryptionService . instance ( ) ) ;
2021-01-22 19:41:11 +02:00
void DecryptionWorker . instance ( ) . scheduleStart ( ) ;
2018-01-02 21:17:14 +02:00
const loadedMasterKeyIds = EncryptionService . instance ( ) . loadedMasterKeyIds ( ) ;
storeDispatch ( {
2018-03-09 22:59:12 +02:00
type : 'MASTERKEY_REMOVE_NOT_LOADED' ,
2018-01-02 21:17:14 +02:00
ids : loadedMasterKeyIds ,
} ) ;
2018-01-08 22:25:38 +02:00
// Schedule a sync operation so that items that need to be encrypted
// are sent to sync target.
2021-03-29 10:35:39 +02:00
void reg . scheduleSync ( null , null , true ) ;
2018-01-02 21:17:14 +02:00
}
2017-11-03 20:51:13 +02:00
2023-07-18 15:46:11 +02:00
if (
action . type === 'AUTODETECT_THEME'
|| action . type === 'SETTING_UPDATE_ALL'
|| ( action . type === 'SETTING_UPDATE_ONE' && [ 'themeAutoDetect' , 'preferredLightTheme' , 'preferredDarkTheme' ] . includes ( action . key ) )
) {
autodetectTheme ( ) ;
}
2022-07-23 09:31:32 +02:00
if ( action . type === 'NAV_GO' && action . routeName === 'Notes' ) {
2024-05-28 14:43:33 +02:00
if ( 'selectedFolderId' in newState ) {
Setting . setValue ( 'activeFolderId' , newState . selectedFolderId ) ;
}
2023-09-24 21:21:58 +02:00
const notesParent : NotesParent = {
type : action . smartFilterId ? 'SmartFilter' : 'Folder' ,
selectedItemId : action.smartFilterId ? action.smartFilterId : newState.selectedFolderId ,
} ;
Setting . setValue ( 'notesParent' , serializeNotesParent ( notesParent ) ) ;
2017-07-25 20:41:53 +02:00
}
2018-03-09 22:59:12 +02:00
if ( action . type === 'SYNC_GOT_ENCRYPTED_ITEM' ) {
2021-01-22 19:41:11 +02:00
void DecryptionWorker . instance ( ) . scheduleStart ( ) ;
2017-12-30 21:57:34 +02:00
}
2020-05-31 01:31:29 +02:00
if ( action . type === 'SYNC_CREATED_OR_UPDATED_RESOURCE' ) {
2021-01-22 19:41:11 +02:00
void ResourceFetcher . instance ( ) . autoAddResources ( ) ;
2018-10-09 23:01:50 +02:00
}
2024-03-02 16:25:27 +02:00
if ( doRefreshFolders ) {
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2024-04-27 10:54:47 +02:00
await scheduleRefreshFolders ( ( action : any ) = > storeDispatch ( action ) , newState . selectedFolderId ) ;
2024-03-02 16:25:27 +02:00
}
2019-07-30 09:35:42 +02:00
return result ;
} ;
2017-07-25 20:41:53 +02:00
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-12-30 12:54:00 +02:00
const navHistory : any [ ] = [ ] ;
2017-11-06 20:05:12 +02:00
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-12-30 12:54:00 +02:00
function historyCanGoBackTo ( route : any ) {
2018-03-16 22:17:52 +02:00
if ( route . routeName === 'Folder' ) return false ;
2017-11-06 20:05:12 +02:00
2018-09-23 21:45:34 +02:00
// There's no point going back to these screens in general and, at least in OneDrive case,
2019-07-30 09:35:42 +02:00
// it can be buggy to do so, due to incorrectly relying on global state (reg.syncTarget...)
2018-09-23 21:45:34 +02:00
if ( route . routeName === 'OneDriveLogin' ) return false ;
if ( route . routeName === 'DropboxLogin' ) return false ;
2017-11-06 20:05:12 +02:00
return true ;
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-12-30 12:54:00 +02:00
const appReducer = ( state = appDefaultState , action : any ) = > {
2017-11-06 20:05:12 +02:00
let newState = state ;
let historyGoingBack = false ;
try {
switch ( action . type ) {
2018-03-09 22:59:12 +02:00
2019-07-30 09:35:42 +02:00
case 'NAV_BACK' :
2023-06-30 11:22:47 +02:00
case 'NAV_GO' :
2018-03-09 22:59:12 +02:00
2023-06-30 11:22:47 +02:00
if ( action . type === 'NAV_BACK' ) {
if ( ! navHistory . length ) break ;
2017-11-06 20:05:12 +02:00
2024-09-21 14:00:31 +02:00
const newAction = navHistory . pop ( ) ;
2023-06-30 11:22:47 +02:00
action = newAction ? newAction : navHistory.pop ( ) ;
2018-03-09 22:59:12 +02:00
2023-06-30 11:22:47 +02:00
historyGoingBack = true ;
}
2017-11-06 20:05:12 +02:00
2019-07-30 09:35:42 +02:00
{
2017-11-06 20:05:12 +02:00
const currentRoute = state . route ;
2020-12-30 12:54:00 +02:00
if ( ! historyGoingBack && historyCanGoBackTo ( currentRoute ) ) {
2024-11-09 14:54:09 +02:00
const previousRoute = navHistory . length && navHistory [ navHistory . length - 1 ] ;
const isDifferentRoute = ! previousRoute || ! fastDeepEqual ( navHistory [ navHistory . length - 1 ] , currentRoute ) ;
// Avoid multiple consecutive duplicate screens in the navigation history -- these can make
// pressing "back" seem to have no effect.
if ( isDifferentRoute ) {
navHistory . push ( currentRoute ) ;
}
}
if ( action . clearHistory ) {
navHistory . splice ( 0 , navHistory . length ) ;
2017-11-06 20:05:12 +02:00
}
2023-06-01 13:02:36 +02:00
newState = { . . . state } ;
2017-11-06 20:05:12 +02:00
2019-09-09 19:16:00 +02:00
newState . selectedNoteHash = '' ;
2024-10-31 10:18:13 +02:00
if ( action . routeName === 'Search' ) {
newState . notesParentType = 'Search' ;
}
2018-03-09 22:59:12 +02:00
if ( 'noteId' in action ) {
2017-11-22 20:35:31 +02:00
newState . selectedNoteIds = action . noteId ? [ action . noteId ] : [ ] ;
2017-11-06 20:05:12 +02:00
}
2018-03-09 22:59:12 +02:00
if ( 'folderId' in action ) {
2017-11-06 20:05:12 +02:00
newState . selectedFolderId = action . folderId ;
2018-03-09 22:59:12 +02:00
newState . notesParentType = 'Folder' ;
2017-11-06 20:05:12 +02:00
}
2018-03-09 22:59:12 +02:00
if ( 'tagId' in action ) {
2017-11-06 20:05:12 +02:00
newState . selectedTagId = action . tagId ;
2018-03-09 22:59:12 +02:00
newState . notesParentType = 'Tag' ;
2017-11-06 20:05:12 +02:00
}
2019-06-28 01:51:02 +02:00
if ( 'smartFilterId' in action ) {
newState . smartFilterId = action . smartFilterId ;
2023-11-19 12:43:57 +02:00
newState . selectedSmartFilterId = action . smartFilterId ;
2019-06-28 01:51:02 +02:00
newState . notesParentType = 'SmartFilter' ;
}
2018-03-09 22:59:12 +02:00
if ( 'itemType' in action ) {
2017-11-06 20:05:12 +02:00
newState . selectedItemType = action . itemType ;
}
2019-09-09 19:16:00 +02:00
if ( 'noteHash' in action ) {
newState . selectedNoteHash = action . noteHash ;
}
2018-07-20 11:04:25 +02:00
if ( 'sharedData' in action ) {
newState . sharedData = action . sharedData ;
} else {
newState . sharedData = null ;
}
2017-11-06 20:05:12 +02:00
newState . route = action ;
newState . historyCanGoBack = ! ! navHistory . length ;
2024-10-31 10:18:13 +02:00
logger . debug ( 'Navigated to route:' , newState . route ? . routeName , 'with notesParentType:' , newState . notesParentType ) ;
2019-07-30 09:35:42 +02:00
}
break ;
2017-11-06 20:05:12 +02:00
2019-07-30 09:35:42 +02:00
case 'SIDE_MENU_TOGGLE' :
2018-03-09 22:59:12 +02:00
2023-06-01 13:02:36 +02:00
newState = { . . . state } ;
2019-07-30 09:35:42 +02:00
newState . showSideMenu = ! newState . showSideMenu ;
break ;
2017-11-06 20:05:12 +02:00
2019-07-30 09:35:42 +02:00
case 'SIDE_MENU_OPEN' :
2018-03-09 22:59:12 +02:00
2023-06-01 13:02:36 +02:00
newState = { . . . state } ;
2019-07-30 09:35:42 +02:00
newState . showSideMenu = true ;
break ;
2017-11-06 20:05:12 +02:00
2019-07-30 09:35:42 +02:00
case 'SIDE_MENU_CLOSE' :
2018-03-09 22:59:12 +02:00
2023-06-01 13:02:36 +02:00
newState = { . . . state } ;
2019-07-30 09:35:42 +02:00
newState . showSideMenu = false ;
break ;
2017-11-06 20:05:12 +02:00
2024-03-29 14:40:54 +02:00
case 'SET_PLUGIN_PANELS_DIALOG_VISIBLE' :
2024-03-25 13:39:48 +02:00
newState = { . . . state } ;
2024-03-29 14:40:54 +02:00
newState . showPanelsDialog = action . visible ;
2024-03-25 13:39:48 +02:00
break ;
2019-07-30 09:35:42 +02:00
case 'NOTE_SELECTION_TOGGLE' :
2018-03-09 22:59:12 +02:00
2019-07-30 09:35:42 +02:00
{
2023-06-01 13:02:36 +02:00
newState = { . . . state } ;
2017-11-23 20:47:51 +02:00
const noteId = action . id ;
const newSelectedNoteIds = state . selectedNoteIds . slice ( ) ;
const existingIndex = state . selectedNoteIds . indexOf ( noteId ) ;
if ( existingIndex >= 0 ) {
newSelectedNoteIds . splice ( existingIndex , 1 ) ;
} else {
newSelectedNoteIds . push ( noteId ) ;
}
newState . selectedNoteIds = newSelectedNoteIds ;
newState . noteSelectionEnabled = ! ! newSelectedNoteIds . length ;
2019-07-30 09:35:42 +02:00
}
break ;
2017-11-23 20:47:51 +02:00
2019-07-30 09:35:42 +02:00
case 'NOTE_SELECTION_START' :
2018-03-09 22:59:12 +02:00
2019-07-30 09:35:42 +02:00
if ( ! state . noteSelectionEnabled ) {
2023-06-01 13:02:36 +02:00
newState = { . . . state } ;
2019-07-30 09:35:42 +02:00
newState . noteSelectionEnabled = true ;
newState . selectedNoteIds = [ action . id ] ;
}
break ;
2017-11-23 20:47:51 +02:00
2019-07-30 09:35:42 +02:00
case 'NOTE_SELECTION_END' :
2018-03-09 22:59:12 +02:00
2023-06-01 13:02:36 +02:00
newState = { . . . state } ;
2019-07-30 09:35:42 +02:00
newState . noteSelectionEnabled = false ;
newState . selectedNoteIds = [ ] ;
break ;
2018-03-09 22:59:12 +02:00
2019-07-30 09:35:42 +02:00
case 'NOTE_SIDE_MENU_OPTIONS_SET' :
2019-07-11 18:43:55 +02:00
2023-06-01 13:02:36 +02:00
newState = { . . . state } ;
2019-07-30 09:35:42 +02:00
newState . noteSideMenuOptions = action . options ;
break ;
2018-03-09 22:59:12 +02:00
2023-10-02 16:15:51 +02:00
case 'SET_SIDE_MENU_TOUCH_GESTURES_DISABLED' :
newState = { . . . state } ;
newState . disableSideMenuGestures = action . disableSideMenuGestures ;
break ;
2021-03-29 10:35:39 +02:00
case 'MOBILE_DATA_WARNING_UPDATE' :
2023-06-01 13:02:36 +02:00
newState = { . . . state } ;
2021-03-29 10:35:39 +02:00
newState . isOnMobileData = action . isOnMobileData ;
break ;
2017-11-06 20:05:12 +02:00
}
} catch ( error ) {
2019-09-19 23:51:18 +02:00
error . message = ` In reducer: ${ error . message } Action: ${ JSON . stringify ( action ) } ` ;
2017-11-06 20:05:12 +02:00
throw error ;
}
return reducer ( newState , action ) ;
2019-07-30 09:35:42 +02:00
} ;
2017-11-06 20:05:12 +02:00
2020-03-14 01:46:14 +02:00
const store = createStore ( appReducer , applyMiddleware ( generalMiddleware ) ) ;
2018-01-02 21:17:14 +02:00
storeDispatch = store . dispatch ;
2017-05-09 22:46:54 +02:00
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-12-30 12:54:00 +02:00
function resourceFetcher_downloadComplete ( event : any ) {
2019-05-22 16:56:07 +02:00
if ( event . encrypted ) {
2021-01-22 19:41:11 +02:00
void DecryptionWorker . instance ( ) . scheduleStart ( ) ;
2019-05-22 16:56:07 +02:00
}
}
2020-06-09 21:56:48 +02:00
function decryptionWorker_resourceMetadataButNotBlobDecrypted() {
ResourceFetcher . instance ( ) . scheduleAutoAddResources ( ) ;
}
2023-07-18 15:58:06 +02:00
const initializeTempDir = async ( ) = > {
const tempDir = ` ${ getProfilesRootDir ( ) } /tmp ` ;
// Re-create the temporary directory.
try {
await shim . fsDriver ( ) . remove ( tempDir ) ;
} catch ( _error ) {
// The logger may not exist yet. Do nothing.
}
await shim . fsDriver ( ) . mkdir ( tempDir ) ;
return tempDir ;
} ;
2024-05-28 14:43:33 +02:00
const getInitialActiveFolder = async ( ) = > {
let folderId = Setting . value ( 'activeFolderId' ) ;
// In some cases (e.g. new profile/install), activeFolderId hasn't been set yet.
// Because activeFolderId is used to determine the parent for new notes, initialize
// it here:
if ( ! folderId ) {
folderId = ( await Folder . defaultFolder ( ) ) ? . id ;
if ( folderId ) {
Setting . setValue ( 'activeFolderId' , folderId ) ;
}
}
return await Folder . load ( folderId ) ;
} ;
2024-08-02 15:51:49 +02:00
const singleInstanceLock = lockToSingleInstance ( ) ;
2023-06-30 11:30:29 +02:00
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
2024-07-01 17:21:17 +02:00
async function initialize ( dispatch : Dispatch ) {
2017-07-10 20:09:58 +02:00
shimInit ( ) ;
2017-07-07 19:19:24 +02:00
2023-01-10 14:08:13 +02:00
setDispatch ( dispatch ) ;
const { profileConfig , isSubProfile } = await initProfile ( getProfilesRootDir ( ) ) ;
const currentProfile = getCurrentProfile ( profileConfig ) ;
dispatch ( {
type : 'PROFILE_CONFIG_SET' ,
value : profileConfig ,
} ) ;
2024-06-25 15:01:39 +02:00
Setting . setConstant ( 'env' , __DEV__ ? Env.Dev : Env.Prod ) ;
2018-03-09 22:59:12 +02:00
Setting . setConstant ( 'appId' , 'net.cozic.joplin-mobile' ) ;
2024-06-25 15:01:39 +02:00
Setting . setConstant ( 'appType' , AppType . Mobile ) ;
2023-07-18 15:58:06 +02:00
Setting . setConstant ( 'tempDir' , await initializeTempDir ( ) ) ;
2024-03-11 17:02:15 +02:00
Setting . setConstant ( 'cacheDir' , ` ${ getProfilesRootDir ( ) } /cache ` ) ;
2023-01-10 14:08:13 +02:00
const resourceDir = getResourceDir ( currentProfile , isSubProfile ) ;
Setting . setConstant ( 'resourceDir' , resourceDir ) ;
2024-03-11 17:02:15 +02:00
Setting . setConstant ( 'pluginDir' , ` ${ getProfilesRootDir ( ) } /plugins ` ) ;
2024-04-04 13:10:59 +02:00
Setting . setConstant ( 'pluginDataDir' , getPluginDataDir ( currentProfile , isSubProfile ) ) ;
2023-01-10 14:08:13 +02:00
await shim . fsDriver ( ) . mkdir ( resourceDir ) ;
2017-07-10 00:57:15 +02:00
2024-08-02 15:51:49 +02:00
// Do as much setup as possible before checking the lock -- the lock intentionally waits for
// messages from other clients for several hundred ms.
await singleInstanceLock ;
2017-07-10 00:57:15 +02:00
const logDatabase = new Database ( new DatabaseDriverReactNative ( ) ) ;
2018-03-09 22:59:12 +02:00
await logDatabase . open ( { name : 'log.sqlite' } ) ;
2017-07-10 00:57:15 +02:00
await logDatabase . exec ( Logger . databaseCreateTableSql ( ) ) ;
2017-07-15 01:12:32 +02:00
const mainLogger = new Logger ( ) ;
2020-12-30 12:54:00 +02:00
mainLogger . addTarget ( TargetType . Database , { database : logDatabase , source : 'm' } ) ;
2017-12-31 15:58:50 +02:00
mainLogger . setLevel ( Logger . LEVEL_INFO ) ;
2019-07-30 09:35:42 +02:00
2022-07-23 09:31:32 +02:00
if ( Setting . value ( 'env' ) === 'dev' ) {
2020-12-30 12:54:00 +02:00
mainLogger . addTarget ( TargetType . Console ) ;
2017-12-31 15:58:50 +02:00
mainLogger . setLevel ( Logger . LEVEL_DEBUG ) ;
}
2017-07-15 01:12:32 +02:00
2020-11-19 17:25:02 +02:00
Logger . initializeGlobalLogger ( mainLogger ) ;
2023-08-04 11:57:54 +02:00
initLib ( mainLogger ) ;
2020-11-19 17:25:02 +02:00
2017-07-15 01:12:32 +02:00
reg . setLogger ( mainLogger ) ;
2020-12-30 12:54:00 +02:00
reg . setShowErrorMessageBoxHandler ( ( message : string ) = > { alert ( message ) ; } ) ;
2024-07-01 17:21:17 +02:00
reg . setDispatch ( dispatch ) ;
2017-07-10 00:57:15 +02:00
2018-03-15 20:08:46 +02:00
BaseService . logger_ = mainLogger ;
2020-11-07 17:59:37 +02:00
// require('@joplin/lib/ntpDate').setLogger(reg.logger());
2018-03-15 20:08:46 +02:00
2018-03-09 22:59:12 +02:00
reg . logger ( ) . info ( '====================================' ) ;
2021-06-17 19:19:32 +02:00
reg . logger ( ) . info ( ` Starting application ${ Setting . value ( 'appId' ) } v ${ VersionInfo . appVersion } ( ${ Setting . value ( 'env' ) } ) ` ) ;
2017-07-10 00:57:15 +02:00
2017-07-15 01:12:32 +02:00
const dbLogger = new Logger ( ) ;
2020-12-30 12:54:00 +02:00
dbLogger . addTarget ( TargetType . Database , { database : logDatabase , source : 'm' } ) ;
2022-07-23 09:31:32 +02:00
if ( Setting . value ( 'env' ) === 'dev' ) {
2020-12-30 12:54:00 +02:00
dbLogger . addTarget ( TargetType . Console ) ;
2017-07-18 00:43:29 +02:00
dbLogger . setLevel ( Logger . LEVEL_INFO ) ; // Set to LEVEL_DEBUG for full SQL queries
2017-07-15 19:08:54 +02:00
} else {
dbLogger . setLevel ( Logger . LEVEL_INFO ) ;
}
2017-07-15 01:12:32 +02:00
2020-03-14 01:46:14 +02:00
const db = new JoplinDatabase ( new DatabaseDriverReactNative ( ) ) ;
2017-07-15 01:12:32 +02:00
db . setLogger ( dbLogger ) ;
2017-07-10 00:57:15 +02:00
reg . setDb ( db ) ;
2021-01-29 20:45:11 +02:00
// reg.dispatch = dispatch;
2017-07-10 00:57:15 +02:00
BaseModel . dispatch = dispatch ;
2017-11-24 20:06:30 +02:00
BaseSyncTarget . dispatch = dispatch ;
2018-02-22 00:08:34 +02:00
NavService . dispatch = dispatch ;
2020-03-16 04:30:54 +02:00
BaseModel . setDb ( db ) ;
2017-07-10 00:57:15 +02:00
2019-06-08 00:11:08 +02:00
KvStore . instance ( ) . setDb ( reg . db ( ) ) ;
2018-03-09 22:59:12 +02:00
BaseItem . loadClass ( 'Note' , Note ) ;
BaseItem . loadClass ( 'Folder' , Folder ) ;
BaseItem . loadClass ( 'Resource' , Resource ) ;
BaseItem . loadClass ( 'Tag' , Tag ) ;
BaseItem . loadClass ( 'NoteTag' , NoteTag ) ;
BaseItem . loadClass ( 'MasterKey' , MasterKey ) ;
2019-05-06 22:35:29 +02:00
BaseItem . loadClass ( 'Revision' , Revision ) ;
2017-07-10 00:57:15 +02:00
2017-12-30 21:57:34 +02:00
const fsDriver = new FsDriverRN ( ) ;
Resource . fsDriver_ = fsDriver ;
2018-01-17 23:01:41 +02:00
FileApiDriverLocal . fsDriver_ = fsDriver ;
2017-12-30 21:57:34 +02:00
2020-10-16 17:26:19 +02:00
AlarmService . setDriver ( new AlarmServiceDriver ( mainLogger ) ) ;
2017-11-28 00:50:46 +02:00
AlarmService . setLogger ( mainLogger ) ;
2024-03-11 17:02:15 +02:00
// Currently CommandService is just used for plugins.
initializeCommandService ( store ) ;
// KeymapService is also present for plugin compatibility
KeymapService . instance ( ) . initialize ( ) ;
// Even if there are no plugins, we need to initialize the PluginService so that
// plugin search can work.
const platformImplementation = PlatformImplementation . instance ( ) ;
PluginService . instance ( ) . initialize (
platformImplementation . versionInfo . version , platformImplementation , null , store ,
) ;
2021-10-03 17:00:49 +02:00
setRSA ( RSA ) ;
2017-07-10 00:57:15 +02:00
try {
2022-07-23 09:31:32 +02:00
if ( Setting . value ( 'env' ) === 'prod' ) {
2023-01-10 14:08:13 +02:00
await db . open ( { name : getDatabaseName ( currentProfile , isSubProfile ) } ) ;
2017-07-10 00:57:15 +02:00
} else {
2024-01-27 18:59:19 +02:00
await db . open ( { name : getDatabaseName ( currentProfile , isSubProfile , '-20240127-1' ) } ) ;
2019-05-22 16:56:07 +02:00
2019-05-26 20:39:07 +02:00
// await db.clearForTesting();
2017-07-10 00:57:15 +02:00
}
2017-05-16 23:46:21 +02:00
2018-03-09 22:59:12 +02:00
reg . logger ( ) . info ( 'Database is ready.' ) ;
reg . logger ( ) . info ( 'Loading settings...' ) ;
2020-06-05 01:08:09 +02:00
2024-10-26 22:02:34 +02:00
await loadKeychainServiceAndSettings ( [ ] ) ;
2021-08-30 15:15:35 +02:00
await migrateMasterPassword ( ) ;
2021-10-18 13:37:25 +02:00
if ( ! Setting . value ( 'clientId' ) ) Setting . setValue ( 'clientId' , uuid . create ( ) ) ;
2022-09-30 18:38:22 +02:00
reg . logger ( ) . info ( ` Client ID: ${ Setting . value ( 'clientId' ) } ` ) ;
2021-10-18 13:37:25 +02:00
2023-07-16 18:42:42 +02:00
BaseItem . syncShareCache = parseShareCache ( Setting . value ( 'sync.shareCache' ) ) ;
2023-06-01 17:48:02 +02:00
2021-10-18 13:37:25 +02:00
if ( Setting . value ( 'firstStart' ) ) {
2023-06-01 17:48:02 +02:00
const detectedLocale = shim . detectAndSetLocale ( Setting ) ;
reg . logger ( ) . info ( ` First start: detected locale as ${ detectedLocale } ` ) ;
2024-08-02 15:51:49 +02:00
if ( shim . mobilePlatform ( ) === 'web' ) {
// Web browsers generally have more limited storage than desktop and mobile apps:
Setting . setValue ( 'sync.resourceDownloadMode' , 'auto' ) ;
// For now, geolocation is disabled by default on web until the web permissions workflow
// is improved. At present, trackLocation=true causes the "allow location access" prompt
// to appear without a clear indicator for why Joplin wants this information.
Setting . setValue ( 'trackLocation' , false ) ;
logger . info ( 'First start on web: Set resource download mode to auto and disabled location tracking.' ) ;
}
2021-10-18 13:37:25 +02:00
Setting . skipDefaultMigrations ( ) ;
2024-06-25 15:01:39 +02:00
Setting . setValue ( 'firstStart' , false ) ;
2021-10-18 13:37:25 +02:00
} else {
Setting . applyDefaultMigrations ( ) ;
}
2021-08-30 15:15:35 +02:00
if ( Setting . value ( 'env' ) === Env . Dev ) {
// Setting.setValue('sync.10.path', 'https://api.joplincloud.com');
// Setting.setValue('sync.10.userContentPath', 'https://joplinusercontent.com');
Setting . setValue ( 'sync.10.path' , 'http://api.joplincloud.local:22300' ) ;
Setting . setValue ( 'sync.10.userContentPath' , 'http://joplinusercontent.local:22300' ) ;
2024-03-11 17:17:23 +02:00
Setting . setValue ( 'sync.10.website' , 'http://joplincloud.local:22300' ) ;
2021-10-17 18:20:59 +02:00
2021-11-02 18:33:53 +02:00
// Setting.setValue('sync.target', 10);
// Setting.setValue('sync.10.username', 'user1@example.com');
2022-04-12 18:06:53 +02:00
// Setting.setValue('sync.10.password', '111111');
2021-08-30 15:15:35 +02:00
}
2017-07-25 20:57:06 +02:00
2018-12-28 22:40:29 +02:00
if ( Setting . value ( 'db.ftsEnabled' ) === - 1 ) {
const ftsEnabled = await db . ftsEnabled ( ) ;
Setting . setValue ( 'db.ftsEnabled' , ftsEnabled ? 1 : 0 ) ;
reg . logger ( ) . info ( 'db.ftsEnabled = ' , Setting . value ( 'db.ftsEnabled' ) ) ;
2017-07-25 20:57:06 +02:00
}
2019-05-22 16:56:07 +02:00
if ( Setting . value ( 'env' ) === 'dev' ) {
Setting . setValue ( 'welcome.enabled' , false ) ;
2019-07-30 09:35:42 +02:00
}
2019-05-22 16:56:07 +02:00
2019-12-29 19:58:40 +02:00
await PluginAssetsLoader . instance ( ) . importAssets ( ) ;
2019-07-30 09:35:42 +02:00
// eslint-disable-next-line require-atomic-updates
2019-05-06 22:35:29 +02:00
BaseItem . revisionService_ = RevisionService . instance ( ) ;
2019-07-30 09:35:42 +02:00
// Note: for now we hard-code the folder sort order as we need to
2019-03-10 22:30:31 +02:00
// create a UI to allow customisation (started in branch mobile_add_sidebar_buttons)
Setting . setValue ( 'folders.sortOrder.field' , 'title' ) ;
Setting . setValue ( 'folders.sortOrder.reverse' , false ) ;
2019-09-19 23:51:18 +02:00
reg . logger ( ) . info ( ` Sync target: ${ Setting . value ( 'sync.target' ) } ` ) ;
2017-11-24 21:47:24 +02:00
2018-03-09 22:59:12 +02:00
setLocale ( Setting . value ( 'locale' ) ) ;
2017-07-19 23:26:30 +02:00
2021-04-25 10:50:52 +02:00
if ( Platform . OS === 'android' ) {
const ignoreTlsErrors = Setting . value ( 'net.ignoreTlsErrors' ) ;
if ( ignoreTlsErrors ) {
await setIgnoreTlsErrors ( ignoreTlsErrors ) ;
}
}
2017-12-30 21:57:34 +02:00
// ----------------------------------------------------------------
// E2EE SETUP
// ----------------------------------------------------------------
EncryptionService . fsDriver_ = fsDriver ;
2019-07-30 09:35:42 +02:00
// eslint-disable-next-line require-atomic-updates
2017-12-30 21:57:34 +02:00
BaseItem . encryptionService_ = EncryptionService . instance ( ) ;
2021-05-13 18:57:37 +02:00
BaseItem . shareService_ = ShareService . instance ( ) ;
2022-02-09 20:04:27 +02:00
Resource . shareService_ = ShareService . instance ( ) ;
2018-01-02 21:17:14 +02:00
DecryptionWorker . instance ( ) . dispatch = dispatch ;
2017-12-30 21:57:34 +02:00
DecryptionWorker . instance ( ) . setLogger ( mainLogger ) ;
2019-06-08 00:11:08 +02:00
DecryptionWorker . instance ( ) . setKvStore ( KvStore . instance ( ) ) ;
2017-12-30 21:57:34 +02:00
DecryptionWorker . instance ( ) . setEncryptionService ( EncryptionService . instance ( ) ) ;
2021-08-12 17:54:10 +02:00
await loadMasterKeysFromSettings ( EncryptionService . instance ( ) ) ;
2020-06-09 21:56:48 +02:00
DecryptionWorker . instance ( ) . on ( 'resourceMetadataButNotBlobDecrypted' , decryptionWorker_resourceMetadataButNotBlobDecrypted ) ;
2017-12-30 21:57:34 +02:00
// ----------------------------------------------------------------
// / E2EE SETUP
// ----------------------------------------------------------------
2021-11-03 18:24:40 +02:00
await ShareService . instance ( ) . initialize ( store , EncryptionService . instance ( ) ) ;
2021-05-13 18:57:37 +02:00
2018-03-09 22:59:12 +02:00
reg . logger ( ) . info ( 'Loading folders...' ) ;
2017-07-06 23:30:45 +02:00
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2024-04-27 10:54:47 +02:00
await refreshFolders ( ( action : any ) = > dispatch ( action ) , '' ) ;
2017-07-09 00:57:09 +02:00
2019-02-24 20:02:50 +02:00
const tags = await Tag . allWithNotes ( ) ;
2017-07-25 20:36:52 +02:00
dispatch ( {
2018-03-09 22:59:12 +02:00
type : 'TAG_UPDATE_ALL' ,
2017-12-14 19:58:10 +02:00
items : tags ,
2017-07-25 20:36:52 +02:00
} ) ;
2021-08-12 17:54:10 +02:00
// const masterKeys = await MasterKey.all();
2017-12-30 21:57:34 +02:00
2021-08-12 17:54:10 +02:00
// dispatch({
// type: 'MASTERKEY_UPDATE_ALL',
// items: masterKeys,
// });
2017-12-30 21:57:34 +02:00
2024-05-28 14:43:33 +02:00
const folder = await getInitialActiveFolder ( ) ;
2017-07-23 20:26:50 +02:00
2018-05-09 13:39:17 +02:00
dispatch ( {
type : 'FOLDER_SET_COLLAPSED_ALL' ,
ids : Setting.value ( 'collapsedFolderIds' ) ,
} ) ;
2023-09-24 21:21:58 +02:00
const notesParent = parseNotesParent ( Setting . value ( 'notesParent' ) , Setting . value ( 'activeFolderId' ) ) ;
if ( notesParent && notesParent . type === 'SmartFilter' ) {
dispatch ( DEFAULT_ROUTE ) ;
} else if ( ! folder ) {
2019-06-29 01:24:00 +02:00
dispatch ( DEFAULT_ROUTE ) ;
2017-07-24 23:52:30 +02:00
} else {
2017-07-25 20:09:01 +02:00
dispatch ( {
2018-03-09 22:59:12 +02:00
type : 'NAV_GO' ,
routeName : 'Notes' ,
2017-07-25 20:09:01 +02:00
folderId : folder.id ,
} ) ;
2017-07-24 23:52:30 +02:00
}
2021-04-07 19:41:54 +02:00
await clearSharedFilesCache ( ) ;
2017-07-10 00:57:15 +02:00
} catch ( error ) {
2019-09-19 23:51:18 +02:00
alert ( ` Initialization error: ${ error . message } ` ) ;
2018-03-09 22:59:12 +02:00
reg . logger ( ) . error ( 'Initialization error:' , error ) ;
2017-07-10 00:57:15 +02:00
}
2017-07-26 19:49:01 +02:00
reg . setupRecurrentSync ( ) ;
2017-07-22 19:35:39 +02:00
2023-07-23 16:57:55 +02:00
initializeUserFetcher ( ) ;
PoorManIntervals . setInterval ( ( ) = > { void userFetcher ( ) ; } , 1000 * 60 * 60 ) ;
2023-07-18 21:15:45 +02:00
2017-11-28 20:58:04 +02:00
PoorManIntervals . setTimeout ( ( ) = > {
2020-12-30 12:54:00 +02:00
void AlarmService . garbageCollect ( ) ;
2017-11-28 20:58:04 +02:00
} , 1000 * 60 * 60 ) ;
2017-11-24 20:06:45 +02:00
2018-03-16 16:32:47 +02:00
ResourceService . runInBackground ( ) ;
2018-03-15 20:08:46 +02:00
2019-07-30 09:35:42 +02:00
ResourceFetcher . instance ( ) . setFileApi ( ( ) = > { return reg . syncTarget ( ) . fileApi ( ) ; } ) ;
2018-10-09 23:01:50 +02:00
ResourceFetcher . instance ( ) . setLogger ( reg . logger ( ) ) ;
2019-05-22 16:56:07 +02:00
ResourceFetcher . instance ( ) . dispatch = dispatch ;
ResourceFetcher . instance ( ) . on ( 'downloadComplete' , resourceFetcher_downloadComplete ) ;
2021-01-22 19:41:11 +02:00
void ResourceFetcher . instance ( ) . start ( ) ;
2018-10-09 23:01:50 +02:00
2018-12-16 19:32:42 +02:00
SearchEngine . instance ( ) . setDb ( reg . db ( ) ) ;
SearchEngine . instance ( ) . setLogger ( reg . logger ( ) ) ;
2019-01-18 20:31:07 +02:00
SearchEngine . instance ( ) . scheduleSyncTables ( ) ;
2018-12-16 19:32:42 +02:00
2019-05-11 18:55:40 +02:00
await MigrationService . instance ( ) . run ( ) ;
2020-07-29 00:47:24 +02:00
// When the app starts we want the full sync to
// start almost immediately to get the latest data.
2021-03-29 10:35:39 +02:00
// doWifiConnectionCheck set to true so initial sync
// doesn't happen on mobile data
2022-09-30 18:23:14 +02:00
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
2023-05-11 15:58:19 +02:00
void reg . scheduleSync ( 100 , null , true ) . then ( ( ) = > {
2017-11-28 20:58:04 +02:00
// Wait for the first sync before updating the notifications, since synchronisation
// might change the notifications.
2020-12-30 12:54:00 +02:00
void AlarmService . updateAllNotifications ( ) ;
2017-12-30 21:57:34 +02:00
2021-01-22 19:41:11 +02:00
void DecryptionWorker . instance ( ) . scheduleStart ( ) ;
2017-11-28 20:58:04 +02:00
} ) ;
2017-11-24 20:06:45 +02:00
2023-05-10 13:18:59 +02:00
await WelcomeUtils . install ( Setting . value ( 'locale' ) , dispatch ) ;
2019-02-05 20:46:32 +02:00
2019-05-06 22:35:29 +02:00
// Collect revisions more frequently on mobile because it doesn't auto-save
// and it cannot collect anything when the app is not active.
RevisionService . instance ( ) . runInBackground ( 1000 * 30 ) ;
2024-03-11 17:02:15 +02:00
// ----------------------------------------------------------------------------
// Plugin service setup
// ----------------------------------------------------------------------------
// On startup, we can clear plugin update state -- plugins that were updated when the
// user last ran the app have been updated and will be reloaded.
const pluginService = PluginService . instance ( ) ;
const pluginSettings = pluginService . unserializePluginSettings ( Setting . value ( 'plugins.states' ) ) ;
const updatedSettings = pluginService . clearUpdateState ( pluginSettings ) ;
2024-06-25 15:01:39 +02:00
Setting . setValue ( 'plugins.states' , updatedSettings ) ;
2024-03-11 17:02:15 +02:00
2021-10-03 17:00:49 +02:00
// ----------------------------------------------------------------------------
// Keep this below to test react-native-rsa-native
// ----------------------------------------------------------------------------
// const testData = await createTestData();
// await checkTestData(testData);
// const testData = {
// "publicKey": "-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAoMx9NBioka8DUjO3bKrWMn8uJ23LH1xySogQFR6yh6qbl6i5LKTw\nPgqvv55FUuQtYTMtUTVLggYQhdCBvwbBrD1OqO4xU6Ew7x5/TQKPV3MSgYaps3FF\nOdipC4FyA00jBe6Z1CIpL+ZaSnvjDbMUf5lW8bmfRuXfdBGAcdSBjqm9ttajOws+\n7BBSQ9nI5dnBnWRIVEUb7e9bulgANzM1LMUOE+gaef7T3uKzc+Cx3BhHgw1JdFbL\nZAndYtP52KI5N3oiFM4II26DxmDrO1tQokNM88l5xT0BXPhYiEl1CeBpo5VHZBY2\nRHr4MM/OyAXSUdulsDzbntpE+Y85zv7gpQIDAQAB\n-----END RSA PUBLIC KEY-----",
// "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEAoMx9NBioka8DUjO3bKrWMn8uJ23LH1xySogQFR6yh6qbl6i5\nLKTwPgqvv55FUuQtYTMtUTVLggYQhdCBvwbBrD1OqO4xU6Ew7x5/TQKPV3MSgYap\ns3FFOdipC4FyA00jBe6Z1CIpL+ZaSnvjDbMUf5lW8bmfRuXfdBGAcdSBjqm9ttaj\nOws+7BBSQ9nI5dnBnWRIVEUb7e9bulgANzM1LMUOE+gaef7T3uKzc+Cx3BhHgw1J\ndFbLZAndYtP52KI5N3oiFM4II26DxmDrO1tQokNM88l5xT0BXPhYiEl1CeBpo5VH\nZBY2RHr4MM/OyAXSUdulsDzbntpE+Y85zv7gpQIDAQABAoIBAEA0Zmm+ztAcyX6x\nF7RUImLXVV55AHntN9V6rrFAKJjzDl1oCUhCM4sSSUqBr7yBT31YKegbF6M7OK21\nq5jS4dIcSKQ7N4bk/dz8mGfvdby9Pc5qLqhvuex3DkiBzzxyOGHN+64wVbHCkJrd\nDLQTpUOtvoGWVHrCno6Bzn+lEnYbvdr0hqI5H4D0ubk6TYed1/4ZlJf0R/o/4jnl\nou0UG2hpJN4ur506cttkZJSTxLjzdO38JuQIAkCEglrMYVY61lBNPxC11Kr3ZN7o\ncm7gWZVyP26KoU27t/g+2FoiBGsWLqGYiuTaqT6dKZ2vHyJGjJIZZStv5ye2Ez8V\nKQwpjQECgYEA3xtwYu4n/G5UjEMumkXHNd/bDamelo1aQvvjkVvxKeASNBqV8cM0\n6Jb2FCuT9Y3mWbFTM0jpqXehpHUOCCnrPKGKnJ0ZS4/SRIrtw0iM6q17fTAqmuOt\nhX0pJ77Il8lVCtx4ItsW+LUGbm6CwotlYLVUuyluhKe0pGw2yafi2N0CgYEAuIFk\ng4p7x0i1LFAlIP0YQ07bJQ0E6FEWbCfMgrV3VjtbnT99EaqPOHhMasITCuoEFlh8\ncgyZ6oH7GEy4IRWrM+Mlm47S+NTrr6KgnTGf570ZAFuqnJac97oFB7BvlQsQot6F\n0L2JKM7dQKIMlvwA9DoXZdKX/9ykiqqIpawNxmkCgYEAuyJOwAw2ads4+3UWT7wb\nfarIF8ugA3OItAqHNFNEEvWpDx8FigVMCZMl0IFE14AwKCc+PBP6OXTolgLAxEQ0\n1WRB2V9D6kc1/Nvy1guydt0QaU7PTZ+O2hrDPF0f74Cl3jhSZBoUSIO+Yz46W2eE\nnvs5mMsFsirgr9E8myRAd9kCgYAGMCDE4KIiHugkolN8dcCYkU58QaGGgSG1YuhT\nAe8Mr1T1QynYq9W92RsHAZdN6GdWsIUL9iw7VzyqpfgO9AEX7mhWfUXKHqoA6/1j\nCEUKqqbqAikIs2x0SoLcrSgw4XwfWkM2qwSsn7N/9W9iqPUHO+OJALUkWawTEoEe\nvVSA8QKBgQCEYCPnxgeQSZkrv7x5soXzgF1YN5EZRa1mTUqPBubs564ZjIIY66mI\nCTaHl7U1cPAhx7mHkSzP/i5NjjLqPZZNOyawWDEEmOzxX69OIzKImb6mEQNyS3do\nI8jnpN5q9pw5TvuEIYSrGqQVnHeaEjSvcT48W9GuzjNVscGfw76fPg==\n-----END RSA PRIVATE KEY-----",
// "plaintext": "just testing",
// "ciphertext": "BfkKLdrmd2UX4sPf0bzhfqrg3rKwH5DS7dPAqdmoQuHlrvEBrYKqheekwpnWQgGggGcm/orlrsQRwlexLv7jfRbb0bMnElkySMu4w6wTxILB66RX9H3vXCz02SwHKFRcuGJxlzTPUC23ki6f/McYJ2n/2L8qYxBO8fncTKutIWV54jY19RS1wQ4IdVDBqzji8D0QsRxUhVlpRk4qxsVnyuoyg9AyDe91LOYKfRc6NdapFij996nKzjxFcKOdBqpis34fN3Cg7avcs2Dm5vi7zlRhyGqJJhORXTU3x6hVwOBkVAisgaB7xS3lHiYp6Fs5tP3hBd0kFwVVx8gALbHsgg=="
// };
// await checkTestData(testData);
// await printTestData();
// ----------------------------------------------------------------------------
// On desktop and CLI we run various tests to check that node-rsa is working
// as expected. On mobile however we cannot run test units directly on
// device, and that's what would be needed to automatically verify
// react-native-rsa-native. So instead we run the tests every time the
// mobile app is started in dev mode. If there's any regression the below
// call will throw an error, alerting us of the issue. Otherwise it will
// just print some messages in the console.
// ----------------------------------------------------------------------------
2023-10-22 12:51:31 +02:00
if ( Setting . value ( 'env' ) === 'dev' ) {
2024-08-02 15:51:49 +02:00
if ( Platform . OS !== 'web' ) {
await runRsaIntegrationTests ( ) ;
} else {
2024-10-26 22:15:10 +02:00
logger . info ( 'Skipping encryption tests -- not supported on web.' ) ;
2024-08-02 15:51:49 +02:00
}
2024-10-26 22:15:10 +02:00
await runCryptoIntegrationTests ( ) ;
2023-10-22 12:51:31 +02:00
await runOnDeviceFsDriverTests ( ) ;
}
2021-10-03 17:00:49 +02:00
2018-03-09 22:59:12 +02:00
reg . logger ( ) . info ( 'Application initialized' ) ;
2017-07-10 00:57:15 +02:00
}
class AppComponent extends React . Component {
2018-03-09 22:59:12 +02:00
2023-02-08 16:21:59 +02:00
private urlOpenListener_ : EmitterSubscription | null = null ;
private appStateChangeListener_ : NativeEventSubscription | null = null ;
2023-07-18 15:46:11 +02:00
private themeChangeListener_ : NativeEventSubscription | null = null ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-10-22 11:45:05 +02:00
private dropdownAlert_ = ( _data : any ) = > new Promise < any > ( res = > res ) ;
2024-06-15 10:58:23 +02:00
private callbackUrl : string | null = null ;
2023-02-08 16:21:59 +02:00
2021-09-06 17:57:07 +02:00
public constructor ( ) {
2017-07-18 00:43:29 +02:00
super ( ) ;
2019-06-26 20:05:37 +02:00
this . state = {
2023-09-11 09:53:53 +02:00
sideMenuContentOpacity : new Animated . Value ( 0 ) ,
2022-08-25 17:59:38 +02:00
sideMenuWidth : this.getSideMenuWidth ( ) ,
2023-01-04 22:18:51 +02:00
sensorInfo : null ,
2019-06-26 20:05:37 +02:00
} ;
2017-07-18 00:43:29 +02:00
this . lastSyncStarted_ = defaultState . syncStarted ;
2017-12-01 21:44:34 +02:00
this . backButtonHandler_ = ( ) = > {
return this . backButtonHandler ( ) ;
2019-07-30 09:35:42 +02:00
} ;
2018-02-07 21:51:58 +02:00
this . onAppStateChange_ = ( ) = > {
PoorManIntervals . update ( ) ;
2019-07-30 09:35:42 +02:00
} ;
2021-04-24 10:22:11 +02:00
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-04-24 10:22:11 +02:00
this . handleOpenURL_ = ( event : any ) = > {
2023-02-20 18:05:00 +02:00
// logger.info('Sharing: handleOpenURL_: start');
2023-02-19 21:00:17 +02:00
2023-02-10 18:12:14 +02:00
// If this is called while biometrics haven't been done yet, we can
// ignore the call, because handleShareData() will be called once
// biometricsDone is `true`.
if ( event . url === ShareExtension . shareURL && this . props . biometricsDone ) {
2023-02-19 21:00:17 +02:00
logger . info ( 'Sharing: handleOpenURL_: Processing share data' ) ;
2021-04-24 10:22:11 +02:00
void this . handleShareData ( ) ;
2024-06-15 10:58:23 +02:00
} else if ( isCallbackUrl ( event . url ) ) {
logger . info ( 'received callback url: ' , event . url ) ;
this . callbackUrl = event . url ;
if ( this . props . biometricsDone ) {
void this . handleCallbackUrl ( ) ;
}
2021-04-24 10:22:11 +02:00
}
} ;
2022-08-25 17:59:38 +02:00
2023-02-19 20:23:00 +02:00
this . handleNewShare_ = ( ) = > {
2023-02-20 18:05:00 +02:00
// logger.info('Sharing: handleNewShare_: start');
2023-02-19 21:00:17 +02:00
2023-02-19 20:23:00 +02:00
// look at this.handleOpenURL_ comment
if ( this . props . biometricsDone ) {
2023-02-19 21:00:17 +02:00
logger . info ( 'Sharing: handleNewShare_: Processing share data' ) ;
2023-02-19 20:23:00 +02:00
void this . handleShareData ( ) ;
}
} ;
this . unsubscribeNewShareListener_ = ShareExtension . addShareListener ( this . handleNewShare_ ) ;
2022-08-25 17:59:38 +02:00
this . handleScreenWidthChange_ = this . handleScreenWidthChange_ . bind ( this ) ;
2017-07-18 00:43:29 +02:00
}
2020-10-08 12:49:39 +02:00
// 2020-10-08: It seems the initialisation code is quite fragile in general and should be kept simple.
// For example, adding a loading screen as was done in this commit: https://github.com/laurent22/joplin/commit/569355a3182bc12e50a54249882e3d68a72c2b28.
// had for effect that sharing with the app would create multiple instances of the app, thus breaking
// database access and so on. It's unclear why it happens and how to fix it but reverting that commit
// fixed the issue for now.
//
// Changing app launch mode doesn't help.
//
// It's possible that it's a bug in React Native, or perhaps the framework expects that the whole app can be
// mounted/unmounted or multiple ones can be running at the same time, but the app was not designed in this
// way.
//
// More reports and info about the multiple instance bug:
//
// https://github.com/laurent22/joplin/issues/3800
// https://github.com/laurent22/joplin/issues/3804
// https://github.com/laurent22/joplin/issues/3807
// https://discourse.joplinapp.org/t/webdav-config-encryption-config-randomly-lost-on-android/11364
// https://discourse.joplinapp.org/t/android-keeps-on-resetting-my-sync-and-theme/11443
2021-09-06 17:57:07 +02:00
public async componentDidMount() {
2022-07-23 09:31:32 +02:00
if ( this . props . appState === 'starting' ) {
2017-08-01 19:41:58 +02:00
this . props . dispatch ( {
2018-03-09 22:59:12 +02:00
type : 'APP_STATE_SET' ,
state : 'initializing' ,
2017-08-01 19:41:58 +02:00
} ) ;
2021-03-29 10:35:39 +02:00
try {
2021-11-29 14:51:11 +02:00
NetInfo . configure ( {
reachabilityUrl : 'https://joplinapp.org/connection_check/' ,
reachabilityTest : async ( response ) = > response . status === 200 ,
} ) ;
2021-03-29 10:35:39 +02:00
// This will be called right after adding the event listener
// so there's no need to check netinfo on startup
this . unsubscribeNetInfoHandler_ = NetInfo . addEventListener ( ( { type , details } ) = > {
2024-08-02 15:51:49 +02:00
const isMobile = details ? . isConnectionExpensive || type === 'cellular' ;
2021-03-29 10:35:39 +02:00
reg . setIsOnMobileData ( isMobile ) ;
this . props . dispatch ( {
type : 'MOBILE_DATA_WARNING_UPDATE' ,
isOnMobileData : isMobile ,
} ) ;
} ) ;
} catch ( error ) {
reg . logger ( ) . warn ( 'Something went wrong while checking network info' ) ;
reg . logger ( ) . info ( error ) ;
}
2024-08-02 15:51:49 +02:00
try {
await initialize ( this . props . dispatch ) ;
} catch ( error ) {
alert ( ` Something went wrong while starting the application: ${ error } ` ) ;
this . props . dispatch ( {
type : 'APP_STATE_SET' ,
state : 'error' ,
} ) ;
throw error ;
}
2020-10-08 12:35:29 +02:00
2024-10-31 10:19:13 +02:00
// https://reactnative.dev/docs/linking#handling-deep-links
//
// The handler added with Linking.addEventListener() is only triggered when app is already open.
//
// When the app is not already open and the deep link triggered app launch,
// the URL can be obtained with Linking.getInitialURL().
//
// We only save the URL here since we want to show the content only
// after biometrics check is passed or known disabled.
const url = await Linking . getInitialURL ( ) ;
if ( url && isCallbackUrl ( url ) ) {
logger . info ( 'received initial callback url: ' , url ) ;
this . callbackUrl = url ;
}
2023-06-08 17:23:48 +02:00
const loadedSensorInfo = await sensorInfo ( ) ;
this . setState ( { sensorInfo : loadedSensorInfo } ) ;
// If biometrics is disabled we set biometricsDone to `true`. We do
// it with a delay so that the component is properly mounted, and
// the componentDidUpdate gets triggered (which in turns will handle
// the share data, if any).
setTimeout ( ( ) = > {
if ( ! biometricsEnabled ( loadedSensorInfo ) ) {
this . props . dispatch ( {
type : 'BIOMETRICS_DONE_SET' ,
value : true ,
} ) ;
}
} , 100 ) ;
2023-01-04 22:18:51 +02:00
2020-10-08 12:35:29 +02:00
this . props . dispatch ( {
type : 'APP_STATE_SET' ,
state : 'ready' ,
2017-08-01 19:41:58 +02:00
} ) ;
2023-01-10 14:08:13 +02:00
// setTimeout(() => {
// this.props.dispatch({
// type: 'NAV_GO',
// routeName: 'ProfileSwitcher',
// });
// }, 1000);
2020-10-08 12:35:29 +02:00
}
2017-11-28 22:17:34 +02:00
2023-02-08 16:21:59 +02:00
this . urlOpenListener_ = Linking . addEventListener ( 'url' , this . handleOpenURL_ ) ;
2021-04-24 10:22:11 +02:00
2020-10-08 12:35:29 +02:00
BackButtonService . initialize ( this . backButtonHandler_ ) ;
2017-12-01 21:44:34 +02:00
2020-12-30 12:54:00 +02:00
AlarmService . setInAppNotificationHandler ( async ( alarmId : string ) = > {
2020-10-08 12:35:29 +02:00
const alarm = await Alarm . load ( alarmId ) ;
const notification = await Alarm . makeNotification ( alarm ) ;
2023-10-22 11:45:05 +02:00
void this . dropdownAlert_ ( {
type : 'info' ,
title : notification.title ,
message : notification.body ? notification . body : '' ,
} ) ;
2020-10-08 12:35:29 +02:00
} ) ;
2018-02-07 21:51:58 +02:00
2023-02-08 16:21:59 +02:00
this . appStateChangeListener_ = RNAppState . addEventListener ( 'change' , this . onAppStateChange_ ) ;
2022-08-25 17:59:38 +02:00
this . unsubscribeScreenWidthChangeHandler_ = Dimensions . addEventListener ( 'change' , this . handleScreenWidthChange_ ) ;
2020-06-04 19:40:44 +02:00
2023-07-18 15:46:11 +02:00
this . themeChangeListener_ = Appearance . addChangeListener (
2023-08-22 12:58:53 +02:00
( { colorScheme } ) = > onSystemColorSchemeChange ( colorScheme ) ,
2023-07-18 15:46:11 +02:00
) ;
onSystemColorSchemeChange ( Appearance . getColorScheme ( ) ) ;
2024-06-19 13:33:22 +02:00
this . quickActionShortcutListener_ = await setupQuickActions ( this . props . dispatch ) ;
2021-05-19 23:26:42 +02:00
await setupNotifications ( this . props . dispatch ) ;
2021-08-30 15:15:35 +02:00
// Setting.setValue('encryption.masterPassword', 'WRONG');
// setTimeout(() => NavService.go('EncryptionConfig'), 2000);
2018-02-07 21:51:58 +02:00
}
2021-09-06 17:57:07 +02:00
public componentWillUnmount() {
2023-02-08 16:21:59 +02:00
if ( this . appStateChangeListener_ ) {
this . appStateChangeListener_ . remove ( ) ;
this . appStateChangeListener_ = null ;
}
if ( this . urlOpenListener_ ) {
this . urlOpenListener_ . remove ( ) ;
this . urlOpenListener_ = null ;
}
2022-08-25 17:59:38 +02:00
2023-07-18 15:46:11 +02:00
if ( this . themeChangeListener_ ) {
this . themeChangeListener_ . remove ( ) ;
this . themeChangeListener_ = null ;
}
2022-08-25 17:59:38 +02:00
if ( this . unsubscribeScreenWidthChangeHandler_ ) {
this . unsubscribeScreenWidthChangeHandler_ . remove ( ) ;
this . unsubscribeScreenWidthChangeHandler_ = null ;
}
2021-03-29 10:35:39 +02:00
if ( this . unsubscribeNetInfoHandler_ ) this . unsubscribeNetInfoHandler_ ( ) ;
2023-02-19 20:23:00 +02:00
if ( this . unsubscribeNewShareListener_ ) {
this . unsubscribeNewShareListener_ ( ) ;
this . unsubscribeNewShareListener_ = undefined ;
}
2024-06-19 13:33:22 +02:00
if ( this . quickActionShortcutListener_ ) {
this . quickActionShortcutListener_ . remove ( ) ;
this . quickActionShortcutListener_ = undefined ;
}
2017-07-10 23:00:41 +02:00
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-05-29 14:14:09 +02:00
public async componentDidUpdate ( prevProps : any ) {
2023-02-10 18:12:14 +02:00
if ( this . props . biometricsDone !== prevProps . biometricsDone && this . props . biometricsDone ) {
2023-02-19 21:00:17 +02:00
logger . info ( 'Sharing: componentDidUpdate: biometricsDone' ) ;
2023-02-10 18:12:14 +02:00
void this . handleShareData ( ) ;
2024-06-15 10:58:23 +02:00
void this . handleCallbackUrl ( ) ;
2023-02-10 18:12:14 +02:00
}
2019-06-26 20:05:37 +02:00
}
2021-09-06 17:57:07 +02:00
private async backButtonHandler() {
2017-11-23 20:47:51 +02:00
if ( this . props . noteSelectionEnabled ) {
2018-03-09 22:59:12 +02:00
this . props . dispatch ( { type : 'NOTE_SELECTION_END' } ) ;
2017-11-23 20:47:51 +02:00
return true ;
}
2017-07-10 23:00:41 +02:00
if ( this . props . showSideMenu ) {
2018-03-09 22:59:12 +02:00
this . props . dispatch ( { type : 'SIDE_MENU_CLOSE' } ) ;
2018-12-07 01:45:18 +02:00
return true ;
2017-07-10 23:00:41 +02:00
}
if ( this . props . historyCanGoBack ) {
2018-03-09 22:59:12 +02:00
this . props . dispatch ( { type : 'NAV_BACK' } ) ;
2017-07-10 23:00:41 +02:00
return true ;
}
2017-12-01 21:44:34 +02:00
BackHandler . exitApp ( ) ;
2017-07-10 23:00:41 +02:00
return false ;
2017-05-11 22:14:01 +02:00
}
2021-09-06 17:57:07 +02:00
private async handleShareData() {
2021-04-24 10:22:11 +02:00
const sharedData = await ShareExtension . data ( ) ;
2023-02-19 21:00:17 +02:00
2021-04-24 10:22:11 +02:00
if ( sharedData ) {
reg . logger ( ) . info ( 'Received shared data' ) ;
2024-02-08 18:54:23 +02:00
// selectedFolderId can be null if no screens other than "All notes"
// have been opened.
const targetFolder = this . props . selectedFolderId ? ? ( await Folder . defaultFolder ( ) ) ? . id ;
if ( targetFolder ) {
2023-02-19 21:00:17 +02:00
logger . info ( 'Sharing: handleShareData: Processing...' ) ;
2024-02-08 18:54:23 +02:00
await handleShared ( sharedData , targetFolder , this . props . dispatch ) ;
2021-04-24 10:22:11 +02:00
} else {
reg . logger ( ) . info ( 'Cannot handle share - default folder id is not set' ) ;
}
2023-07-18 15:46:43 +02:00
} else {
logger . info ( 'Sharing: received empty share data.' ) ;
2021-04-24 10:22:11 +02:00
}
}
2024-06-15 10:58:23 +02:00
private async handleCallbackUrl() {
const url = this . callbackUrl ;
this . callbackUrl = null ;
if ( url === null ) {
return ;
}
const { command , params } = parseCallbackUrl ( url ) ;
// adopted from app-mobile/utils/shareHandler.ts
// We go back one screen in case there's already a note open -
// if we don't do this, the dispatch below will do nothing
// (because routeName wouldn't change)
this . props . dispatch ( { type : 'NAV_BACK' } ) ;
this . props . dispatch ( { type : 'SIDE_MENU_CLOSE' } ) ;
switch ( command ) {
case CallbackUrlCommand . OpenNote :
this . props . dispatch ( {
type : 'NAV_GO' ,
routeName : 'Note' ,
noteId : params.id ,
} ) ;
break ;
case CallbackUrlCommand . OpenTag :
this . props . dispatch ( {
type : 'NAV_GO' ,
routeName : 'Notes' ,
tagId : params.id ,
} ) ;
break ;
case CallbackUrlCommand . OpenFolder :
this . props . dispatch ( {
type : 'NAV_GO' ,
routeName : 'Notes' ,
folderId : params.id ,
} ) ;
break ;
}
}
2022-08-25 17:59:38 +02:00
private async handleScreenWidthChange_() {
this . setState ( { sideMenuWidth : this.getSideMenuWidth ( ) } ) ;
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-09-06 17:57:07 +02:00
public UNSAFE_componentWillReceiveProps ( newProps : any ) {
2022-07-23 09:31:32 +02:00
if ( newProps . syncStarted !== this . lastSyncStarted_ ) {
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2024-04-27 10:54:47 +02:00
if ( ! newProps . syncStarted ) void refreshFolders ( ( action : any ) = > this . props . dispatch ( action ) , this . props . selectedFolderId ) ;
2017-07-18 00:43:29 +02:00
this . lastSyncStarted_ = newProps . syncStarted ;
}
}
2021-09-06 17:57:07 +02:00
private sideMenu_change ( isOpen : boolean ) {
2017-05-24 21:27:13 +02:00
// Make sure showSideMenu property of state is updated
// when the menu is open/closed.
this . props . dispatch ( {
2018-03-09 22:59:12 +02:00
type : isOpen ? 'SIDE_MENU_OPEN' : 'SIDE_MENU_CLOSE' ,
2017-05-24 21:27:13 +02:00
} ) ;
}
2022-08-25 17:59:38 +02:00
private getSideMenuWidth = ( ) = > {
const sideMenuWidth = getResponsiveValue ( {
sm : 250 ,
md : 260 ,
lg : 270 ,
xl : 280 ,
xxl : 290 ,
} ) ;
return sideMenuWidth ;
} ;
2021-09-06 17:57:07 +02:00
public render() {
2024-08-02 15:51:49 +02:00
if ( this . props . appState !== 'ready' ) {
if ( this . props . appState === 'error' ) {
return < Text > Startup error . < / Text > ;
}
// Loading can take a particularly long time for the first time on web -- show progress.
if ( Platform . OS === 'web' ) {
return < View style = { { marginLeft : 'auto' , marginRight : 'auto' , paddingTop : 20 } } >
< ActivityIndicator accessibilityLabel = { _ ( 'Loading...' ) } / >
< / View > ;
} else {
return null ;
}
}
2023-01-08 14:22:41 +02:00
const theme : Theme = themeStyle ( this . props . themeId ) ;
2017-08-01 19:41:58 +02:00
2023-07-06 20:18:43 +02:00
let sideMenuContent : ReactNode = null ;
2024-09-16 23:17:12 +02:00
let menuPosition = SideMenuPosition . Left ;
2023-11-15 15:31:26 +02:00
let disableSideMenuGestures = this . props . disableSideMenuGestures ;
2019-07-11 18:43:55 +02:00
if ( this . props . routeName === 'Note' ) {
2020-02-05 00:09:34 +02:00
sideMenuContent = < SafeAreaView style = { { flex : 1 , backgroundColor : theme.backgroundColor } } > < SideMenuContentNote options = { this . props . noteSideMenuOptions } / > < / SafeAreaView > ;
2024-09-16 23:17:12 +02:00
menuPosition = SideMenuPosition . Right ;
2023-11-15 15:31:26 +02:00
} else if ( this . props . routeName === 'Config' ) {
disableSideMenuGestures = true ;
2019-07-11 18:43:55 +02:00
} else {
2020-02-05 00:09:34 +02:00
sideMenuContent = < SafeAreaView style = { { flex : 1 , backgroundColor : theme.backgroundColor } } > < SideMenuContent / > < / SafeAreaView > ;
2019-07-11 18:43:55 +02:00
}
2017-05-24 21:09:46 +02:00
2017-07-14 20:49:14 +02:00
const appNavInit = {
Notes : { screen : NotesScreen } ,
Note : { screen : NoteScreen } ,
2019-03-14 00:42:16 +02:00
Tags : { screen : TagsScreen } ,
2017-07-14 20:49:14 +02:00
Folder : { screen : FolderScreen } ,
OneDriveLogin : { screen : OneDriveLoginScreen } ,
2018-03-27 01:55:44 +02:00
DropboxLogin : { screen : DropboxLoginScreen } ,
2024-03-11 17:17:23 +02:00
JoplinCloudLogin : { screen : JoplinCloudLoginScreen } ,
2017-12-30 21:57:34 +02:00
EncryptionConfig : { screen : EncryptionConfigScreen } ,
2020-08-02 13:28:50 +02:00
UpgradeSyncTarget : { screen : UpgradeSyncTargetScreen } ,
2024-04-15 19:17:34 +02:00
ShareManager : { screen : ShareManager } ,
2023-01-10 14:08:13 +02:00
ProfileSwitcher : { screen : ProfileSwitcher } ,
ProfileEditor : { screen : ProfileEditor } ,
2017-07-14 20:49:14 +02:00
Log : { screen : LogScreen } ,
Status : { screen : StatusScreen } ,
2017-07-23 00:52:24 +02:00
Search : { screen : SearchScreen } ,
2017-07-23 20:26:50 +02:00
Config : { screen : ConfigScreen } ,
2017-07-14 20:49:14 +02:00
} ;
2022-08-25 17:59:38 +02:00
2022-07-10 15:59:33 +02:00
// const statusBarStyle = theme.appearance === 'light-content';
const statusBarStyle = 'light-content' ;
2020-06-10 23:08:59 +02:00
2023-06-08 17:23:48 +02:00
const shouldShowMainContent = ! biometricsEnabled ( this . state . sensorInfo ) || this . props . biometricsDone ;
2023-02-20 14:55:40 +02:00
2023-05-29 14:14:09 +02:00
logger . info ( 'root.biometrics: biometricsDone' , this . props . biometricsDone ) ;
2023-06-08 17:23:48 +02:00
logger . info ( 'root.biometrics: biometricsEnabled' , biometricsEnabled ( this . state . sensorInfo ) ) ;
2023-04-08 10:40:53 +02:00
logger . info ( 'root.biometrics: shouldShowMainContent' , shouldShowMainContent ) ;
logger . info ( 'root.biometrics: this.state.sensorInfo' , this . state . sensorInfo ) ;
2024-02-02 19:49:23 +02:00
// The right sidemenu can be difficult to close due to a bug in the sidemenu
// library (right sidemenus can't be swiped closed).
//
// Additionally, it can interfere with scrolling in the note viewer, so we use
// a smaller edge hit width.
const menuEdgeHitWidth = menuPosition === 'right' ? 20 : 30 ;
2023-01-08 14:22:41 +02:00
const mainContent = (
2020-06-10 23:08:59 +02:00
< View style = { { flex : 1 , backgroundColor : theme.backgroundColor } } >
2023-09-11 09:53:53 +02:00
< SideMenu
menu = { sideMenuContent }
2024-02-02 19:49:23 +02:00
edgeHitWidth = { menuEdgeHitWidth }
toleranceX = { 4 }
toleranceY = { 20 }
2023-09-11 09:53:53 +02:00
openMenuOffset = { this . state . sideMenuWidth }
menuPosition = { menuPosition }
onChange = { ( isOpen : boolean ) = > this . sideMenu_change ( isOpen ) }
2023-11-15 15:31:26 +02:00
disableGestures = { disableSideMenuGestures }
2020-06-10 23:08:59 +02:00
>
< StatusBar barStyle = { statusBarStyle } / >
2023-11-17 13:43:01 +02:00
< MenuProvider style = { { flex : 1 } } >
2022-07-10 15:59:33 +02:00
< SafeAreaView style = { { flex : 0 , backgroundColor : theme.backgroundColor2 } } / >
2020-06-10 23:08:59 +02:00
< SafeAreaView style = { { flex : 1 } } >
< View style = { { flex : 1 , backgroundColor : theme.backgroundColor } } >
2023-02-20 14:55:40 +02:00
{ shouldShowMainContent && < AppNav screens = { appNavInit } dispatch = { this . props . dispatch } / > }
2020-06-10 23:08:59 +02:00
< / View >
2024-04-05 13:16:49 +02:00
{ /* eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied */ }
2023-10-24 20:19:09 +02:00
< DropdownAlert alert = { ( func : any ) = > ( this . dropdownAlert_ = func ) } / >
{ ! shouldShowMainContent && < BiometricPopup
2023-02-10 18:12:14 +02:00
dispatch = { this . props . dispatch }
2023-01-04 22:18:51 +02:00
themeId = { this . props . themeId }
sensorInfo = { this . state . sensorInfo }
2023-02-10 18:12:14 +02:00
/ > }
2020-06-10 23:08:59 +02:00
< / SafeAreaView >
2023-11-17 13:43:01 +02:00
< / MenuProvider >
2023-09-11 09:53:53 +02:00
< / SideMenu >
2024-03-14 21:04:32 +02:00
< PluginRunnerWebView / >
2020-06-10 23:08:59 +02:00
< / View >
2017-05-11 22:14:01 +02:00
) ;
2023-01-08 14:22:41 +02:00
2023-01-10 14:08:13 +02:00
const paperTheme = theme . appearance === ThemeAppearance . Dark ? MD3DarkTheme : MD3LightTheme ;
2023-01-08 14:22:41 +02:00
// Wrap everything in a PaperProvider -- this allows using components from react-native-paper
return (
< PaperProvider theme = { {
. . . paperTheme ,
2023-01-10 14:08:13 +02:00
version : 3 ,
2023-01-08 14:22:41 +02:00
colors : {
. . . paperTheme . colors ,
2023-01-10 14:08:13 +02:00
onPrimaryContainer : theme.color5 ,
primaryContainer : theme.backgroundColor5 ,
2024-05-02 15:58:29 +02:00
2024-06-04 10:57:52 +02:00
outline : theme.codeBorderColor ,
primary : theme.color4 ,
onPrimary : theme.backgroundColor4 ,
2024-05-02 15:58:29 +02:00
background : theme.backgroundColor ,
surface : theme.backgroundColor ,
onSurface : theme.color ,
2024-03-11 17:02:15 +02:00
secondaryContainer : theme.raisedBackgroundColor ,
onSecondaryContainer : theme.raisedColor ,
surfaceVariant : theme.backgroundColor3 ,
onSurfaceVariant : theme.color3 ,
elevation : {
level0 : 'transparent' ,
level1 : theme.oddBackgroundColor ,
level2 : theme.raisedBackgroundColor ,
level3 : theme.raisedBackgroundColor ,
level4 : theme.raisedBackgroundColor ,
level5 : theme.raisedBackgroundColor ,
} ,
2023-01-08 14:22:41 +02:00
} ,
} } >
2024-11-16 23:09:50 +02:00
< DialogManager themeId = { this . props . themeId } >
2024-08-02 15:51:49 +02:00
{ mainContent }
< / DialogManager >
2023-01-08 14:22:41 +02:00
< / PaperProvider >
) ;
2017-05-11 22:14:01 +02:00
}
2017-05-10 20:58:02 +02:00
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-12-30 12:54:00 +02:00
const mapStateToProps = ( state : any ) = > {
2017-05-10 20:58:02 +02:00
return {
2017-11-04 18:40:34 +02:00
historyCanGoBack : state.historyCanGoBack ,
showSideMenu : state.showSideMenu ,
syncStarted : state.syncStarted ,
appState : state.appState ,
2017-11-23 20:47:51 +02:00
noteSelectionEnabled : state.noteSelectionEnabled ,
2018-09-04 12:07:33 +02:00
selectedFolderId : state.selectedFolderId ,
2019-07-11 18:43:55 +02:00
routeName : state.route.routeName ,
2020-09-15 15:01:07 +02:00
themeId : state.settings.theme ,
2019-07-11 18:43:55 +02:00
noteSideMenuOptions : state.noteSideMenuOptions ,
2023-10-02 16:15:51 +02:00
disableSideMenuGestures : state.disableSideMenuGestures ,
2023-02-10 18:12:14 +02:00
biometricsDone : state.biometricsDone ,
2023-05-29 14:14:09 +02:00
biometricsEnabled : state.settings [ 'security.biometricsEnabled' ] ,
2017-11-04 18:40:34 +02:00
} ;
2017-05-10 20:58:02 +02:00
} ;
const App = connect ( mapStateToProps ) ( AppComponent ) ;
2020-12-30 12:54:00 +02:00
export default class Root extends React . Component {
2021-10-03 17:00:49 +02:00
public render() {
2017-05-09 22:46:54 +02:00
return (
< Provider store = { store } >
2018-03-09 22:59:12 +02:00
< App / >
2017-05-09 22:46:54 +02:00
< / Provider >
) ;
}
2017-05-09 21:59:14 +02:00
}