Skip to content

Commit

Permalink
Merge branch 'develop' into db/debug-utils
Browse files Browse the repository at this point in the history
  • Loading branch information
dnbrwstr committed Mar 13, 2024
2 parents 7bdbf80 + 4bc0fe7 commit e7aa4cc
Show file tree
Hide file tree
Showing 33 changed files with 699 additions and 510 deletions.
2 changes: 1 addition & 1 deletion apps/tlon-mobile/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
compileSdk rootProject.ext.compileSdkVersion
versionCode 48
versionCode 58
versionName "4.0.0"

buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString())
Expand Down
4 changes: 2 additions & 2 deletions apps/tlon-mobile/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,14 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
},
ios: {
runtimeVersion: '4.0.0',
buildNumber: '48',
buildNumber: '58',
config: {
usesNonExemptEncryption: false,
},
},
android: {
runtimeVersion: '4.0.0',
versionCode: 48,
versionCode: 58,
},
updates: {
url: `https://u.expo.dev/${projectId}`,
Expand Down
156 changes: 57 additions & 99 deletions apps/tlon-mobile/ios/Landscape.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/tlon-mobile/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1682,7 +1682,7 @@ SPEC CHECKSUMS:
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
sqlite3: f163dbbb7aa3339ad8fc622782c2d9d7b72f7e9c
UMAppLoader: 5df85360d65cabaef544be5424ac64672e648482
Yoga: 1b901a6d6eeba4e8a2e8f308f708691cdb5db312
Yoga: 64cd2a583ead952b0315d5135bf39e053ae9be70

PODFILE CHECKSUM: 82da24eb176d4abdeaf445b3581717ec492dd7e8

Expand Down
72 changes: 17 additions & 55 deletions apps/tlon-mobile/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,26 @@
import NetInfo from '@react-native-community/netinfo';
import {
CommonActions,
DarkTheme,
DefaultTheme,
NavigationContainer,
useNavigation,
} from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { TamaguiProvider } from '@tloncorp/ui';
import { PostHogProvider } from 'posthog-react-native';
import { useEffect, useState } from 'react';
import { Alert, StatusBar, Text, View } from 'react-native';
import { StatusBar, Text, View } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { useTailwind } from 'tailwind-rn';

import AuthenticatedApp from './components/AuthenticatedApp';
import { LoadingSpinner } from './components/LoadingSpinner';
import { BranchProvider, useBranch } from './contexts/branch';
import { ShipProvider, useShip } from './contexts/ship';
import * as db from './db';
import { useDeepLink } from './hooks/useDeepLink';
import { useIsDarkMode } from './hooks/useIsDarkMode';
import { useScreenOptions } from './hooks/useScreenOptions';
import { inviteShipWithLure } from './lib/hostingApi';
import { syncContacts } from './lib/sync';
import { TabStack } from './navigation/TabStack';
import { CheckVerifyScreen } from './screens/CheckVerifyScreen';
import { EULAScreen } from './screens/EULAScreen';
import { JoinWaitListScreen } from './screens/JoinWaitListScreen';
Expand All @@ -39,7 +36,7 @@ import { SignUpPasswordScreen } from './screens/SignUpPasswordScreen';
import { TlonLoginScreen } from './screens/TlonLoginScreen';
import { WelcomeScreen } from './screens/WelcomeScreen';
import type { OnboardingStackParamList } from './types';
import { posthogAsync, trackError } from './utils/posthog';
import { posthogAsync } from './utils/posthog';
import { getPathFromWer } from './utils/string';

type Props = {
Expand All @@ -48,15 +45,15 @@ type Props = {

const OnboardingStack = createNativeStackNavigator<OnboardingStackParamList>();

const App = ({ wer: initialWer }: Props) => {
// on Android if a notification click causes the app to open, the corresponding notification
// path is passed in here as "wer"
const App = ({ wer }: Props) => {
const isDarkMode = useIsDarkMode();
const tailwind = useTailwind();
const { isLoading, isAuthenticated, ship } = useShip();
const { isLoading, isAuthenticated } = useShip();
const [connected, setConnected] = useState(true);
const { wer, lure, priorityToken, clearDeepLink } = useDeepLink();
const navigation = useNavigation();
const { lure, priorityToken } = useBranch();
const screenOptions = useScreenOptions();
const gotoPath = initialWer ? getPathFromWer(initialWer) : wer;

useEffect(() => {
if (isAuthenticated) {
Expand All @@ -76,45 +73,6 @@ const App = ({ wer: initialWer }: Props) => {
};
}, []);

useEffect(() => {
// User received a lure link while authenticated
if (ship && lure) {
(async () => {
try {
await inviteShipWithLure({ ship, lure });
Alert.alert(
'',
'Your invitation to the group is on its way. It will appear in the Groups list.',
[
{
text: 'OK',
onPress: () => null,
},
],
{ cancelable: true }
);
} catch (err) {
console.error('Error inviting ship with lure:', err);
if (err instanceof Error) {
trackError(err);
}
}

clearDeepLink();
})();
}
}, [ship, lure, clearDeepLink]);

useEffect(() => {
// Broadcast path update to webview when changed
if (isAuthenticated && gotoPath) {
navigation.dispatch(CommonActions.setParams({ gotoPath }));

// Clear the deep link to mark it as handled
clearDeepLink();
}
}, [isAuthenticated, gotoPath, navigation, clearDeepLink]);

return (
<GestureHandlerRootView style={tailwind('flex-1')}>
<SafeAreaProvider>
Expand All @@ -125,7 +83,9 @@ const App = ({ wer: initialWer }: Props) => {
<LoadingSpinner />
</View>
) : isAuthenticated ? (
<TabStack />
<AuthenticatedApp
initialNotificationPath={getPathFromWer(wer ?? '')}
/>
) : (
<OnboardingStack.Navigator
initialRouteName="Welcome"
Expand Down Expand Up @@ -239,9 +199,11 @@ export default function ConnectedApp(props: Props) {
<TamaguiProvider defaultTheme={isDarkMode ? 'dark' : 'light'}>
<ShipProvider>
<NavigationContainer theme={isDarkMode ? DarkTheme : DefaultTheme}>
<PostHogProvider client={posthogAsync} autocapture>
<App {...props} />
</PostHogProvider>
<BranchProvider>
<PostHogProvider client={posthogAsync} autocapture>
<App {...props} />
</PostHogProvider>
</BranchProvider>
</NavigationContainer>
</ShipProvider>
</TamaguiProvider>
Expand Down
36 changes: 36 additions & 0 deletions apps/tlon-mobile/src/components/AuthenticatedApp.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ZStack } from '@tloncorp/ui';

import { WebviewPositionProvider } from '../contexts/webview/position';
import { WebviewProvider } from '../contexts/webview/webview';
import { useDeepLinkListener } from '../hooks/useDeepLinkListener';
import useNotificationListener from '../hooks/useNotificationListener';
import { TabStack } from '../navigation/TabStack';
import WebviewOverlay from './WebviewOverlay';

export interface AuthenticatedAppProps {
initialNotificationPath?: string;
}

function AuthenticatedApp({ initialNotificationPath }: AuthenticatedAppProps) {
useNotificationListener(initialNotificationPath);
useDeepLinkListener();

return (
<ZStack flex={1}>
<TabStack />
<WebviewOverlay />
</ZStack>
);
}

export default function ConnectedAuthenticatedApp(
props: AuthenticatedAppProps
) {
return (
<WebviewPositionProvider>
<WebviewProvider>
<AuthenticatedApp {...props} />
</WebviewProvider>
</WebviewPositionProvider>
);
}
91 changes: 9 additions & 82 deletions apps/tlon-mobile/src/components/SingletonWebview.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { useFocusEffect } from '@react-navigation/native';
// import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import {
type MobileNavTab,
type NativeWebViewOptions,
type WebAppAction,
} from '@tloncorp/shared';
import * as Clipboard from 'expo-clipboard';
import { addNotificationResponseReceivedListener } from 'expo-notifications';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useCallback, useEffect, useRef } from 'react';
import { Alert, Linking, useColorScheme } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { URL } from 'react-native-url-polyfill';
Expand All @@ -19,14 +16,8 @@ import { useShip } from '../contexts/ship';
import { useWebViewContext } from '../contexts/webview/webview';
import { useWebView } from '../hooks/useWebView';
import WebAppHelpers from '../lib/WebAppHelpers';
import { markChatRead } from '../lib/chatApi';
import { getHostingUser } from '../lib/hostingApi';
// import { connectNotifications } from '../lib/notifications';
import {
getHostingUserId,
removeHostingToken,
removeHostingUserId,
} from '../utils/hosting';
import { isUsingTlonAuth } from '../lib/hostingApi';
import { removeHostingToken, removeHostingUserId } from '../utils/hosting';

// TODO: add typing for data beyond generic value string
type WebAppCommand = {
Expand All @@ -46,8 +37,6 @@ export const SingletonWebview = () => {
const colorScheme = useColorScheme();
const safeAreaInsets = useSafeAreaInsets();
const webviewRef = useRef<WebView>(null);
const didManageAccount = useRef(false);
const [appLoaded, setAppLoaded] = useState(false);
const webviewContext = useWebViewContext();

const handleLogout = useCallback(() => {
Expand Down Expand Up @@ -84,7 +73,7 @@ export const SingletonWebview = () => {
);
break;
case 'activeTabChange':
webviewContext.setGotoTab(value as MobileNavTab);
webviewContext.setNewWebappTab(value as MobileNavTab);
break;
case 'saveLastPath': {
if (!value || typeof value !== 'object' || !value.tab || !value.path) {
Expand All @@ -99,81 +88,18 @@ export const SingletonWebview = () => {
}
break;
}
// TODO: handle manage account
case 'manageAccount':
webviewContext.setDidManageAccount(true);
webviewContext.setManageAccountState('triggered');
break;
case 'appLoaded':
// Slight delay otherwise white background flashes
setTimeout(() => {
setAppLoaded(true);
webviewContext.setAppLoaded(true);
}, 100);
break;
}
};

useEffect(() => {
// Start notification tap listener
// This only seems to get triggered on iOS. Android handles the tap and other intents in native code.
const notificationTapListener = addNotificationResponseReceivedListener(
(response) => {
const {
actionIdentifier,
userText,
notification: {
request: {
content: { data },
},
},
} = response;
if (actionIdentifier === 'markAsRead' && data.channelId) {
markChatRead(data.channelId);
} else if (actionIdentifier === 'reply' && userText) {
// Send reply
} else if (data.wer) {
// TODO: handle wer
// setUri((curr) => ({
// ...curr,
// uri: createUri(shipUrl, data.wer),
// key: curr.key + 1,
// }));
}
}
);

// connectNotifications();

return () => {
// Clean up listeners
notificationTapListener.remove();
};
}, [shipUrl]);

// 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(() => {
if (!didManageAccount.current) {
return;
}

(async () => {
const hostingUserId = await getHostingUserId();
if (hostingUserId) {
try {
const user = await getHostingUser(hostingUserId);
if (user.verified) {
didManageAccount.current = false;
} else {
handleLogout();
}
} catch (err) {
handleLogout();
}
}
})();
}, [handleLogout])
);

useEffect(() => {
// Path was changed by the parent view
if (webviewContext.gotoPath) {
Expand All @@ -192,6 +118,7 @@ export const SingletonWebview = () => {
const nativeOptions: NativeWebViewOptions = {
colorScheme,
hideTabBar: true,
isUsingTlonAuth: isUsingTlonAuth(),
safeAreaInsets,
};

Expand All @@ -202,7 +129,7 @@ export const SingletonWebview = () => {
ref={webviewRef}
source={{ uri: createUri(shipUrl, '/') }}
style={
appLoaded
webviewContext.appLoaded
? tailwind('bg-transparent')
: { flex: 0, height: 0, opacity: 0 }
}
Expand All @@ -220,7 +147,7 @@ export const SingletonWebview = () => {
`}
onLoad={() => {
// Start a timeout in case the web app doesn't send the appLoaded message
setTimeout(() => setAppLoaded(true), 10_000);
setTimeout(() => webviewContext.setAppLoaded(true), 10_000);
}}
onShouldStartLoadWithRequest={({ url }) => {
const parsedUrl = new URL(url);
Expand Down
23 changes: 23 additions & 0 deletions apps/tlon-mobile/src/components/WebviewOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { View } from '@tloncorp/ui';

import { useWebviewPositionContext } from '../contexts/webview/position';
import { SingletonWebview } from './SingletonWebview';

export default function WebviewOverlay() {
const { position, visible } = useWebviewPositionContext();
return (
<View
style={{
position: 'absolute',
top: position.y,
left: position.x,
width: position.width,
height: position.height,
opacity: !visible ? 0 : undefined,
pointerEvents: !visible ? 'none' : undefined,
}}
>
<SingletonWebview />
</View>
);
}
Loading

0 comments on commit e7aa4cc

Please sign in to comment.