Skip to content
This repository has been archived by the owner on Apr 29, 2024. It is now read-only.

Commit

Permalink
Added basic error handling (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
spietras authored May 25, 2022
1 parent 51c6e5b commit a601d23
Show file tree
Hide file tree
Showing 9 changed files with 151 additions and 52 deletions.
17 changes: 8 additions & 9 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { NavigationContainer } from "@react-navigation/native";
import Stack from "./src/navigation/Stack";
import Play from "./src/screens/Play";
import Settings from "./src/screens/Settings";
import Error from "./src/screens/Error";
import { gestureHandlerRootHOC } from "react-native-gesture-handler";
import * as SplashScreen from "expo-splash-screen";
import React from "react";
Expand All @@ -17,8 +18,6 @@ import { MidiProvider } from "./src/contexts/midi";
import { RoomProvider } from "./src/contexts/room";
import { PlayerProvider } from "./src/contexts/player";
import { LogBox } from "react-native";
import useFetch from "./src/hooks/useFetch";
import { EntriesResponse } from "./src/server/models";
import { fonts, iceServers, urls } from "./src/constants";

LogBox.ignoreLogs([
Expand All @@ -29,12 +28,12 @@ LogBox.ignoreLogs([
const mainScreen = gestureHandlerRootHOC(Main);
const playScreen = gestureHandlerRootHOC(Play);
const settingsScreen = gestureHandlerRootHOC(Settings);
const errorScreen = gestureHandlerRootHOC(Error);

export default function App() {
useDeviceContext(tw);

const [fontsLoaded] = useFonts(fonts);
const { data } = useFetch<EntriesResponse>(urls.entries);

const onLayoutReady = React.useCallback(
async () => await SplashScreen.hideAsync(),
Expand All @@ -45,7 +44,7 @@ export default function App() {
SplashScreen.preventAutoHideAsync().then();
}, []);

if (!fontsLoaded || data === undefined) return null;
if (!fontsLoaded) return null;

return (
<NavigationContainer onReady={onLayoutReady}>
Expand All @@ -64,11 +63,6 @@ export default function App() {
<Stack.Screen
name="main"
component={mainScreen}
initialParams={{
availableEmojiCodes: data.available.map(
(emoji) => emoji.id
),
}}
options={{ orientation: "default" }}
/>
<Stack.Screen
Expand All @@ -81,6 +75,11 @@ export default function App() {
component={settingsScreen}
options={{ orientation: "default" }}
/>
<Stack.Screen
name="error"
component={errorScreen}
options={{ orientation: "default" }}
/>
</Stack.Navigator>
</PlayerProvider>
</RoomProvider>
Expand Down
17 changes: 17 additions & 0 deletions src/components/Loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import ReactNative from "react-native";
import tw from "../tailwind";
import Heading from "./text/Heading";

export type LoadingProps = ReactNative.ViewProps;

export default function Loading(props: LoadingProps) {
return (
<ReactNative.View
style={tw.style("flex-1", "items-center", "justify-center", "bg-white")}
>
<ReactNative.ActivityIndicator size="large" />
<ReactNative.View style={{ margin: 10 }} />
<Heading>Loading...</Heading>
</ReactNative.View>
);
}
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Constants from "expo-constants";
import { Nunito_700Bold, Nunito_800ExtraBold } from "@expo-google-fonts/nunito";

export const codeLength = 3;

export const fonts = {
Nunito_700Bold,
Nunito_800ExtraBold,
Expand Down
9 changes: 4 additions & 5 deletions src/navigation/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,18 @@ export type RootStackParamList = {
main: MainParams;
play: PlayParams;
settings: SettingsParams;
error: ErrorParams;
};

export type MainParams = {
availableEmojiCodes: Array<string>;
codeLength?: number;
};

export type MainParams = {};
export type PlayParams = {};
export type SettingsParams = {};
export type ErrorParams = { message?: string };

export type MainProps = NativeStackScreenProps<RootStackParamList, "main">;
export type PlayProps = NativeStackScreenProps<RootStackParamList, "play">;
export type SettingsProps = NativeStackScreenProps<
RootStackParamList,
"settings"
>;
export type ErrorProps = NativeStackScreenProps<RootStackParamList, "error">;
32 changes: 32 additions & 0 deletions src/screens/Error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { ErrorProps } from "../navigation/types";
import ReactNative from "react-native";
import tw from "../tailwind";
import Heading from "../components/text/Heading";
import RegularText from "../components/text/RegularText";
import React from "react";

export default function Main({ route, navigation }: ErrorProps) {
const { message } = route.params;

React.useEffect(() => {
const beforeRemove = (e: { preventDefault: () => void }) => {
e.preventDefault();
navigation.removeListener("beforeRemove", beforeRemove);
navigation.reset({
index: 0,
routes: [{ name: "main" }],
});
};
navigation.addListener("beforeRemove", beforeRemove);
return () => navigation.removeListener("beforeRemove", beforeRemove);
}, [navigation]);

return (
<ReactNative.View
style={tw.style("flex-1", "items-center", "justify-center", "bg-white")}
>
<Heading>Error</Heading>
<RegularText>{message}</RegularText>
</ReactNative.View>
);
}
18 changes: 15 additions & 3 deletions src/screens/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ import useAppDispatch from "../hooks/useAppDispatch";
import { resetCode, setCode } from "../state/slices/code";
import useCancellable from "../hooks/useCancellable";
import { resetUsers } from "../state/slices/users";
import { codeLength, urls } from "../constants";
import useFetch from "../hooks/useFetch";
import { EntriesResponse } from "../server/models";
import Loading from "../components/Loading";

export default function Main({ route, navigation }: MainProps) {
const { availableEmojiCodes, codeLength = 3 } = route.params;

const [localCode, setLocalCode] = React.useState<Array<string>>([]);

const { status, data, error } = useFetch<EntriesResponse>(urls.entries);

const dispatch = useAppDispatch();

const onEmojiSelected: (emojiId: string) => void = (emojiId: string) => {
Expand All @@ -34,6 +38,11 @@ export default function Main({ route, navigation }: MainProps) {
};
}, [navigation]);

React.useEffect(() => {
if (error !== undefined)
navigation.navigate("error", { message: "Can't connect to server" });
}, [navigation, JSON.stringify(error)]);

useCancellable(
(cancelInfo) => {
if (localCode.length < codeLength) return;
Expand All @@ -45,6 +54,9 @@ export default function Main({ route, navigation }: MainProps) {
[JSON.stringify(localCode)]
);

if (status === "idle" || status === "fetching" || data === undefined)
return <Loading />;

return (
<ReactNative.View style={tw.style("flex-1", "bg-white")}>
<ReactNative.View
Expand All @@ -62,7 +74,7 @@ export default function Main({ route, navigation }: MainProps) {
style={tw.style("flex-1", "items-center", "justify-center")}
>
<EmojiGrid
emojiIds={availableEmojiCodes}
emojiIds={data.available.map((emoji) => emoji.id)}
onEmojiSelected={onEmojiSelected}
style={tw.style("my-4")}
/>
Expand Down
95 changes: 62 additions & 33 deletions src/screens/Play.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { useMidi } from "../contexts/midi";
import { usePlayer } from "../contexts/player";
import { User } from "../server/models";
import { midiInputs } from "../constants";
import Loading from "../components/Loading";

type KeyboardState = Map<number, string>;

Expand Down Expand Up @@ -74,6 +75,11 @@ export default function Play({ navigation }: PlayProps) {
new Map()
);

const [roomReady, setRoomReady] = React.useState<boolean>(false);
const [playerReady, setPlayerReady] = React.useState<boolean>(false);

const [error, setError] = React.useState<string>();

const room = useRoom();
const midi = useMidi();
const player = usePlayer();
Expand Down Expand Up @@ -235,28 +241,36 @@ export default function Play({ navigation }: PlayProps) {
[player.player]
);

useCancellable(() => {
if (!code) return;
room
.connect(
code,
handleRoomConnected,
handleUserConnected,
handleUserDisconnected,
handleRoomEvent
)
.then();
return () => {
room.disconnect().then();
};
}, [
room,
JSON.stringify(code),
handleRoomConnected,
handleUserConnected,
handleUserDisconnected,
handleRoomEvent,
]);
useCancellable(
(cancelInfo) => {
if (!code) return;
room
.connect(
code,
handleRoomConnected,
handleUserConnected,
handleUserDisconnected,
handleRoomEvent
)
.then(() => {
if (!cancelInfo.cancelled) setRoomReady(true);
})
.catch(() => {
if (!cancelInfo.cancelled) setError("Can't connect to room.");
});
return () => {
room.disconnect().then();
};
},
[
room,
JSON.stringify(code),
handleRoomConnected,
handleUserConnected,
handleUserDisconnected,
handleRoomEvent,
]
);

useCancellable(() => {
if (
Expand All @@ -280,20 +294,35 @@ export default function Play({ navigation }: PlayProps) {
handleUserMidiKeyPressedOut,
]);

useCancellable(() => {
if (settings.instrument.value === undefined) return;
useCancellable(
(cancelInfo) => {
if (settings.instrument.value === undefined) return;

const load = async (instrument: string, notes: number[]) => {
await player.player.unload();
await player.player.load(instrument, notes);
};
const load = async (instrument: string, notes: number[]) => {
await player.player.unload();
await player.player.load(instrument, notes);
};

load(settings.instrument.value, allNotes).then();
load(settings.instrument.value, allNotes)
.then(() => {
if (!cancelInfo.cancelled) setPlayerReady(true);
})
.catch(() => {
if (!cancelInfo.cancelled) setError("Can't load sound player.");
});

return () => {
player.player.unload().then();
};
}, [player.player, settings.instrument.value, JSON.stringify(allNotes)]);
return () => {
player.player.unload().then();
};
},
[player.player, settings.instrument.value, JSON.stringify(allNotes)]
);

React.useEffect(() => {
if (error !== undefined) navigation.navigate("error", { message: error });
}, [navigation, error]);

if (!roomReady || !playerReady || user === undefined) return <Loading />;

return (
<ReactNative.View style={tw.style("flex-1", "bg-white")}>
Expand Down
3 changes: 2 additions & 1 deletion src/screens/Settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useSettings } from "../contexts/settings";
import useAppSelector from "../hooks/useAppSelector";
import { useMidi } from "../contexts/midi";
import { usePlayer } from "../contexts/player";
import Loading from "../components/Loading";

const margins = {
vertical: 16,
Expand Down Expand Up @@ -123,7 +124,7 @@ export default function Settings({ navigation }: SettingsProps) {
[settings.midiInput.setValue]
);

if (!code) return null;
if (!code) return <Loading />;

return (
<ReactNative.View style={tw.style("flex-1", "bg-white")}>
Expand Down
10 changes: 9 additions & 1 deletion src/server/ServerConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,22 @@ export default class ServerConnection {
return await this.lock.acquire("connection", async () => {
const ws = new WebSocket(url);

const open = new Promise<void>((resolve) => {
const open = new Promise<void>((resolve, reject) => {
const onOpen = () => {
ws.send(JSON.stringify(ServerConnection.createRequestMessage(code)));
ws.removeEventListener("open", onOpen);
ws.removeEventListener("error", onError);
resolve();
};

const onError = (e: Event) => {
ws.removeEventListener("error", onError);
ws.removeEventListener("open", onOpen);
reject(e);
};

ws.addEventListener("open", onOpen);
ws.addEventListener("error", onError);

this.callbacks.forEach((callback, event) =>
ws.addEventListener(event, callback)
Expand Down

0 comments on commit a601d23

Please sign in to comment.