2025-04-24 00:48:58 -07:00
import * as React from 'react' ;
2020-12-30 11:51:43 +00:00
import shim from '@joplin/lib/shim' ;
2025-07-25 01:20:38 -07:00
import PerformanceLogger from '@joplin/lib/PerformanceLogger' ;
2020-12-30 11:51:43 +00:00
shim . setReact ( React ) ;
2025-07-25 01:20:38 -07:00
PerformanceLogger . onAppStartBegin ( ) ;
2020-12-30 11:51:43 +00:00
2021-05-19 22:26:42 +01:00
import setupQuickActions from './setupQuickActions' ;
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' ;
2025-07-25 01:20:38 -07:00
import Logger from '@joplin/utils/Logger' ;
2024-12-11 04:31:05 -08:00
import NoteScreen from './components/screens/Note/Note' ;
2020-12-30 10:54:00 +00:00
import UpgradeSyncTargetScreen from './components/screens/UpgradeSyncTargetScreen' ;
2025-07-25 01:20:38 -07:00
import Setting , { } from '@joplin/lib/models/Setting' ;
2020-12-30 10:54:00 +00:00
import PoorManIntervals from '@joplin/lib/PoorManIntervals' ;
2025-09-08 16:31:12 -07:00
import { NotesParent , serializeNotesParent } from '@joplin/lib/reducer' ;
2025-04-24 00:48:58 -07:00
import ShareExtension , { UnsubscribeShareListener } from './utils/ShareExtension' ;
2020-12-30 10:54:00 +00:00
import handleShared from './utils/shareHandler' ;
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' ;
2024-12-11 04:31:05 -08:00
import { Keyboard , BackHandler , Animated , StatusBar , Platform , Dimensions } from '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' ;
2025-04-24 00:48:58 -07:00
import NetInfo , { NetInfoSubscription } from '@react-native-community/netinfo' ;
2020-12-30 10:54:00 +00:00
const DropdownAlert = require ( 'react-native-dropdownalert' ) . default ;
2025-07-25 01:19:23 -07:00
import SafeAreaView from './components/SafeAreaView' ;
2018-03-09 20:59:12 +00:00
const { connect , Provider } = require ( 'react-redux' ) ;
2023-01-10 12:08:13 +00:00
import { Provider as PaperProvider , MD3DarkTheme , MD3LightTheme } from 'react-native-paper' ;
2025-04-24 00:48:58 -07:00
import BackButtonService , { BackButtonHandler } 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' ;
2020-11-05 16:58:23 +00:00
const { AppNav } = require ( './components/app-nav.js' ) ;
2021-01-22 17:41:11 +00:00
import Folder from '@joplin/lib/models/Folder' ;
2025-04-07 12:12:10 -07:00
import NotesScreen from './components/screens/Notes/Notes' ;
2024-11-20 03:37:09 -08:00
import TagsScreen from './components/screens/tags' ;
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' ;
2025-04-24 00:48:58 -07:00
import SideMenuContentNote , { SideMenuContentOptions } from './components/SideMenuContentNote' ;
2021-01-29 18:45:11 +00:00
import { reg } from '@joplin/lib/registry' ;
2025-04-24 00:48:58 -07:00
import { defaultState } from '@joplin/lib/reducer' ;
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' ;
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' ) ;
2025-06-02 17:34:08 +02:00
import SyncTargetJoplinServerSAML from '@joplin/lib/SyncTargetJoplinServerSAML' ;
2023-01-04 20:18:51 +00:00
import BiometricPopup from './components/biometrics/BiometricPopup' ;
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 ) ;
2025-06-02 17:34:08 +02:00
SyncTargetRegistry . addClass ( SyncTargetJoplinServerSAML ) ;
2021-06-03 17:12:07 +02:00
SyncTargetRegistry . addClass ( SyncTargetJoplinCloud ) ;
2018-01-17 21:01:41 +00:00
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-05-19 22:26:42 +01:00
import setupNotifications from './utils/setupNotifications' ;
2025-07-25 01:20:38 -07:00
import { loadMasterKeysFromSettings } from '@joplin/lib/services/e2ee/utils' ;
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' ;
2025-07-25 01:20:38 -07:00
import { setDispatch } from './services/profiles' ;
2023-09-11 00:53:53 -07:00
import { ReactNode } from 'react' ;
2023-07-18 06:46:11 -07:00
import autodetectTheme , { onSystemColorSchemeChange } from './utils/autodetectTheme' ;
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-04-15 10:17:34 -07:00
import ShareManager from './components/screens/ShareManager' ;
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 DialogManager from './components/DialogManager' ;
2024-07-26 04:35:50 -07:00
import { AppState } from './utils/types' ;
import { getDisplayParentId } from '@joplin/lib/services/trash' ;
2025-02-06 10:03:50 -08:00
import PluginNotification from './components/plugins/PluginNotification' ;
2025-03-08 03:53:06 -08:00
import FocusControl from './components/accessibility/FocusControl/FocusControl' ;
2025-06-02 17:34:08 +02:00
import SsoLoginScreen from './components/screens/SsoLoginScreen' ;
import SamlShared from '@joplin/lib/components/shared/SamlShared' ;
2025-05-27 09:22:52 -07:00
import NoteRevisionViewer from './components/screens/NoteRevisionViewer' ;
2025-07-18 06:33:58 -07:00
import DocumentScanner from './components/screens/DocumentScanner/DocumentScanner' ;
2025-07-25 01:20:38 -07:00
import buildStartupTasks from './utils/buildStartupTasks' ;
2025-07-25 01:19:23 -07:00
import { SafeAreaProvider } from 'react-native-safe-area-context' ;
2025-09-08 16:31:12 -07:00
import appReducer from './utils/appReducer' ;
2025-10-01 01:34:18 -07:00
import SyncWizard from './components/SyncWizard/SyncWizard' ;
2023-07-06 11:18:43 -07:00
2023-02-19 19:00:17 +00:00
const logger = Logger . create ( 'root' ) ;
2025-08-01 03:43:05 -07:00
const perfLogger = PerformanceLogger . create ( ) ;
2023-02-19 19:00:17 +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-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 ) {
2025-10-10 09:36:42 +01:00
if ( ! await reg . syncTarget ( ) . syncStarted ( ) ) void reg . scheduleSync ( reg . syncAsYouTypeInterval ( ) , { 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
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-07-01 12:21:17 -03:00
async function initialize ( dispatch : Dispatch ) {
2023-01-10 12:08:13 +00:00
setDispatch ( dispatch ) ;
2017-07-09 23:57:15 +01:00
2025-07-25 01:20:38 -07:00
const startupTasks = buildStartupTasks ( dispatch , store ) ;
2021-10-18 12:37:25 +01:00
2025-07-25 01:20:38 -07:00
for ( const [ name , task ] of startupTasks ) {
await perfLogger . track ( name , async ( ) = > {
try {
await task ( ) ;
} catch ( error ) {
logger . error ( ` Startup failure during task: ${ name } ` ) ;
throw error ;
2021-04-25 09:50:52 +01:00
}
2017-07-25 19:36:52 +01:00
} ) ;
2017-07-09 23:57:15 +01:00
}
2025-07-25 01:20:38 -07:00
logger . info ( 'Application initialized' ) ;
2017-07-09 23:57:15 +01:00
}
2025-04-24 00:48:58 -07:00
interface AppComponentProps {
dispatch : Dispatch ;
themeId : number ;
biometricsDone : boolean ;
routeName : string ;
selectedFolderId : string ;
appState : string ;
noteSideMenuOptions : SideMenuContentOptions ;
disableSideMenuGestures : boolean ;
historyCanGoBack : boolean ;
showSideMenu : boolean ;
noteSelectionEnabled : boolean ;
}
interface AppComponentState {
sideMenuWidth : number ;
sensorInfo : SensorInfo ;
sideMenuContentOpacity : Animated.Value ;
}
class AppComponent extends React . Component < AppComponentProps , AppComponentState > {
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-12-11 04:31:05 -08:00
private keyboardShowListener_ : EmitterSubscription | null = null ;
private keyboardHideListener_ : EmitterSubscription | 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
2025-04-24 00:48:58 -07:00
private lastSyncStarted_ = false ;
private quickActionShortcutListener_ : EmitterSubscription | undefined ;
private unsubscribeScreenWidthChangeHandler_ : EmitterSubscription | undefined ;
private unsubscribeNetInfoHandler_ : NetInfoSubscription | undefined ;
private unsubscribeNewShareListener_ : UnsubscribeShareListener | undefined ;
private onAppStateChange_ : ( ) = > void ;
private backButtonHandler_ : BackButtonHandler ;
private handleNewShare_ : ( ) = > void ;
private handleOpenURL_ : ( event : unknown ) = > void ;
public constructor ( props : AppComponentProps ) {
super ( props ) ;
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 {
2025-08-01 03:43:05 -07:00
await perfLogger . track ( 'root/initialize' , ( ) = > initialize ( this . props . dispatch ) ) ;
2024-08-02 06:51:49 -07:00
} 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 ( ) ) ;
2025-08-01 03:43:05 -07:00
this . quickActionShortcutListener_ = await perfLogger . track ( 'root/setupQuickActions' ,
2025-07-25 01:20:38 -07:00
( ) = > setupQuickActions ( this . props . dispatch ) ,
) ;
2021-05-19 22:26:42 +01:00
2025-08-01 03:43:05 -07:00
await perfLogger . track ( 'root/setupNotifications' ,
2025-07-25 01:20:38 -07:00
( ) = > setupNotifications ( this . props . dispatch ) ,
) ;
2021-08-30 14:15:35 +01:00
2024-12-11 04:31:05 -08:00
this . keyboardShowListener_ = Keyboard . addListener ( 'keyboardDidShow' , ( ) = > {
this . props . dispatch ( {
type : 'KEYBOARD_VISIBLE_CHANGE' ,
visible : true ,
} ) ;
} ) ;
this . keyboardHideListener_ = Keyboard . addListener ( 'keyboardDidHide' , ( ) = > {
this . props . dispatch ( {
type : 'KEYBOARD_VISIBLE_CHANGE' ,
visible : false ,
} ) ;
} ) ;
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 ;
}
2024-12-11 04:31:05 -08:00
if ( this . keyboardShowListener_ ) {
this . keyboardShowListener_ . remove ( ) ;
this . keyboardShowListener_ = undefined ;
}
if ( this . keyboardHideListener_ ) {
this . keyboardHideListener_ . remove ( ) ;
this . keyboardHideListener_ = 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 ;
}
}
2025-06-10 01:03:32 -07: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.
2025-06-10 01:03:32 -07:00
// Avoid dispatching unnecessarily. See https://github.com/laurent22/joplin/issues/12427
if ( isOpen !== this . props . showSideMenu ) {
this . props . dispatch ( {
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 } ,
2025-06-02 17:34:08 +02:00
JoplinServerSamlLogin : { screen : SsoLoginScreen ( new SamlShared ( ) ) } ,
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 } ,
2025-05-27 09:22:52 -07:00
NoteRevisionViewer : { screen : NoteRevisionViewer } ,
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 } ,
2025-07-18 06:33:58 -07:00
DocumentScanner : { screen : DocumentScanner } ,
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 }
2025-06-10 01:03:32 -07:00
onChange = { this . sideMenu_change }
isOpen = { this . props . showSideMenu }
2023-11-15 05:31:26 -08:00
disableGestures = { disableSideMenuGestures }
2020-06-10 22:08:59 +01:00
>
2025-03-08 03:53:06 -08:00
< View style = { { flexGrow : 1 , flexShrink : 1 , flexBasis : '100%' } } >
2025-07-25 01:19:23 -07:00
< SafeAreaView style = { { flex : 1 } } titleBarUnderlayColor = { theme . backgroundColor2 } >
2020-06-10 22:08:59 +01:00
< 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 ) } / >
2025-10-01 01:34:18 -07:00
< SyncWizard / >
2020-06-10 22:08:59 +01:00
< / SafeAreaView >
2025-03-08 03:53:06 -08:00
< / View >
2023-09-11 00:53:53 -07:00
< / SideMenu >
2024-03-14 12:04:32 -07:00
< PluginRunnerWebView / >
2025-02-06 10:03:50 -08:00
< PluginNotification / >
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 (
2025-03-08 03:53:06 -08:00
< FocusControl.Provider >
2025-09-08 02:59:40 -07:00
< MenuProvider
style = { { flex : 1 } }
closeButtonLabel = { _ ( 'Dismiss' ) }
>
< PaperProvider theme = { {
. . . paperTheme ,
version : 3 ,
colors : {
. . . paperTheme . colors ,
onPrimaryContainer : theme.color5 ,
primaryContainer : theme.backgroundColor5 ,
outline : theme.codeBorderColor ,
primary : theme.color4 ,
onPrimary : theme.backgroundColor4 ,
background : theme.backgroundColor ,
surface : theme.backgroundColor ,
onSurface : theme.color ,
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 ,
} ,
2025-03-08 03:53:06 -08:00
} ,
2025-09-08 02:59:40 -07:00
} } >
< DialogManager themeId = { this . props . themeId } >
< StatusBar barStyle = { statusBarStyle } / >
2025-07-25 01:19:23 -07:00
< SafeAreaProvider >
< FocusControl.MainAppContent style = { { flex : 1 } } >
{ shouldShowMainContent ? mainContent : (
< SafeAreaView >
< BiometricPopup
dispatch = { this . props . dispatch }
themeId = { this . props . themeId }
sensorInfo = { this . state . sensorInfo }
/ >
< / SafeAreaView >
) }
< / FocusControl.MainAppContent >
< / SafeAreaProvider >
2025-09-08 02:59:40 -07:00
< / DialogManager >
< / PaperProvider >
< / MenuProvider >
2025-03-08 03:53:06 -08:00
< / FocusControl.Provider >
2023-01-08 04:22:41 -08:00
) ;
2017-05-11 20:14:01 +00:00
}
2017-05-10 18:58:02 +00:00
}
2025-04-24 00:48:58 -07:00
const mapStateToProps = ( state : AppState ) = > {
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
}