2020-12-30 11:51:43 +00:00
const React = require ( 'react' ) ;
import shim from '@joplin/lib/shim' ;
shim . setReact ( React ) ;
2021-05-19 22:26:42 +01:00
import setupQuickActions from './setupQuickActions' ;
2020-06-02 20:13:15 +00:00
import PluginAssetsLoader from './PluginAssetsLoader' ;
2020-12-30 10:54:00 +00:00
import AlarmService from '@joplin/lib/services/AlarmService' ;
import Alarm from '@joplin/lib/models/Alarm' ;
import time from '@joplin/lib/time' ;
2023-07-27 16:05:56 +01:00
import Logger , { TargetType } from '@joplin/utils/Logger' ;
2020-12-30 10:54:00 +00: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 06:01:39 -07:00
import Setting , { AppType , Env } from '@joplin/lib/models/Setting' ;
2020-12-30 10:54:00 +00:00
import PoorManIntervals from '@joplin/lib/PoorManIntervals' ;
2023-09-24 20:21:58 +01:00
import reducer , { NotesParent , parseNotesParent , serializeNotesParent } from '@joplin/lib/reducer' ;
2020-12-30 10:54:00 +00: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 12:44:15 -07:00
import { _ , setLocale } from '@joplin/lib/locale' ;
2020-12-30 10:54:00 +00:00
import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer' ;
2021-06-03 17:12:07 +02:00
import SyncTargetJoplinCloud from '@joplin/lib/SyncTargetJoplinCloud' ;
2021-01-20 15:49:02 +00:00
import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive' ;
2023-01-10 12:08:13 +00:00
import initProfile from '@joplin/lib/services/profileConfig/initProfile' ;
2021-06-17 18:19:32 +01:00
const VersionInfo = require ( 'react-native-version-info' ) . default ;
2024-08-02 06:51:49 -07:00
const { Keyboard , BackHandler , Animated , StatusBar , Platform , Dimensions } = require ( 'react-native' ) ;
2024-09-16 14:17:12 -07:00
import { AppState as RNAppState , EmitterSubscription , View , Text , Linking , NativeEventSubscription , Appearance , ActivityIndicator } from 'react-native' ;
2022-08-25 16:59:38 +01:00
import getResponsiveValue from './components/getResponsiveValue' ;
2021-03-29 10:35:39 +02:00
import NetInfo from '@react-native-community/netinfo' ;
2020-12-30 10:54:00 +00:00
const DropdownAlert = require ( 'react-native-dropdownalert' ) . default ;
2021-01-27 17:42:58 +00:00
const AlarmServiceDriver = require ( './services/AlarmServiceDriver' ) . default ;
2020-11-05 16:58:23 +00:00
const SafeAreaView = require ( './components/SafeAreaView' ) ;
2018-03-09 20:59:12 +00:00
const { connect , Provider } = require ( 'react-redux' ) ;
2024-11-09 04:54:09 -08:00
import fastDeepEqual = require ( 'fast-deep-equal' ) ;
2023-01-10 12:08:13 +00:00
import { Provider as PaperProvider , MD3DarkTheme , MD3LightTheme } from 'react-native-paper' ;
2024-09-21 10:28:41 -07:00
import BackButtonService from './services/BackButtonService' ;
2021-01-23 15:51:19 +00:00
import NavService from '@joplin/lib/services/NavService' ;
2024-07-01 12:21:17 -03:00
import { createStore , applyMiddleware , Dispatch } from 'redux' ;
2024-01-03 18:02:05 +00:00
import reduxSharedMiddleware from '@joplin/lib/components/shared/reduxSharedMiddleware' ;
2024-07-16 11:23:03 -07:00
import shimInit from './utils/shim-init-react' ;
2020-11-05 16:58:23 +00:00
const { AppNav } = require ( './components/app-nav.js' ) ;
2021-01-22 17:41:11 +00:00
import Note from '@joplin/lib/models/Note' ;
import Folder from '@joplin/lib/models/Folder' ;
2021-03-30 22:58:47 +01:00
import BaseSyncTarget from '@joplin/lib/BaseSyncTarget' ;
2021-01-22 17:41:11 +00: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 15:51:19 +00:00
import RevisionService from '@joplin/lib/services/RevisionService' ;
2021-01-29 18:45:11 +00:00
import JoplinDatabase from '@joplin/lib/JoplinDatabase' ;
import Database from '@joplin/lib/database' ;
2023-02-18 15:05:48 +00:00
import NotesScreen from './components/screens/Notes' ;
2020-11-05 16:58:23 +00:00
const { TagsScreen } = require ( './components/screens/tags.js' ) ;
2023-07-18 06:58:06 -07:00
import ConfigScreen from './components/screens/ConfigScreen/ConfigScreen' ;
2020-11-05 16:58:23 +00:00
const { FolderScreen } = require ( './components/screens/folder.js' ) ;
2023-10-02 11:14:08 -07:00
import LogScreen from './components/screens/LogScreen' ;
2024-04-08 04:35:57 -07:00
import StatusScreen from './components/screens/status' ;
2024-09-24 07:12:02 -07:00
import SearchScreen from './components/screens/SearchScreen' ;
2020-11-05 16:58:23 +00:00
const { OneDriveLoginScreen } = require ( './components/screens/onedrive-login.js' ) ;
2021-08-07 12:22:37 +01:00
import EncryptionConfigScreen from './components/screens/encryption-config' ;
2024-11-16 13:09:50 -08:00
import DropboxLoginScreen from './components/screens/dropbox-login.js' ;
2023-11-17 03:43:01 -08:00
import { MenuProvider } from 'react-native-popup-menu' ;
2024-09-16 14:17:12 -07:00
import SideMenu , { SideMenuPosition } from './components/SideMenu' ;
2022-10-11 12:31:09 +01:00
import SideMenuContent from './components/side-menu-content' ;
2024-09-21 04:57:08 -07:00
import SideMenuContentNote from './components/SideMenuContentNote' ;
2021-01-29 18:45:11 +00:00
import { reg } from '@joplin/lib/registry' ;
2020-11-07 15:59:37 +00:00
const { defaultState } = require ( '@joplin/lib/reducer' ) ;
2024-04-25 05:31:48 -07:00
import FileApiDriverLocal from '@joplin/lib/file-api-driver-local' ;
2021-01-23 15:51:19 +00:00
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher' ;
2024-01-05 14:15:47 +00:00
import SearchEngine from '@joplin/lib/services/search/SearchEngine' ;
2023-05-10 12:18:59 +01:00
import WelcomeUtils from '@joplin/lib/WelcomeUtils' ;
2024-03-09 03:15:13 -08:00
import { themeStyle } from './components/global-style' ;
2021-08-16 15:20:14 +01:00
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry' ;
2024-04-25 05:31:48 -07:00
import SyncTargetFilesystem from '@joplin/lib/SyncTargetFilesystem' ;
2020-11-07 15:59:37 +00: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 20:18:51 +00:00
import BiometricPopup from './components/biometrics/BiometricPopup' ;
2023-08-04 10:57:54 +01:00
import initLib from '@joplin/lib/initLib' ;
2024-06-15 16:58:23 +08:00
import { isCallbackUrl , parseCallbackUrl , CallbackUrlCommand } from '@joplin/lib/callbackUrlUtils' ;
2024-03-11 12:17:23 -03:00
import JoplinCloudLoginScreen from './components/screens/JoplinCloudLoginScreen' ;
2020-02-08 11:59:19 +00:00
2024-08-02 06:51:49 -07:00
import SyncTargetNone from '@joplin/lib/SyncTargetNone' ;
2024-11-09 04:54:09 -08:00
2021-08-16 18:05:22 +01:00
SyncTargetRegistry . addClass ( SyncTargetNone ) ;
2017-11-24 19:21:30 +00:00
SyncTargetRegistry . addClass ( SyncTargetOneDrive ) ;
2018-01-25 19:01:14 +00:00
SyncTargetRegistry . addClass ( SyncTargetNextcloud ) ;
2018-02-01 23:40:05 +00:00
SyncTargetRegistry . addClass ( SyncTargetWebDAV ) ;
2018-03-27 00:55:44 +01:00
SyncTargetRegistry . addClass ( SyncTargetDropbox ) ;
2018-05-02 10:27:37 +01:00
SyncTargetRegistry . addClass ( SyncTargetFilesystem ) ;
2020-07-28 22:47:24 +00:00
SyncTargetRegistry . addClass ( SyncTargetAmazonS3 ) ;
2020-12-30 10:54:00 +00:00
SyncTargetRegistry . addClass ( SyncTargetJoplinServer ) ;
2021-06-03 17:12:07 +02:00
SyncTargetRegistry . addClass ( SyncTargetJoplinCloud ) ;
2018-01-17 21:01:41 +00:00
2023-10-22 03:51:31 -07:00
import FsDriverRN from './utils/fs-driver/fs-driver-rn' ;
2021-01-23 15:51:19 +00:00
import DecryptionWorker from '@joplin/lib/services/DecryptionWorker' ;
2021-08-23 18:47:07 +01:00
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService' ;
2021-01-23 15:51:19 +00:00
import MigrationService from '@joplin/lib/services/MigrationService' ;
2021-04-07 18:41:54 +01:00
import { clearSharedFilesCache } from './utils/ShareUtils' ;
2021-04-25 09:50:52 +01: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 22:26:42 +01:00
import setupNotifications from './utils/setupNotifications' ;
2021-08-30 14:15:35 +01:00
import { loadMasterKeysFromSettings , migrateMasterPassword } from '@joplin/lib/services/e2ee/utils' ;
2021-10-03 16:00:49 +01:00
import { setRSA } from '@joplin/lib/services/e2ee/ppk' ;
import RSA from './services/e2ee/RSA.react-native' ;
2023-10-22 03:51:31 -07:00
import { runIntegrationTests as runRsaIntegrationTests } from '@joplin/lib/services/e2ee/ppkTestUtils' ;
2024-10-27 04:15:10 +08:00
import { runIntegrationTests as runCryptoIntegrationTests } from '@joplin/lib/services/e2ee/cryptoTestUtils' ;
2023-01-08 04:22:41 -08:00
import { Theme , ThemeAppearance } from '@joplin/lib/themes/type' ;
2023-01-10 12:08:13 +00:00
import ProfileSwitcher from './components/ProfileSwitcher/ProfileSwitcher' ;
import ProfileEditor from './components/ProfileSwitcher/ProfileEditor' ;
2023-06-08 16:23:48 +01:00
import sensorInfo , { SensorInfo } from './components/biometrics/sensorInfo' ;
2023-01-10 12:08:13 +00:00
import { getCurrentProfile } from '@joplin/lib/services/profileConfig' ;
2024-04-04 04:10:59 -07:00
import { getDatabaseName , getPluginDataDir , getProfilesRootDir , getResourceDir , setDispatch } from './services/profiles' ;
2023-09-10 11:30:18 +01:00
import userFetcher , { initializeUserFetcher } from '@joplin/lib/utils/userFetcher' ;
2023-09-11 00:53:53 -07:00
import { ReactNode } from 'react' ;
2023-07-16 17:42:42 +01:00
import { parseShareCache } from '@joplin/lib/services/share/reducer' ;
2023-07-18 06:46:11 -07:00
import autodetectTheme , { onSystemColorSchemeChange } from './utils/autodetectTheme' ;
2023-10-22 03:51:31 -07:00
import runOnDeviceFsDriverTests from './utils/fs-driver/runOnDeviceTests' ;
2024-06-25 05:59:59 -07:00
import PluginRunnerWebView from './components/plugins/PluginRunnerWebView' ;
2024-03-02 14:25:27 +00:00
import { refreshFolders , scheduleRefreshFolders } from '@joplin/lib/folders-screen-utils' ;
2024-03-11 08:02:15 -07:00
import KeymapService from '@joplin/lib/services/KeymapService' ;
import PluginService from '@joplin/lib/services/plugins/PluginService' ;
import initializeCommandService from './utils/initializeCommandService' ;
2024-06-25 05:59:59 -07:00
import PlatformImplementation from './services/plugins/PlatformImplementation' ;
2024-04-15 10:17:34 -07:00
import ShareManager from './components/screens/ShareManager' ;
import appDefaultState , { DEFAULT_ROUTE } from './utils/appDefaultState' ;
2024-06-20 14:01:13 +01:00
import { setDateFormat , setTimeFormat , setTimeLocale } from '@joplin/utils/time' ;
2024-08-02 06:51:49 -07:00
import DatabaseDriverReactNative from './utils/database-driver-react-native' ;
import DialogManager from './components/DialogManager' ;
import lockToSingleInstance from './utils/lockToSingleInstance' ;
2024-07-26 04:35:50 -07:00
import { AppState } from './utils/types' ;
import { getDisplayParentId } from '@joplin/lib/services/trash' ;
2023-07-06 11:18:43 -07:00
2023-02-19 19:00:17 +00:00
const logger = Logger . create ( 'root' ) ;
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2024-03-02 14:25:27 +00:00
let storeDispatch : any = function ( _action : any ) { } ;
2018-01-02 20:17:14 +01:00
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-12-30 10:54:00 +00: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-13 23:46:14 +00:00
const msg = [ action . type ] ;
2018-04-16 15:15:29 +02:00
if ( action . routeName ) msg . push ( action . routeName ) ;
2019-05-28 22:05:11 +01: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 16:23:48 +01:00
const biometricsEnabled = ( sensorInfo : SensorInfo ) : boolean = > {
return ! ! sensorInfo && sensorInfo . enabled ;
} ;
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-12-30 10:54:00 +00:00
const generalMiddleware = ( store : any ) = > ( next : any ) = > async ( action : any ) = > {
2018-04-16 15:15:29 +02:00
logReducerAction ( action ) ;
2017-07-25 18:41:53 +00:00
PoorManIntervals . update ( ) ; // This function needs to be called regularly so put it here
const result = next ( action ) ;
2024-07-26 04:35:50 -07:00
const newState : AppState = store . getState ( ) ;
2024-03-02 14:25:27 +00:00
let doRefreshFolders = false ;
2017-07-25 18:41:53 +00:00
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2024-01-03 18:02:05 +00:00
await reduxSharedMiddleware ( store , next , action , storeDispatch as any ) ;
2018-05-09 12:39:17 +01:00
2022-07-23 09:31:32 +02:00
if ( action . type === 'NAV_GO' ) Keyboard . dismiss ( ) ;
2017-07-25 18:41:53 +00: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 14:58:19 +01:00
if ( ! await reg . syncTarget ( ) . syncStarted ( ) ) void reg . scheduleSync ( 1000 , { syncSteps : [ 'update_remote' , 'delete_remote' ] } , true ) ;
2019-01-18 18:31:07 +00:00
SearchEngine . instance ( ) . scheduleSyncTables ( ) ;
2017-07-26 17:49:01 +00:00
}
2024-03-02 14:25:27 +00:00
if ( [ 'FOLDER_UPDATE_ONE' ] . indexOf ( action . type ) >= 0 ) {
doRefreshFolders = true ;
}
2018-03-09 20:59:12 +00: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 18:58:04 +00:00
}
2024-07-26 04:35:50 -07: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 17:49:01 +00: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 20:59:12 +00:00
time . setDateFormat ( Setting . value ( 'dateFormat' ) ) ;
time . setTimeFormat ( Setting . value ( 'timeFormat' ) ) ;
2024-06-20 14:01:13 +01:00
setDateFormat ( Setting . value ( 'dateFormat' ) ) ;
setTimeFormat ( Setting . value ( 'timeFormat' ) ) ;
2017-11-28 18:02:54 +00: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 20:59:12 +00:00
setLocale ( Setting . value ( 'locale' ) ) ;
2024-06-20 14:01:13 +01:00
setTimeLocale ( Setting . value ( 'locale' ) ) ;
2018-01-02 20:17:14 +01:00
}
2024-09-07 03:56:13 -07: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 16:54:10 +01:00
await loadMasterKeysFromSettings ( EncryptionService . instance ( ) ) ;
2021-01-22 17:41:11 +00:00
void DecryptionWorker . instance ( ) . scheduleStart ( ) ;
2018-01-02 20:17:14 +01:00
const loadedMasterKeyIds = EncryptionService . instance ( ) . loadedMasterKeyIds ( ) ;
storeDispatch ( {
2018-03-09 20:59:12 +00:00
type : 'MASTERKEY_REMOVE_NOT_LOADED' ,
2018-01-02 20:17:14 +01:00
ids : loadedMasterKeyIds ,
} ) ;
2018-01-08 21:25:38 +01: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 20:17:14 +01:00
}
2017-11-03 18:51:13 +00:00
2023-07-18 06:46:11 -07: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 05:43:33 -07:00
if ( 'selectedFolderId' in newState ) {
Setting . setValue ( 'activeFolderId' , newState . selectedFolderId ) ;
}
2023-09-24 20:21:58 +01:00
const notesParent : NotesParent = {
type : action . smartFilterId ? 'SmartFilter' : 'Folder' ,
selectedItemId : action.smartFilterId ? action.smartFilterId : newState.selectedFolderId ,
} ;
Setting . setValue ( 'notesParent' , serializeNotesParent ( notesParent ) ) ;
2017-07-25 18:41:53 +00:00
}
2018-03-09 20:59:12 +00:00
if ( action . type === 'SYNC_GOT_ENCRYPTED_ITEM' ) {
2021-01-22 17:41:11 +00:00
void DecryptionWorker . instance ( ) . scheduleStart ( ) ;
2017-12-30 20:57:34 +01:00
}
2020-05-31 00:31:29 +01:00
if ( action . type === 'SYNC_CREATED_OR_UPDATED_RESOURCE' ) {
2021-01-22 17:41:11 +00:00
void ResourceFetcher . instance ( ) . autoAddResources ( ) ;
2018-10-09 22:01:50 +01:00
}
2024-03-02 14:25:27 +00:00
if ( doRefreshFolders ) {
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2024-04-27 09:54:47 +01:00
await scheduleRefreshFolders ( ( action : any ) = > storeDispatch ( action ) , newState . selectedFolderId ) ;
2024-03-02 14:25:27 +00:00
}
2019-07-30 09:35:42 +02:00
return result ;
} ;
2017-07-25 18:41:53 +00:00
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-12-30 10:54:00 +00:00
const navHistory : any [ ] = [ ] ;
2017-11-06 18:05:12 +00:00
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-12-30 10:54:00 +00:00
function historyCanGoBackTo ( route : any ) {
2018-03-16 20:17:52 +00:00
if ( route . routeName === 'Folder' ) return false ;
2017-11-06 18:05:12 +00:00
2018-09-23 20:45:34 +01: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 20:45:34 +01:00
if ( route . routeName === 'OneDriveLogin' ) return false ;
if ( route . routeName === 'DropboxLogin' ) return false ;
2017-11-06 18:05:12 +00:00
return true ;
}
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-12-30 10:54:00 +00:00
const appReducer = ( state = appDefaultState , action : any ) = > {
2017-11-06 18:05:12 +00:00
let newState = state ;
let historyGoingBack = false ;
try {
switch ( action . type ) {
2018-03-09 20:59:12 +00:00
2019-07-30 09:35:42 +02:00
case 'NAV_BACK' :
2023-06-30 10:22:47 +01:00
case 'NAV_GO' :
2018-03-09 20:59:12 +00:00
2023-06-30 10:22:47 +01:00
if ( action . type === 'NAV_BACK' ) {
if ( ! navHistory . length ) break ;
2017-11-06 18:05:12 +00:00
2024-09-21 05:00:31 -07:00
const newAction = navHistory . pop ( ) ;
2023-06-30 10:22:47 +01:00
action = newAction ? newAction : navHistory.pop ( ) ;
2018-03-09 20:59:12 +00:00
2023-06-30 10:22:47 +01:00
historyGoingBack = true ;
}
2017-11-06 18:05:12 +00:00
2019-07-30 09:35:42 +02:00
{
2017-11-06 18:05:12 +00:00
const currentRoute = state . route ;
2020-12-30 10:54:00 +00:00
if ( ! historyGoingBack && historyCanGoBackTo ( currentRoute ) ) {
2024-11-09 04:54:09 -08: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 18:05:12 +00:00
}
2023-06-01 12:02:36 +01:00
newState = { . . . state } ;
2017-11-06 18:05:12 +00:00
2019-09-09 18:16:00 +01:00
newState . selectedNoteHash = '' ;
2024-10-31 01:18:13 -07:00
if ( action . routeName === 'Search' ) {
newState . notesParentType = 'Search' ;
}
2018-03-09 20:59:12 +00:00
if ( 'noteId' in action ) {
2017-11-22 18:35:31 +00:00
newState . selectedNoteIds = action . noteId ? [ action . noteId ] : [ ] ;
2017-11-06 18:05:12 +00:00
}
2018-03-09 20:59:12 +00:00
if ( 'folderId' in action ) {
2017-11-06 18:05:12 +00:00
newState . selectedFolderId = action . folderId ;
2018-03-09 20:59:12 +00:00
newState . notesParentType = 'Folder' ;
2017-11-06 18:05:12 +00:00
}
2018-03-09 20:59:12 +00:00
if ( 'tagId' in action ) {
2017-11-06 18:05:12 +00:00
newState . selectedTagId = action . tagId ;
2018-03-09 20:59:12 +00:00
newState . notesParentType = 'Tag' ;
2017-11-06 18:05:12 +00:00
}
2019-06-28 00:51:02 +01:00
if ( 'smartFilterId' in action ) {
newState . smartFilterId = action . smartFilterId ;
2023-11-19 02:43:57 -08:00
newState . selectedSmartFilterId = action . smartFilterId ;
2019-06-28 00:51:02 +01:00
newState . notesParentType = 'SmartFilter' ;
}
2018-03-09 20:59:12 +00:00
if ( 'itemType' in action ) {
2017-11-06 18:05:12 +00:00
newState . selectedItemType = action . itemType ;
}
2019-09-09 18:16:00 +01: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 18:05:12 +00:00
newState . route = action ;
newState . historyCanGoBack = ! ! navHistory . length ;
2024-10-31 01:18:13 -07: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 18:05:12 +00:00
2019-07-30 09:35:42 +02:00
case 'SIDE_MENU_TOGGLE' :
2018-03-09 20:59:12 +00:00
2023-06-01 12:02:36 +01:00
newState = { . . . state } ;
2019-07-30 09:35:42 +02:00
newState . showSideMenu = ! newState . showSideMenu ;
break ;
2017-11-06 18:05:12 +00:00
2019-07-30 09:35:42 +02:00
case 'SIDE_MENU_OPEN' :
2018-03-09 20:59:12 +00:00
2023-06-01 12:02:36 +01:00
newState = { . . . state } ;
2019-07-30 09:35:42 +02:00
newState . showSideMenu = true ;
break ;
2017-11-06 18:05:12 +00:00
2019-07-30 09:35:42 +02:00
case 'SIDE_MENU_CLOSE' :
2018-03-09 20:59:12 +00:00
2023-06-01 12:02:36 +01:00
newState = { . . . state } ;
2019-07-30 09:35:42 +02:00
newState . showSideMenu = false ;
break ;
2017-11-06 18:05:12 +00:00
2024-03-29 05:40:54 -07:00
case 'SET_PLUGIN_PANELS_DIALOG_VISIBLE' :
2024-03-25 04:39:48 -07:00
newState = { . . . state } ;
2024-03-29 05:40:54 -07:00
newState . showPanelsDialog = action . visible ;
2024-03-25 04:39:48 -07:00
break ;
2019-07-30 09:35:42 +02:00
case 'NOTE_SELECTION_TOGGLE' :
2018-03-09 20:59:12 +00:00
2019-07-30 09:35:42 +02:00
{
2023-06-01 12:02:36 +01:00
newState = { . . . state } ;
2017-11-23 18:47:51 +00: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 18:47:51 +00:00
2019-07-30 09:35:42 +02:00
case 'NOTE_SELECTION_START' :
2018-03-09 20:59:12 +00:00
2019-07-30 09:35:42 +02:00
if ( ! state . noteSelectionEnabled ) {
2023-06-01 12:02:36 +01:00
newState = { . . . state } ;
2019-07-30 09:35:42 +02:00
newState . noteSelectionEnabled = true ;
newState . selectedNoteIds = [ action . id ] ;
}
break ;
2017-11-23 18:47:51 +00:00
2019-07-30 09:35:42 +02:00
case 'NOTE_SELECTION_END' :
2018-03-09 20:59:12 +00:00
2023-06-01 12:02:36 +01:00
newState = { . . . state } ;
2019-07-30 09:35:42 +02:00
newState . noteSelectionEnabled = false ;
newState . selectedNoteIds = [ ] ;
break ;
2018-03-09 20:59:12 +00:00
2019-07-30 09:35:42 +02:00
case 'NOTE_SIDE_MENU_OPTIONS_SET' :
2019-07-11 17:43:55 +01:00
2023-06-01 12:02:36 +01:00
newState = { . . . state } ;
2019-07-30 09:35:42 +02:00
newState . noteSideMenuOptions = action . options ;
break ;
2018-03-09 20:59:12 +00:00
2023-10-02 07:15:51 -07: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 12:02:36 +01:00
newState = { . . . state } ;
2021-03-29 10:35:39 +02:00
newState . isOnMobileData = action . isOnMobileData ;
break ;
2017-11-06 18:05:12 +00:00
}
} catch ( error ) {
2019-09-19 22:51:18 +01:00
error . message = ` In reducer: ${ error . message } Action: ${ JSON . stringify ( action ) } ` ;
2017-11-06 18:05:12 +00:00
throw error ;
}
return reducer ( newState , action ) ;
2019-07-30 09:35:42 +02:00
} ;
2017-11-06 18:05:12 +00:00
2020-03-13 23:46:14 +00:00
const store = createStore ( appReducer , applyMiddleware ( generalMiddleware ) ) ;
2018-01-02 20:17:14 +01:00
storeDispatch = store . dispatch ;
2017-05-09 20:46:54 +00:00
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-12-30 10:54:00 +00:00
function resourceFetcher_downloadComplete ( event : any ) {
2019-05-22 15:56:07 +01:00
if ( event . encrypted ) {
2021-01-22 17:41:11 +00:00
void DecryptionWorker . instance ( ) . scheduleStart ( ) ;
2019-05-22 15:56:07 +01:00
}
}
2020-06-09 19:56:48 +00:00
function decryptionWorker_resourceMetadataButNotBlobDecrypted() {
ResourceFetcher . instance ( ) . scheduleAutoAddResources ( ) ;
}
2023-07-18 06:58:06 -07: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 05:43:33 -07: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 06:51:49 -07:00
const singleInstanceLock = lockToSingleInstance ( ) ;
2023-06-30 10:30:29 +01:00
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
2024-07-01 12:21:17 -03:00
async function initialize ( dispatch : Dispatch ) {
2017-07-10 18:09:58 +00:00
shimInit ( ) ;
2017-07-07 18:19:24 +01:00
2023-01-10 12:08:13 +00:00
setDispatch ( dispatch ) ;
const { profileConfig , isSubProfile } = await initProfile ( getProfilesRootDir ( ) ) ;
const currentProfile = getCurrentProfile ( profileConfig ) ;
dispatch ( {
type : 'PROFILE_CONFIG_SET' ,
value : profileConfig ,
} ) ;
2024-06-25 06:01:39 -07:00
Setting . setConstant ( 'env' , __DEV__ ? Env.Dev : Env.Prod ) ;
2018-03-09 20:59:12 +00:00
Setting . setConstant ( 'appId' , 'net.cozic.joplin-mobile' ) ;
2024-06-25 06:01:39 -07:00
Setting . setConstant ( 'appType' , AppType . Mobile ) ;
2023-07-18 06:58:06 -07:00
Setting . setConstant ( 'tempDir' , await initializeTempDir ( ) ) ;
2024-03-11 08:02:15 -07:00
Setting . setConstant ( 'cacheDir' , ` ${ getProfilesRootDir ( ) } /cache ` ) ;
2023-01-10 12:08:13 +00:00
const resourceDir = getResourceDir ( currentProfile , isSubProfile ) ;
Setting . setConstant ( 'resourceDir' , resourceDir ) ;
2024-03-11 08:02:15 -07:00
Setting . setConstant ( 'pluginDir' , ` ${ getProfilesRootDir ( ) } /plugins ` ) ;
2024-04-04 04:10:59 -07:00
Setting . setConstant ( 'pluginDataDir' , getPluginDataDir ( currentProfile , isSubProfile ) ) ;
2023-01-10 12:08:13 +00:00
await shim . fsDriver ( ) . mkdir ( resourceDir ) ;
2017-07-09 23:57:15 +01:00
2024-08-02 06:51:49 -07: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-09 23:57:15 +01:00
const logDatabase = new Database ( new DatabaseDriverReactNative ( ) ) ;
2018-03-09 20:59:12 +00:00
await logDatabase . open ( { name : 'log.sqlite' } ) ;
2017-07-09 23:57:15 +01:00
await logDatabase . exec ( Logger . databaseCreateTableSql ( ) ) ;
2017-07-15 00:12:32 +01:00
const mainLogger = new Logger ( ) ;
2020-12-30 10:54:00 +00:00
mainLogger . addTarget ( TargetType . Database , { database : logDatabase , source : 'm' } ) ;
2017-12-31 14:58:50 +01: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 10:54:00 +00:00
mainLogger . addTarget ( TargetType . Console ) ;
2017-12-31 14:58:50 +01:00
mainLogger . setLevel ( Logger . LEVEL_DEBUG ) ;
}
2017-07-15 00:12:32 +01:00
2020-11-19 15:25:02 +00:00
Logger . initializeGlobalLogger ( mainLogger ) ;
2023-08-04 10:57:54 +01:00
initLib ( mainLogger ) ;
2020-11-19 15:25:02 +00:00
2017-07-15 00:12:32 +01:00
reg . setLogger ( mainLogger ) ;
2020-12-30 10:54:00 +00:00
reg . setShowErrorMessageBoxHandler ( ( message : string ) = > { alert ( message ) ; } ) ;
2024-07-01 12:21:17 -03:00
reg . setDispatch ( dispatch ) ;
2017-07-09 23:57:15 +01:00
2018-03-15 18:08:46 +00:00
BaseService . logger_ = mainLogger ;
2020-11-07 15:59:37 +00:00
// require('@joplin/lib/ntpDate').setLogger(reg.logger());
2018-03-15 18:08:46 +00:00
2018-03-09 20:59:12 +00:00
reg . logger ( ) . info ( '====================================' ) ;
2021-06-17 18:19:32 +01:00
reg . logger ( ) . info ( ` Starting application ${ Setting . value ( 'appId' ) } v ${ VersionInfo . appVersion } ( ${ Setting . value ( 'env' ) } ) ` ) ;
2017-07-09 23:57:15 +01:00
2017-07-15 00:12:32 +01:00
const dbLogger = new Logger ( ) ;
2020-12-30 10:54:00 +00: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 10:54:00 +00:00
dbLogger . addTarget ( TargetType . Console ) ;
2017-07-17 23:43:29 +01:00
dbLogger . setLevel ( Logger . LEVEL_INFO ) ; // Set to LEVEL_DEBUG for full SQL queries
2017-07-15 18:08:54 +01:00
} else {
dbLogger . setLevel ( Logger . LEVEL_INFO ) ;
}
2017-07-15 00:12:32 +01:00
2020-03-13 23:46:14 +00:00
const db = new JoplinDatabase ( new DatabaseDriverReactNative ( ) ) ;
2017-07-15 00:12:32 +01:00
db . setLogger ( dbLogger ) ;
2017-07-09 23:57:15 +01:00
reg . setDb ( db ) ;
2021-01-29 18:45:11 +00:00
// reg.dispatch = dispatch;
2017-07-09 23:57:15 +01:00
BaseModel . dispatch = dispatch ;
2017-11-24 18:06:30 +00:00
BaseSyncTarget . dispatch = dispatch ;
2018-02-21 22:08:34 +00:00
NavService . dispatch = dispatch ;
2020-03-16 13:30:54 +11:00
BaseModel . setDb ( db ) ;
2017-07-09 23:57:15 +01:00
2019-06-07 23:11:08 +01:00
KvStore . instance ( ) . setDb ( reg . db ( ) ) ;
2018-03-09 20:59:12 +00: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 21:35:29 +01:00
BaseItem . loadClass ( 'Revision' , Revision ) ;
2017-07-09 23:57:15 +01:00
2017-12-30 20:57:34 +01:00
const fsDriver = new FsDriverRN ( ) ;
Resource . fsDriver_ = fsDriver ;
2018-01-17 21:01:41 +00:00
FileApiDriverLocal . fsDriver_ = fsDriver ;
2017-12-30 20:57:34 +01:00
2020-10-16 16:26:19 +01:00
AlarmService . setDriver ( new AlarmServiceDriver ( mainLogger ) ) ;
2017-11-27 22:50:46 +00:00
AlarmService . setLogger ( mainLogger ) ;
2024-03-11 08:02:15 -07: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 16:00:49 +01:00
setRSA ( RSA ) ;
2017-07-09 23:57:15 +01:00
try {
2022-07-23 09:31:32 +02:00
if ( Setting . value ( 'env' ) === 'prod' ) {
2023-01-10 12:08:13 +00:00
await db . open ( { name : getDatabaseName ( currentProfile , isSubProfile ) } ) ;
2017-07-09 23:57:15 +01:00
} else {
2024-01-27 16:59:19 +00:00
await db . open ( { name : getDatabaseName ( currentProfile , isSubProfile , '-20240127-1' ) } ) ;
2019-05-22 15:56:07 +01:00
2019-05-26 19:39:07 +01:00
// await db.clearForTesting();
2017-07-09 23:57:15 +01:00
}
2017-05-16 23:46:21 +02:00
2018-03-09 20:59:12 +00:00
reg . logger ( ) . info ( 'Database is ready.' ) ;
reg . logger ( ) . info ( 'Loading settings...' ) ;
2020-06-05 00:08:09 +01:00
2024-10-26 13:02:34 -07:00
await loadKeychainServiceAndSettings ( [ ] ) ;
2021-08-30 14:15:35 +01:00
await migrateMasterPassword ( ) ;
2021-10-18 12:37:25 +01:00
if ( ! Setting . value ( 'clientId' ) ) Setting . setValue ( 'clientId' , uuid . create ( ) ) ;
2022-10-01 00:38:22 +08:00
reg . logger ( ) . info ( ` Client ID: ${ Setting . value ( 'clientId' ) } ` ) ;
2021-10-18 12:37:25 +01:00
2023-07-16 17:42:42 +01:00
BaseItem . syncShareCache = parseShareCache ( Setting . value ( 'sync.shareCache' ) ) ;
2023-06-01 16:48:02 +01:00
2021-10-18 12:37:25 +01:00
if ( Setting . value ( 'firstStart' ) ) {
2023-06-01 16:48:02 +01:00
const detectedLocale = shim . detectAndSetLocale ( Setting ) ;
reg . logger ( ) . info ( ` First start: detected locale as ${ detectedLocale } ` ) ;
2024-08-02 06:51:49 -07: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 12:37:25 +01:00
Setting . skipDefaultMigrations ( ) ;
2024-06-25 06:01:39 -07:00
Setting . setValue ( 'firstStart' , false ) ;
2021-10-18 12:37:25 +01:00
} else {
Setting . applyDefaultMigrations ( ) ;
}
2021-08-30 14:15:35 +01: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 12:17:23 -03:00
Setting . setValue ( 'sync.10.website' , 'http://joplincloud.local:22300' ) ;
2021-10-17 17:20:59 +01:00
2021-11-02 16:33:53 +00:00
// Setting.setValue('sync.target', 10);
// Setting.setValue('sync.10.username', 'user1@example.com');
2022-04-12 17:06:53 +01:00
// Setting.setValue('sync.10.password', '111111');
2021-08-30 14:15:35 +01:00
}
2017-07-25 18:57:06 +00:00
2018-12-28 21:40:29 +01: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 18:57:06 +00:00
}
2019-05-22 15:56:07 +01:00
if ( Setting . value ( 'env' ) === 'dev' ) {
Setting . setValue ( 'welcome.enabled' , false ) ;
2019-07-30 09:35:42 +02:00
}
2019-05-22 15:56:07 +01:00
2019-12-29 18:58:40 +01:00
await PluginAssetsLoader . instance ( ) . importAssets ( ) ;
2019-07-30 09:35:42 +02:00
// eslint-disable-next-line require-atomic-updates
2019-05-06 21:35:29 +01: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 20:30:31 +00: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 22:51:18 +01:00
reg . logger ( ) . info ( ` Sync target: ${ Setting . value ( 'sync.target' ) } ` ) ;
2017-11-24 19:47:24 +00:00
2018-03-09 20:59:12 +00:00
setLocale ( Setting . value ( 'locale' ) ) ;
2017-07-19 22:26:30 +01:00
2021-04-25 09:50:52 +01:00
if ( Platform . OS === 'android' ) {
const ignoreTlsErrors = Setting . value ( 'net.ignoreTlsErrors' ) ;
if ( ignoreTlsErrors ) {
await setIgnoreTlsErrors ( ignoreTlsErrors ) ;
}
}
2017-12-30 20:57:34 +01:00
// ----------------------------------------------------------------
// E2EE SETUP
// ----------------------------------------------------------------
EncryptionService . fsDriver_ = fsDriver ;
2019-07-30 09:35:42 +02:00
// eslint-disable-next-line require-atomic-updates
2017-12-30 20:57:34 +01:00
BaseItem . encryptionService_ = EncryptionService . instance ( ) ;
2021-05-13 18:57:37 +02:00
BaseItem . shareService_ = ShareService . instance ( ) ;
2022-02-09 18:04:27 +00:00
Resource . shareService_ = ShareService . instance ( ) ;
2018-01-02 20:17:14 +01:00
DecryptionWorker . instance ( ) . dispatch = dispatch ;
2017-12-30 20:57:34 +01:00
DecryptionWorker . instance ( ) . setLogger ( mainLogger ) ;
2019-06-07 23:11:08 +01:00
DecryptionWorker . instance ( ) . setKvStore ( KvStore . instance ( ) ) ;
2017-12-30 20:57:34 +01:00
DecryptionWorker . instance ( ) . setEncryptionService ( EncryptionService . instance ( ) ) ;
2021-08-12 16:54:10 +01:00
await loadMasterKeysFromSettings ( EncryptionService . instance ( ) ) ;
2020-06-09 19:56:48 +00:00
DecryptionWorker . instance ( ) . on ( 'resourceMetadataButNotBlobDecrypted' , decryptionWorker_resourceMetadataButNotBlobDecrypted ) ;
2017-12-30 20:57:34 +01:00
// ----------------------------------------------------------------
// / E2EE SETUP
// ----------------------------------------------------------------
2021-11-03 16:24:40 +00:00
await ShareService . instance ( ) . initialize ( store , EncryptionService . instance ( ) ) ;
2021-05-13 18:57:37 +02:00
2018-03-09 20:59:12 +00:00
reg . logger ( ) . info ( 'Loading folders...' ) ;
2017-07-06 22:30:45 +01:00
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2024-04-27 09:54:47 +01:00
await refreshFolders ( ( action : any ) = > dispatch ( action ) , '' ) ;
2017-07-08 23:57:09 +01:00
2019-02-24 18:02:50 +00:00
const tags = await Tag . allWithNotes ( ) ;
2017-07-25 19:36:52 +01:00
dispatch ( {
2018-03-09 20:59:12 +00:00
type : 'TAG_UPDATE_ALL' ,
2017-12-14 17:58:10 +00:00
items : tags ,
2017-07-25 19:36:52 +01:00
} ) ;
2021-08-12 16:54:10 +01:00
// const masterKeys = await MasterKey.all();
2017-12-30 20:57:34 +01:00
2021-08-12 16:54:10 +01:00
// dispatch({
// type: 'MASTERKEY_UPDATE_ALL',
// items: masterKeys,
// });
2017-12-30 20:57:34 +01:00
2024-05-28 05:43:33 -07:00
const folder = await getInitialActiveFolder ( ) ;
2017-07-23 19:26:50 +01:00
2018-05-09 12:39:17 +01:00
dispatch ( {
type : 'FOLDER_SET_COLLAPSED_ALL' ,
ids : Setting.value ( 'collapsedFolderIds' ) ,
} ) ;
2023-09-24 20:21:58 +01:00
const notesParent = parseNotesParent ( Setting . value ( 'notesParent' ) , Setting . value ( 'activeFolderId' ) ) ;
if ( notesParent && notesParent . type === 'SmartFilter' ) {
dispatch ( DEFAULT_ROUTE ) ;
} else if ( ! folder ) {
2019-06-29 00:24:00 +01:00
dispatch ( DEFAULT_ROUTE ) ;
2017-07-24 22:52:30 +01:00
} else {
2017-07-25 18:09:01 +00:00
dispatch ( {
2018-03-09 20:59:12 +00:00
type : 'NAV_GO' ,
routeName : 'Notes' ,
2017-07-25 18:09:01 +00:00
folderId : folder.id ,
} ) ;
2017-07-24 22:52:30 +01:00
}
2021-04-07 18:41:54 +01:00
await clearSharedFilesCache ( ) ;
2017-07-09 23:57:15 +01:00
} catch ( error ) {
2019-09-19 22:51:18 +01:00
alert ( ` Initialization error: ${ error . message } ` ) ;
2018-03-09 20:59:12 +00:00
reg . logger ( ) . error ( 'Initialization error:' , error ) ;
2017-07-09 23:57:15 +01:00
}
2017-07-26 17:49:01 +00:00
reg . setupRecurrentSync ( ) ;
2017-07-22 18:35:39 +01:00
2023-07-23 15:57:55 +01:00
initializeUserFetcher ( ) ;
PoorManIntervals . setInterval ( ( ) = > { void userFetcher ( ) ; } , 1000 * 60 * 60 ) ;
2023-07-18 16:15:45 -03:00
2017-11-28 18:58:04 +00:00
PoorManIntervals . setTimeout ( ( ) = > {
2020-12-30 10:54:00 +00:00
void AlarmService . garbageCollect ( ) ;
2017-11-28 18:58:04 +00:00
} , 1000 * 60 * 60 ) ;
2017-11-24 18:06:45 +00:00
2018-03-16 14:32:47 +00:00
ResourceService . runInBackground ( ) ;
2018-03-15 18:08:46 +00:00
2019-07-30 09:35:42 +02:00
ResourceFetcher . instance ( ) . setFileApi ( ( ) = > { return reg . syncTarget ( ) . fileApi ( ) ; } ) ;
2018-10-09 22:01:50 +01:00
ResourceFetcher . instance ( ) . setLogger ( reg . logger ( ) ) ;
2019-05-22 15:56:07 +01:00
ResourceFetcher . instance ( ) . dispatch = dispatch ;
ResourceFetcher . instance ( ) . on ( 'downloadComplete' , resourceFetcher_downloadComplete ) ;
2021-01-22 17:41:11 +00:00
void ResourceFetcher . instance ( ) . start ( ) ;
2018-10-09 22:01:50 +01:00
2018-12-16 18:32:42 +01:00
SearchEngine . instance ( ) . setDb ( reg . db ( ) ) ;
SearchEngine . instance ( ) . setLogger ( reg . logger ( ) ) ;
2019-01-18 18:31:07 +00:00
SearchEngine . instance ( ) . scheduleSyncTables ( ) ;
2018-12-16 18:32:42 +01:00
2019-05-11 17:55:40 +01:00
await MigrationService . instance ( ) . run ( ) ;
2020-07-28 22:47:24 +00: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 17:23:14 +01:00
// eslint-disable-next-line promise/prefer-await-to-then -- Old code before rule was applied
2023-05-11 14:58:19 +01:00
void reg . scheduleSync ( 100 , null , true ) . then ( ( ) = > {
2017-11-28 18:58:04 +00:00
// Wait for the first sync before updating the notifications, since synchronisation
// might change the notifications.
2020-12-30 10:54:00 +00:00
void AlarmService . updateAllNotifications ( ) ;
2017-12-30 20:57:34 +01:00
2021-01-22 17:41:11 +00:00
void DecryptionWorker . instance ( ) . scheduleStart ( ) ;
2017-11-28 18:58:04 +00:00
} ) ;
2017-11-24 18:06:45 +00:00
2023-05-10 12:18:59 +01:00
await WelcomeUtils . install ( Setting . value ( 'locale' ) , dispatch ) ;
2019-02-05 18:46:32 +00:00
2019-05-06 21:35:29 +01: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 08:02:15 -07: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 06:01:39 -07:00
Setting . setValue ( 'plugins.states' , updatedSettings ) ;
2024-03-11 08:02:15 -07:00
2021-10-03 16:00:49 +01: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 03:51:31 -07:00
if ( Setting . value ( 'env' ) === 'dev' ) {
2024-08-02 06:51:49 -07:00
if ( Platform . OS !== 'web' ) {
await runRsaIntegrationTests ( ) ;
} else {
2024-10-27 04:15:10 +08:00
logger . info ( 'Skipping encryption tests -- not supported on web.' ) ;
2024-08-02 06:51:49 -07:00
}
2024-10-27 04:15:10 +08:00
await runCryptoIntegrationTests ( ) ;
2023-10-22 03:51:31 -07:00
await runOnDeviceFsDriverTests ( ) ;
}
2021-10-03 16:00:49 +01:00
2018-03-09 20:59:12 +00:00
reg . logger ( ) . info ( 'Application initialized' ) ;
2017-07-09 23:57:15 +01:00
}
class AppComponent extends React . Component {
2018-03-09 20:59:12 +00:00
2023-02-08 06:21:59 -08:00
private urlOpenListener_ : EmitterSubscription | null = null ;
private appStateChangeListener_ : NativeEventSubscription | null = null ;
2023-07-18 06:46:11 -07:00
private themeChangeListener_ : NativeEventSubscription | null = null ;
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-10-22 10:45:05 +01:00
private dropdownAlert_ = ( _data : any ) = > new Promise < any > ( res = > res ) ;
2024-06-15 16:58:23 +08:00
private callbackUrl : string | null = null ;
2023-02-08 06:21:59 -08:00
2021-09-06 16:57:07 +01:00
public constructor ( ) {
2017-07-17 23:43:29 +01:00
super ( ) ;
2019-06-26 18:05:37 +00:00
this . state = {
2023-09-11 00:53:53 -07:00
sideMenuContentOpacity : new Animated . Value ( 0 ) ,
2022-08-25 16:59:38 +01:00
sideMenuWidth : this.getSideMenuWidth ( ) ,
2023-01-04 20:18:51 +00:00
sensorInfo : null ,
2019-06-26 18:05:37 +00:00
} ;
2017-07-17 23:43:29 +01:00
this . lastSyncStarted_ = defaultState . syncStarted ;
2017-12-01 19:44:34 +00:00
this . backButtonHandler_ = ( ) = > {
return this . backButtonHandler ( ) ;
2019-07-30 09:35:42 +02:00
} ;
2018-02-07 19:51:58 +00:00
this . onAppStateChange_ = ( ) = > {
PoorManIntervals . update ( ) ;
2019-07-30 09:35:42 +02:00
} ;
2021-04-24 10:22:11 +02:00
2024-04-05 12:16:49 +01: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 16:05:00 +00:00
// logger.info('Sharing: handleOpenURL_: start');
2023-02-19 19:00:17 +00:00
2023-02-10 16:12:14 +00: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 19:00:17 +00:00
logger . info ( 'Sharing: handleOpenURL_: Processing share data' ) ;
2021-04-24 10:22:11 +02:00
void this . handleShareData ( ) ;
2024-06-15 16:58:23 +08: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 16:59:38 +01:00
2023-02-19 21:53:00 +03:30
this . handleNewShare_ = ( ) = > {
2023-02-20 16:05:00 +00:00
// logger.info('Sharing: handleNewShare_: start');
2023-02-19 19:00:17 +00:00
2023-02-19 21:53:00 +03:30
// look at this.handleOpenURL_ comment
if ( this . props . biometricsDone ) {
2023-02-19 19:00:17 +00:00
logger . info ( 'Sharing: handleNewShare_: Processing share data' ) ;
2023-02-19 21:53:00 +03:30
void this . handleShareData ( ) ;
}
} ;
this . unsubscribeNewShareListener_ = ShareExtension . addShareListener ( this . handleNewShare_ ) ;
2022-08-25 16:59:38 +01:00
this . handleScreenWidthChange_ = this . handleScreenWidthChange_ . bind ( this ) ;
2017-07-17 23:43:29 +01:00
}
2020-10-08 11:49:39 +01: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 16:57:07 +01:00
public async componentDidMount() {
2022-07-23 09:31:32 +02:00
if ( this . props . appState === 'starting' ) {
2017-08-01 17:41:58 +00:00
this . props . dispatch ( {
2018-03-09 20:59:12 +00:00
type : 'APP_STATE_SET' ,
state : 'initializing' ,
2017-08-01 17:41:58 +00:00
} ) ;
2021-03-29 10:35:39 +02:00
try {
2021-11-29 12:51:11 +00: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 06:51:49 -07: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 06:51:49 -07: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 11:35:29 +01:00
2024-10-31 16:19:13 +08: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 16:23:48 +01: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 20:18:51 +00:00
2020-10-08 11:35:29 +01:00
this . props . dispatch ( {
type : 'APP_STATE_SET' ,
state : 'ready' ,
2017-08-01 17:41:58 +00:00
} ) ;
2023-01-10 12:08:13 +00:00
// setTimeout(() => {
// this.props.dispatch({
// type: 'NAV_GO',
// routeName: 'ProfileSwitcher',
// });
// }, 1000);
2020-10-08 11:35:29 +01:00
}
2017-11-28 20:17:34 +00:00
2023-02-08 06:21:59 -08:00
this . urlOpenListener_ = Linking . addEventListener ( 'url' , this . handleOpenURL_ ) ;
2021-04-24 10:22:11 +02:00
2020-10-08 11:35:29 +01:00
BackButtonService . initialize ( this . backButtonHandler_ ) ;
2017-12-01 19:44:34 +00:00
2020-12-30 10:54:00 +00:00
AlarmService . setInAppNotificationHandler ( async ( alarmId : string ) = > {
2020-10-08 11:35:29 +01:00
const alarm = await Alarm . load ( alarmId ) ;
const notification = await Alarm . makeNotification ( alarm ) ;
2023-10-22 10:45:05 +01:00
void this . dropdownAlert_ ( {
type : 'info' ,
title : notification.title ,
message : notification.body ? notification . body : '' ,
} ) ;
2020-10-08 11:35:29 +01:00
} ) ;
2018-02-07 19:51:58 +00:00
2023-02-08 06:21:59 -08:00
this . appStateChangeListener_ = RNAppState . addEventListener ( 'change' , this . onAppStateChange_ ) ;
2022-08-25 16:59:38 +01:00
this . unsubscribeScreenWidthChangeHandler_ = Dimensions . addEventListener ( 'change' , this . handleScreenWidthChange_ ) ;
2020-06-04 18:40:44 +01:00
2023-07-18 06:46:11 -07:00
this . themeChangeListener_ = Appearance . addChangeListener (
2023-08-22 11:58:53 +01:00
( { colorScheme } ) = > onSystemColorSchemeChange ( colorScheme ) ,
2023-07-18 06:46:11 -07:00
) ;
onSystemColorSchemeChange ( Appearance . getColorScheme ( ) ) ;
2024-06-19 08:33:22 -03:00
this . quickActionShortcutListener_ = await setupQuickActions ( this . props . dispatch ) ;
2021-05-19 22:26:42 +01:00
await setupNotifications ( this . props . dispatch ) ;
2021-08-30 14:15:35 +01:00
// Setting.setValue('encryption.masterPassword', 'WRONG');
// setTimeout(() => NavService.go('EncryptionConfig'), 2000);
2018-02-07 19:51:58 +00:00
}
2021-09-06 16:57:07 +01:00
public componentWillUnmount() {
2023-02-08 06:21:59 -08:00
if ( this . appStateChangeListener_ ) {
this . appStateChangeListener_ . remove ( ) ;
this . appStateChangeListener_ = null ;
}
if ( this . urlOpenListener_ ) {
this . urlOpenListener_ . remove ( ) ;
this . urlOpenListener_ = null ;
}
2022-08-25 16:59:38 +01:00
2023-07-18 06:46:11 -07:00
if ( this . themeChangeListener_ ) {
this . themeChangeListener_ . remove ( ) ;
this . themeChangeListener_ = null ;
}
2022-08-25 16:59:38 +01: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 21:53:00 +03:30
if ( this . unsubscribeNewShareListener_ ) {
this . unsubscribeNewShareListener_ ( ) ;
this . unsubscribeNewShareListener_ = undefined ;
}
2024-06-19 08:33:22 -03:00
if ( this . quickActionShortcutListener_ ) {
this . quickActionShortcutListener_ . remove ( ) ;
this . quickActionShortcutListener_ = undefined ;
}
2017-07-10 22:00:41 +01:00
}
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-05-29 13:14:09 +01:00
public async componentDidUpdate ( prevProps : any ) {
2023-02-10 16:12:14 +00:00
if ( this . props . biometricsDone !== prevProps . biometricsDone && this . props . biometricsDone ) {
2023-02-19 19:00:17 +00:00
logger . info ( 'Sharing: componentDidUpdate: biometricsDone' ) ;
2023-02-10 16:12:14 +00:00
void this . handleShareData ( ) ;
2024-06-15 16:58:23 +08:00
void this . handleCallbackUrl ( ) ;
2023-02-10 16:12:14 +00:00
}
2019-06-26 18:05:37 +00:00
}
2021-09-06 16:57:07 +01:00
private async backButtonHandler() {
2017-11-23 18:47:51 +00:00
if ( this . props . noteSelectionEnabled ) {
2018-03-09 20:59:12 +00:00
this . props . dispatch ( { type : 'NOTE_SELECTION_END' } ) ;
2017-11-23 18:47:51 +00:00
return true ;
}
2017-07-10 22:00:41 +01:00
if ( this . props . showSideMenu ) {
2018-03-09 20:59:12 +00:00
this . props . dispatch ( { type : 'SIDE_MENU_CLOSE' } ) ;
2018-12-07 00:45:18 +01:00
return true ;
2017-07-10 22:00:41 +01:00
}
if ( this . props . historyCanGoBack ) {
2018-03-09 20:59:12 +00:00
this . props . dispatch ( { type : 'NAV_BACK' } ) ;
2017-07-10 22:00:41 +01:00
return true ;
}
2017-12-01 19:44:34 +00:00
BackHandler . exitApp ( ) ;
2017-07-10 22:00:41 +01:00
return false ;
2017-05-11 20:14:01 +00:00
}
2021-09-06 16:57:07 +01:00
private async handleShareData() {
2021-04-24 10:22:11 +02:00
const sharedData = await ShareExtension . data ( ) ;
2023-02-19 19:00:17 +00:00
2021-04-24 10:22:11 +02:00
if ( sharedData ) {
reg . logger ( ) . info ( 'Received shared data' ) ;
2024-02-08 08:54:23 -08: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 19:00:17 +00:00
logger . info ( 'Sharing: handleShareData: Processing...' ) ;
2024-02-08 08:54:23 -08: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 06:46:43 -07:00
} else {
logger . info ( 'Sharing: received empty share data.' ) ;
2021-04-24 10:22:11 +02:00
}
}
2024-06-15 16:58:23 +08: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 16:59:38 +01:00
private async handleScreenWidthChange_() {
this . setState ( { sideMenuWidth : this.getSideMenuWidth ( ) } ) ;
}
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2021-09-06 16:57:07 +01:00
public UNSAFE_componentWillReceiveProps ( newProps : any ) {
2022-07-23 09:31:32 +02:00
if ( newProps . syncStarted !== this . lastSyncStarted_ ) {
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2024-04-27 09:54:47 +01:00
if ( ! newProps . syncStarted ) void refreshFolders ( ( action : any ) = > this . props . dispatch ( action ) , this . props . selectedFolderId ) ;
2017-07-17 23:43:29 +01:00
this . lastSyncStarted_ = newProps . syncStarted ;
}
}
2021-09-06 16:57:07 +01:00
private sideMenu_change ( isOpen : boolean ) {
2017-05-24 19:27:13 +00:00
// Make sure showSideMenu property of state is updated
// when the menu is open/closed.
this . props . dispatch ( {
2018-03-09 20:59:12 +00:00
type : isOpen ? 'SIDE_MENU_OPEN' : 'SIDE_MENU_CLOSE' ,
2017-05-24 19:27:13 +00:00
} ) ;
}
2022-08-25 16:59:38 +01:00
private getSideMenuWidth = ( ) = > {
const sideMenuWidth = getResponsiveValue ( {
sm : 250 ,
md : 260 ,
lg : 270 ,
xl : 280 ,
xxl : 290 ,
} ) ;
return sideMenuWidth ;
} ;
2021-09-06 16:57:07 +01:00
public render() {
2024-08-02 06:51:49 -07: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 04:22:41 -08:00
const theme : Theme = themeStyle ( this . props . themeId ) ;
2017-08-01 17:41:58 +00:00
2023-07-06 11:18:43 -07:00
let sideMenuContent : ReactNode = null ;
2024-09-16 14:17:12 -07:00
let menuPosition = SideMenuPosition . Left ;
2023-11-15 05:31:26 -08:00
let disableSideMenuGestures = this . props . disableSideMenuGestures ;
2019-07-11 17:43:55 +01:00
if ( this . props . routeName === 'Note' ) {
2020-02-04 22:09:34 +00:00
sideMenuContent = < SafeAreaView style = { { flex : 1 , backgroundColor : theme.backgroundColor } } > < SideMenuContentNote options = { this . props . noteSideMenuOptions } / > < / SafeAreaView > ;
2024-09-16 14:17:12 -07:00
menuPosition = SideMenuPosition . Right ;
2023-11-15 05:31:26 -08:00
} else if ( this . props . routeName === 'Config' ) {
disableSideMenuGestures = true ;
2019-07-11 17:43:55 +01:00
} else {
2020-02-04 22:09:34 +00:00
sideMenuContent = < SafeAreaView style = { { flex : 1 , backgroundColor : theme.backgroundColor } } > < SideMenuContent / > < / SafeAreaView > ;
2019-07-11 17:43:55 +01:00
}
2017-05-24 19:09:46 +00:00
2017-07-14 18:49:14 +00:00
const appNavInit = {
Notes : { screen : NotesScreen } ,
Note : { screen : NoteScreen } ,
2019-03-13 22:42:16 +00:00
Tags : { screen : TagsScreen } ,
2017-07-14 18:49:14 +00:00
Folder : { screen : FolderScreen } ,
OneDriveLogin : { screen : OneDriveLoginScreen } ,
2018-03-27 00:55:44 +01:00
DropboxLogin : { screen : DropboxLoginScreen } ,
2024-03-11 12:17:23 -03:00
JoplinCloudLogin : { screen : JoplinCloudLoginScreen } ,
2017-12-30 20:57:34 +01:00
EncryptionConfig : { screen : EncryptionConfigScreen } ,
2020-08-02 12:28:50 +01:00
UpgradeSyncTarget : { screen : UpgradeSyncTargetScreen } ,
2024-04-15 10:17:34 -07:00
ShareManager : { screen : ShareManager } ,
2023-01-10 12:08:13 +00:00
ProfileSwitcher : { screen : ProfileSwitcher } ,
ProfileEditor : { screen : ProfileEditor } ,
2017-07-14 18:49:14 +00:00
Log : { screen : LogScreen } ,
Status : { screen : StatusScreen } ,
2017-07-22 23:52:24 +01:00
Search : { screen : SearchScreen } ,
2017-07-23 19:26:50 +01:00
Config : { screen : ConfigScreen } ,
2017-07-14 18:49:14 +00:00
} ;
2022-08-25 16:59:38 +01:00
2022-07-10 14:59:33 +01:00
// const statusBarStyle = theme.appearance === 'light-content';
const statusBarStyle = 'light-content' ;
2020-06-10 22:08:59 +01:00
2023-06-08 16:23:48 +01:00
const shouldShowMainContent = ! biometricsEnabled ( this . state . sensorInfo ) || this . props . biometricsDone ;
2023-02-20 09:55:40 -03:00
2023-05-29 13:14:09 +01:00
logger . info ( 'root.biometrics: biometricsDone' , this . props . biometricsDone ) ;
2023-06-08 16:23:48 +01: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 09:49:23 -08: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 04:22:41 -08:00
const mainContent = (
2020-06-10 22:08:59 +01:00
< View style = { { flex : 1 , backgroundColor : theme.backgroundColor } } >
2023-09-11 00:53:53 -07:00
< SideMenu
menu = { sideMenuContent }
2024-02-02 09:49:23 -08:00
edgeHitWidth = { menuEdgeHitWidth }
toleranceX = { 4 }
toleranceY = { 20 }
2023-09-11 00:53:53 -07:00
openMenuOffset = { this . state . sideMenuWidth }
menuPosition = { menuPosition }
onChange = { ( isOpen : boolean ) = > this . sideMenu_change ( isOpen ) }
2023-11-15 05:31:26 -08:00
disableGestures = { disableSideMenuGestures }
2020-06-10 22:08:59 +01:00
>
< StatusBar barStyle = { statusBarStyle } / >
2023-11-17 03:43:01 -08:00
< MenuProvider style = { { flex : 1 } } >
2022-07-10 14:59:33 +01:00
< SafeAreaView style = { { flex : 0 , backgroundColor : theme.backgroundColor2 } } / >
2020-06-10 22:08:59 +01:00
< SafeAreaView style = { { flex : 1 } } >
< View style = { { flex : 1 , backgroundColor : theme.backgroundColor } } >
2023-02-20 09:55:40 -03:00
{ shouldShowMainContent && < AppNav screens = { appNavInit } dispatch = { this . props . dispatch } / > }
2020-06-10 22:08:59 +01:00
< / View >
2024-04-05 12:16:49 +01:00
{ /* eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied */ }
2023-10-24 19:19:09 +01:00
< DropdownAlert alert = { ( func : any ) = > ( this . dropdownAlert_ = func ) } / >
{ ! shouldShowMainContent && < BiometricPopup
2023-02-10 16:12:14 +00:00
dispatch = { this . props . dispatch }
2023-01-04 20:18:51 +00:00
themeId = { this . props . themeId }
sensorInfo = { this . state . sensorInfo }
2023-02-10 16:12:14 +00:00
/ > }
2020-06-10 22:08:59 +01:00
< / SafeAreaView >
2023-11-17 03:43:01 -08:00
< / MenuProvider >
2023-09-11 00:53:53 -07:00
< / SideMenu >
2024-03-14 12:04:32 -07:00
< PluginRunnerWebView / >
2020-06-10 22:08:59 +01:00
< / View >
2017-05-11 20:14:01 +00:00
) ;
2023-01-08 04:22:41 -08:00
2023-01-10 12:08:13 +00:00
const paperTheme = theme . appearance === ThemeAppearance . Dark ? MD3DarkTheme : MD3LightTheme ;
2023-01-08 04:22:41 -08:00
// Wrap everything in a PaperProvider -- this allows using components from react-native-paper
return (
< PaperProvider theme = { {
. . . paperTheme ,
2023-01-10 12:08:13 +00:00
version : 3 ,
2023-01-08 04:22:41 -08:00
colors : {
. . . paperTheme . colors ,
2023-01-10 12:08:13 +00:00
onPrimaryContainer : theme.color5 ,
primaryContainer : theme.backgroundColor5 ,
2024-05-02 06:58:29 -07:00
2024-06-04 01:57:52 -07:00
outline : theme.codeBorderColor ,
primary : theme.color4 ,
onPrimary : theme.backgroundColor4 ,
2024-05-02 06:58:29 -07:00
background : theme.backgroundColor ,
surface : theme.backgroundColor ,
onSurface : theme.color ,
2024-03-11 08:02:15 -07: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 04:22:41 -08:00
} ,
} } >
2024-11-16 13:09:50 -08:00
< DialogManager themeId = { this . props . themeId } >
2024-08-02 06:51:49 -07:00
{ mainContent }
< / DialogManager >
2023-01-08 04:22:41 -08:00
< / PaperProvider >
) ;
2017-05-11 20:14:01 +00:00
}
2017-05-10 18:58:02 +00:00
}
2024-04-05 12:16:49 +01:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-12-30 10:54:00 +00:00
const mapStateToProps = ( state : any ) = > {
2017-05-10 18:58:02 +00:00
return {
2017-11-04 16:40:34 +00:00
historyCanGoBack : state.historyCanGoBack ,
showSideMenu : state.showSideMenu ,
syncStarted : state.syncStarted ,
appState : state.appState ,
2017-11-23 18:47:51 +00:00
noteSelectionEnabled : state.noteSelectionEnabled ,
2018-09-04 11:07:33 +01:00
selectedFolderId : state.selectedFolderId ,
2019-07-11 17:43:55 +01:00
routeName : state.route.routeName ,
2020-09-15 14:01:07 +01:00
themeId : state.settings.theme ,
2019-07-11 17:43:55 +01:00
noteSideMenuOptions : state.noteSideMenuOptions ,
2023-10-02 07:15:51 -07:00
disableSideMenuGestures : state.disableSideMenuGestures ,
2023-02-10 16:12:14 +00:00
biometricsDone : state.biometricsDone ,
2023-05-29 13:14:09 +01:00
biometricsEnabled : state.settings [ 'security.biometricsEnabled' ] ,
2017-11-04 16:40:34 +00:00
} ;
2017-05-10 18:58:02 +00:00
} ;
const App = connect ( mapStateToProps ) ( AppComponent ) ;
2020-12-30 10:54:00 +00:00
export default class Root extends React . Component {
2021-10-03 16:00:49 +01:00
public render() {
2017-05-09 20:46:54 +00:00
return (
< Provider store = { store } >
2018-03-09 20:59:12 +00:00
< App / >
2017-05-09 20:46:54 +00:00
< / Provider >
) ;
}
2017-05-09 19:59:14 +00:00
}