feat: add settings to fully switch between native/webview (#1587)

This commit is contained in:
Aman Harwara
2022-09-17 02:52:38 +05:30
committed by GitHub
parent 146b3329e0
commit 85c90e07fc
6 changed files with 61 additions and 66 deletions

View File

@@ -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}>

View File

@@ -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}

View File

@@ -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 && (

View File

@@ -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}

View File

@@ -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>

View File

@@ -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>
) )