From f4ef29a8b8cd6128bd4c047afe68667ef12d333d Mon Sep 17 00:00:00 2001 From: Alec Ananian <1013230+alecananian@users.noreply.github.com> Date: Mon, 19 Feb 2024 16:53:11 -0800 Subject: [PATCH] mobile: add tab navigation (#3261) * add tab navigation * add tab icons * update tab bar icon styling * hide tab bar within webview * fix tab bar rendering * fix extra bottom padding * reset back to initial path on tab press * add comments * fix typecheck error --- apps/tlon-mobile/.svgrc | 6 + .../assets/icons/filled/Activity.svg | 14 + apps/tlon-mobile/assets/icons/filled/Home.svg | 8 + .../assets/icons/filled/Messages.svg | 8 + apps/tlon-mobile/assets/icons/index.ts | 6 + .../assets/icons/outlined/Activity.svg | 13 + .../assets/icons/outlined/Home.svg | 7 + .../assets/icons/outlined/Messages.svg | 7 + .../ios/Landscape.xcodeproj/project.pbxproj | 36 +- apps/tlon-mobile/ios/Podfile.lock | 8 +- apps/tlon-mobile/metro.config.js | 3 + apps/tlon-mobile/package.json | 4 + apps/tlon-mobile/src/App.tsx | 34 +- .../tlon-mobile/src/hooks/useScreenOptions.ts | 16 + apps/tlon-mobile/src/navigation/TabStack.tsx | 78 +++ .../src/navigation/WebViewStack.tsx | 29 + .../src/screens/ExternalWebViewScreen.tsx | 4 +- .../WebViewScreen.tsx} | 57 +- apps/tlon-mobile/src/types.ts | 16 +- apps/tlon-mobile/svg.d.ts | 7 + apps/tlon-web/src/app.tsx | 2 +- apps/tlon-web/src/chat/ChatChannel.tsx | 7 +- .../src/components/Sidebar/MobileSidebar.tsx | 115 ++-- apps/tlon-web/src/dms/Dm.tsx | 4 +- apps/tlon-web/src/dms/MultiDm.tsx | 4 +- apps/tlon-web/src/heap/HeapDetail.tsx | 4 +- apps/tlon-web/src/logic/SafeAreaContext.tsx | 2 +- apps/tlon-web/src/logic/useShowTabBar.ts | 6 + apps/tlon-web/src/window.ts | 14 +- package-lock.json | 528 +++++++++++++++++- packages/shared/src/index.ts | 1 + packages/shared/src/types/native.ts | 10 + 32 files changed, 870 insertions(+), 188 deletions(-) create mode 100644 apps/tlon-mobile/.svgrc create mode 100644 apps/tlon-mobile/assets/icons/filled/Activity.svg create mode 100644 apps/tlon-mobile/assets/icons/filled/Home.svg create mode 100644 apps/tlon-mobile/assets/icons/filled/Messages.svg create mode 100644 apps/tlon-mobile/assets/icons/index.ts create mode 100644 apps/tlon-mobile/assets/icons/outlined/Activity.svg create mode 100644 apps/tlon-mobile/assets/icons/outlined/Home.svg create mode 100644 apps/tlon-mobile/assets/icons/outlined/Messages.svg create mode 100644 apps/tlon-mobile/src/hooks/useScreenOptions.ts create mode 100644 apps/tlon-mobile/src/navigation/TabStack.tsx create mode 100644 apps/tlon-mobile/src/navigation/WebViewStack.tsx rename apps/tlon-mobile/src/{AppWebView.tsx => screens/WebViewScreen.tsx} (79%) create mode 100644 apps/tlon-mobile/svg.d.ts create mode 100644 apps/tlon-web/src/logic/useShowTabBar.ts create mode 100644 packages/shared/src/types/native.ts diff --git a/apps/tlon-mobile/.svgrc b/apps/tlon-mobile/.svgrc new file mode 100644 index 0000000000..6d33d59699 --- /dev/null +++ b/apps/tlon-mobile/.svgrc @@ -0,0 +1,6 @@ +{ + "ref": true, + "replaceAttrValues": { + "#333333": "{props.color}" + } +} diff --git a/apps/tlon-mobile/assets/icons/filled/Activity.svg b/apps/tlon-mobile/assets/icons/filled/Activity.svg new file mode 100644 index 0000000000..c9133347cd --- /dev/null +++ b/apps/tlon-mobile/assets/icons/filled/Activity.svg @@ -0,0 +1,14 @@ + + + + \ No newline at end of file diff --git a/apps/tlon-mobile/assets/icons/filled/Home.svg b/apps/tlon-mobile/assets/icons/filled/Home.svg new file mode 100644 index 0000000000..bc437cdf13 --- /dev/null +++ b/apps/tlon-mobile/assets/icons/filled/Home.svg @@ -0,0 +1,8 @@ + + + \ No newline at end of file diff --git a/apps/tlon-mobile/assets/icons/filled/Messages.svg b/apps/tlon-mobile/assets/icons/filled/Messages.svg new file mode 100644 index 0000000000..9ec80c7891 --- /dev/null +++ b/apps/tlon-mobile/assets/icons/filled/Messages.svg @@ -0,0 +1,8 @@ + + + \ No newline at end of file diff --git a/apps/tlon-mobile/assets/icons/index.ts b/apps/tlon-mobile/assets/icons/index.ts new file mode 100644 index 0000000000..7cd0ce2a65 --- /dev/null +++ b/apps/tlon-mobile/assets/icons/index.ts @@ -0,0 +1,6 @@ +export { default as ActivityFilled } from './filled/Activity.svg'; +export { default as ActivityOutlined } from './outlined/Activity.svg'; +export { default as HomeFilled } from './filled/Home.svg'; +export { default as HomeOutlined } from './outlined/Home.svg'; +export { default as MessagesFilled } from './filled/Messages.svg'; +export { default as MessagesOutlined } from './outlined/Messages.svg'; diff --git a/apps/tlon-mobile/assets/icons/outlined/Activity.svg b/apps/tlon-mobile/assets/icons/outlined/Activity.svg new file mode 100644 index 0000000000..62afef25a7 --- /dev/null +++ b/apps/tlon-mobile/assets/icons/outlined/Activity.svg @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/apps/tlon-mobile/assets/icons/outlined/Home.svg b/apps/tlon-mobile/assets/icons/outlined/Home.svg new file mode 100644 index 0000000000..9b6c24014e --- /dev/null +++ b/apps/tlon-mobile/assets/icons/outlined/Home.svg @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/apps/tlon-mobile/assets/icons/outlined/Messages.svg b/apps/tlon-mobile/assets/icons/outlined/Messages.svg new file mode 100644 index 0000000000..f9d184f8d9 --- /dev/null +++ b/apps/tlon-mobile/assets/icons/outlined/Messages.svg @@ -0,0 +1,7 @@ + + + \ No newline at end of file diff --git a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj index c1b761cc2a..15fab22e5a 100644 --- a/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj +++ b/apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj @@ -835,18 +835,6 @@ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", ); IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -858,11 +846,7 @@ ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; - OTHER_LDFLAGS = ( - "$(inherited)", - "-Wl", - "-ld_classic", - ); + OTHER_LDFLAGS = "$(inherited)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; @@ -924,18 +908,6 @@ " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", - " ${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers", - " ${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx", - " ${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers", ); IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( @@ -946,11 +918,7 @@ MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; - OTHER_LDFLAGS = ( - "$(inherited)", - "-Wl", - "-ld_classic", - ); + OTHER_LDFLAGS = "$(inherited)"; REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native"; SDKROOT = iphoneos; USE_HERMES = true; diff --git a/apps/tlon-mobile/ios/Podfile.lock b/apps/tlon-mobile/ios/Podfile.lock index 4572ce51ac..7874d75fc0 100644 --- a/apps/tlon-mobile/ios/Podfile.lock +++ b/apps/tlon-mobile/ios/Podfile.lock @@ -1205,6 +1205,8 @@ PODS: - glog - RCT-Folly (= 2022.05.16.00) - React-Core + - RNSVG (14.1.0): + - React-Core - SocketRocket (0.6.1) - sqlite3 (3.42.0): - sqlite3/common (= 3.42.0) @@ -1295,6 +1297,7 @@ DEPENDENCIES: - RNGestureHandler (from `../../../node_modules/react-native-gesture-handler`) - RNReanimated (from `../../../node_modules/react-native-reanimated`) - RNScreens (from `../../../node_modules/react-native-screens`) + - RNSVG (from `../../../node_modules/react-native-svg`) - UMAppLoader (from `../../../node_modules/unimodules-app-loader/ios`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) @@ -1481,6 +1484,8 @@ EXTERNAL SOURCES: :path: "../../../node_modules/react-native-reanimated" RNScreens: :path: "../../../node_modules/react-native-screens" + RNSVG: + :path: "../../../node_modules/react-native-svg" UMAppLoader: :path: "../../../node_modules/unimodules-app-loader/ios" Yoga: @@ -1584,6 +1589,7 @@ SPEC CHECKSUMS: RNGestureHandler: 28bdf9a766c081e603120f79e925b72817c751c6 RNReanimated: 1873432b0b86d0224bbe19aacea7ad8eef4e7518 RNScreens: 2b73f5eb2ac5d94fbd61fa4be0bfebd345716825 + RNSVG: ba3e7232f45e34b7b47e74472386cf4e1a676d0a SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17 sqlite3: f163dbbb7aa3339ad8fc622782c2d9d7b72f7e9c UMAppLoader: 5df85360d65cabaef544be5424ac64672e648482 @@ -1591,4 +1597,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: bebef443864c4d459a5b44fcd000912004c764e1 -COCOAPODS: 1.13.0 +COCOAPODS: 1.15.2 diff --git a/apps/tlon-mobile/metro.config.js b/apps/tlon-mobile/metro.config.js index 96c8ae898b..f424d9eb3e 100644 --- a/apps/tlon-mobile/metro.config.js +++ b/apps/tlon-mobile/metro.config.js @@ -10,6 +10,7 @@ const config = getDefaultConfig(projectRoot); module.exports = mergeConfig(config, { watchFolders: [workspaceRoot], transformer: { + babelTransformerPath: require.resolve('react-native-svg-transformer'), getTransformOptions: async () => ({ transform: { experimentalImportSupport: false, @@ -24,10 +25,12 @@ module.exports = mergeConfig(config, { }), }, resolver: { + assetExts: config.resolver.assetExts.filter((ext) => ext !== 'svg'), disableHierarchicalLookup: true, nodeModulesPaths: [ path.resolve(projectRoot, 'node_modules'), path.resolve(workspaceRoot, 'node_modules'), ], + sourceExts: [...config.resolver.sourceExts, 'svg'], }, }); diff --git a/apps/tlon-mobile/package.json b/apps/tlon-mobile/package.json index 56accd0d12..eecfcb7038 100644 --- a/apps/tlon-mobile/package.json +++ b/apps/tlon-mobile/package.json @@ -35,8 +35,10 @@ "@react-native-community/netinfo": "11.1.0", "@react-native-firebase/app": "^18.4.0", "@react-native-firebase/crashlytics": "^18.4.0", + "@react-navigation/bottom-tabs": "^6.5.12", "@react-navigation/native": "^6.1.7", "@react-navigation/native-stack": "^6.9.13", + "@tloncorp/shared": "*", "@urbit/aura": "^1.0.0", "classnames": "^2.3.2", "expo": "^50.0.6", @@ -67,6 +69,7 @@ "react-native-safe-area-context": "4.8.2", "react-native-screens": "~3.29.0", "react-native-storage": "^1.0.1", + "react-native-svg": "^14.1.0", "react-native-webview": "13.6.4", "tailwind-rn": "^4.2.0" }, @@ -88,6 +91,7 @@ "postcss": "^8.4.26", "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.4", + "react-native-svg-transformer": "^1.3.0", "tailwindcss": "^3.3.3", "typescript": "^5.1.3", "vitest": "^1.0.4" diff --git a/apps/tlon-mobile/src/App.tsx b/apps/tlon-mobile/src/App.tsx index 019b6187a0..01b9d6c058 100644 --- a/apps/tlon-mobile/src/App.tsx +++ b/apps/tlon-mobile/src/App.tsx @@ -14,15 +14,15 @@ import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { SafeAreaProvider } from 'react-native-safe-area-context'; import { useTailwind } from 'tailwind-rn'; -import { AppWebView } from './AppWebView'; import { LoadingSpinner } from './components/LoadingSpinner'; import { ShipProvider, useShip } from './contexts/ship'; import { useDeepLink } from './hooks/useDeepLink'; import { useIsDarkMode } from './hooks/useIsDarkMode'; +import { useScreenOptions } from './hooks/useScreenOptions'; import { inviteShipWithLure } from './lib/hostingApi'; +import { TabStack } from './navigation/TabStack'; import { CheckVerifyScreen } from './screens/CheckVerifyScreen'; import { EULAScreen } from './screens/EULAScreen'; -import { ExternalWebViewScreen } from './screens/ExternalWebViewScreen'; import { JoinWaitListScreen } from './screens/JoinWaitListScreen'; import { RequestPhoneVerifyScreen } from './screens/RequestPhoneVerifyScreen'; import { ReserveShipScreen } from './screens/ReserveShipScreen'; @@ -35,14 +35,13 @@ import { SignUpEmailScreen } from './screens/SignUpEmailScreen'; import { SignUpPasswordScreen } from './screens/SignUpPasswordScreen'; import { TlonLoginScreen } from './screens/TlonLoginScreen'; import { WelcomeScreen } from './screens/WelcomeScreen'; -import type { MainStackParamList, OnboardingStackParamList } from './types'; +import type { OnboardingStackParamList } from './types'; import { posthogAsync, trackError } from './utils/posthog'; type Props = { wer?: string; }; -const MainStack = createNativeStackNavigator(); const OnboardingStack = createNativeStackNavigator(); const App = ({ wer: initialWer }: Props) => { @@ -52,18 +51,9 @@ const App = ({ wer: initialWer }: Props) => { const [connected, setConnected] = useState(true); const { wer, lure, priorityToken, clearDeepLink } = useDeepLink(); const navigation = useNavigation(); + const screenOptions = useScreenOptions(); const gotoPath = wer ?? initialWer; - const screenOptions = { - headerTitle: '', - headerBackTitleVisible: false, - headerShadowVisible: false, - headerStyle: { - backgroundColor: isDarkMode ? '#000' : '#fff', - }, - headerTintColor: isDarkMode ? '#fff' : '#333', - }; - useEffect(() => { const unsubscribeFromNetInfo = NetInfo.addEventListener( ({ isConnected }) => { @@ -125,21 +115,7 @@ const App = ({ wer: initialWer }: Props) => { ) : isAuthenticated ? ( - - - - + ) : ( { + const isDarkMode = useIsDarkMode(); + return { + headerTitle: '', + headerBackTitleVisible: false, + headerShadowVisible: false, + headerStyle: { + backgroundColor: isDarkMode ? '#000' : '#fff', + }, + headerTintColor: isDarkMode ? '#fff' : '#333', + }; +}; diff --git a/apps/tlon-mobile/src/navigation/TabStack.tsx b/apps/tlon-mobile/src/navigation/TabStack.tsx new file mode 100644 index 0000000000..cd8f73ed6c --- /dev/null +++ b/apps/tlon-mobile/src/navigation/TabStack.tsx @@ -0,0 +1,78 @@ +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; + +import { + ActivityFilled, + ActivityOutlined, + HomeFilled, + HomeOutlined, + MessagesFilled, + MessagesOutlined, +} from '../../assets/icons'; +import type { TabParamList } from '../types'; +import { WebViewStack } from './WebViewStack'; + +const Tab = createBottomTabNavigator(); + +const ICON_SIZE = { + width: 20, + height: 20, +}; + +export const TabStack = () => ( + + + focused ? ( + + ) : ( + + ), + tabBarShowLabel: false, + }} + /> + + focused ? ( + + ) : ( + + ), + tabBarShowLabel: false, + }} + /> + + focused ? ( + + ) : ( + + ), + tabBarShowLabel: false, + }} + /> + + +); diff --git a/apps/tlon-mobile/src/navigation/WebViewStack.tsx b/apps/tlon-mobile/src/navigation/WebViewStack.tsx new file mode 100644 index 0000000000..bba7134679 --- /dev/null +++ b/apps/tlon-mobile/src/navigation/WebViewStack.tsx @@ -0,0 +1,29 @@ +import type { BottomTabScreenProps } from '@react-navigation/bottom-tabs'; +import { createNativeStackNavigator } from '@react-navigation/native-stack'; + +import { useScreenOptions } from '../hooks/useScreenOptions'; +import { ExternalWebViewScreen } from '../screens/ExternalWebViewScreen'; +import { WebViewScreen } from '../screens/WebViewScreen'; +import type { TabParamList, WebViewStackParamList } from '../types'; + +type Props = BottomTabScreenProps< + TabParamList, + 'Groups' | 'Messages' | 'Activity' | 'Profile' +>; + +const Stack = createNativeStackNavigator(); + +export const WebViewStack = (props: Props) => { + const screenOptions = useScreenOptions(); + return ( + + + + + ); +}; diff --git a/apps/tlon-mobile/src/screens/ExternalWebViewScreen.tsx b/apps/tlon-mobile/src/screens/ExternalWebViewScreen.tsx index 0bfbe659b8..d5fc0436ca 100644 --- a/apps/tlon-mobile/src/screens/ExternalWebViewScreen.tsx +++ b/apps/tlon-mobile/src/screens/ExternalWebViewScreen.tsx @@ -2,9 +2,9 @@ import type { NativeStackScreenProps } from '@react-navigation/native-stack'; import { WebView } from 'react-native-webview'; import { useWebView } from '../hooks/useWebView'; -import type { MainStackParamList } from '../types'; +import type { WebViewStackParamList } from '../types'; -type Props = NativeStackScreenProps; +type Props = NativeStackScreenProps; export const ExternalWebViewScreen = ({ route: { diff --git a/apps/tlon-mobile/src/AppWebView.tsx b/apps/tlon-mobile/src/screens/WebViewScreen.tsx similarity index 79% rename from apps/tlon-mobile/src/AppWebView.tsx rename to apps/tlon-mobile/src/screens/WebViewScreen.tsx index db84de1294..0c305b72b8 100644 --- a/apps/tlon-mobile/src/AppWebView.tsx +++ b/apps/tlon-mobile/src/screens/WebViewScreen.tsx @@ -1,5 +1,6 @@ import { useFocusEffect } from '@react-navigation/native'; import type { NativeStackScreenProps } from '@react-navigation/native-stack'; +import type { NativeWebViewOptions } from '@tloncorp/shared'; import * as Clipboard from 'expo-clipboard'; import { addNotificationResponseReceivedListener } from 'expo-notifications'; import { useCallback, useEffect, useRef, useState } from 'react'; @@ -9,36 +10,36 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { WebView } from 'react-native-webview'; import { useTailwind } from 'tailwind-rn'; -import { IS_IOS } from './constants'; -import { useShip } from './contexts/ship'; -import { useWebView } from './hooks/useWebView'; -import { markChatRead } from './lib/chatApi'; -import { getHostingUser } from './lib/hostingApi'; -import { connectNotifications } from './lib/notifications'; -import type { MainStackParamList } from './types'; +import { IS_IOS } from '../constants'; +import { useShip } from '../contexts/ship'; +import { useWebView } from '../hooks/useWebView'; +import { markChatRead } from '../lib/chatApi'; +import { getHostingUser } from '../lib/hostingApi'; +import { connectNotifications } from '../lib/notifications'; +import type { WebViewStackParamList } from '../types'; import { getHostingToken, getHostingUserId, removeHostingToken, removeHostingUserId, -} from './utils/hosting'; +} from '../utils/hosting'; type WebViewMessage = { action: 'copy' | 'logout' | 'manageAccount' | 'appLoaded'; value?: string; }; -type Props = NativeStackScreenProps; +export type Props = NativeStackScreenProps; const createUri = (shipUrl: string, path?: string) => `${shipUrl}/apps/groups${ path ? (path.startsWith('/') ? path : `/${path}`) : '/' }`; -export const RawAppWebView = ({ +const InnerWebViewScreen = ({ navigation, route: { - params: { gotoPath }, + params: { initialPath, gotoPath }, }, }: Props) => { const tailwind = useTailwind(); @@ -51,7 +52,7 @@ export const RawAppWebView = ({ uri: string; key: number; }>({ - uri: createUri(shipUrl, gotoPath), + uri: createUri(shipUrl, initialPath), key: 1, }); const [appLoaded, setAppLoaded] = useState(false); @@ -151,6 +152,18 @@ export const RawAppWebView = ({ }; }, [shipUrl]); + // If the tab for this screen gets tapped while focused, navigate back to the initial path + useEffect( + () => + // @ts-expect-error: react-navigation ID and event name mismatch + navigation.getParent('TabBar')?.addListener('tabPress', () => { + if (navigation.isFocused()) { + navigation.setParams({ gotoPath: initialPath }); + } + }), + [navigation, initialPath] + ); + // When this view regains focus from Manage Account, query for hosting user's details and bump back to login if an error occurs useFocusEffect( useCallback(() => { @@ -190,6 +203,13 @@ export const RawAppWebView = ({ } }, [shipUrl, gotoPath, navigation]); + // Injected web settings + const nativeOptions: NativeWebViewOptions = { + colorScheme, + hideTabBar: true, + safeAreaInsets, + }; + return ( { // Start a timeout in case the web app doesn't send the appLoaded message @@ -231,14 +253,15 @@ export const RawAppWebView = ({ /> ); }; -export const AppWebView = (props: Props) => { + +export const WebViewScreen = (props: Props) => { const tailwind = useTailwind(); if (IS_IOS) { return ( - + ); } - return ; + return ; }; diff --git a/apps/tlon-mobile/src/types.ts b/apps/tlon-mobile/src/types.ts index 82b302f0d0..fd88eb9fb4 100644 --- a/apps/tlon-mobile/src/types.ts +++ b/apps/tlon-mobile/src/types.ts @@ -4,8 +4,13 @@ export type SignUpExtras = { telemetry?: boolean; }; -export type MainStackParamList = { - AppWebView: { gotoPath?: string }; +type WebViewScreenParams = { + initialPath: string; + gotoPath?: string; +}; + +export type WebViewStackParamList = { + WebView: WebViewScreenParams; ExternalWebView: { uri: string; headers?: Record; @@ -13,6 +18,13 @@ export type MainStackParamList = { }; }; +export type TabParamList = { + Groups: WebViewScreenParams; + Messages: WebViewScreenParams; + Activity: WebViewScreenParams; + Profile: WebViewScreenParams; +}; + export type OnboardingStackParamList = { Welcome: undefined; SignUpEmail: { lure?: string; priorityToken?: string } | undefined; diff --git a/apps/tlon-mobile/svg.d.ts b/apps/tlon-mobile/svg.d.ts new file mode 100644 index 0000000000..385ee88daa --- /dev/null +++ b/apps/tlon-mobile/svg.d.ts @@ -0,0 +1,7 @@ +declare module '*.svg' { + import type { RefAttributes } from 'react'; + import type React from 'react'; + import type { SvgProps } from 'react-native-svg'; + const content: React.FC; + export default content; +} diff --git a/apps/tlon-web/src/app.tsx b/apps/tlon-web/src/app.tsx index c40421aeba..904be905a6 100644 --- a/apps/tlon-web/src/app.tsx +++ b/apps/tlon-web/src/app.tsx @@ -638,7 +638,7 @@ function RoutedApp() { const analyticsId = useAnalyticsId(); const { needsUpdate, triggerUpdate } = useAppUpdates(); const body = document.querySelector('body'); - const colorSchemeFromNative = window.colorscheme; + const colorSchemeFromNative = window.nativeOptions?.colorScheme; const appUpdateContextValue = useMemo( () => ({ needsUpdate, triggerUpdate }), diff --git a/apps/tlon-web/src/chat/ChatChannel.tsx b/apps/tlon-web/src/chat/ChatChannel.tsx index 574cb4985e..2ecbf8d8f8 100644 --- a/apps/tlon-web/src/chat/ChatChannel.tsx +++ b/apps/tlon-web/src/chat/ChatChannel.tsx @@ -18,6 +18,7 @@ import { useDragAndDrop } from '@/logic/DragAndDropContext'; import { useFullChannel } from '@/logic/channel'; import { useIsScrolling } from '@/logic/scroll'; import useMedia, { useIsMobile } from '@/logic/useMedia'; +import useShowTabBar from '@/logic/useShowTabBar'; import { useAddPostMutation, useLeaveMutation, @@ -60,15 +61,13 @@ function ChatChannel({ title }: ViewProps) { const replyingWrit = useReplyPost(nest, chatReplyId); const scrollElementRef = useRef(null); const isScrolling = useIsScrolling(scrollElementRef); + const showTabBar = useShowTabBar(); const root = `${ activeTab === 'messages' ? '/dm' : '' }/groups/${groupFlag}/channels/${nest}`; // We only inset the bottom for groups, since DMs display the navbar // underneath this view - const shouldApplyPaddingBottom = useMemo( - () => isMobile && !isChatInputFocused, - [isMobile, isChatInputFocused] - ); + const shouldApplyPaddingBottom = showTabBar && !isChatInputFocused; const { group, diff --git a/apps/tlon-web/src/components/Sidebar/MobileSidebar.tsx b/apps/tlon-web/src/components/Sidebar/MobileSidebar.tsx index 4512e6aaca..4f7916caaf 100644 --- a/apps/tlon-web/src/components/Sidebar/MobileSidebar.tsx +++ b/apps/tlon-web/src/components/Sidebar/MobileSidebar.tsx @@ -8,6 +8,7 @@ import { isNativeApp, useSafeAreaInsets } from '@/logic/native'; import { AppUpdateContext } from '@/logic/useAppUpdates'; import { useIsAnyGroupUnread } from '@/logic/useIsGroupUnread'; import { useIsDark } from '@/logic/useMedia'; +import useShowTabBar from '@/logic/useShowTabBar'; import { useNotifications } from '@/notifications/useNotifications'; import { useHasUnreadMessages } from '@/state/chat'; import { useCharge } from '@/state/docket'; @@ -141,6 +142,7 @@ export default function MobileSidebar() { const safeAreaInsets = useSafeAreaInsets(); const { isChatInputFocused } = useChatInputFocus(); const groupsCharge = useCharge('groups'); + const showTabBar = useShowTabBar(); useEffect(() => { if (groupsCharge && needsUpdate && !informedOfUpdate) { @@ -159,63 +161,68 @@ export default function MobileSidebar() { return (
-
- +
+ ) : null}
); } diff --git a/apps/tlon-web/src/dms/Dm.tsx b/apps/tlon-web/src/dms/Dm.tsx index d189cef739..d765d3b416 100644 --- a/apps/tlon-web/src/dms/Dm.tsx +++ b/apps/tlon-web/src/dms/Dm.tsx @@ -29,6 +29,7 @@ import { useDragAndDrop } from '@/logic/DragAndDropContext'; import { useIsScrolling } from '@/logic/scroll'; import { useIsMobile } from '@/logic/useMedia'; import useMessageSelector from '@/logic/useMessageSelector'; +import useShowTabBar from '@/logic/useShowTabBar'; import { dmListPath } from '@/logic/utils'; import { useDmIsPending, useDmUnread, useSendMessage } from '@/state/chat'; import { useContact } from '@/state/contact'; @@ -118,9 +119,10 @@ export default function Dm() { const unread = useDmUnread(ship); const scrollElementRef = useRef(null); const isScrolling = useIsScrolling(scrollElementRef); + const showTabBar = useShowTabBar(); const canStart = ship && !!unread; const root = `/dm/${ship}`; - const shouldApplyPaddingBottom = isMobile && !isChatInputFocused; + const shouldApplyPaddingBottom = showTabBar && !isChatInputFocused; const { matchedOrPending, isLoading: negotiationLoading } = useNegotiate( ship, 'chat', diff --git a/apps/tlon-web/src/dms/MultiDm.tsx b/apps/tlon-web/src/dms/MultiDm.tsx index 89221bd27a..6ef06297e9 100644 --- a/apps/tlon-web/src/dms/MultiDm.tsx +++ b/apps/tlon-web/src/dms/MultiDm.tsx @@ -23,6 +23,7 @@ import { useDragAndDrop } from '@/logic/DragAndDropContext'; import { useIsScrolling } from '@/logic/scroll'; import { useIsMobile } from '@/logic/useMedia'; import useMessageSelector from '@/logic/useMessageSelector'; +import useShowTabBar from '@/logic/useShowTabBar'; import { dmListPath, pluralize } from '@/logic/utils'; import { useMultiDm, useMultiDmIsPending, useSendMessage } from '@/state/chat'; import { useNegotiateMulti } from '@/state/negotiation'; @@ -85,7 +86,8 @@ export default function MultiDm() { const root = `/dm/${clubId}`; const scrollElementRef = useRef(null); const isScrolling = useIsScrolling(scrollElementRef); - const shouldApplyPaddingBottom = isMobile && !isChatInputFocused; + const showTabBar = useShowTabBar(); + const shouldApplyPaddingBottom = showTabBar && !isChatInputFocused; const dmParticipants = [...(club?.team ?? []), ...(club?.hive ?? [])]; const { match: negotiationMatch, isLoading: negotiationLoading } = useNegotiateMulti(dmParticipants, 'chat', 'chat'); diff --git a/apps/tlon-web/src/heap/HeapDetail.tsx b/apps/tlon-web/src/heap/HeapDetail.tsx index 83b399d09c..19343b4b41 100644 --- a/apps/tlon-web/src/heap/HeapDetail.tsx +++ b/apps/tlon-web/src/heap/HeapDetail.tsx @@ -13,6 +13,7 @@ import { useFullChannel } from '@/logic/channel'; import getKindDataFromEssay from '@/logic/getKindData'; import { useGroupsAnalyticsEvent } from '@/logic/useAnalyticsEvent'; import { useIsMobile } from '@/logic/useMedia'; +import useShowTabBar from '@/logic/useShowTabBar'; import { useIsPostUndelivered, useOrderedPosts, @@ -45,7 +46,8 @@ export default function HeapDetail({ title }: ViewProps) { const { post: note, isLoading } = usePost(nest, idTime || ''); const { title: curioTitle } = getKindDataFromEssay(note.essay); const { isChatInputFocused } = useChatInputFocus(); - const shouldApplyPaddingBottom = isMobile && !isChatInputFocused; + const showTabBar = useShowTabBar(); + const shouldApplyPaddingBottom = showTabBar && !isChatInputFocused; const { nextPost: nextNote, prevPost: prevNote } = useOrderedPosts( nest, idTime || '' diff --git a/apps/tlon-web/src/logic/SafeAreaContext.tsx b/apps/tlon-web/src/logic/SafeAreaContext.tsx index 5b3d321a9b..5ba2eb54a3 100644 --- a/apps/tlon-web/src/logic/SafeAreaContext.tsx +++ b/apps/tlon-web/src/logic/SafeAreaContext.tsx @@ -7,7 +7,7 @@ import { useState, } from 'react'; -const defaultSafeAreaInsets = window.safeAreaInsets ?? { +const defaultSafeAreaInsets = window.nativeOptions?.safeAreaInsets ?? { top: 0, bottom: 0, left: 0, diff --git a/apps/tlon-web/src/logic/useShowTabBar.ts b/apps/tlon-web/src/logic/useShowTabBar.ts new file mode 100644 index 0000000000..ef19b12887 --- /dev/null +++ b/apps/tlon-web/src/logic/useShowTabBar.ts @@ -0,0 +1,6 @@ +import { useIsMobile } from './useMedia'; + +export default function useShowTabBar() { + const isMobile = useIsMobile(); + return isMobile && !window.nativeOptions?.hideTabBar; +} diff --git a/apps/tlon-web/src/window.ts b/apps/tlon-web/src/window.ts index 3f81f13fd2..0337504ac4 100644 --- a/apps/tlon-web/src/window.ts +++ b/apps/tlon-web/src/window.ts @@ -1,3 +1,5 @@ +import type { NativeWebViewOptions } from '@tloncorp/shared'; + import { Rope } from './types/hark'; declare global { @@ -8,18 +10,12 @@ declare global { scroller?: string; bootstrapApi: boolean; toggleDevTools: () => void; - ReactNativeWebView?: { - postMessage: (message: string) => void; - }; markRead: Rope; recents: any; - colorscheme: any; - safeAreaInsets?: { - top: number; - bottom: number; - left: number; - right: number; + ReactNativeWebView?: { + postMessage: (message: string) => void; }; + nativeOptions?: NativeWebViewOptions; } } diff --git a/package-lock.json b/package-lock.json index d49436eb65..d7248f9579 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "homestead", + "name": "landscape-apps", "lockfileVersion": 3, "requires": true, "packages": { @@ -97,8 +97,10 @@ "@react-native-community/netinfo": "11.1.0", "@react-native-firebase/app": "^18.4.0", "@react-native-firebase/crashlytics": "^18.4.0", + "@react-navigation/bottom-tabs": "^6.5.12", "@react-navigation/native": "^6.1.7", "@react-navigation/native-stack": "^6.9.13", + "@tloncorp/shared": "*", "@urbit/aura": "^1.0.0", "classnames": "^2.3.2", "expo": "^50.0.6", @@ -129,6 +131,7 @@ "react-native-safe-area-context": "4.8.2", "react-native-screens": "~3.29.0", "react-native-storage": "^1.0.1", + "react-native-svg": "^14.1.0", "react-native-webview": "13.6.4", "tailwind-rn": "^4.2.0" }, @@ -150,6 +153,7 @@ "postcss": "^8.4.26", "prettier": "^3.2.5", "prettier-plugin-tailwindcss": "^0.5.4", + "react-native-svg-transformer": "^1.3.0", "tailwindcss": "^3.3.3", "typescript": "^5.1.3", "vitest": "^1.0.4" @@ -12547,6 +12551,23 @@ "react-native": "*" } }, + "node_modules/@react-navigation/bottom-tabs": { + "version": "6.5.12", + "resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-6.5.12.tgz", + "integrity": "sha512-8gBHHvgmJSRGfQ5fcFUgDFcXj1MzDzEZJ/llDYvcSb6ZxgN5xVq+4oVkwPMxOM6v+Qm2nKvXiUKuB/YydhzpLw==", + "dependencies": { + "@react-navigation/elements": "^1.3.22", + "color": "^4.2.3", + "warn-once": "^0.1.0" + }, + "peerDependencies": { + "@react-navigation/native": "^6.0.0", + "react": "*", + "react-native": "*", + "react-native-safe-area-context": ">= 3.0.0", + "react-native-screens": ">= 3.0.0" + } + }, "node_modules/@react-navigation/core": { "version": "6.4.10", "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-6.4.10.tgz", @@ -13056,6 +13077,334 @@ "sourcemap-codec": "^1.4.8" } }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@svgr/core/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@svgr/core/node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/plugin-svgo": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz", + "integrity": "sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==", + "dev": true, + "dependencies": { + "cosmiconfig": "^8.1.3", + "deepmerge": "^4.3.1", + "svgo": "^3.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, + "node_modules/@svgr/plugin-svgo/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@svgr/plugin-svgo/node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "optional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/@swc/helpers": { "version": "0.5.1", "license": "Apache-2.0", @@ -14003,6 +14352,15 @@ "node": ">=0.10.0" } }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/@types/aria-query": { "version": "5.0.1", "dev": true, @@ -16102,9 +16460,7 @@ "node_modules/boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "optional": true, - "peer": true + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, "node_modules/bowser": { "version": "2.11.0", @@ -16822,7 +17178,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "dev": true, "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" @@ -16855,7 +17210,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -16866,8 +17220,7 @@ "node_modules/color/node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/color2k": { "version": "2.0.3", @@ -17398,8 +17751,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "optional": true, - "peer": true, "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", @@ -17438,8 +17789,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "optional": true, - "peer": true, "engines": { "node": ">= 6" }, @@ -17462,6 +17811,39 @@ "node": ">=4" } }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true + }, "node_modules/cssstyle": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.0.1.tgz", @@ -17967,8 +18349,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "optional": true, - "peer": true, "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", @@ -17987,16 +18367,12 @@ "type": "github", "url": "https://github.com/sponsors/fb55" } - ], - "optional": true, - "peer": true + ] }, "node_modules/domhandler": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "optional": true, - "peer": true, "dependencies": { "domelementtype": "^2.3.0" }, @@ -18016,8 +18392,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", - "optional": true, - "peer": true, "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", @@ -18027,6 +18401,16 @@ "url": "https://github.com/fb55/domutils?sponsor=1" } }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/dotenv": { "version": "16.4.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.1.tgz", @@ -18132,7 +18516,6 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "devOptional": true, "engines": { "node": ">=0.12" }, @@ -24152,6 +24535,15 @@ "get-func-name": "^2.0.1" } }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "license": "ISC", @@ -25489,6 +25881,16 @@ "version": "1.0.5", "license": "MIT" }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node_modules/nocache": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", @@ -25666,8 +26068,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "optional": true, - "peer": true, "dependencies": { "boolbase": "^1.0.0" }, @@ -26603,6 +27003,12 @@ "which": "bin/which" } }, + "node_modules/path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==", + "dev": true + }, "node_modules/path-exists": { "version": "4.0.0", "license": "MIT", @@ -28110,8 +28516,6 @@ "version": "14.1.0", "resolved": "https://registry.npmjs.org/react-native-svg/-/react-native-svg-14.1.0.tgz", "integrity": "sha512-HeseElmEk+AXGwFZl3h56s0LtYD9HyGdrpg8yd9QM26X+d7kjETrRQ9vCjtxuT5dCZEIQ5uggU1dQhzasnsCWA==", - "optional": true, - "peer": true, "dependencies": { "css-select": "^5.1.0", "css-tree": "^1.1.3" @@ -28121,12 +28525,26 @@ "react-native": "*" } }, + "node_modules/react-native-svg-transformer": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/react-native-svg-transformer/-/react-native-svg-transformer-1.3.0.tgz", + "integrity": "sha512-SV92uRjENDuanHLVuLy2Sdvt6f8vu7qnG8vC9CwBiAXV0BpWN4/wPvfc+r2WPAkcctRZLLOvrGnGA2o8nZd0cg==", + "dev": true, + "dependencies": { + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0", + "@svgr/plugin-svgo": "^8.1.0", + "path-dirname": "^1.0.2" + }, + "peerDependencies": { + "react-native": ">=0.59.0", + "react-native-svg": ">=12.0.0" + } + }, "node_modules/react-native-svg/node_modules/css-tree": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", - "optional": true, - "peer": true, "dependencies": { "mdn-data": "2.0.14", "source-map": "^0.6.1" @@ -28138,9 +28556,7 @@ "node_modules/react-native-svg/node_modules/mdn-data": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", - "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", - "optional": true, - "peer": true + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==" }, "node_modules/react-native-webview": { "version": "13.6.4", @@ -29527,6 +29943,16 @@ "node": ">=8.0.0" } }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/sorted-btree": { "version": "1.8.1", "license": "MIT" @@ -30235,6 +30661,46 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true + }, + "node_modules/svgo": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz", + "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==", + "dev": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/swr": { "version": "2.2.0", "license": "MIT", diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index 3e6a85ed4f..a0ad76b180 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -1 +1,2 @@ export type { GroupMeta } from "./types/groups"; +export type { NativeWebViewOptions } from "./types/native"; diff --git a/packages/shared/src/types/native.ts b/packages/shared/src/types/native.ts new file mode 100644 index 0000000000..69b55b05ae --- /dev/null +++ b/packages/shared/src/types/native.ts @@ -0,0 +1,10 @@ +export interface NativeWebViewOptions { + colorScheme?: "light" | "dark" | null; + hideTabBar?: boolean; + safeAreaInsets?: { + top: number; + bottom: number; + left: number; + right: number; + }; +}