diff --git a/app/app/(tabs)/(home)/_layout.tsx b/app/app/(tabs)/(home)/_layout.tsx new file mode 100644 index 0000000..cb69062 --- /dev/null +++ b/app/app/(tabs)/(home)/_layout.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import { Stack } from "expo-router"; +import TaskNavigationBar from "@/components/navigation/TaskNavigationBar"; +import ListNavigationBar from "@/components/navigation/ListNavigationBar"; + +export default function HomeLayout() { + return ( + + , + }} + /> + , + }} + /> + , + }} + /> + + ); +} diff --git a/app/app/(tabs)/(home)/index.tsx b/app/app/(tabs)/(home)/index.tsx new file mode 100644 index 0000000..01dca8e --- /dev/null +++ b/app/app/(tabs)/(home)/index.tsx @@ -0,0 +1,3 @@ +import ListContent from "./lists/[listId]"; + +export default ListContent; diff --git a/app/app/(tabs)/(home)/lists/[listId].tsx b/app/app/(tabs)/(home)/lists/[listId].tsx new file mode 100644 index 0000000..689878f --- /dev/null +++ b/app/app/(tabs)/(home)/lists/[listId].tsx @@ -0,0 +1,157 @@ +import { StyleSheet, ScrollView, View } from "react-native"; +import { Text, Avatar, Card, List, useTheme } from "react-native-paper"; +import useSWR, { preload } from "swr"; +import { ListData, listFetcher, taskFetcher } from "@/api/fetcher"; +import { router, useLocalSearchParams, useNavigation } from "expo-router"; +import { useEffect } from "react"; + +function ListItem(props: { data: ListData["lists"][0]; onPress(): void }) { + const { name, listCount, taskCount } = props.data; + let subtitle = ""; + if (listCount) { + subtitle += `${listCount} lists`; + } + if (listCount && taskCount) { + subtitle += " • "; + } + if (taskCount) { + subtitle += `${taskCount} tasks`; + } + if (!listCount && !taskCount) { + subtitle += "Empty"; + } + + return ( + + } + /*right={(props) => ( + {}} /> + )}*/ + /> + + ); +} + +function TaskItem(props: { data: ListData["tasks"][0]; onPress(): void }) { + const { id, name, schedule } = props.data; + const date = new Date(schedule); + const now = new Date(); + const ready = date <= now; + + return ( + } + onPress={props.onPress} + onPressIn={() => preload(id, taskFetcher)} + /> + ); +} + +export default function ListContent() { + const theme = useTheme(); + const { listId, listName } = useLocalSearchParams<{ + listId: string; + listName?: string; + }>(); + + const navigation = useNavigation(); + const { isLoading, data, error } = useSWR(listId || "all", listFetcher); + useEffect(() => { + let title = ""; + if (isLoading) { + title = listName || "Loading..."; + } else if (data) { + title = data.name; + } else if (error) { + title = "Error"; + } + navigation.setOptions({ title }); + }, [isLoading, data?.name, error, navigation]); + + if (isLoading) { + return ( + + Loading... + + ); + } + if (error || !data) { + return ( + + Something went wrong: {error.toString()} + + ); + } + + const { name, parentId, lists, tasks } = data; + + return ( + + {lists.map((l) => ( + + router.push({ + pathname: "/lists/[listId]", + params: { listId: l.id, listName: l.name }, + }) + } + /> + ))} + {tasks.length > 0 && ( + + Tasks + {tasks.map((t) => ( + + router.push({ + pathname: "/tasks/[taskId]", + params: { taskId: t.id, taskName: t.name }, + }) + } + /> + ))} + + )} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + paddingVertical: 8, + //backgroundColor: "#fff", + /*alignItems: "center", + justifyContent: "center",*/ + }, + padded: { + padding: 16, + flex: 1, + }, + centered: { + alignItems: "center", + }, + image: { + width: 300, + height: 300, + borderRadius: 15, + }, +}); diff --git a/app/app/(tabs)/(home)/tasks/[taskId].tsx b/app/app/(tabs)/(home)/tasks/[taskId].tsx new file mode 100644 index 0000000..7ee25da --- /dev/null +++ b/app/app/(tabs)/(home)/tasks/[taskId].tsx @@ -0,0 +1,99 @@ +import { taskFetcher } from "@/api/fetcher"; +import React, { useEffect } from "react"; +import { useWindowDimensions, View, Image, StyleSheet } from "react-native"; +import { useTheme, Text, Button } from "react-native-paper"; +import useSWR from "swr"; +import RenderHtml from "react-native-render-html"; +import { useLocalSearchParams, useNavigation } from "expo-router"; + +export default function TaskContent() { + const dimensions = useWindowDimensions(); + const theme = useTheme(); + const { taskId, taskName } = useLocalSearchParams<{ + taskId: string; + taskName?: string; + }>(); + + const { isLoading, data, error } = useSWR(taskId, taskFetcher); + const navigation = useNavigation(); + useEffect(() => { + let title = ""; + if (isLoading) { + title = taskName || "Loading..."; + } else if (data) { + title = data.name; + } else if (error) { + title = "Error"; + } + navigation.setOptions({ title }); + }, [isLoading, data?.name, error, navigation]); + + if (isLoading) { + return ( + + Loading... + + ); + } + if (error || !data) { + return ( + + Something went wrong: {error?.toString()} + + ); + } + + const { name, description, schedule, imageSource } = data; + const date = new Date(schedule); + const now = new Date(); + const ready = date <= now; + + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + paddingVertical: 8, + //backgroundColor: "#fff", + /*alignItems: "center", + justifyContent: "center",*/ + }, + padded: { + padding: 16, + flex: 1, + }, + centered: { + alignItems: "center", + }, + image: { + width: 300, + height: 300, + borderRadius: 15, + }, +}); diff --git a/app/app/(tabs)/_layout.tsx b/app/app/(tabs)/_layout.tsx index 5317e13..7b5cb90 100644 --- a/app/app/(tabs)/_layout.tsx +++ b/app/app/(tabs)/_layout.tsx @@ -19,7 +19,7 @@ export default function TabLayout() { }} > , diff --git a/app/app/(tabs)/index.tsx b/app/app/(tabs)/index.tsx deleted file mode 100644 index a982910..0000000 --- a/app/app/(tabs)/index.tsx +++ /dev/null @@ -1,295 +0,0 @@ -import { - StyleSheet, - ScrollView, - View, - useWindowDimensions, - Image, -} from "react-native"; -import { Appbar, Text, Avatar, Card, List, useTheme, Button } from "react-native-paper"; -import { - StackHeaderProps, - createStackNavigator, -} from "@react-navigation/stack"; -import RenderHtml from "react-native-render-html"; -import { HomeScreenNavigationProp } from "./types"; -import useSWR, { preload } from "swr"; -import { getHeaderTitle } from "@react-navigation/elements"; -import { useEffect } from "react"; -import { ListData, listFetcher, randomTask, taskFetcher } from "@/api/fetcher"; - -function ListItem(props: { data: ListData["lists"][0]; onPress(): void }) { - const { name, listCount, taskCount } = props.data; - let subtitle = ""; - if (listCount) { - subtitle += `${listCount} lists`; - } - if (listCount && taskCount) { - subtitle += " • " - } - if (taskCount) { - subtitle += `${taskCount} tasks`; - } - if (!listCount && !taskCount) { - subtitle += "Empty"; - } - - return ( - - } - /*right={(props) => ( - {}} /> - )}*/ - /> - - ); -} - -function TaskItem(props: { data: ListData["tasks"][0]; onPress(): void }) { - const { id, name, schedule } = props.data; - const date = new Date(schedule); - const now = new Date(); - const ready = date <= now; - - return ( - } - onPress={props.onPress} - onPressIn={() => preload(id, taskFetcher)} - /> - ); -} - -function ListContent({ route, navigation }: HomeScreenNavigationProp<"List">) { - const theme = useTheme(); - const { listId, listName } = route.params ?? {}; - - const { isLoading, data, error } = useSWR(listId || "all", listFetcher); - useEffect(() => { - let title = ""; - if (isLoading) { - title = listName || "Loading..."; - } else if (data) { - title = data.name; - } else if (error) { - title = "Error"; - } - navigation.setOptions({ title }); - }, [isLoading, data?.name, error, navigation]); - if (isLoading) { - return ( - - Loading... - - ); - } - if (error || !data) { - return ( - - Something went wrong: {error.toString()} - - ); - } - - const { name, parentId, lists, tasks } = data; - - return ( - - {lists.map((l) => ( - - navigation.push("List", { listId: l.id, listName: l.name }) - } - /> - ))} - {tasks.length > 0 && ( - - Tasks - {tasks.map((t) => ( - - navigation.push("Task", { taskId: t.id, taskName: t.name }) - } - /> - ))} - - )} - - ); -} - -function TaskContent({ route, navigation }: HomeScreenNavigationProp<"Task">) { - const dimensions = useWindowDimensions(); - const theme = useTheme(); - const { taskId, taskName } = route.params; - - const { isLoading, data, error } = useSWR(taskId, taskFetcher); - useEffect(() => { - let title = ""; - if (isLoading) { - title = taskName || "Loading..."; - } else if (data) { - title = data.name; - } else if (error) { - title = "Error"; - } - navigation.setOptions({ title }); - }, [isLoading, data?.name, error, navigation]); - if (isLoading) { - return ( - - Loading... - - ); - } - if (error || !data) { - return ( - - Something went wrong: {error?.toString()} - - ); - } - - const { name, description, schedule, imageSource } = data; - const date = new Date(schedule); - const now = new Date(); - const ready = date <= now; - - return ( - - - - - - ); -} - -function ListNavigationBar({ - navigation, - route, - options, - back, -}: StackHeaderProps) { - const params = route.params as any | undefined; - const title = getHeaderTitle(options, params?.listName || route.name); - - return ( - - {(back || params?.listId) ? (back ? navigation.pop() : navigation.replace("List"))} - /> : null} - - - randomTask(params?.listId || null).then((data) => - navigation.push("Task", { taskId: data.id, taskName: data.name }), - ) - } - /> - {/* {}} />*/} - - ); -} - -function TaskNavigationBar({ - navigation, - route, - options, - back, -}: StackHeaderProps) { - const title = getHeaderTitle( - options, - (route.params as any)?.taskName || route.name, - ); - - return ( - - (back ? navigation.pop() : navigation.replace("List"))} - /> - - {/* {}} />*/} - {/* {}} />*/} - - ); -} - -const Stack = createStackNavigator(); - -export default function ScreenList() { - return ( - - , - }} - /> - , - }} - /> - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - paddingVertical: 8, - //backgroundColor: "#fff", - /*alignItems: "center", - justifyContent: "center",*/ - }, - padded: { - padding: 16, - flex: 1, - }, - centered: { - alignItems: "center", - }, - image: { - width: 300, - height: 300, - borderRadius: 15, - }, -}); diff --git a/app/app/(tabs)/types.ts b/app/app/(tabs)/types.ts deleted file mode 100644 index bea9af6..0000000 --- a/app/app/(tabs)/types.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { - NavigatorScreenParams, - ParamListBase, -} from "@react-navigation/native"; -import type { StackScreenProps } from "@react-navigation/stack"; - -export interface RootBottomParamList extends ParamListBase { - Home: NavigatorScreenParams; - History: undefined; - Profile: undefined; -} - -interface HomeStackParamList extends ParamListBase { - List: { listId: string | null, listName?: string }; - Task: { taskId: string | null, taskName?: string }; -} - -export type HomeScreenNavigationProp< - RouteName extends keyof HomeStackParamList = keyof HomeStackParamList, -> = StackScreenProps; - -declare global { - namespace ReactNavigation { - interface RootParamList extends RootBottomParamList {} - } -} diff --git a/app/app/_layout.tsx b/app/app/_layout.tsx index 458b464..e0804d3 100644 --- a/app/app/_layout.tsx +++ b/app/app/_layout.tsx @@ -1,10 +1,9 @@ import { DarkTheme as NavigationDarkTheme, DefaultTheme as NavigationDefaultTheme, - DefaultTheme, ThemeProvider, } from "@react-navigation/native"; -import { PaperProvider, Text, adaptNavigationTheme } from "react-native-paper"; +import { PaperProvider, adaptNavigationTheme } from "react-native-paper"; import { useFonts } from "expo-font"; import { Stack } from "expo-router"; import * as SplashScreen from "expo-splash-screen"; diff --git a/app/components/navigation/ListNavigationBar.tsx b/app/components/navigation/ListNavigationBar.tsx new file mode 100644 index 0000000..244d38a --- /dev/null +++ b/app/components/navigation/ListNavigationBar.tsx @@ -0,0 +1,40 @@ +import { randomTask } from "@/api/fetcher"; +import { getHeaderTitle } from "@react-navigation/elements"; +import type { NativeStackHeaderProps } from "@react-navigation/native-stack"; +import { router, useGlobalSearchParams } from "expo-router"; +import { Appbar } from "react-native-paper"; + +export default function ListNavigationBar({ + options, + route, +}: NativeStackHeaderProps) { + const { listId, listName } = useGlobalSearchParams<{ + listId: string; + listName?: string; + }>(); + const title = getHeaderTitle(options, listName || route.name); + const back = router.canGoBack(); + + return ( + + {back || (route.path !== "/") ? ( + (back ? router.back() : router.replace("/"))} + /> + ) : null} + + + randomTask(listId || null).then((data) => + router.push({ + pathname: "/tasks/[taskId]", + params: { taskId: data.id, taskName: data.name }, + }) + ) + } + /> + {/* {}} />*/} + + ); +} diff --git a/app/components/navigation/TaskNavigationBar.tsx b/app/components/navigation/TaskNavigationBar.tsx new file mode 100644 index 0000000..ac09920 --- /dev/null +++ b/app/components/navigation/TaskNavigationBar.tsx @@ -0,0 +1,28 @@ +import { router, useGlobalSearchParams } from "expo-router"; +import type { NativeStackHeaderProps } from "@react-navigation/native-stack"; +import { Appbar } from "react-native-paper"; +import { getHeaderTitle } from "@react-navigation/elements"; + +export default function TaskNavigationBar({ + options, + route, +}: NativeStackHeaderProps) { + const { taskId, taskName } = useGlobalSearchParams<{ + taskId: string; + taskName?: string; + }>(); + const title = getHeaderTitle(options, taskName || route.name); + + return ( + + + router.canGoBack() ? router.back() : router.replace("/") + } + /> + + {/* {}} />*/} + {/* {}} />*/} + + ); +}