feat: add settings to fully switch between native/webview (#1587)
This commit is contained in:
@@ -9,7 +9,6 @@ import { MobileThemeVariables } from '@Root/Style/Themes/styled-components'
|
|||||||
import { ApplicationGroupEvent, DeinitMode, DeinitSource } from '@standardnotes/snjs'
|
import { ApplicationGroupEvent, DeinitMode, DeinitSource } from '@standardnotes/snjs'
|
||||||
import { ThemeService, ThemeServiceContext } from '@Style/ThemeService'
|
import { ThemeService, ThemeServiceContext } from '@Style/ThemeService'
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { StatusBar } from 'react-native'
|
|
||||||
import { ThemeProvider } from 'styled-components/native'
|
import { ThemeProvider } from 'styled-components/native'
|
||||||
import { ApplicationContext } from './ApplicationContext'
|
import { ApplicationContext } from './ApplicationContext'
|
||||||
import { MainStackComponent } from './ModalStack'
|
import { MainStackComponent } from './ModalStack'
|
||||||
@@ -107,7 +106,6 @@ const AppComponent: React.FC<{
|
|||||||
}}
|
}}
|
||||||
ref={navigationRef}
|
ref={navigationRef}
|
||||||
>
|
>
|
||||||
<StatusBar translucent />
|
|
||||||
{themeService.current && (
|
{themeService.current && (
|
||||||
<>
|
<>
|
||||||
<ThemeProvider theme={activeTheme}>
|
<ThemeProvider theme={activeTheme}>
|
||||||
|
|||||||
@@ -3,22 +3,22 @@ import { AlwaysOpenWebAppOnLaunchKey } from '@Lib/constants'
|
|||||||
import { useHasEditor, useIsLocked } from '@Lib/SnjsHelperHooks'
|
import { useHasEditor, useIsLocked } from '@Lib/SnjsHelperHooks'
|
||||||
import { ScreenStatus } from '@Lib/StatusManager'
|
import { ScreenStatus } from '@Lib/StatusManager'
|
||||||
import { IsDev } from '@Lib/Utils'
|
import { IsDev } from '@Lib/Utils'
|
||||||
import { CompositeNavigationProp, RouteProp, useNavigation } from '@react-navigation/native'
|
import { CompositeNavigationProp, RouteProp } from '@react-navigation/native'
|
||||||
import { createStackNavigator, StackNavigationProp } from '@react-navigation/stack'
|
import { createStackNavigator, StackNavigationProp } from '@react-navigation/stack'
|
||||||
import { HeaderTitleView } from '@Root/Components/HeaderTitleView'
|
import { HeaderTitleView } from '@Root/Components/HeaderTitleView'
|
||||||
import { IoniconsHeaderButton } from '@Root/Components/IoniconsHeaderButton'
|
import { IoniconsHeaderButton } from '@Root/Components/IoniconsHeaderButton'
|
||||||
import { Compose } from '@Root/Screens/Compose/Compose'
|
import { Compose } from '@Root/Screens/Compose/Compose'
|
||||||
import { SCREEN_COMPOSE, SCREEN_NOTES, SCREEN_VIEW_PROTECTED_NOTE, SCREEN_WEB_APP } from '@Root/Screens/screens'
|
import { SCREEN_COMPOSE, SCREEN_NOTES, SCREEN_VIEW_PROTECTED_NOTE } from '@Root/Screens/screens'
|
||||||
import { MainSideMenu } from '@Root/Screens/SideMenu/MainSideMenu'
|
import { MainSideMenu } from '@Root/Screens/SideMenu/MainSideMenu'
|
||||||
import { NoteSideMenu } from '@Root/Screens/SideMenu/NoteSideMenu'
|
import { NoteSideMenu } from '@Root/Screens/SideMenu/NoteSideMenu'
|
||||||
import { ViewProtectedNote } from '@Root/Screens/ViewProtectedNote/ViewProtectedNote'
|
import { ViewProtectedNote } from '@Root/Screens/ViewProtectedNote/ViewProtectedNote'
|
||||||
import { Root } from '@Screens/Root'
|
import { Root } from '@Screens/Root'
|
||||||
import { ApplicationEvent, StorageValueModes, UuidString } from '@standardnotes/snjs'
|
import { StorageValueModes, UuidString } from '@standardnotes/snjs'
|
||||||
import { ICON_MENU } from '@Style/Icons'
|
import { ICON_MENU } from '@Style/Icons'
|
||||||
import { ThemeService } from '@Style/ThemeService'
|
import { ThemeService } from '@Style/ThemeService'
|
||||||
import { getDefaultDrawerWidth } from '@Style/Utils'
|
import { getDefaultDrawerWidth } from '@Style/Utils'
|
||||||
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'
|
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'
|
||||||
import { Dimensions, Keyboard, ScaledSize } from 'react-native'
|
import { Dimensions, Keyboard, ScaledSize, StatusBar } from 'react-native'
|
||||||
import DrawerLayout, { DrawerState } from 'react-native-gesture-handler/DrawerLayout'
|
import DrawerLayout, { DrawerState } from 'react-native-gesture-handler/DrawerLayout'
|
||||||
import { HeaderButtons, Item } from 'react-navigation-header-buttons'
|
import { HeaderButtons, Item } from 'react-navigation-header-buttons'
|
||||||
import { ThemeContext } from 'styled-components'
|
import { ThemeContext } from 'styled-components'
|
||||||
@@ -27,9 +27,6 @@ import { ApplicationContext } from './ApplicationContext'
|
|||||||
import { MobileWebAppContainer } from './MobileWebAppContainer'
|
import { MobileWebAppContainer } from './MobileWebAppContainer'
|
||||||
import { ModalStackNavigationProp } from './ModalStack'
|
import { ModalStackNavigationProp } from './ModalStack'
|
||||||
|
|
||||||
const IS_DEBUGGING_WEB_APP = false
|
|
||||||
const DEFAULT_TO_WEB_APP = IsDev && IS_DEBUGGING_WEB_APP
|
|
||||||
|
|
||||||
export type AppStackNavigatorParamList = {
|
export type AppStackNavigatorParamList = {
|
||||||
[SCREEN_NOTES]: HeaderTitleParams
|
[SCREEN_NOTES]: HeaderTitleParams
|
||||||
[SCREEN_COMPOSE]: HeaderTitleParams & {
|
[SCREEN_COMPOSE]: HeaderTitleParams & {
|
||||||
@@ -124,28 +121,6 @@ export const AppStackComponent = (props: ModalStackNavigationProp<'AppStack'>) =
|
|||||||
[application],
|
[application],
|
||||||
)
|
)
|
||||||
|
|
||||||
const navigation = useNavigation<ModalStackNavigationProp<'AppStack'>['navigation']>()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!application) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeObserver = application.addEventObserver(async (event) => {
|
|
||||||
if (event === ApplicationEvent.Launched) {
|
|
||||||
const value = (await application.getValue(AlwaysOpenWebAppOnLaunchKey, StorageValueModes.Nonwrapped)) as
|
|
||||||
| boolean
|
|
||||||
| undefined
|
|
||||||
const shouldAlwaysOpenWebAppOnLaunch = value ?? false
|
|
||||||
if (shouldAlwaysOpenWebAppOnLaunch) {
|
|
||||||
navigation.push(SCREEN_WEB_APP)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return removeObserver
|
|
||||||
}, [application, navigation])
|
|
||||||
|
|
||||||
if (IsDev) {
|
if (IsDev) {
|
||||||
return (
|
return (
|
||||||
<AppStack.Navigator
|
<AppStack.Navigator
|
||||||
@@ -154,11 +129,17 @@ export const AppStackComponent = (props: ModalStackNavigationProp<'AppStack'>) =
|
|||||||
})}
|
})}
|
||||||
initialRouteName={SCREEN_NOTES}
|
initialRouteName={SCREEN_NOTES}
|
||||||
>
|
>
|
||||||
<AppStack.Screen name={SCREEN_NOTES} component={IsDev ? MobileWebAppContainer : Root} />
|
<AppStack.Screen name={SCREEN_NOTES} component={MobileWebAppContainer} />
|
||||||
</AppStack.Navigator>
|
</AppStack.Navigator>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!application) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldOpenWebApp = application.getValue(AlwaysOpenWebAppOnLaunchKey, StorageValueModes.Nonwrapped) as boolean
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DrawerLayout
|
<DrawerLayout
|
||||||
ref={drawerRef}
|
ref={drawerRef}
|
||||||
@@ -169,6 +150,7 @@ export const AppStackComponent = (props: ModalStackNavigationProp<'AppStack'>) =
|
|||||||
onDrawerStateChanged={handleDrawerStateChange}
|
onDrawerStateChanged={handleDrawerStateChange}
|
||||||
renderNavigationView={() => !isLocked && <MainSideMenu drawerRef={drawerRef.current} />}
|
renderNavigationView={() => !isLocked && <MainSideMenu drawerRef={drawerRef.current} />}
|
||||||
>
|
>
|
||||||
|
<StatusBar translucent={!shouldOpenWebApp} />
|
||||||
<DrawerLayout
|
<DrawerLayout
|
||||||
ref={noteDrawerRef}
|
ref={noteDrawerRef}
|
||||||
drawerWidth={getDefaultDrawerWidth(dimensions)}
|
drawerWidth={getDefaultDrawerWidth(dimensions)}
|
||||||
@@ -198,6 +180,7 @@ export const AppStackComponent = (props: ModalStackNavigationProp<'AppStack'>) =
|
|||||||
name={SCREEN_NOTES}
|
name={SCREEN_NOTES}
|
||||||
options={({ route }) => ({
|
options={({ route }) => ({
|
||||||
title: 'All notes',
|
title: 'All notes',
|
||||||
|
headerShown: !shouldOpenWebApp,
|
||||||
headerTitle: ({ children }) => {
|
headerTitle: ({ children }) => {
|
||||||
const screenStatus = isInTabletMode ? composeStatus || notesStatus : notesStatus
|
const screenStatus = isInTabletMode ? composeStatus || notesStatus : notesStatus
|
||||||
|
|
||||||
@@ -237,7 +220,7 @@ export const AppStackComponent = (props: ModalStackNavigationProp<'AppStack'>) =
|
|||||||
</HeaderButtons>
|
</HeaderButtons>
|
||||||
),
|
),
|
||||||
})}
|
})}
|
||||||
component={DEFAULT_TO_WEB_APP ? MobileWebAppContainer : Root}
|
component={shouldOpenWebApp ? MobileWebAppContainer : Root}
|
||||||
/>
|
/>
|
||||||
<AppStack.Screen
|
<AppStack.Screen
|
||||||
name={SCREEN_COMPOSE}
|
name={SCREEN_COMPOSE}
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import { SectionHeader } from '@Root/Components/SectionHeader'
|
|||||||
import { TableSection } from '@Root/Components/TableSection'
|
import { TableSection } from '@Root/Components/TableSection'
|
||||||
import { useSafeApplicationContext } from '@Root/Hooks/useSafeApplicationContext'
|
import { useSafeApplicationContext } from '@Root/Hooks/useSafeApplicationContext'
|
||||||
import { ModalStackNavigationProp } from '@Root/ModalStack'
|
import { ModalStackNavigationProp } from '@Root/ModalStack'
|
||||||
import { SCREEN_MANAGE_SESSIONS, SCREEN_SETTINGS, SCREEN_WEB_APP } from '@Root/Screens/screens'
|
import { SCREEN_MANAGE_SESSIONS, SCREEN_SETTINGS } from '@Root/Screens/screens'
|
||||||
import { ButtonType, PrefKey, StorageValueModes } from '@standardnotes/snjs'
|
import { ButtonType, PrefKey, StorageValueModes } from '@standardnotes/snjs'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
import React, { useCallback, useMemo, useState } from 'react'
|
||||||
import { Platform } from 'react-native'
|
import { Platform } from 'react-native'
|
||||||
import DocumentPicker from 'react-native-document-picker'
|
import DocumentPicker from 'react-native-document-picker'
|
||||||
import RNFS from 'react-native-fs'
|
import RNFS from 'react-native-fs'
|
||||||
@@ -183,22 +183,6 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
|
|||||||
)
|
)
|
||||||
}, [application.alertService])
|
}, [application.alertService])
|
||||||
|
|
||||||
const [shouldAlwaysOpenWebAppOnLaunch, setShouldAlwaysOpenWebAppOnLaunch] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const getSetting = async () => {
|
|
||||||
const value = (await application.getValue(AlwaysOpenWebAppOnLaunchKey, StorageValueModes.Nonwrapped)) as
|
|
||||||
| boolean
|
|
||||||
| undefined
|
|
||||||
setShouldAlwaysOpenWebAppOnLaunch(value ?? false)
|
|
||||||
}
|
|
||||||
void getSetting()
|
|
||||||
}, [application])
|
|
||||||
|
|
||||||
const openWebApp = useCallback(() => {
|
|
||||||
navigation.push(SCREEN_WEB_APP)
|
|
||||||
}, [navigation])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableSection>
|
<TableSection>
|
||||||
<SectionHeader title={title} />
|
<SectionHeader title={title} />
|
||||||
@@ -238,15 +222,19 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
|
|||||||
onPress={onExportPress}
|
onPress={onExportPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ButtonCell testID="openWebApp" leftAligned title="Open Web App" onPress={() => openWebApp()} />
|
<ButtonCell
|
||||||
<SectionedAccessoryTableCell
|
onPress={async () => {
|
||||||
onPress={() => {
|
const confirmationText =
|
||||||
const newValue = !shouldAlwaysOpenWebAppOnLaunch
|
'This will close the app and fully switch to the web view next time you open it. You will be able to switch back from the settings.'
|
||||||
setShouldAlwaysOpenWebAppOnLaunch(newValue)
|
|
||||||
void application.setValue(AlwaysOpenWebAppOnLaunchKey, newValue, StorageValueModes.Nonwrapped)
|
if (
|
||||||
|
await application.alertService.confirm(confirmationText, 'Switch To Web View?', 'Switch', ButtonType.Info)
|
||||||
|
) {
|
||||||
|
application.setValue(AlwaysOpenWebAppOnLaunchKey, true, StorageValueModes.Nonwrapped)
|
||||||
|
setTimeout(() => application.deviceInterface.performSoftReset(), 1000)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
text="Always Open Web App On Launch"
|
title="Switch to Web View"
|
||||||
selected={() => shouldAlwaysOpenWebAppOnLaunch}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!signedIn && (
|
{!signedIn && (
|
||||||
|
|||||||
@@ -200,7 +200,6 @@ const ComponentView: FunctionComponent<IProps> = ({ application, onLoad, compone
|
|||||||
{error === ComponentViewerError.MissingUrl && <UrlMissing componentName={component.displayName} />}
|
{error === ComponentViewerError.MissingUrl && <UrlMissing componentName={component.displayName} />}
|
||||||
{component.uuid && isComponentValid && (
|
{component.uuid && isComponentValid && (
|
||||||
<iframe
|
<iframe
|
||||||
className="min-h-[40rem]"
|
|
||||||
ref={iframeRef}
|
ref={iframeRef}
|
||||||
onLoad={onIframeLoad}
|
onLoad={onIframeLoad}
|
||||||
data-component-viewer-id={componentViewer.identifier}
|
data-component-viewer-id={componentViewer.identifier}
|
||||||
|
|||||||
@@ -1,6 +1,13 @@
|
|||||||
import Dropdown from '@/Components/Dropdown/Dropdown'
|
import Dropdown from '@/Components/Dropdown/Dropdown'
|
||||||
import { DropdownItem } from '@/Components/Dropdown/DropdownItem'
|
import { DropdownItem } from '@/Components/Dropdown/DropdownItem'
|
||||||
import { FeatureIdentifier, PrefKey, ComponentArea, ComponentMutator, SNComponent } from '@standardnotes/snjs'
|
import {
|
||||||
|
FeatureIdentifier,
|
||||||
|
PrefKey,
|
||||||
|
ComponentArea,
|
||||||
|
ComponentMutator,
|
||||||
|
SNComponent,
|
||||||
|
StorageValueModes,
|
||||||
|
} from '@standardnotes/snjs'
|
||||||
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
import { Subtitle, Text, Title } from '@/Components/Preferences/PreferencesComponents/Content'
|
||||||
import { WebApplication } from '@/Application/Application'
|
import { WebApplication } from '@/Application/Application'
|
||||||
import { FunctionComponent, useEffect, useState } from 'react'
|
import { FunctionComponent, useEffect, useState } from 'react'
|
||||||
@@ -9,6 +16,7 @@ import Switch from '@/Components/Switch/Switch'
|
|||||||
import { PLAIN_EDITOR_NAME } from '@/Constants/Constants'
|
import { PLAIN_EDITOR_NAME } from '@/Constants/Constants'
|
||||||
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
import PreferencesGroup from '../../PreferencesComponents/PreferencesGroup'
|
||||||
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
import PreferencesSegment from '../../PreferencesComponents/PreferencesSegment'
|
||||||
|
import Button from '@/Components/Button/Button'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
application: WebApplication
|
application: WebApplication
|
||||||
@@ -43,6 +51,8 @@ const getDefaultEditor = (application: WebApplication) => {
|
|||||||
return application.componentManager.componentsForArea(ComponentArea.Editor).filter((e) => e.isDefaultEditor())[0]
|
return application.componentManager.componentsForArea(ComponentArea.Editor).filter((e) => e.isDefaultEditor())[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AlwaysOpenWebAppOnLaunchKey = 'AlwaysOpenWebAppOnLaunch'
|
||||||
|
|
||||||
const Defaults: FunctionComponent<Props> = ({ application }) => {
|
const Defaults: FunctionComponent<Props> = ({ application }) => {
|
||||||
const [editorItems, setEditorItems] = useState<DropdownItem[]>([])
|
const [editorItems, setEditorItems] = useState<DropdownItem[]>([])
|
||||||
const [defaultEditorValue, setDefaultEditorValue] = useState(
|
const [defaultEditorValue, setDefaultEditorValue] = useState(
|
||||||
@@ -102,10 +112,30 @@ const Defaults: FunctionComponent<Props> = ({ application }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const switchToNativeView = async () => {
|
||||||
|
application.setValue(AlwaysOpenWebAppOnLaunchKey, false, StorageValueModes.Nonwrapped)
|
||||||
|
setTimeout(() => {
|
||||||
|
application.deviceInterface.performSoftReset()
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PreferencesGroup>
|
<PreferencesGroup>
|
||||||
<PreferencesSegment>
|
<PreferencesSegment>
|
||||||
<Title>Defaults</Title>
|
<Title>Defaults</Title>
|
||||||
|
{application.isNativeMobileWeb() && (
|
||||||
|
<>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<Subtitle>Switch to Native View</Subtitle>
|
||||||
|
<Text>
|
||||||
|
This will close the app and fully switch to the native view next time you open it. You will be able to
|
||||||
|
switch back from the settings.
|
||||||
|
</Text>
|
||||||
|
<Button className="mt-3 min-w-20" label="Switch" onClick={switchToNativeView} />
|
||||||
|
</div>
|
||||||
|
<HorizontalSeparator classes="my-4" />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<Subtitle>Default Note Type</Subtitle>
|
<Subtitle>Default Note Type</Subtitle>
|
||||||
<Text>New notes will be created using this type.</Text>
|
<Text>New notes will be created using this type.</Text>
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ interface SecurityProps extends MfaProps {
|
|||||||
application: WebApplication
|
application: WebApplication
|
||||||
}
|
}
|
||||||
|
|
||||||
const SHOW_MULTITASKING_PRIVACY = false
|
|
||||||
const SHOW_BIOMETRICS_LOCK = false
|
|
||||||
|
|
||||||
const Security: FunctionComponent<SecurityProps> = (props) => {
|
const Security: FunctionComponent<SecurityProps> = (props) => {
|
||||||
const isNativeMobileWeb = props.application.isNativeMobileWeb()
|
const isNativeMobileWeb = props.application.isNativeMobileWeb()
|
||||||
|
|
||||||
@@ -31,9 +28,9 @@ const Security: FunctionComponent<SecurityProps> = (props) => {
|
|||||||
)}
|
)}
|
||||||
<Protections application={props.application} />
|
<Protections application={props.application} />
|
||||||
<TwoFactorAuthWrapper mfaProvider={props.mfaProvider} userProvider={props.userProvider} />
|
<TwoFactorAuthWrapper mfaProvider={props.mfaProvider} userProvider={props.userProvider} />
|
||||||
{SHOW_MULTITASKING_PRIVACY && isNativeMobileWeb && <MultitaskingPrivacy application={props.application} />}
|
{isNativeMobileWeb && <MultitaskingPrivacy application={props.application} />}
|
||||||
<PasscodeLock viewControllerManager={props.viewControllerManager} application={props.application} />
|
<PasscodeLock viewControllerManager={props.viewControllerManager} application={props.application} />
|
||||||
{SHOW_BIOMETRICS_LOCK && isNativeMobileWeb && <BiometricsLock application={props.application} />}
|
{isNativeMobileWeb && <BiometricsLock application={props.application} />}
|
||||||
{props.application.getUser() && <Privacy application={props.application} />}
|
{props.application.getUser() && <Privacy application={props.application} />}
|
||||||
</PreferencesPane>
|
</PreferencesPane>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user