Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions packages/mobile/index.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { IsMobileWeb } from '@Lib/Utils'
import { MobileWebApp } from '@Root/MobileWebApp'
import { SNLog } from '@standardnotes/snjs'
import { AppRegistry } from 'react-native'
import 'react-native-gesture-handler'
import { enableScreens } from 'react-native-screens'
import 'react-native-url-polyfill/auto'
import { name as appName } from './app.json'
import { App } from './src/App'
import { NativeApp } from './src/NativeApp'
import { enableAndroidFontFix } from './src/Style/android_text_fix'

enableScreens()
Expand All @@ -28,11 +30,11 @@ console.warn = function filterWarnings(msg) {
"[react-native-gesture-handler] Seems like you're using an old API with gesture components",
]

if (!supressedWarnings.some(entry => msg.includes(entry))) {
if (!supressedWarnings.some((entry) => msg.includes(entry))) {
originalWarn.apply(console, arguments)
}
}

enableAndroidFontFix()

AppRegistry.registerComponent(appName, () => App)
AppRegistry.registerComponent(appName, () => (IsMobileWeb ? MobileWebApp : NativeApp))
6 changes: 3 additions & 3 deletions packages/mobile/src/AppStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AppStateEventType, AppStateType, TabletModeChangeData } from '@Lib/Appl
import { AlwaysOpenWebAppOnLaunchKey } from '@Lib/constants'
import { useHasEditor, useIsLocked } from '@Lib/SnjsHelperHooks'
import { ScreenStatus } from '@Lib/StatusManager'
import { IsDev } from '@Lib/Utils'
import { IsMobileWeb } from '@Lib/Utils'
import { CompositeNavigationProp, RouteProp } from '@react-navigation/native'
import { createStackNavigator, StackNavigationProp } from '@react-navigation/stack'
import { HeaderTitleView } from '@Root/Components/HeaderTitleView'
Expand All @@ -22,10 +22,10 @@ import { Dimensions, Keyboard, ScaledSize, StatusBar } from 'react-native'
import DrawerLayout, { DrawerState } from 'react-native-gesture-handler/DrawerLayout'
import { HeaderButtons, Item } from 'react-navigation-header-buttons'
import { ThemeContext } from 'styled-components'
import { HeaderTitleParams } from './App'
import { ApplicationContext } from './ApplicationContext'
import { MobileWebAppContainer } from './MobileWebAppContainer'
import { ModalStackNavigationProp } from './ModalStack'
import { HeaderTitleParams } from './NativeApp'

export type AppStackNavigatorParamList = {
[SCREEN_NOTES]: HeaderTitleParams
Expand Down Expand Up @@ -121,7 +121,7 @@ export const AppStackComponent = (props: ModalStackNavigationProp<'AppStack'>) =
[application],
)

if (IsDev) {
if (IsMobileWeb) {
return (
<AppStack.Navigator
screenOptions={() => ({
Expand Down
62 changes: 62 additions & 0 deletions packages/mobile/src/AppStateObserverService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { AbstractService, InternalEventBus, ReactNativeToWebEvent } from '@standardnotes/snjs'
import { AppState, AppStateStatus, NativeEventSubscription } from 'react-native'

export class AppStateObserverService extends AbstractService<ReactNativeToWebEvent> {
private mostRecentState?: ReactNativeToWebEvent
private removeListener: NativeEventSubscription
private ignoringStateChanges = false

constructor() {
const bus = new InternalEventBus()
super(bus)

this.removeListener = AppState.addEventListener('change', async (nextAppState: AppStateStatus) => {
if (this.ignoringStateChanges) {
return
}

// if the most recent state is not 'background' ('inactive'), then we're going
// from inactive to active, which doesn't really happen unless you, say, swipe
// notification center in iOS down then back up. We don't want to lock on this state change.
const isResuming = nextAppState === 'active'
const isResumingFromBackground = isResuming && this.mostRecentState === ReactNativeToWebEvent.EnteringBackground
const isEnteringBackground = nextAppState === 'background'
const isLosingFocus = nextAppState === 'inactive'

if (isEnteringBackground) {
this.notifyStateChange(ReactNativeToWebEvent.EnteringBackground)
}

if (isResumingFromBackground || isResuming) {
if (isResumingFromBackground) {
this.notifyStateChange(ReactNativeToWebEvent.ResumingFromBackground)
}

// Notify of GainingFocus even if resuming from background
this.notifyStateChange(ReactNativeToWebEvent.GainingFocus)
return
}

if (isLosingFocus) {
this.notifyStateChange(ReactNativeToWebEvent.LosingFocus)
}
})
}

beginIgnoringStateChanges() {
this.ignoringStateChanges = true
}

stopIgnoringStateChanges() {
this.ignoringStateChanges = false
}

deinit() {
this.removeListener.remove()
}

private notifyStateChange(state: ReactNativeToWebEvent): void {
this.mostRecentState = state
void this.notifyEvent(state)
}
}
2 changes: 1 addition & 1 deletion packages/mobile/src/HistoryStack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import React, { useContext } from 'react'
import { Platform } from 'react-native'
import { HeaderButtons, Item } from 'react-navigation-header-buttons'
import { ThemeContext } from 'styled-components'
import { HeaderTitleParams } from './App'
import { HeaderTitleParams } from './NativeApp'

type HistoryStackNavigatorParamList = {
[SCREEN_NOTE_HISTORY]: (HeaderTitleParams & { noteUuid: string }) | (undefined & { noteUuid: string })
Expand Down
8 changes: 4 additions & 4 deletions packages/mobile/src/Lib/Application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ import { BackupsService } from './BackupsService'
import { ComponentManager } from './ComponentManager'
import { FilesService } from './FilesService'
import { InstallationService } from './InstallationService'
import { MobileDeviceInterface } from './Interface'
import { MobileDevice } from './Interface'
import { push } from './NavigationService'
import { PreferencesManager } from './PreferencesManager'
import { SNReactNativeCrypto } from './ReactNativeCrypto'
import { ReviewService } from './ReviewService'
import { StatusManager } from './StatusManager'
import { IsDev } from './Utils'
import { IsDev, IsMobileWeb } from './Utils'

type MobileServices = {
applicationState: ApplicationState
Expand All @@ -52,7 +52,7 @@ export class MobileApplication extends SNApplication {

static previouslyLaunched = false

constructor(deviceInterface: MobileDeviceInterface, identifier: string) {
constructor(deviceInterface: MobileDevice, identifier: string) {
super({
environment: Environment.Mobile,
platform: platformFromString(Platform.OS),
Expand Down Expand Up @@ -135,7 +135,7 @@ export class MobileApplication extends SNApplication {
}

promptForChallenge(challenge: Challenge) {
if (IsDev) {
if (IsMobileWeb) {
return
}

Expand Down
6 changes: 3 additions & 3 deletions packages/mobile/src/Lib/ApplicationGroup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { ApplicationState } from './ApplicationState'
import { BackupsService } from './BackupsService'
import { FilesService } from './FilesService'
import { InstallationService } from './InstallationService'
import { MobileDeviceInterface } from './Interface'
import { MobileDevice } from './Interface'
import { PreferencesManager } from './PreferencesManager'
import { ReviewService } from './ReviewService'
import { StatusManager } from './StatusManager'

export class ApplicationGroup extends SNApplicationGroup {
constructor() {
super(new MobileDeviceInterface())
super(new MobileDevice())
}

override async initialize(_callback?: any): Promise<void> {
Expand All @@ -21,7 +21,7 @@ export class ApplicationGroup extends SNApplicationGroup {
}

private createApplication = async (descriptor: ApplicationDescriptor, deviceInterface: DeviceInterface) => {
const application = new MobileApplication(deviceInterface as MobileDeviceInterface, descriptor.identifier)
const application = new MobileApplication(deviceInterface as MobileDevice, descriptor.identifier)
const internalEventBus = new InternalEventBus()
const applicationState = new ApplicationState(application)
const reviewService = new ReviewService(application, internalEventBus)
Expand Down
73 changes: 37 additions & 36 deletions packages/mobile/src/Lib/ApplicationState.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MobileDeviceInterface } from '@Lib/Interface'
import { MobileDevice } from '@Lib/Interface'
import {
ApplicationEvent,
ApplicationService,
Expand Down Expand Up @@ -169,9 +169,7 @@ export class ApplicationState extends ApplicationService {
override async onAppLaunch() {
MobileApplication.setPreviouslyLaunched()
this.screenshotPrivacyEnabled = (await this.getScreenshotPrivacyEnabled()) ?? true
await (this.application.deviceInterface as MobileDeviceInterface).setAndroidScreenshotPrivacy(
this.screenshotPrivacyEnabled,
)
await (this.application.deviceInterface as MobileDevice).setAndroidScreenshotPrivacy(this.screenshotPrivacyEnabled)
}

/**
Expand Down Expand Up @@ -480,28 +478,35 @@ export class ApplicationState extends ApplicationService {

private async checkAndLockApplication() {
const isLocked = await this.application.isLocked()
if (!isLocked) {
const hasBiometrics = await this.application.hasBiometrics()
const hasPasscode = this.application.hasPasscode()
if (hasPasscode && this.passcodeTiming === MobileUnlockTiming.Immediately) {
await this.application.lock()
} else if (hasBiometrics && this.biometricsTiming === MobileUnlockTiming.Immediately && !this.locked) {
const challenge = new Challenge(
[new ChallengePrompt(ChallengeValidation.Biometric)],
ChallengeReason.ApplicationUnlock,
false,
)
void this.application.promptForCustomChallenge(challenge)

this.locked = true
this.notifyLockStateObservers(LockStateType.Locked)
this.application.addChallengeObserver(challenge, {
onComplete: () => {
this.locked = false
this.notifyLockStateObservers(LockStateType.Unlocked)
},
})
}

if (isLocked) {
return
}

const hasBiometrics = this.application.hasBiometrics()
const hasPasscode = this.application.hasPasscode()
const passcodeLockImmediately = hasPasscode && this.passcodeTiming === MobileUnlockTiming.Immediately
const biometricsLockImmediately =
hasBiometrics && this.biometricsTiming === MobileUnlockTiming.Immediately && !this.locked

if (passcodeLockImmediately) {
await this.application.lock()
} else if (biometricsLockImmediately) {
const challenge = new Challenge(
[new ChallengePrompt(ChallengeValidation.Biometric)],
ChallengeReason.ApplicationUnlock,
false,
)
void this.application.promptForCustomChallenge(challenge)

this.locked = true
this.notifyLockStateObservers(LockStateType.Locked)
this.application.addChallengeObserver(challenge, {
onComplete: () => {
this.locked = false
this.notifyLockStateObservers(LockStateType.Unlocked)
},
})
}
}

Expand Down Expand Up @@ -570,30 +575,26 @@ export class ApplicationState extends ApplicationService {
}

private async getPasscodeTiming(): Promise<MobileUnlockTiming | undefined> {
return this.application.getValue(StorageKey.MobilePasscodeTiming, StorageValueModes.Nonwrapped) as Promise<
MobileUnlockTiming | undefined
>
return this.application.getMobilePasscodeTiming()
}

private async getBiometricsTiming(): Promise<MobileUnlockTiming | undefined> {
return this.application.getValue(StorageKey.MobileBiometricsTiming, StorageValueModes.Nonwrapped) as Promise<
MobileUnlockTiming | undefined
>
return this.application.getMobileBiometricsTiming()
}

public async setScreenshotPrivacyEnabled(enabled: boolean) {
await this.application.setMobileScreenshotPrivacyEnabled(enabled)
this.screenshotPrivacyEnabled = enabled
await (this.application.deviceInterface as MobileDeviceInterface).setAndroidScreenshotPrivacy(enabled)
await (this.application.deviceInterface as MobileDevice).setAndroidScreenshotPrivacy(enabled)
}

public async setPasscodeTiming(timing: MobileUnlockTiming) {
await this.application.setValue(StorageKey.MobilePasscodeTiming, timing, StorageValueModes.Nonwrapped)
this.application.setValue(StorageKey.MobilePasscodeTiming, timing, StorageValueModes.Nonwrapped)
this.passcodeTiming = timing
}

public async setBiometricsTiming(timing: MobileUnlockTiming) {
await this.application.setValue(StorageKey.MobileBiometricsTiming, timing, StorageValueModes.Nonwrapped)
this.application.setValue(StorageKey.MobileBiometricsTiming, timing, StorageValueModes.Nonwrapped)
this.biometricsTiming = timing
}

Expand All @@ -605,7 +606,7 @@ export class ApplicationState extends ApplicationService {
}

public async setPasscodeKeyboardType(type: PasscodeKeyboardType) {
await this.application.setValue(MobileStorageKey.PasscodeKeyboardTypeKey, type, StorageValueModes.Nonwrapped)
this.application.setValue(MobileStorageKey.PasscodeKeyboardTypeKey, type, StorageValueModes.Nonwrapped)
}

public onDrawerOpen() {
Expand Down
4 changes: 2 additions & 2 deletions packages/mobile/src/Lib/InstallationService.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import SNReactNative from '@standardnotes/react-native-utils'
import { ApplicationService, ButtonType, StorageValueModes } from '@standardnotes/snjs'
import { MobileDeviceInterface } from './Interface'
import { MobileDevice } from './Interface'

const FIRST_RUN_KEY = 'first_run'

Expand All @@ -23,7 +23,7 @@ export class InstallationService extends ApplicationService {
*/
async needsWipe() {
const hasAccountOrPasscode = this.application.hasAccount() || this.application?.hasPasscode()
const deviceInterface = this.application.deviceInterface as MobileDeviceInterface
const deviceInterface = this.application.deviceInterface as MobileDevice
const keychainKey = await deviceInterface.getNamespacedKeychainValue(this.application.identifier)

const hasKeychainValue = keychainKey != undefined
Expand Down
Loading