feat(dev): option to render web app inside the mobile app (#1164)
* feat (WIP): render web app inside the mobile app * fix: web app loading * chore: build scripts related to mobile web bundle * feat: show WebView header, which lets to close the WebView * refactor: remove extra component * chore: correct type * chore: remove TODO Co-authored-by: Mo <mo@standardnotes.com>
This commit is contained in:
2
packages/mobile/.gitignore
vendored
2
packages/mobile/.gitignore
vendored
@@ -21,6 +21,8 @@ DerivedData
|
|||||||
*.ipa
|
*.ipa
|
||||||
*.xcuserstate
|
*.xcuserstate
|
||||||
|
|
||||||
|
html/Web.bundle/src/web-src
|
||||||
|
|
||||||
.env
|
.env
|
||||||
|
|
||||||
# Android/IntelliJ
|
# Android/IntelliJ
|
||||||
|
|||||||
29
packages/mobile/MobileWebAppContainer.tsx
Normal file
29
packages/mobile/MobileWebAppContainer.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { Platform } from 'react-native'
|
||||||
|
import { WebView } from 'react-native-webview'
|
||||||
|
|
||||||
|
export const MobileWebAppContainer = () => {
|
||||||
|
const sourceUri = (Platform.OS === 'android' ? 'file:///android_asset/' : '') + 'Web.bundle/loader.html'
|
||||||
|
const params = 'platform=' + Platform.OS
|
||||||
|
const injectedJS = `
|
||||||
|
if (!window.location.search) {
|
||||||
|
var link = document.getElementById('web-bundle-progress-bar');
|
||||||
|
link.href = './src/index.html?${params}';
|
||||||
|
link.click();
|
||||||
|
}`
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||||
|
return (
|
||||||
|
<WebView
|
||||||
|
source={{ uri: sourceUri }}
|
||||||
|
originWhitelist={['*']}
|
||||||
|
onLoad={() => {}}
|
||||||
|
onError={(err) => console.error('An error has occurred', err)}
|
||||||
|
onHttpError={() => console.error('An HTTP error occurred')}
|
||||||
|
onMessage={() => {}}
|
||||||
|
allowFileAccess={true}
|
||||||
|
injectedJavaScript={injectedJS}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
/* eslint-enable @typescript-eslint/no-empty-function */
|
||||||
|
}
|
||||||
@@ -219,6 +219,9 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sourceSets {
|
||||||
|
main { assets.srcDirs = ['src/main/assets', '../../html'] }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|||||||
1
packages/mobile/html/Web.bundle/loader.html
Normal file
1
packages/mobile/html/Web.bundle/loader.html
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<a id="web-bundle-progress-bar" href="#"/></a>
|
||||||
25
packages/mobile/html/Web.bundle/src/index.html
Normal file
25
packages/mobile/html/Web.bundle/src/index.html
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta content="IE=edge" http-equiv="X-UA-Compatible" />
|
||||||
|
<meta content="width=device-width, initial-scale=1" name="viewport" />
|
||||||
|
<link rel="stylesheet" href="web-src/app.css" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
window.defaultSyncServer = "https://api.standardnotes.com";
|
||||||
|
window.defaultFilesHost = "https://files.standardnotes.com";
|
||||||
|
window.enabledUnfinishedFeatures = "" === 'true';
|
||||||
|
window.websocketUrl = "";
|
||||||
|
window.purchaseUrl = "https://standardnotes.com/purchase";
|
||||||
|
window.plansUrl = "https://standardnotes.com/plans";
|
||||||
|
window.dashboardUrl = "https://standardnotes.com/dashboard";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script src="web-src/app.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -434,7 +434,7 @@ PODS:
|
|||||||
- RNZipArchive/Core (6.0.8):
|
- RNZipArchive/Core (6.0.8):
|
||||||
- React-Core
|
- React-Core
|
||||||
- SSZipArchive (= 2.2.3)
|
- SSZipArchive (= 2.2.3)
|
||||||
- sn-textview (1.0.2):
|
- sn-textview (1.1.0):
|
||||||
- React-Core
|
- React-Core
|
||||||
- SNReactNative (1.0.1):
|
- SNReactNative (1.0.1):
|
||||||
- React-Core
|
- React-Core
|
||||||
@@ -749,7 +749,7 @@ SPEC CHECKSUMS:
|
|||||||
RNSVG: 302bfc9905bd8122f08966dc2ce2d07b7b52b9f8
|
RNSVG: 302bfc9905bd8122f08966dc2ce2d07b7b52b9f8
|
||||||
RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8
|
RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8
|
||||||
RNZipArchive: 3f89b114cfeb89dac027fc5c7afd7e58d3dc4ebf
|
RNZipArchive: 3f89b114cfeb89dac027fc5c7afd7e58d3dc4ebf
|
||||||
sn-textview: aaeeb41791e7d1784072adfb6b610db9b764acda
|
sn-textview: 494186ad03660daa52b1d3fd7b50b116b550f925
|
||||||
SNReactNative: b5e9e529c175c13f3a618e27c76cf3071213d5e1
|
SNReactNative: b5e9e529c175c13f3a618e27c76cf3071213d5e1
|
||||||
SSZipArchive: 62d4947b08730e4cda640473b0066d209ff033c9
|
SSZipArchive: 62d4947b08730e4cda640473b0066d209ff033c9
|
||||||
TrustKit: 073855e3adecd317417bda4ac9e9ac54a2e3b9f2
|
TrustKit: 073855e3adecd317417bda4ac9e9ac54a2e3b9f2
|
||||||
|
|||||||
@@ -26,6 +26,8 @@
|
|||||||
CD7D8400277FF8CB00915D89 /* Red@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = CD7D83FD277FF8CB00915D89 /* Red@3x.png */; };
|
CD7D8400277FF8CB00915D89 /* Red@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = CD7D83FD277FF8CB00915D89 /* Red@3x.png */; };
|
||||||
CD7D8401277FF8CB00915D89 /* Red.png in Resources */ = {isa = PBXBuildFile; fileRef = CD7D83FE277FF8CB00915D89 /* Red.png */; };
|
CD7D8401277FF8CB00915D89 /* Red.png in Resources */ = {isa = PBXBuildFile; fileRef = CD7D83FE277FF8CB00915D89 /* Red.png */; };
|
||||||
CD7D8402277FF8CB00915D89 /* Red@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = CD7D83FF277FF8CB00915D89 /* Red@2x.png */; };
|
CD7D8402277FF8CB00915D89 /* Red@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = CD7D83FF277FF8CB00915D89 /* Red@2x.png */; };
|
||||||
|
D261494528699DCE00B17102 /* Web.bundle in Resources */ = {isa = PBXBuildFile; fileRef = D261494428699DCE00B17102 /* Web.bundle */; };
|
||||||
|
D261494628699DCE00B17102 /* Web.bundle in Resources */ = {isa = PBXBuildFile; fileRef = D261494428699DCE00B17102 /* Web.bundle */; };
|
||||||
DD3D1CE428EC1C8BA0C49211 /* libPods-StandardNotes.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 41388EB4F886116157DA092C /* libPods-StandardNotes.a */; };
|
DD3D1CE428EC1C8BA0C49211 /* libPods-StandardNotes.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 41388EB4F886116157DA092C /* libPods-StandardNotes.a */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
@@ -67,6 +69,7 @@
|
|||||||
CD7D83FD277FF8CB00915D89 /* Red@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Red@3x.png"; sourceTree = "<group>"; };
|
CD7D83FD277FF8CB00915D89 /* Red@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Red@3x.png"; sourceTree = "<group>"; };
|
||||||
CD7D83FE277FF8CB00915D89 /* Red.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Red.png; sourceTree = "<group>"; };
|
CD7D83FE277FF8CB00915D89 /* Red.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Red.png; sourceTree = "<group>"; };
|
||||||
CD7D83FF277FF8CB00915D89 /* Red@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Red@2x.png"; sourceTree = "<group>"; };
|
CD7D83FF277FF8CB00915D89 /* Red@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Red@2x.png"; sourceTree = "<group>"; };
|
||||||
|
D261494428699DCE00B17102 /* Web.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; name = Web.bundle; path = ../html/Web.bundle; sourceTree = "<group>"; };
|
||||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
@@ -132,6 +135,7 @@
|
|||||||
13B07FAE1A68108700A75B9A /* StandardNotes */ = {
|
13B07FAE1A68108700A75B9A /* StandardNotes */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
D261494428699DCE00B17102 /* Web.bundle */,
|
||||||
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
|
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
|
||||||
13B07FB01A68108700A75B9A /* AppDelegate.m */,
|
13B07FB01A68108700A75B9A /* AppDelegate.m */,
|
||||||
CD7D5EE527801F10005FE1BF /* StandardNotes.entitlements */,
|
CD7D5EE527801F10005FE1BF /* StandardNotes.entitlements */,
|
||||||
@@ -308,6 +312,7 @@
|
|||||||
isa = PBXResourcesBuildPhase;
|
isa = PBXResourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
D261494528699DCE00B17102 /* Web.bundle in Resources */,
|
||||||
CD7D8401277FF8CB00915D89 /* Red.png in Resources */,
|
CD7D8401277FF8CB00915D89 /* Red.png in Resources */,
|
||||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||||
CD7D8402277FF8CB00915D89 /* Red@2x.png in Resources */,
|
CD7D8402277FF8CB00915D89 /* Red@2x.png in Resources */,
|
||||||
@@ -321,6 +326,7 @@
|
|||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
CD7D5ED4278015D2005FE1BF /* Red.png in Resources */,
|
CD7D5ED4278015D2005FE1BF /* Red.png in Resources */,
|
||||||
|
D261494628699DCE00B17102 /* Web.bundle in Resources */,
|
||||||
CD7D5EE3278016E3005FE1BF /* Dev.xcassets in Resources */,
|
CD7D5EE3278016E3005FE1BF /* Dev.xcassets in Resources */,
|
||||||
CD7D5ED5278015D2005FE1BF /* Images.xcassets in Resources */,
|
CD7D5ED5278015D2005FE1BF /* Images.xcassets in Resources */,
|
||||||
CD7D5ED6278015D2005FE1BF /* Red@2x.png in Resources */,
|
CD7D5ED6278015D2005FE1BF /* Red@2x.png in Resources */,
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
"lint:fix": "yarn lint --fix",
|
"lint:fix": "yarn lint --fix",
|
||||||
"format": "prettier ./src",
|
"format": "prettier ./src",
|
||||||
"format:fix": "yarn format --write",
|
"format:fix": "yarn format --write",
|
||||||
"build": "yarn android:bundle && yarn install:pods",
|
"build": "yarn web:bundle && yarn install:pods && yarn android:bundle",
|
||||||
|
"web:bundle": "cp -r ../web/dist/. html/Web.bundle/src/web-src/",
|
||||||
"pods": "yarn install:pods",
|
"pods": "yarn install:pods",
|
||||||
"tsc": "tsc --noEmit",
|
"tsc": "tsc --noEmit",
|
||||||
"start": "react-native start",
|
"start": "react-native start",
|
||||||
@@ -43,6 +44,7 @@
|
|||||||
"@standardnotes/sncrypto-common": "1.9.0",
|
"@standardnotes/sncrypto-common": "1.9.0",
|
||||||
"@standardnotes/snjs": "^2.118.3",
|
"@standardnotes/snjs": "^2.118.3",
|
||||||
"@standardnotes/stylekit": "5.29.3",
|
"@standardnotes/stylekit": "5.29.3",
|
||||||
|
"@standardnotes/web": "workspace:^",
|
||||||
"@types/styled-components-react-native": "5.1.3",
|
"@types/styled-components-react-native": "5.1.3",
|
||||||
"js-base64": "^3.7.2",
|
"js-base64": "^3.7.2",
|
||||||
"moment": "^2.29.2",
|
"moment": "^2.29.2",
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
import { IsDev } from '@Lib/Utils'
|
||||||
|
|
||||||
export enum ErrorMessage {
|
export enum ErrorMessage {
|
||||||
GeneralText = 'An error occurred. Please try again later.',
|
GeneralText = 'An error occurred. Please try again later.',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const WebAppOptionEnabled = IsDev
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
SCREEN_MANAGE_SESSIONS,
|
SCREEN_MANAGE_SESSIONS,
|
||||||
SCREEN_SETTINGS,
|
SCREEN_SETTINGS,
|
||||||
SCREEN_UPLOADED_FILES_LIST,
|
SCREEN_UPLOADED_FILES_LIST,
|
||||||
|
SCREEN_WEB_APP,
|
||||||
} from '@Root/Screens/screens'
|
} from '@Root/Screens/screens'
|
||||||
import { Settings } from '@Root/Screens/Settings/Settings'
|
import { Settings } from '@Root/Screens/Settings/Settings'
|
||||||
import { UploadedFilesList } from '@Root/Screens/UploadedFilesList/UploadedFilesList'
|
import { UploadedFilesList } from '@Root/Screens/UploadedFilesList/UploadedFilesList'
|
||||||
@@ -29,6 +30,7 @@ import React, { memo, useContext } from 'react'
|
|||||||
import { Platform } from 'react-native'
|
import { Platform } from 'react-native'
|
||||||
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'
|
||||||
|
import { MobileWebAppContainer } from '../MobileWebAppContainer'
|
||||||
import { HeaderTitleParams, TEnvironment } from './App'
|
import { HeaderTitleParams, TEnvironment } from './App'
|
||||||
import { ApplicationContext } from './ApplicationContext'
|
import { ApplicationContext } from './ApplicationContext'
|
||||||
import { AppStackComponent } from './AppStack'
|
import { AppStackComponent } from './AppStack'
|
||||||
@@ -63,6 +65,7 @@ export type ModalStackNavigatorParamList = {
|
|||||||
title?: string
|
title?: string
|
||||||
text: string
|
text: string
|
||||||
}
|
}
|
||||||
|
[SCREEN_WEB_APP]: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ModalStackNavigationProp<T extends keyof ModalStackNavigatorParamList> = {
|
export type ModalStackNavigationProp<T extends keyof ModalStackNavigatorParamList> = {
|
||||||
@@ -303,6 +306,7 @@ export const MainStackComponent = ({ env }: { env: TEnvironment }) => {
|
|||||||
})}
|
})}
|
||||||
component={WorkspaceInputModal}
|
component={WorkspaceInputModal}
|
||||||
/>
|
/>
|
||||||
|
<MainStack.Screen name={SCREEN_WEB_APP} options={() => ({ title: 'App' })} component={MobileWebAppContainer} />
|
||||||
</MainStack.Navigator>
|
</MainStack.Navigator>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { WebAppOptionEnabled } from '@Lib/constants'
|
||||||
import { useSignedIn } from '@Lib/SnjsHelperHooks'
|
import { useSignedIn } from '@Lib/SnjsHelperHooks'
|
||||||
import { useNavigation } from '@react-navigation/native'
|
import { useNavigation } from '@react-navigation/native'
|
||||||
import { ButtonCell } from '@Root/Components/ButtonCell'
|
import { ButtonCell } from '@Root/Components/ButtonCell'
|
||||||
@@ -7,7 +8,7 @@ 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 } from '@Root/Screens/screens'
|
import { SCREEN_MANAGE_SESSIONS, SCREEN_SETTINGS, SCREEN_WEB_APP } from '@Root/Screens/screens'
|
||||||
import { ButtonType, PrefKey } from '@standardnotes/snjs'
|
import { ButtonType, PrefKey } from '@standardnotes/snjs'
|
||||||
import moment from 'moment'
|
import moment from 'moment'
|
||||||
import React, { useCallback, useMemo, useState } from 'react'
|
import React, { useCallback, useMemo, useState } from 'react'
|
||||||
@@ -182,6 +183,10 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
|
|||||||
)
|
)
|
||||||
}, [application.alertService])
|
}, [application.alertService])
|
||||||
|
|
||||||
|
const openWebApp = useCallback(() => {
|
||||||
|
navigation.push(SCREEN_WEB_APP)
|
||||||
|
}, [navigation])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableSection>
|
<TableSection>
|
||||||
<SectionHeader title={title} />
|
<SectionHeader title={title} />
|
||||||
@@ -221,6 +226,10 @@ export const OptionsSection = ({ title, encryptionAvailable }: Props) => {
|
|||||||
onPress={onExportPress}
|
onPress={onExportPress}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{WebAppOptionEnabled && (
|
||||||
|
<ButtonCell testID="openWebApp" leftAligned title={'Open Web App'} onPress={() => openWebApp()} />
|
||||||
|
)}
|
||||||
|
|
||||||
{!signedIn && (
|
{!signedIn && (
|
||||||
<SectionedAccessoryTableCell
|
<SectionedAccessoryTableCell
|
||||||
testID="lastExportDate"
|
testID="lastExportDate"
|
||||||
|
|||||||
@@ -16,3 +16,5 @@ export const SCREEN_MANAGE_SESSIONS = 'ManageSessions' as const
|
|||||||
export const MODAL_BLOCKING_ALERT = 'ModalBlockingAlert' as const
|
export const MODAL_BLOCKING_ALERT = 'ModalBlockingAlert' as const
|
||||||
|
|
||||||
export const SCREEN_VIEW_PROTECTED_NOTE = 'ViewProtectedNote' as const
|
export const SCREEN_VIEW_PROTECTED_NOTE = 'ViewProtectedNote' as const
|
||||||
|
|
||||||
|
export const SCREEN_WEB_APP = 'WebApp' as const
|
||||||
|
|||||||
@@ -5718,6 +5718,7 @@ __metadata:
|
|||||||
"@standardnotes/sncrypto-common": 1.9.0
|
"@standardnotes/sncrypto-common": 1.9.0
|
||||||
"@standardnotes/snjs": ^2.118.3
|
"@standardnotes/snjs": ^2.118.3
|
||||||
"@standardnotes/stylekit": 5.29.3
|
"@standardnotes/stylekit": 5.29.3
|
||||||
|
"@standardnotes/web": "workspace:^"
|
||||||
"@types/detox": ^18.1.0
|
"@types/detox": ^18.1.0
|
||||||
"@types/faker": ^6.6.9
|
"@types/faker": ^6.6.9
|
||||||
"@types/jest": ^27.4.1
|
"@types/jest": ^27.4.1
|
||||||
@@ -6187,7 +6188,7 @@ __metadata:
|
|||||||
languageName: unknown
|
languageName: unknown
|
||||||
linkType: soft
|
linkType: soft
|
||||||
|
|
||||||
"@standardnotes/web@workspace:*, @standardnotes/web@workspace:packages/web":
|
"@standardnotes/web@workspace:*, @standardnotes/web@workspace:^, @standardnotes/web@workspace:packages/web":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@standardnotes/web@workspace:packages/web"
|
resolution: "@standardnotes/web@workspace:packages/web"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user