Skip to content

Commit

Permalink
Use config files to enable iOS deep linking (#104)
Browse files Browse the repository at this point in the history
* Use config files to enable iOS deep linking

* Use https to enable deep linking

* fixup! Use https to enable deep linking

* fixup! Use https to enable deep linking

* Fix global reactions for summaries

* Fix expo incompatibilities

* fixup! Fix expo incompatibilities

* fixup! Fix expo incompatibilities

* Fix expo deep linking

* Fix login state race condition

* fixup! Fix login state race condition

* fixup! Fix login state race condition

* fixup! Fix login state race condition

* fixup! Fix login state race condition
  • Loading branch information
bobness authored Dec 29, 2022
1 parent f1d7f18 commit fb25fc1
Show file tree
Hide file tree
Showing 16 changed files with 1,793 additions and 1,635 deletions.
228 changes: 147 additions & 81 deletions App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import "react-native-gesture-handler";
import React, { useCallback, useState, useRef, useEffect } from "react";
import React, {
useCallback,
useState,
useRef,
useEffect,
useMemo,
} from "react";
import { StatusBar } from "expo-status-bar";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { Platform } from "react-native";
import { ActivityIndicator, Platform, View } from "react-native";
import {
NavigationContainer,
useNavigationContainerRef,
Expand All @@ -12,6 +18,7 @@ import ReceiveSharingIntent from "react-native-receive-sharing-intent";
import * as TaskManager from "expo-task-manager";
import * as Notifications from "expo-notifications";
import * as Device from "expo-device";
import * as Linking from "expo-linking";

import { createNativeStackNavigator } from "@react-navigation/native-stack";
import LoginScreen from "./src/pages/LoginScreen";
Expand Down Expand Up @@ -52,13 +59,12 @@ interface ShareObject {
weblink: string | null;
}

interface NavigationPath {
pathString: string;
args: any;
interface RouteObejct {
path: string;
args?: any;
}

export default function App() {
// TODO: not sure how to type this for its .navigate() arguments
const navigationRef = useNavigationContainerRef();
const [user, setUser] = useState<any | undefined>();
const [notification, setNotification] = useState<Notification | undefined>();
Expand All @@ -68,31 +74,51 @@ export default function App() {
const [currentSummaryId, setCurrentSummaryId] = useState<
number | undefined
>();

const [userLoading, setUserLoading] = useState(true);
const [deepLinkLoading, setDeepLinkLoading] = useState(false);
const [navigationIsReady, setNavigationIsReady] = useState(false);
const [desiredRoute, setDesiredRoute] = useState<RouteObejct | undefined>();
const deepLinkUrlRegex = useMemo(
() => RegExp("https?://inspect.datagotchi.net/facts/([a-z0-9]+).*"),
[]
);
const deepLinkUrl = Linking.useURL();
const currentRoute = useMemo(
() =>
navigationIsReady ? navigationRef.getCurrentRoute()?.name : undefined,
[navigationIsReady, navigationRef]
);

const [queuedPath, setQueuedPath] = useState<NavigationPath | undefined>();
useEffect(() => {
if (navigationIsReady && queuedPath) {
const { pathString, args } = queuedPath;
setQueuedPath(undefined);
navigationRef.navigate(pathString, args);
return () => {
setDesiredRoute(undefined);
};
}, []);

useEffect(() => {
if (navigationIsReady) {
if (desiredRoute && currentRoute !== desiredRoute.path) {
navigationRef.navigate(desiredRoute.path, desiredRoute.args);
}
}
}, [navigationIsReady]);
}, [navigationIsReady, currentRoute, desiredRoute]);

instance.interceptors.response.use(
(response) => response,
(error) => {
if (error?.response?.status === 401) {
navigationRef.navigate("Login");
setDesiredRoute({ path: "Login" });
}
return error;
}
);

const handleShare = useCallback(([shareObject]: ShareObject[]) => {
navigationRef.navigate("CreateSummary", {
data: shareObject,
setDesiredRoute({
path: "CreateSummary",
args: {
data: shareObject,
},
});
}, []);
ReceiveSharingIntent.getReceivedFiles(
Expand All @@ -105,12 +131,15 @@ export default function App() {

useEffect(() => {
if (!user) {
AsyncStorage.getItem("@user").then((userInfo) => {
if (userInfo) {
const storedUserInfo = JSON.parse(userInfo);
setUser(storedUserInfo);
}
});
setUserLoading(true);
AsyncStorage.getItem("@user")
.then((userInfo) => {
if (userInfo) {
const storedUserInfo = JSON.parse(userInfo);
setUser(storedUserInfo);
}
})
.finally(() => setUserLoading(false));
}
}, [user]);

Expand All @@ -126,7 +155,7 @@ export default function App() {
Notifications.addNotificationResponseReceivedListener((response) => {
const data = response.notification.request.content.data;
if (data && data.id) {
navigationRef.navigate("NewsView", { data });
setDesiredRoute({ path: "NewsView", args: { data } });
}
});

Expand All @@ -142,67 +171,104 @@ export default function App() {
};
}, []);

const handleOnLogin = (userObject: any) => {
if (expoToken) {
updateUserExpoToken(expoToken);
userObject.expo_token = expoToken;
AsyncStorage.setItem("@user", JSON.stringify(userObject));
setUser(userObject);
const handleOnLogin = useCallback(
(userObject: any) => {
if (expoToken && !userObject.expo_token) {
updateUserExpoToken(expoToken);
userObject.expo_token = expoToken;
}
if (userObject.expo_token) {
AsyncStorage.setItem("@user", JSON.stringify(userObject));
setUser(userObject);
setDesiredRoute({ path: "Home" });
} else {
alert("Error: no push notification token available");
}
},
[expoToken]
);

useEffect(() => {
if (user && deepLinkUrl && deepLinkUrl.match(deepLinkUrlRegex)) {
setDeepLinkLoading(true);
const match = deepLinkUrl.match(deepLinkUrlRegex);
const uid = match![1];
setDesiredRoute({ path: "NewsView", args: { data: { uid } } });
}
};
}, [user, deepLinkUrl, deepLinkUrlRegex]);

// ReceiveSharingIntent.clearReceivedFiles();
return (
<NavigationContainer
ref={navigationRef}
onReady={() => setNavigationIsReady(true)}
>
<Stack.Navigator initialRouteName={user ? "Home" : "Login"}>
<Stack.Screen name="Login" options={{ headerShown: false }}>
{(props: any) => (
<LoginScreen {...props} onLoginCallback={handleOnLogin} />
)}
</Stack.Screen>
<Stack.Screen
name="Register"
component={RegisterScreen}
options={{ headerShown: false }}
/>
<Stack.Screen name="Home" options={{ headerShown: false }}>
{(props: any) => (
<HomeScreen
{...props}
clearCurrentSummaryId={() => setCurrentSummaryId(undefined)}
/>
)}
</Stack.Screen>
<Stack.Screen name="NewsView" options={{ headerShown: true }}>
{(props: any) => (
<NewsViewScreen
{...props}
setCurrentSummaryId={setCurrentSummaryId}
/>
)}
</Stack.Screen>
<Stack.Screen
name="AuthorView"
component={AuthorViewScreen}
options={{ headerShown: false }}
/>
<Stack.Screen
name="My Profile"
component={ProfileScreen}
options={{ headerShown: true }}
/>
<Stack.Screen name="CreateSummary" options={{ headerShown: false }}>
{(props: any) => (
<SummaryScreen {...props} currentSummaryId={currentSummaryId} />
)}
</Stack.Screen>
</Stack.Navigator>
<StatusBar style="auto" />
</NavigationContainer>
);

// TODO: add || deepLinkLoading
if (userLoading) {
return (
<View
style={{
flex: 1,
flexDirection: "row",
justifyContent: "space-around",
padding: 10,
}}
>
<ActivityIndicator />
</View>
);
} else {
return (
<NavigationContainer
ref={navigationRef}
onReady={() => setNavigationIsReady(true)}
>
<Stack.Navigator
initialRouteName={desiredRoute?.path ?? "Login"}
initialRouteParams={desiredRoute?.args ?? {}}
>
<Stack.Screen name="Login" options={{ headerShown: false }}>
{(props: any) => (
<LoginScreen {...props} onLoginCallback={handleOnLogin} />
)}
</Stack.Screen>
<Stack.Screen
name="Register"
component={RegisterScreen}
options={{ headerShown: false }}
/>
<Stack.Screen name="Home" options={{ headerShown: false }}>
{(props: any) => (
<HomeScreen
{...props}
clearCurrentSummaryId={() => setCurrentSummaryId(undefined)}
/>
)}
</Stack.Screen>
<Stack.Screen name="NewsView" options={{ headerShown: true }}>
{(props: any) => (
<NewsViewScreen
{...props}
setCurrentSummaryId={setCurrentSummaryId}
/>
)}
</Stack.Screen>
<Stack.Screen
name="AuthorView"
component={AuthorViewScreen}
options={{ headerShown: false }}
/>
<Stack.Screen
name="My Profile"
component={ProfileScreen}
options={{ headerShown: true }}
/>
<Stack.Screen name="CreateSummary" options={{ headerShown: false }}>
{(props: any) => (
<SummaryScreen {...props} currentSummaryId={currentSummaryId} />
)}
</Stack.Screen>
</Stack.Navigator>
<StatusBar style="auto" />
</NavigationContainer>
);
}
}

async function registerForPushNotificationsAsync() {
Expand Down
9 changes: 4 additions & 5 deletions app.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@
"updates": {
"fallbackToCacheTimeout": 0
},
"assetBundlePatterns": [
"**/*"
],
"assetBundlePatterns": ["**/*"],
"plugins": [
[
"expo-notifications",
Expand All @@ -29,11 +27,12 @@
"ios": {
"supportsTablet": true,
"bundleIdentifier": "net.datagotchi.inspect",
"buildNumber": "22"
"buildNumber": "23",
"associatedDomains": ["applinks:inspect.datagotchi.net"]
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"foregroundImage": "./assets/icon.png",
"backgroundColor": "#FFFFFF"
},
"package": "net.datagotchi.inspect"
Expand Down
Binary file modified assets/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit fb25fc1

Please sign in to comment.