From 82f0b70886eceebd8cec2833d692467fdcbaa590 Mon Sep 17 00:00:00 2001 From: AB-102 <61863411+AB-102@users.noreply.github.com> Date: Mon, 18 Nov 2024 01:50:47 -0600 Subject: [PATCH 01/13] small fixes, still need to unmount offscreen elements --- thi-app/app.json | 2 +- thi-app/app/(drawer)/_layout.tsx | 16 +++++++--------- thi-app/assets/images/students_icon.png | Bin 4365 -> 0 bytes thi-app/components/Sidebar.tsx | 10 ++++++---- 4 files changed, 14 insertions(+), 14 deletions(-) delete mode 100644 thi-app/assets/images/students_icon.png diff --git a/thi-app/app.json b/thi-app/app.json index 873b5c9..f5b79d1 100644 --- a/thi-app/app.json +++ b/thi-app/app.json @@ -4,7 +4,7 @@ "slug": "thi-app", "version": "1.0.0", "orientation": "landscape", - "icon": "./assets/images/icon.png", + "icon": "@/assets/icon.png", "scheme": "myapp", "userInterfaceStyle": "automatic", "newArchEnabled": true, diff --git a/thi-app/app/(drawer)/_layout.tsx b/thi-app/app/(drawer)/_layout.tsx index fb0df97..98d1477 100644 --- a/thi-app/app/(drawer)/_layout.tsx +++ b/thi-app/app/(drawer)/_layout.tsx @@ -8,7 +8,7 @@ import Animated, { } from 'react-native-reanimated'; import { GestureDetector, Gesture } from 'react-native-gesture-handler'; import { Slot } from 'expo-router'; -import Sidebar, { SidebarContext, useSidebarContext } from '../../components/Sidebar'; +import Sidebar, { SidebarContext, useSidebarContext } from '@/components/Sidebar'; // Customize transition settings const TransitionCustomization = createContext({ @@ -19,7 +19,7 @@ const TransitionCustomization = createContext({ export const useTransitionCustomization = () => useContext(TransitionCustomization); -export default function Layout() { +function DrawerLayout() { // Sidebar state, dimensions const [isSidebarOpen, setIsSidebarOpen] = useState(true); const [isTransitioning, setIsTransitioning] = useState(false); @@ -72,17 +72,14 @@ export default function Layout() { ); }; - // Horizontal swipes trigger + // Horizontal swipe trigger toggles sidebar const swipeGesture = Gesture.Pan() .onUpdate((event) => { // Ignore swipes mid-transition if (isTransitioning) return; // Threshold horizontal distance in px for swipe to trigger - if (event.translationX > 50 && !isSidebarOpen) { - // Swipe right opens sidebar - toggleSidebar(); - } else if (event.translationX < -50 && isSidebarOpen) { - // Swipe left closes sidebar + if ((event.translationX > 50 && !isSidebarOpen)||(event.translationX < -50 && isSidebarOpen)) { + // Swipe right opens sidebar, left closes sidebar toggleSidebar(); } }); @@ -114,4 +111,5 @@ export default function Layout() { ); -} \ No newline at end of file +}; +export default DrawerLayout; \ No newline at end of file diff --git a/thi-app/assets/images/students_icon.png b/thi-app/assets/images/students_icon.png deleted file mode 100644 index 2d983c5db2e227cf43a04618649b66668640ff75..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4365 zcmeHKeNYo;8sF#%5(;vHV68{D5vX_9O*WfNNLErs5=hQQQ@}#1SKaKs8&>m0vXCHN zE!0kHt(1?d9m-J|+rzO(r4`{kydH`i^{jYzPIb_0YxSI-rya3V{3t5-Zukmwow;GA z{f}feyZgMq=l4F(?|I&L@@})P%!!dMkRk{YV>TIb;XMIf^JYiD-|SEO_QBinP5FgF zE*(Iu?u)J<1cq_Hx z<(eq-(6zg!4oR9hH?gJVtg||8M`vpN&+f1PtFJX_?t)YI4XHnAPkiFoik9Nx>t}kq zvO0dh_O)-5k5SRsbKHHxK4*UXj?K|~6SLPR_5Hnb%P-IU?&M(fwMNIjw@H(bJk&A#3t5Uiff|!jmCxVzG`f>o|m>zZ?64rmSsV0 zLQ>ArLl-ah+CM$oV=IU($c(#(6qkB)g_^m;yAz&09`ye6^}y}qzzw9y2KM)>BKOsU zcjAkcyVuQ|d0@7z63DWg~DmVJ&k z_O}UrL&xc7b|qZ$Uy6EBZo2nzN&l7fzSNgzH9oBQ@M_s5{DZ>(*bi^F^;@?!Z`=Fo z{--~x_S?QZ(qn5mYa433_fk`3d+nFhsh_5_ZcWLJdu(r&bbo8y-qnkG4E47zCqH=Q z@~WB@X$7-hTNu4B^QC{?IXZkX(7_dN_=iQ>Q}KDyg$MIz&gfkB9)019-uBNZ$-UEE zJI~%cSKoF0@vA$k67@48+AFqSt4g~e*}bG|picg9?V`K9v`04ZeD)V_L`HZjF4kXI zQ~qf4oMpTI`0PiL*w~7jbHRb~Bb^62JGPu|8yb?FarsUqee);|`>vFS{Z(jriefx2 zIn8?PKpu2?VdEi4#?qjdW;Ox=wF3w5*2`{m{#l0dtX@{2w%`_T7AWFPn|vT|)5?5i z(?&+e%9cJU%?MJEzy$;v4Z56eKNZx=LcA1w7R?G78iEKL^|C^X70vSa07}Y9IgS~F zd?_J&Qi^8ySdPjyWRFt7H@&P#5WJK^5eNk20hQe2b10NLolb!h3WC5O0`r%-1v-ei z{fk735e@_JGd|uc@E$iRa?*BBiJ+Is;5a%KpUZ2pjMKaQqbfi>6hYdnP|9(I%cThS z@C(LLNHQAG_j>sA;WAa^0>7uk#{gp~a0`pVDOhIQ-&^8yhSFgf1#kivg!U391})mcKZd|%>a=KlFNC> zLo-T_B(xl+)@cZgWR(o2)v@WAhGsa1wF8|>XAh%T<>O&j($4UxL@HLKBGouSYE&3Q zGa8J<2_>c_SscRwsn)PKoQctds91)|_V`>hEGO@x9YEoAJ3<4Z;8dp7td|jTe8OUN z(gFt^;2PlFtS8`~7|Q2eAWxu0J(cN%PM5C6aUG09qai0i&j6nv_M*tD#N{e&XhbXw z1=E4j(qgAVf{-0%LuL5@EqHwS9*}6&<0unkQ63ODRGL>5l?RGP-bP-Xd}uMDXlPl$DvqS! zr%M4FiW72;3^7Ht+X3MIG1{3+zA!qTj8i);->Q>en#u?BhCP+rze?`zSD9|%QYnhrUahuu4%cZ zq`;KG)7|xh$t9gQOaV9i2NZxurL^*b`S6$(VPBDBK!(Ndojt57bK)8@AQ?XLen42hRl5J$t!i4F~^q08HdT)g;6ETQ_-f_>KC?F zRwhO*Mk0G8+C8z!yOV!aj|*(ZI@Z-zJXUl(4N#HFmRjW7pPk9@dg=B=*n zt6~p#Z`jp;<)Q1ptvJWVNY>sw#g#`l_r?z&&L4m+{?Ux5R z?yJF+S&LJ5)*d_lPFou?&)Z=B_>IWF%~<`()?Grqt?q@Y&Gm{H=EN)Y%CzP?hfcnA e;_#Vu0} useContext(SidebarCustomization); export default function Sidebar({ animatedValue }: { animatedValue: SharedValue }) { // Sidebar state and screen details const router = useRouter(); - const { toggleSidebar, openSidebarWidth, closedSidebarWidth } = useSidebarContext(); + const { isSidebarOpen, toggleSidebar, openSidebarWidth, closedSidebarWidth } = useSidebarContext(); + const { transitionDuration, transitionEasing } = useTransitionCustomization(); const [currentScreen, setCurrentScreen] = useState(""); const segments: string[] = useSegments(); // Sidebar customizations @@ -121,7 +123,8 @@ export default function Sidebar({ animatedValue }: { animatedValue: SharedValue< {/* Navigable screens */} - + - ); From b5338d0f05f94617af3a361c8cca9a339a64ed96 Mon Sep 17 00:00:00 2001 From: AB-102 <61863411+AB-102@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:01:35 -0600 Subject: [PATCH 02/13] added schedule, sidebar memopt, updated dependencies --- thi-app/app.json | 4 +- thi-app/app/(drawer)/_layout.tsx | 71 +++++------ thi-app/app/(drawer)/schedule.tsx | 10 ++ thi-app/app/index.tsx | 2 +- thi-app/assets/{ => images}/icon.png | Bin thi-app/components/Login.tsx | 2 +- thi-app/components/Sidebar.tsx | 178 +++++++++++++++++++-------- thi-app/package.json | 8 +- 8 files changed, 171 insertions(+), 104 deletions(-) create mode 100644 thi-app/app/(drawer)/schedule.tsx rename thi-app/assets/{ => images}/icon.png (100%) diff --git a/thi-app/app.json b/thi-app/app.json index f5b79d1..65fa239 100644 --- a/thi-app/app.json +++ b/thi-app/app.json @@ -4,7 +4,7 @@ "slug": "thi-app", "version": "1.0.0", "orientation": "landscape", - "icon": "@/assets/icon.png", + "icon": "./assets/images/icon.png", "scheme": "myapp", "userInterfaceStyle": "automatic", "newArchEnabled": true, @@ -40,7 +40,7 @@ [ "expo-screen-orientation", { - "initialOrientation": "DEFAULT" + "initialOrientation": "LANDSCAPE_LEFT" } ], "expo-font" diff --git a/thi-app/app/(drawer)/_layout.tsx b/thi-app/app/(drawer)/_layout.tsx index 98d1477..12b926b 100644 --- a/thi-app/app/(drawer)/_layout.tsx +++ b/thi-app/app/(drawer)/_layout.tsx @@ -1,25 +1,11 @@ -import React, { createContext, useContext, useState } from 'react'; +import React, { useState } from 'react'; import { View, Dimensions, SafeAreaView } from 'react-native'; -import Animated, { - useSharedValue, - useAnimatedStyle, - withTiming, - Easing, -} from 'react-native-reanimated'; +import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated'; import { GestureDetector, Gesture } from 'react-native-gesture-handler'; import { Slot } from 'expo-router'; -import Sidebar, { SidebarContext, useSidebarContext } from '@/components/Sidebar'; +import Sidebar, { SidebarContext, useSidebarContext, useTransitionCustomization } from '@/components/Sidebar'; -// Customize transition settings -const TransitionCustomization = createContext({ - transitionEasing: Easing.out(Easing.cubic), - transitionDuration: 400, // in ms - // Add more -}) - -export const useTransitionCustomization = () => useContext(TransitionCustomization); - -function DrawerLayout() { +const DrawerLayout = () => { // Sidebar state, dimensions const [isSidebarOpen, setIsSidebarOpen] = useState(true); const [isTransitioning, setIsTransitioning] = useState(false); @@ -32,16 +18,17 @@ function DrawerLayout() { const { transitionEasing, transitionDuration } = useTransitionCustomization(); const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - // Toggle sidebar state const toggleSidebar = async () => { - // Perform transitions - setIsTransitioning(true); - setIsSidebarOpen(!isSidebarOpen); - transitionMainScreen(); - transitionSidebar(); - // Allow swipes after 400ms duration - await delay(transitionDuration); - setIsTransitioning(false); + if ((sidebarAnimatedValue.value === 1 && isSidebarOpen) || + (sidebarAnimatedValue.value === 0 && !isSidebarOpen)) { + setIsTransitioning(true); + setIsSidebarOpen(!isSidebarOpen); + transitionSidebar(); + transitionMainScreen(); + // Allow swipes after complete transition + await delay(transitionDuration); + setIsTransitioning(false); + } }; // Set main screen dynamic width @@ -49,10 +36,18 @@ function DrawerLayout() { return { width: mainScreenWidth.value }; }); + // Transitions sidebar for 400ms duration + const transitionSidebar = () => { + sidebarAnimatedValue.value = withTiming(isSidebarOpen ? 0 : 1, { + duration: transitionDuration, + easing: transitionEasing, + } + ); + }; + // Transitions main screen for 400ms duration const transitionMainScreen = () => { - mainScreenWidth.value = withTiming( - (mainScreenWidth.value === Dimensions.get('window').width - (openSidebarWidth + closedSidebarWidth) && isSidebarOpen) ? + mainScreenWidth.value = withTiming(isSidebarOpen ? Dimensions.get('window').width - closedSidebarWidth : Dimensions.get('window').width - (openSidebarWidth + closedSidebarWidth), { @@ -62,24 +57,13 @@ function DrawerLayout() { ); }; - // Transitions sidebar for 400ms duration - const transitionSidebar = () => { - sidebarAnimatedValue.value = withTiming( - sidebarAnimatedValue.value === 1 && isSidebarOpen ? 0 : 1, { - duration: transitionDuration, - easing: transitionEasing, - } - ); - }; - - // Horizontal swipe trigger toggles sidebar + // Horizontal swipe triggers sidebar toggle const swipeGesture = Gesture.Pan() .onUpdate((event) => { // Ignore swipes mid-transition if (isTransitioning) return; - // Threshold horizontal distance in px for swipe to trigger + // Threshold horizontal distance is 50 px to trigger if ((event.translationX > 50 && !isSidebarOpen)||(event.translationX < -50 && isSidebarOpen)) { - // Swipe right opens sidebar, left closes sidebar toggleSidebar(); } }); @@ -88,7 +72,8 @@ function DrawerLayout() { - + {/* Sidebar */} diff --git a/thi-app/app/(drawer)/schedule.tsx b/thi-app/app/(drawer)/schedule.tsx new file mode 100644 index 0000000..2ae11c8 --- /dev/null +++ b/thi-app/app/(drawer)/schedule.tsx @@ -0,0 +1,10 @@ +import React from 'react'; +import { View, Text } from 'react-native'; + +export default function SchedulePage() { + return ( + + Placeholder schedule page + + ); +} \ No newline at end of file diff --git a/thi-app/app/index.tsx b/thi-app/app/index.tsx index 679891b..30a1524 100644 --- a/thi-app/app/index.tsx +++ b/thi-app/app/index.tsx @@ -1,4 +1,4 @@ -import Login from '@/components/login'; +import Login from '@/components/Login'; import React from 'react'; import { View } from 'react-native'; diff --git a/thi-app/assets/icon.png b/thi-app/assets/images/icon.png similarity index 100% rename from thi-app/assets/icon.png rename to thi-app/assets/images/icon.png diff --git a/thi-app/components/Login.tsx b/thi-app/components/Login.tsx index 1ad32c4..3440710 100644 --- a/thi-app/components/Login.tsx +++ b/thi-app/components/Login.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { View, Text, Image, TextInput, TouchableOpacity, Dimensions } from 'react-native'; import { useRouter } from 'expo-router'; -import { FontAwesome } from '@expo/vector-icons'; +import { FontAwesome } from '@expo/vector-icons'; const Login = () => { const [username, setUsername] = useState(''); diff --git a/thi-app/components/Sidebar.tsx b/thi-app/components/Sidebar.tsx index aaec09c..c04ba92 100644 --- a/thi-app/components/Sidebar.tsx +++ b/thi-app/components/Sidebar.tsx @@ -1,10 +1,29 @@ import React, { createContext, useContext, useState } from 'react'; -import { View, Text, TouchableOpacity, Image, Dimensions } from 'react-native'; -import Animated, { SharedValue, useAnimatedStyle, interpolate, FadeIn, FadeOut } from 'react-native-reanimated'; import { useFocusEffect } from '@react-navigation/native'; import { useRouter, useSegments } from 'expo-router'; -import { Entypo, FontAwesome, FontAwesome6, MaterialIcons, MaterialCommunityIcons } from '@expo/vector-icons'; -import { useTransitionCustomization } from '@/app/(drawer)/_layout'; +import { + View, + Text, + TouchableOpacity, + Image, + Dimensions, + Pressable, +} from 'react-native'; +import Animated, { + SharedValue, + useAnimatedStyle, + interpolate, + FadeInLeft, + FadeOutLeft, + Easing, +} from 'react-native-reanimated'; +import { + Entypo, + FontAwesome, + FontAwesome6, + MaterialIcons, + MaterialCommunityIcons +} from '@expo/vector-icons'; // Context for current screen export const ScreenContext = createContext({ @@ -27,15 +46,21 @@ const SidebarCustomization = createContext({ currentDrawerColor: '#10536699', defaultDrawerColor: 'transparent', buttonColor: '#105366', - buttonSize: 2.5, // in rem (1 rem = 16px) - // Add more + buttonSize: 2.3, // in rem (1 rem = 16px) +}) + +// Customize transition settings +const TransitionCustomization = createContext({ + transitionEasing: Easing.out(Easing.cubic), + transitionDuration: 400, // in ms }) export const useScreenContext = () => useContext(ScreenContext); export const useSidebarContext = () => useContext(SidebarContext); export const useSidebarCustomization = () => useContext(SidebarCustomization); +export const useTransitionCustomization = () => useContext(TransitionCustomization); -export default function Sidebar({ animatedValue }: { animatedValue: SharedValue }) { +const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { // Sidebar state and screen details const router = useRouter(); const { isSidebarOpen, toggleSidebar, openSidebarWidth, closedSidebarWidth } = useSidebarContext(); @@ -52,26 +77,33 @@ export default function Sidebar({ animatedValue }: { animatedValue: SharedValue< buttonSize, } = useSidebarCustomization(); - /* Animation interpolations */ - - // Sidebar slide + // Sidebar slide interpolation const sidebarStyle = useAnimatedStyle(() => ({ transform: [ { translateX: interpolate(animatedValue.value, [0, 1], [-openSidebarWidth, 0]) } ], })); - // Button rotation (> when closed, < when open) + // Button rotation interpolation (> when closed, < when open) const buttonStyle = useAnimatedStyle(() => ({ transform: [ { rotate: `${interpolate(animatedValue.value, [0, 0.5, 1], [0, -90, -180])}deg` } ], })); - // Move current screen highlight off-screen when closed - const currentHighlightStyle = useAnimatedStyle(() => ({ - transform: [ - { translateX: interpolate(animatedValue.value, [0, 1], [-closedSidebarWidth * 0.5, 0]) } - ], - })); + + // Dynamic icon resizing + function dynamicIconSize(): number { + let size; + const height = Dimensions.get('window').height; + const outerContainerHeight = closedSidebarWidth; + if ((height >= 800) && (height > outerContainerHeight)) { + size = 32; // lg:text-2xl + } else if ((height >= 600) && (height > outerContainerHeight)) { + size = 24; // md:text-xl + } else { + size = 16; // default for small phones + } + return size; + } // Set current screen name (e.g. "students" for (drawer)/students) useFocusEffect( @@ -98,36 +130,41 @@ export default function Sidebar({ animatedValue }: { animatedValue: SharedValue< }}> {/* Open/collapse button */} - - + }} onPress={toggleSidebar}> - - + {/* Sidebar */} {/* App icon */} - - - + {isSidebarOpen && + ( + + + + )} {/* Navigable screens */} - + {isSidebarOpen && + ( {/* Home */} @@ -137,10 +174,11 @@ export default function Sidebar({ animatedValue }: { animatedValue: SharedValue< backgroundColor: currentScreen.includes('home') ? currentDrawerColor : defaultDrawerColor, }} onPress={() => router.push('/(drawer)/home')}> - + - Home @@ -153,15 +191,33 @@ export default function Sidebar({ animatedValue }: { animatedValue: SharedValue< backgroundColor: currentScreen.includes('students') ? currentDrawerColor : defaultDrawerColor, }} onPress={() => router.push('/students')}> - + - Students + {/* Schedule */} + + router.push('/schedule')}> + + + + + Schedule + + + + {/* Games */} router.push('/games')}> - + - Games @@ -185,10 +242,11 @@ export default function Sidebar({ animatedValue }: { animatedValue: SharedValue< backgroundColor: currentScreen.includes('timer') ? currentDrawerColor : defaultDrawerColor, }} onPress={() => router.push('/timer')}> - + - Timer @@ -201,32 +259,45 @@ export default function Sidebar({ animatedValue }: { animatedValue: SharedValue< backgroundColor: currentScreen.includes('settings') ? currentDrawerColor : defaultDrawerColor, }} onPress={() => router.push('/settings')}> - + - Settings - + )} {/* Sign out */} - - - + + router.push('/')}> - - - - - Sign Out - + {isSidebarOpen && + ( + + + + + + Sign Out + + + )} @@ -235,4 +306,5 @@ export default function Sidebar({ animatedValue }: { animatedValue: SharedValue< ); -} \ No newline at end of file +} +export default Sidebar; \ No newline at end of file diff --git a/thi-app/package.json b/thi-app/package.json index 97c6a63..3c919b1 100644 --- a/thi-app/package.json +++ b/thi-app/package.json @@ -20,9 +20,9 @@ "expo": "^52.0.7", "expo-font": "~13.0.1", "expo-linking": "~7.0.2", - "expo-router": "~4.0.5", + "expo-router": "^4.0.6", "expo-screen-orientation": "~8.0.0", - "expo-splash-screen": "~0.29.10", + "expo-splash-screen": "^0.29.11", "expo-status-bar": "~2.0.0", "expo-system-ui": "~4.0.3", "expo-web-browser": "~14.0.1", @@ -33,7 +33,7 @@ "react-native-gesture-handler": "~2.20.2", "react-native-reanimated": "~3.16.1", "react-native-safe-area-context": "4.12.0", - "react-native-screens": "~4.0.0", + "react-native-screens": "^4.1.0", "react-native-web": "~0.19.6", "tailwindcss": "3.4.4" }, @@ -41,7 +41,7 @@ "@babel/core": "^7.20.0", "@types/react": "~18.3.12", "jest": "~29.7.0", - "jest-expo": "~52.0.1", + "jest-expo": "^52.0.2", "react-test-renderer": "18.2.0", "typescript": "~5.3.3" }, From e66654ab946988de4269f5e0f5b53c5f110b4549 Mon Sep 17 00:00:00 2001 From: AB-102 <61863411+AB-102@users.noreply.github.com> Date: Mon, 18 Nov 2024 16:47:49 -0600 Subject: [PATCH 03/13] added jost font support --- thi-app/app.json | 10 +++++++--- thi-app/app/_layout.tsx | 19 ++++++------------- thi-app/components/Sidebar.tsx | 14 +++++++------- thi-app/tailwind.config.js | 16 ++++++++++++++++ 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/thi-app/app.json b/thi-app/app.json index 65fa239..38c2b14 100644 --- a/thi-app/app.json +++ b/thi-app/app.json @@ -36,14 +36,18 @@ }, "plugins": [ "expo-router", - "expo-font", + [ + "expo-font", + { + "fonts": ["./assets/fonts/Jost-VariableFont_wght.tff"] + } + ], [ "expo-screen-orientation", { "initialOrientation": "LANDSCAPE_LEFT" } - ], - "expo-font" + ] ], "experiments": { "tsconfigPaths": true, diff --git a/thi-app/app/_layout.tsx b/thi-app/app/_layout.tsx index 85231a9..5b5baac 100644 --- a/thi-app/app/_layout.tsx +++ b/thi-app/app/_layout.tsx @@ -1,10 +1,9 @@ import "../global.css"; -import FontAwesome from "@expo/vector-icons/FontAwesome"; -import { useFonts } from "expo-font"; -import { SplashScreen, Stack } from "expo-router"; import { useEffect } from "react"; import { View } from "react-native"; import { GestureHandlerRootView } from "react-native-gesture-handler"; +import { useFonts } from "expo-font"; +import { SplashScreen, Stack } from "expo-router"; export { // Catch any errors thrown by the Layout component. @@ -21,22 +20,16 @@ SplashScreen.preventAutoHideAsync(); export default function RootLayout() { const [loaded, error] = useFonts({ - Jost: require("../assets/fonts/Jost-VariableFont_wght.ttf"), - ...FontAwesome.font, + 'Jost': require("../assets/fonts/Jost-VariableFont_wght.ttf") }); - // Expo Router uses Error Boundaries to catch errors in the navigation tree. - useEffect(() => { - if (error) throw error; - }, [error]); - useEffect(() => { - if (loaded) { + if (loaded || error) { SplashScreen.hideAsync(); } - }, [loaded]); + }, [loaded, error]); - if (!loaded) { + if (!loaded && !error) { return null; } diff --git a/thi-app/components/Sidebar.tsx b/thi-app/components/Sidebar.tsx index c04ba92..dbd7521 100644 --- a/thi-app/components/Sidebar.tsx +++ b/thi-app/components/Sidebar.tsx @@ -178,7 +178,7 @@ const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { style={{ color: currentScreen.includes('home') ? currentIconTextColor : defaultIconTextColor }}/> - Home @@ -195,7 +195,7 @@ const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { style={{ color: currentScreen.includes('students') ? currentIconTextColor : defaultIconTextColor }}/> - Students @@ -212,7 +212,7 @@ const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { style={{ color: currentScreen.includes('schedule') ? currentIconTextColor : defaultIconTextColor }}/> - Schedule @@ -229,7 +229,7 @@ const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { style={{ color: currentScreen.includes('games') ? currentIconTextColor : defaultIconTextColor }}/> - Games @@ -246,7 +246,7 @@ const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { style={{ color: currentScreen.includes('timer') ? currentIconTextColor : defaultIconTextColor }}/> - Timer @@ -263,7 +263,7 @@ const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { style={{ color: currentScreen.includes('settings') ? currentIconTextColor : defaultIconTextColor }}/> - Settings @@ -294,7 +294,7 @@ const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { - Sign Out + Sign Out )} diff --git a/thi-app/tailwind.config.js b/thi-app/tailwind.config.js index 122838a..9224879 100644 --- a/thi-app/tailwind.config.js +++ b/thi-app/tailwind.config.js @@ -4,4 +4,20 @@ module.exports = { content: ["app/**/*.{js,jsx,ts,tsx}", "components/**/*.{js,jsx,ts,tsx}"], presets: [require("nativewind/preset")], plugins: [], + theme: { + extend: { + fontFamily: { + jost: ["Jost-VariableFont_wght", "sans-serif"], + }, + fontWeight: { + thin: 100, + light: 300, + regular: 400, + medium: 500, + semibold: 600, + bold: 700, + extrabold: 800, + }, + }, + }, }; \ No newline at end of file From 7e772fec25490a4f8537e883020c1b94aa6925a4 Mon Sep 17 00:00:00 2001 From: AB-102 <61863411+AB-102@users.noreply.github.com> Date: Tue, 19 Nov 2024 00:21:09 -0600 Subject: [PATCH 04/13] revert changes to dependencies for this PR --- thi-app/package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/thi-app/package.json b/thi-app/package.json index 3c919b1..97c6a63 100644 --- a/thi-app/package.json +++ b/thi-app/package.json @@ -20,9 +20,9 @@ "expo": "^52.0.7", "expo-font": "~13.0.1", "expo-linking": "~7.0.2", - "expo-router": "^4.0.6", + "expo-router": "~4.0.5", "expo-screen-orientation": "~8.0.0", - "expo-splash-screen": "^0.29.11", + "expo-splash-screen": "~0.29.10", "expo-status-bar": "~2.0.0", "expo-system-ui": "~4.0.3", "expo-web-browser": "~14.0.1", @@ -33,7 +33,7 @@ "react-native-gesture-handler": "~2.20.2", "react-native-reanimated": "~3.16.1", "react-native-safe-area-context": "4.12.0", - "react-native-screens": "^4.1.0", + "react-native-screens": "~4.0.0", "react-native-web": "~0.19.6", "tailwindcss": "3.4.4" }, @@ -41,7 +41,7 @@ "@babel/core": "^7.20.0", "@types/react": "~18.3.12", "jest": "~29.7.0", - "jest-expo": "^52.0.2", + "jest-expo": "~52.0.1", "react-test-renderer": "18.2.0", "typescript": "~5.3.3" }, From 3317bcaa4fbad2272c2a0fd2d82452f52a1c07a3 Mon Sep 17 00:00:00 2001 From: AB-102 <61863411+AB-102@users.noreply.github.com> Date: Tue, 19 Nov 2024 03:29:41 -0600 Subject: [PATCH 05/13] refactor sidebar --- thi-app/app/(drawer)/_layout.tsx | 36 +++- thi-app/components/Sidebar.tsx | 340 ++++++++++++------------------- 2 files changed, 159 insertions(+), 217 deletions(-) diff --git a/thi-app/app/(drawer)/_layout.tsx b/thi-app/app/(drawer)/_layout.tsx index 12b926b..6f68b4e 100644 --- a/thi-app/app/(drawer)/_layout.tsx +++ b/thi-app/app/(drawer)/_layout.tsx @@ -6,17 +6,25 @@ import { Slot } from 'expo-router'; import Sidebar, { SidebarContext, useSidebarContext, useTransitionCustomization } from '@/components/Sidebar'; const DrawerLayout = () => { - // Sidebar state, dimensions + // Sidebar state, dimensions, and transition settings + const { transitionEasing, transitionDuration } = useTransitionCustomization(); + const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); const [isSidebarOpen, setIsSidebarOpen] = useState(true); const [isTransitioning, setIsTransitioning] = useState(false); - const { openSidebarWidth, closedSidebarWidth } = useSidebarContext(); + const { + openSidebarWidth, + closedSidebarWidth, + activeIconTextColor, + defaultIconTextColor, + activeTabColor, + defaultTabColor, + buttonColor, + buttonSize, + } = useSidebarContext(); // Tie animations to initial sidebar state (currently set to open) const sidebarAnimatedValue = useSharedValue(isSidebarOpen ? 1 : 0); const mainScreenWidth = useSharedValue( Dimensions.get('window').width - (openSidebarWidth + closedSidebarWidth)); - // Transition settings - const { transitionEasing, transitionDuration } = useTransitionCustomization(); - const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); const toggleSidebar = async () => { if ((sidebarAnimatedValue.value === 1 && isSidebarOpen) || @@ -70,13 +78,23 @@ const DrawerLayout = () => { return ( - + - + {/* Sidebar */} - + {/* Main screen in ./(drawer)/ */} {}, -}); - -// Context for sidebar state and dimensions +// Sidebar state, dimensions, and customization export const SidebarContext = createContext({ isSidebarOpen: true, toggleSidebar: () => {}, openSidebarWidth: Dimensions.get('window').width * 0.18, closedSidebarWidth: Dimensions.get('window').width * 0.02, -}); - -// Customize sidebar -const SidebarCustomization = createContext({ - currentIconTextColor: 'white', + activeIconTextColor: 'white', defaultIconTextColor: 'black', - currentDrawerColor: '#10536699', - defaultDrawerColor: 'transparent', + activeTabColor: '#10536699', + defaultTabColor: 'transparent', buttonColor: '#105366', buttonSize: 2.3, // in rem (1 rem = 16px) -}) +}); // Customize transition settings const TransitionCustomization = createContext({ @@ -55,55 +25,63 @@ const TransitionCustomization = createContext({ transitionDuration: 400, // in ms }) -export const useScreenContext = () => useContext(ScreenContext); export const useSidebarContext = () => useContext(SidebarContext); -export const useSidebarCustomization = () => useContext(SidebarCustomization); export const useTransitionCustomization = () => useContext(TransitionCustomization); -const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { - // Sidebar state and screen details - const router = useRouter(); - const { isSidebarOpen, toggleSidebar, openSidebarWidth, closedSidebarWidth } = useSidebarContext(); - const { transitionDuration, transitionEasing } = useTransitionCustomization(); - const [currentScreen, setCurrentScreen] = useState(""); - const segments: string[] = useSegments(); - // Sidebar customizations +// Open/close sidebar button +const SidebarButton = ({ animatedStyle }: { animatedStyle: AnimatedStyle }) => { const { - currentIconTextColor, - defaultIconTextColor, - currentDrawerColor, - defaultDrawerColor, + toggleSidebar, + openSidebarWidth, + closedSidebarWidth, buttonColor, buttonSize, - } = useSidebarCustomization(); + } = useSidebarContext(); - // Sidebar slide interpolation - const sidebarStyle = useAnimatedStyle(() => ({ - transform: [ - { translateX: interpolate(animatedValue.value, [0, 1], [-openSidebarWidth, 0]) } - ], - })); - // Button rotation interpolation (> when closed, < when open) - const buttonStyle = useAnimatedStyle(() => ({ - transform: [ - { rotate: `${interpolate(animatedValue.value, [0, 0.5, 1], [0, -90, -180])}deg` } - ], - })); + return ( + + + + + + ); +} - // Dynamic icon resizing - function dynamicIconSize(): number { - let size; - const height = Dimensions.get('window').height; - const outerContainerHeight = closedSidebarWidth; - if ((height >= 800) && (height > outerContainerHeight)) { - size = 32; // lg:text-2xl - } else if ((height >= 600) && (height > outerContainerHeight)) { - size = 24; // md:text-xl - } else { - size = 16; // default for small phones - } - return size; - } +// Define props for sidebar tab +interface SidebarTabProps { + iconSet: typeof Entypo | typeof FontAwesome | typeof FontAwesome6 | typeof MaterialIcons | typeof MaterialCommunityIcons; + iconName: string; + iconSize?: number; + label: string; + useActiveColor?: boolean; + tabWidth?: DimensionValue; +} + +// Sidebar navigation tab +const SidebarTab = ({ iconSet: Icon, iconName, iconSize = dynamicIconSize(), label, useActiveColor = true, tabWidth = '100%' }: SidebarTabProps) => { + // Sidebar details + const { + closedSidebarWidth, + activeIconTextColor, + defaultIconTextColor, + activeTabColor, + defaultTabColor, + } = useSidebarContext(); + // Screen routing details + const router = useRouter(); + const [currentScreen, setCurrentScreen] = useState(""); + const segments: string[] = useSegments(); + const tabName = label === 'Sign out' ? '' : label.toLowerCase(); + const directory = '/' + tabName; + const isActive = currentScreen.includes(tabName); + // Sidebar tab details + const tabHeight = Dimensions.get('window').height * 0.08; + const tabIconTextColor = useActiveColor && isActive ? activeIconTextColor : defaultIconTextColor; + const tabButtonColor = useActiveColor && isActive ? activeTabColor : defaultTabColor; // Set current screen name (e.g. "students" for (drawer)/students) useFocusEffect( @@ -116,6 +94,58 @@ const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { }, [segments]) ); + return ( + + router.push(directory as RelativePathString)}> + + + + + {label} + + + + ); +}; + +// Dynamic icon resizing +function dynamicIconSize(): number { + const { closedSidebarWidth } = useSidebarContext(); + let size; + const height = Dimensions.get('window').height; + const outerContainerHeight = closedSidebarWidth; + if ((height >= 800) && (height > outerContainerHeight)) { + size = 32; // lg:text-2xl + } else if ((height >= 600) && (height > outerContainerHeight)) { + size = 24; // md:text-xl + } else { + size = 16; // default for small phones + } + return size; +} + +const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { + // Sidebar and transition details + const { transitionDuration, transitionEasing } = useTransitionCustomization(); + const { isSidebarOpen, openSidebarWidth, closedSidebarWidth } = useSidebarContext(); + + // Sidebar slide interpolation + const sidebarStyle = useAnimatedStyle(() => ({ + transform: [ + { translateX: interpolate(animatedValue.value, [0, 1], [-openSidebarWidth, 0]) } + ], + })); + // Button rotation interpolation (> when closed, < when open) + const buttonStyle = useAnimatedStyle(() => ({ + transform: [ + { rotate: `${interpolate(animatedValue.value, [0, 0.5, 1], [0, -90, -180])}deg` } + ], + })); + return ( // Animated sidebar container }) => { }}> {/* Open/collapse button */} - - - - - + {/* Sidebar */} @@ -159,7 +181,7 @@ const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { {/* Navigable screens */} {isSidebarOpen && - ( }) => { }}> {/* Home */} - - router.push('/(drawer)/home')}> - - - - - Home - - - + {/* Students */} - - router.push('/students')}> - - - - - Students - - - + {/* Schedule */} - - router.push('/schedule')}> - - - - - Schedule - - - + {/* Games */} - - router.push('/games')}> - - - - - Games - - - + {/* Timer */} - - router.push('/timer')}> - - - - - Timer - - - + {/* Settings */} - - router.push('/settings')}> - - - - - Settings - - - + )} - {/* Sign out */} - - - router.push('/')}> - {isSidebarOpen && - ( - - - - - - Sign Out - - - )} - - + {/* Spacer */} + + {isSidebarOpen && + ( + + {/* Sign out */} + + + )} + {/* Bottom padding */} + + From a6a624c1ef6d41a4819e0a994ebe602568a1a700 Mon Sep 17 00:00:00 2001 From: AB-102 <61863411+AB-102@users.noreply.github.com> Date: Tue, 19 Nov 2024 04:01:12 -0600 Subject: [PATCH 06/13] contd refactor --- thi-app/components/Sidebar.tsx | 49 +++++++++++++++------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/thi-app/components/Sidebar.tsx b/thi-app/components/Sidebar.tsx index d3520d7..d5ed6b2 100644 --- a/thi-app/components/Sidebar.tsx +++ b/thi-app/components/Sidebar.tsx @@ -118,6 +118,7 @@ function dynamicIconSize(): number { let size; const height = Dimensions.get('window').height; const outerContainerHeight = closedSidebarWidth; + if ((height >= 800) && (height > outerContainerHeight)) { size = 32; // lg:text-2xl } else if ((height >= 600) && (height > outerContainerHeight)) { @@ -132,6 +133,8 @@ const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { // Sidebar and transition details const { transitionDuration, transitionEasing } = useTransitionCustomization(); const { isSidebarOpen, openSidebarWidth, closedSidebarWidth } = useSidebarContext(); + const enteringAnimation = FadeInLeft.duration(transitionDuration / 1.5).easing(transitionEasing); + const exitingAnimation = FadeOutLeft.duration(transitionDuration).easing(transitionEasing); // Sidebar slide interpolation const sidebarStyle = useAnimatedStyle(() => ({ @@ -139,6 +142,7 @@ const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { { translateX: interpolate(animatedValue.value, [0, 1], [-openSidebarWidth, 0]) } ], })); + // Button rotation interpolation (> when closed, < when open) const buttonStyle = useAnimatedStyle(() => ({ transform: [ @@ -147,7 +151,6 @@ const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { })); return ( - // Animated sidebar container }) => { {/* Open/collapse button */} - {/* Sidebar */} + {/* Sidebar contents */} - - {/* App icon */} {isSidebarOpen && - ( + ( + + {/* App icon */} }) => { width: openSidebarWidth * .60, }} /> - )} - {/* Navigable screens */} - {isSidebarOpen && - ( + {/* Navigable screens */} }) => { + )} - {/* Spacer */} - - {isSidebarOpen && - ( - - {/* Sign out */} - - - )} - + {/* Spacer */} + + {isSidebarOpen && + ( + + {/* Sign out */} + + + )} + - {/* Bottom padding */} - + {/* Bottom padding */} + From 92a49f61c8633c2e6a76274aa65cb0c0930755d1 Mon Sep 17 00:00:00 2001 From: AB-102 <61863411+AB-102@users.noreply.github.com> Date: Tue, 19 Nov 2024 04:20:48 -0600 Subject: [PATCH 07/13] refactor SidebarTab to new component --- thi-app/components/Sidebar.tsx | 85 ++----------------------------- thi-app/components/SidebarTab.tsx | 85 +++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 82 deletions(-) create mode 100644 thi-app/components/SidebarTab.tsx diff --git a/thi-app/components/Sidebar.tsx b/thi-app/components/Sidebar.tsx index d5ed6b2..95ebe90 100644 --- a/thi-app/components/Sidebar.tsx +++ b/thi-app/components/Sidebar.tsx @@ -1,9 +1,8 @@ -import React, { createContext, useContext, useState } from 'react'; -import { useFocusEffect } from '@react-navigation/native'; -import { RelativePathString, useRouter, useSegments } from 'expo-router'; -import { View, Text, TouchableOpacity, Image, Dimensions, Pressable, ViewStyle, DimensionValue } from 'react-native'; +import React, { createContext, useContext } from 'react'; +import { View, Image, Dimensions, Pressable, ViewStyle } from 'react-native'; import Animated, { SharedValue, useAnimatedStyle, interpolate, FadeInLeft, FadeOutLeft, Easing, AnimatedStyle } from 'react-native-reanimated'; import { Entypo, FontAwesome, FontAwesome6, MaterialIcons, MaterialCommunityIcons } from '@expo/vector-icons'; +import SidebarTab, { dynamicIconSize } from '@/components/SidebarTab'; // Sidebar state, dimensions, and customization export const SidebarContext = createContext({ @@ -51,84 +50,6 @@ const SidebarButton = ({ animatedStyle }: { animatedStyle: AnimatedStyle { - // Sidebar details - const { - closedSidebarWidth, - activeIconTextColor, - defaultIconTextColor, - activeTabColor, - defaultTabColor, - } = useSidebarContext(); - // Screen routing details - const router = useRouter(); - const [currentScreen, setCurrentScreen] = useState(""); - const segments: string[] = useSegments(); - const tabName = label === 'Sign out' ? '' : label.toLowerCase(); - const directory = '/' + tabName; - const isActive = currentScreen.includes(tabName); - // Sidebar tab details - const tabHeight = Dimensions.get('window').height * 0.08; - const tabIconTextColor = useActiveColor && isActive ? activeIconTextColor : defaultIconTextColor; - const tabButtonColor = useActiveColor && isActive ? activeTabColor : defaultTabColor; - - // Set current screen name (e.g. "students" for (drawer)/students) - useFocusEffect( - React.useCallback(() => { - if (segments.length > 0) { - // Set screen name to last segment of URL - const screenName = segments[segments.length - 1]; - setCurrentScreen(screenName); - } - }, [segments]) - ); - - return ( - - router.push(directory as RelativePathString)}> - - - - - {label} - - - - ); -}; - -// Dynamic icon resizing -function dynamicIconSize(): number { - const { closedSidebarWidth } = useSidebarContext(); - let size; - const height = Dimensions.get('window').height; - const outerContainerHeight = closedSidebarWidth; - - if ((height >= 800) && (height > outerContainerHeight)) { - size = 32; // lg:text-2xl - } else if ((height >= 600) && (height > outerContainerHeight)) { - size = 24; // md:text-xl - } else { - size = 16; // default for small phones - } - return size; -} - const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { // Sidebar and transition details const { transitionDuration, transitionEasing } = useTransitionCustomization(); diff --git a/thi-app/components/SidebarTab.tsx b/thi-app/components/SidebarTab.tsx new file mode 100644 index 0000000..722fb42 --- /dev/null +++ b/thi-app/components/SidebarTab.tsx @@ -0,0 +1,85 @@ +import React, { useState } from 'react'; +import { useFocusEffect } from '@react-navigation/native'; +import { RelativePathString, useRouter, useSegments } from 'expo-router'; +import { View, Text, TouchableOpacity, Dimensions, DimensionValue } from 'react-native'; +import { Entypo, FontAwesome, FontAwesome6, MaterialIcons, MaterialCommunityIcons } from '@expo/vector-icons'; +import { useSidebarContext } from '@/components/Sidebar'; + +// Dynamic icon resizing +export function dynamicIconSize(): number { + const { closedSidebarWidth } = useSidebarContext(); + let size; + const height = Dimensions.get('window').height; + const outerContainerHeight = closedSidebarWidth; + + if ((height >= 800) && (height > outerContainerHeight)) { + size = 32; // lg:text-2xl + } else if ((height >= 600) && (height > outerContainerHeight)) { + size = 24; // md:text-xl + } else { + size = 16; // default for small phones + } + return size; +} + +// Define props for sidebar tab +interface SidebarTabProps { + iconSet: typeof Entypo | typeof FontAwesome | typeof FontAwesome6 | typeof MaterialIcons | typeof MaterialCommunityIcons; + iconName: string; + iconSize?: number; + label: string; + useActiveColor?: boolean; + tabWidth?: DimensionValue; +} + +// Sidebar navigation tab +const SidebarTab = ({ iconSet: Icon, iconName, iconSize = dynamicIconSize(), label, useActiveColor = true, tabWidth = '100%' }: SidebarTabProps) => { + // Sidebar details + const { + closedSidebarWidth, + activeIconTextColor, + defaultIconTextColor, + activeTabColor, + defaultTabColor, + } = useSidebarContext(); + // Screen routing details + const router = useRouter(); + const [currentScreen, setCurrentScreen] = useState(""); + const segments: string[] = useSegments(); + const tabName = label === 'Sign out' ? '' : label.toLowerCase(); + const directory = '/' + tabName; + const isActive = currentScreen.includes(tabName); + // Sidebar tab details + const tabHeight = Dimensions.get('window').height * 0.08; + const tabIconTextColor = useActiveColor && isActive ? activeIconTextColor : defaultIconTextColor; + const tabButtonColor = useActiveColor && isActive ? activeTabColor : defaultTabColor; + + // Set current screen name (e.g. "students" for (drawer)/students) + useFocusEffect( + React.useCallback(() => { + if (segments.length > 0) { + // Set screen name to last segment of URL + const screenName = segments[segments.length - 1]; + setCurrentScreen(screenName); + } + }, [segments]) + ); + + return ( + + router.push(directory as RelativePathString)}> + + + + + {label} + + + + ); +}; +export default SidebarTab; \ No newline at end of file From 9874d31e0779621c1b73e5efbd43698f444ee979 Mon Sep 17 00:00:00 2001 From: AB-102 <61863411+AB-102@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:32:01 -0600 Subject: [PATCH 08/13] style formatting --- thi-app/app.json | 4 +- thi-app/app/(drawer)/_layout.tsx | 159 ++++++++------- thi-app/app/_layout.tsx | 40 ++-- thi-app/components/Sidebar.tsx | 315 +++++++++++++++++------------- thi-app/components/SidebarTab.tsx | 157 ++++++++------- thi-app/tailwind.config.js | 10 +- 6 files changed, 376 insertions(+), 309 deletions(-) diff --git a/thi-app/app.json b/thi-app/app.json index 38c2b14..88254b2 100644 --- a/thi-app/app.json +++ b/thi-app/app.json @@ -13,9 +13,7 @@ "resizeMode": "contain", "backgroundColor": "#ffffff" }, - "assetBundlePatterns": [ - "**/*" - ], + "assetBundlePatterns": ["**/*"], "ios": { "supportsTablet": true, "userInterfaceStyle": "automatic", diff --git a/thi-app/app/(drawer)/_layout.tsx b/thi-app/app/(drawer)/_layout.tsx index 6f68b4e..b2cd7e2 100644 --- a/thi-app/app/(drawer)/_layout.tsx +++ b/thi-app/app/(drawer)/_layout.tsx @@ -1,16 +1,20 @@ -import React, { useState } from 'react'; -import { View, Dimensions, SafeAreaView } from 'react-native'; -import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated'; -import { GestureDetector, Gesture } from 'react-native-gesture-handler'; -import { Slot } from 'expo-router'; -import Sidebar, { SidebarContext, useSidebarContext, useTransitionCustomization } from '@/components/Sidebar'; +import React, { useState } from 'react' +import { View, Dimensions, SafeAreaView } from 'react-native' +import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated' +import { GestureDetector, Gesture } from 'react-native-gesture-handler' +import { Slot } from 'expo-router' +import Sidebar, { + SidebarContext, + useSidebarContext, + useTransitionCustomization, +} from '@/components/Sidebar' const DrawerLayout = () => { // Sidebar state, dimensions, and transition settings - const { transitionEasing, transitionDuration } = useTransitionCustomization(); - const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); - const [isSidebarOpen, setIsSidebarOpen] = useState(true); - const [isTransitioning, setIsTransitioning] = useState(false); + const { transitionEasing, transitionDuration } = useTransitionCustomization() + const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) + const [isSidebarOpen, setIsSidebarOpen] = useState(true) + const [isTransitioning, setIsTransitioning] = useState(false) const { openSidebarWidth, closedSidebarWidth, @@ -20,99 +24,106 @@ const DrawerLayout = () => { defaultTabColor, buttonColor, buttonSize, - } = useSidebarContext(); + } = useSidebarContext() // Tie animations to initial sidebar state (currently set to open) - const sidebarAnimatedValue = useSharedValue(isSidebarOpen ? 1 : 0); + const sidebarAnimatedValue = useSharedValue(isSidebarOpen ? 1 : 0) const mainScreenWidth = useSharedValue( - Dimensions.get('window').width - (openSidebarWidth + closedSidebarWidth)); + Dimensions.get('window').width - (openSidebarWidth + closedSidebarWidth) + ) const toggleSidebar = async () => { - if ((sidebarAnimatedValue.value === 1 && isSidebarOpen) || - (sidebarAnimatedValue.value === 0 && !isSidebarOpen)) { - setIsTransitioning(true); - setIsSidebarOpen(!isSidebarOpen); - transitionSidebar(); - transitionMainScreen(); + if ( + (sidebarAnimatedValue.value === 1 && isSidebarOpen) || + (sidebarAnimatedValue.value === 0 && !isSidebarOpen) + ) { + setIsTransitioning(true) + setIsSidebarOpen(!isSidebarOpen) + transitionSidebar() + transitionMainScreen() // Allow swipes after complete transition - await delay(transitionDuration); - setIsTransitioning(false); + await delay(transitionDuration) + setIsTransitioning(false) } - }; + } // Set main screen dynamic width const mainScreenAnimatedStyle = useAnimatedStyle(() => { - return { width: mainScreenWidth.value }; - }); + return { width: mainScreenWidth.value } + }) // Transitions sidebar for 400ms duration const transitionSidebar = () => { sidebarAnimatedValue.value = withTiming(isSidebarOpen ? 0 : 1, { - duration: transitionDuration, - easing: transitionEasing, - } - ); - }; + duration: transitionDuration, + easing: transitionEasing, + }) + } // Transitions main screen for 400ms duration const transitionMainScreen = () => { - mainScreenWidth.value = withTiming(isSidebarOpen ? - Dimensions.get('window').width - closedSidebarWidth : - Dimensions.get('window').width - (openSidebarWidth + closedSidebarWidth), - { - duration: transitionDuration, - easing: transitionEasing, - } - ); - }; + mainScreenWidth.value = withTiming( + isSidebarOpen + ? Dimensions.get('window').width - closedSidebarWidth + : Dimensions.get('window').width - (openSidebarWidth + closedSidebarWidth), + { + duration: transitionDuration, + easing: transitionEasing, + } + ) + } // Horizontal swipe triggers sidebar toggle - const swipeGesture = Gesture.Pan() - .onUpdate((event) => { + const swipeGesture = Gesture.Pan().onUpdate((event) => { // Ignore swipes mid-transition - if (isTransitioning) return; + if (isTransitioning) return // Threshold horizontal distance is 50 px to trigger - if ((event.translationX > 50 && !isSidebarOpen)||(event.translationX < -50 && isSidebarOpen)) { - toggleSidebar(); + if ( + (event.translationX > 50 && !isSidebarOpen) || + (event.translationX < -50 && isSidebarOpen) + ) { + toggleSidebar() } - }); + }) return ( - + - - + {/* Sidebar */} - - + + {/* Main screen in ./(drawer)/ */} - - + + - - ); -}; -export default DrawerLayout; \ No newline at end of file + ) +} +export default DrawerLayout diff --git a/thi-app/app/_layout.tsx b/thi-app/app/_layout.tsx index 5b5baac..30c6bc6 100644 --- a/thi-app/app/_layout.tsx +++ b/thi-app/app/_layout.tsx @@ -1,14 +1,14 @@ -import "../global.css"; -import { useEffect } from "react"; -import { View } from "react-native"; -import { GestureHandlerRootView } from "react-native-gesture-handler"; -import { useFonts } from "expo-font"; -import { SplashScreen, Stack } from "expo-router"; +import '../global.css' +import { useEffect } from 'react' +import { View } from 'react-native' +import { GestureHandlerRootView } from 'react-native-gesture-handler' +import { useFonts } from 'expo-font' +import { SplashScreen, Stack } from 'expo-router' export { // Catch any errors thrown by the Layout component. ErrorBoundary, -} from "expo-router"; +} from 'expo-router' // export const unstable_settings = { // // Ensure that reloading on `/modal` keeps a back button present. @@ -16,35 +16,35 @@ export { // }; // Prevent the splash screen from auto-hiding before asset loading is complete. -SplashScreen.preventAutoHideAsync(); +SplashScreen.preventAutoHideAsync() export default function RootLayout() { const [loaded, error] = useFonts({ - 'Jost': require("../assets/fonts/Jost-VariableFont_wght.ttf") - }); + Jost: require('../assets/fonts/Jost-VariableFont_wght.ttf'), + }) useEffect(() => { if (loaded || error) { - SplashScreen.hideAsync(); + SplashScreen.hideAsync() } - }, [loaded, error]); + }, [loaded, error]) if (!loaded && !error) { - return null; + return null } - return ; + return } function RootLayoutNav() { return ( - - + + - - + + - ); -} \ No newline at end of file + ) +} diff --git a/thi-app/components/Sidebar.tsx b/thi-app/components/Sidebar.tsx index 95ebe90..7a81e33 100644 --- a/thi-app/components/Sidebar.tsx +++ b/thi-app/components/Sidebar.tsx @@ -1,150 +1,195 @@ -import React, { createContext, useContext } from 'react'; -import { View, Image, Dimensions, Pressable, ViewStyle } from 'react-native'; -import Animated, { SharedValue, useAnimatedStyle, interpolate, FadeInLeft, FadeOutLeft, Easing, AnimatedStyle } from 'react-native-reanimated'; -import { Entypo, FontAwesome, FontAwesome6, MaterialIcons, MaterialCommunityIcons } from '@expo/vector-icons'; -import SidebarTab, { dynamicIconSize } from '@/components/SidebarTab'; +import React, { createContext, useContext } from 'react' +import { View, Image, Dimensions, Pressable, ViewStyle } from 'react-native' +import Animated, { + SharedValue, + useAnimatedStyle, + interpolate, + FadeInLeft, + FadeOutLeft, + Easing, + AnimatedStyle, +} from 'react-native-reanimated' +import { + Entypo, + FontAwesome, + FontAwesome6, + MaterialIcons, + MaterialCommunityIcons, +} from '@expo/vector-icons' +import SidebarTab, { dynamicIconSize } from '@/components/SidebarTab' // Sidebar state, dimensions, and customization export const SidebarContext = createContext({ - isSidebarOpen: true, - toggleSidebar: () => {}, - openSidebarWidth: Dimensions.get('window').width * 0.18, - closedSidebarWidth: Dimensions.get('window').width * 0.02, - activeIconTextColor: 'white', - defaultIconTextColor: 'black', - activeTabColor: '#10536699', - defaultTabColor: 'transparent', - buttonColor: '#105366', - buttonSize: 2.3, // in rem (1 rem = 16px) -}); + isSidebarOpen: true, + toggleSidebar: () => {}, + openSidebarWidth: Dimensions.get('window').width * 0.18, + closedSidebarWidth: Dimensions.get('window').width * 0.02, + activeIconTextColor: 'white', + defaultIconTextColor: 'black', + activeTabColor: '#10536699', + defaultTabColor: 'transparent', + buttonColor: '#105366', + buttonSize: 2.3, // in rem (1 rem = 16px) +}) // Customize transition settings const TransitionCustomization = createContext({ - transitionEasing: Easing.out(Easing.cubic), - transitionDuration: 400, // in ms + transitionEasing: Easing.out(Easing.cubic), + transitionDuration: 400, // in ms }) -export const useSidebarContext = () => useContext(SidebarContext); -export const useTransitionCustomization = () => useContext(TransitionCustomization); +export const useSidebarContext = () => useContext(SidebarContext) +export const useTransitionCustomization = () => useContext(TransitionCustomization) // Open/close sidebar button const SidebarButton = ({ animatedStyle }: { animatedStyle: AnimatedStyle }) => { - const { - toggleSidebar, - openSidebarWidth, - closedSidebarWidth, - buttonColor, - buttonSize, - } = useSidebarContext(); - - return ( - - - - - - ); + const { toggleSidebar, openSidebarWidth, closedSidebarWidth, buttonColor, buttonSize } = + useSidebarContext() + + return ( + + + + + + ) } const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { - // Sidebar and transition details - const { transitionDuration, transitionEasing } = useTransitionCustomization(); - const { isSidebarOpen, openSidebarWidth, closedSidebarWidth } = useSidebarContext(); - const enteringAnimation = FadeInLeft.duration(transitionDuration / 1.5).easing(transitionEasing); - const exitingAnimation = FadeOutLeft.duration(transitionDuration).easing(transitionEasing); - - // Sidebar slide interpolation - const sidebarStyle = useAnimatedStyle(() => ({ - transform: [ - { translateX: interpolate(animatedValue.value, [0, 1], [-openSidebarWidth, 0]) } - ], - })); - - // Button rotation interpolation (> when closed, < when open) - const buttonStyle = useAnimatedStyle(() => ({ - transform: [ - { rotate: `${interpolate(animatedValue.value, [0, 0.5, 1], [0, -90, -180])}deg` } - ], - })); - - return ( - - - - {/* Open/collapse button */} - - - {/* Sidebar contents */} - - {isSidebarOpen && - ( - - {/* App icon */} - - - - - {/* Navigable screens */} - - - {/* Home */} - - - {/* Students */} - - - {/* Schedule */} - - - {/* Games */} - - - {/* Timer */} - - - {/* Settings */} - - - - - )} - - {/* Spacer */} - - {isSidebarOpen && - ( - - {/* Sign out */} - - - )} - - - {/* Bottom padding */} - - - - - - ); + // Sidebar and transition details + const { transitionDuration, transitionEasing } = useTransitionCustomization() + const { isSidebarOpen, openSidebarWidth, closedSidebarWidth } = useSidebarContext() + const enteringAnimation = FadeInLeft.duration(transitionDuration / 1.5).easing(transitionEasing) + const exitingAnimation = FadeOutLeft.duration(transitionDuration).easing(transitionEasing) + + // Sidebar slide interpolation + const sidebarStyle = useAnimatedStyle(() => ({ + transform: [{ translateX: interpolate(animatedValue.value, [0, 1], [-openSidebarWidth, 0]) }], + })) + + // Button rotation interpolation (> when closed, < when open) + const buttonStyle = useAnimatedStyle(() => ({ + transform: [{ rotate: `${interpolate(animatedValue.value, [0, 0.5, 1], [0, -90, -180])}deg` }], + })) + + return ( + + + {/* Open/collapse button */} + + + {/* Sidebar contents */} + + {isSidebarOpen && ( + + {/* App icon */} + + + + + {/* Navigable screens */} + + {/* Home */} + + + {/* Students */} + + + {/* Schedule */} + + + {/* Games */} + + + {/* Timer */} + + + {/* Settings */} + + + + )} + + {/* Spacer */} + + {isSidebarOpen && ( + + {/* Sign out */} + + + )} + + + {/* Bottom padding */} + + + + + ) } -export default Sidebar; \ No newline at end of file +export default Sidebar diff --git a/thi-app/components/SidebarTab.tsx b/thi-app/components/SidebarTab.tsx index 722fb42..c547424 100644 --- a/thi-app/components/SidebarTab.tsx +++ b/thi-app/components/SidebarTab.tsx @@ -1,85 +1,98 @@ -import React, { useState } from 'react'; -import { useFocusEffect } from '@react-navigation/native'; -import { RelativePathString, useRouter, useSegments } from 'expo-router'; -import { View, Text, TouchableOpacity, Dimensions, DimensionValue } from 'react-native'; -import { Entypo, FontAwesome, FontAwesome6, MaterialIcons, MaterialCommunityIcons } from '@expo/vector-icons'; -import { useSidebarContext } from '@/components/Sidebar'; +import React, { useState } from 'react' +import { useFocusEffect } from '@react-navigation/native' +import { RelativePathString, useRouter, useSegments } from 'expo-router' +import { View, Text, TouchableOpacity, Dimensions, DimensionValue } from 'react-native' +import { Icon } from '@expo/vector-icons/build/createIconSet' +import { useSidebarContext } from '@/components/Sidebar' // Dynamic icon resizing export function dynamicIconSize(): number { - const { closedSidebarWidth } = useSidebarContext(); - let size; - const height = Dimensions.get('window').height; - const outerContainerHeight = closedSidebarWidth; - - if ((height >= 800) && (height > outerContainerHeight)) { - size = 32; // lg:text-2xl - } else if ((height >= 600) && (height > outerContainerHeight)) { - size = 24; // md:text-xl - } else { - size = 16; // default for small phones - } - return size; + const { closedSidebarWidth } = useSidebarContext() + let size + const height = Dimensions.get('window').height + const outerContainerHeight = closedSidebarWidth + + if (height >= 800 && height > outerContainerHeight) { + size = 32 // lg:text-2xl + } else if (height >= 600 && height > outerContainerHeight) { + size = 24 // md:text-xl + } else { + size = 16 // default for small phones + } + return size } // Define props for sidebar tab interface SidebarTabProps { - iconSet: typeof Entypo | typeof FontAwesome | typeof FontAwesome6 | typeof MaterialIcons | typeof MaterialCommunityIcons; - iconName: string; - iconSize?: number; - label: string; - useActiveColor?: boolean; - tabWidth?: DimensionValue; + iconSet: Icon + iconName: string + iconSize?: number + label: string + useActiveColor?: boolean + tabWidth?: DimensionValue } // Sidebar navigation tab -const SidebarTab = ({ iconSet: Icon, iconName, iconSize = dynamicIconSize(), label, useActiveColor = true, tabWidth = '100%' }: SidebarTabProps) => { - // Sidebar details - const { - closedSidebarWidth, - activeIconTextColor, - defaultIconTextColor, - activeTabColor, - defaultTabColor, - } = useSidebarContext(); - // Screen routing details - const router = useRouter(); - const [currentScreen, setCurrentScreen] = useState(""); - const segments: string[] = useSegments(); - const tabName = label === 'Sign out' ? '' : label.toLowerCase(); - const directory = '/' + tabName; - const isActive = currentScreen.includes(tabName); - // Sidebar tab details - const tabHeight = Dimensions.get('window').height * 0.08; - const tabIconTextColor = useActiveColor && isActive ? activeIconTextColor : defaultIconTextColor; - const tabButtonColor = useActiveColor && isActive ? activeTabColor : defaultTabColor; +const SidebarTab = ({ + iconSet: Icon, + iconName, + iconSize = dynamicIconSize(), + label, + useActiveColor = true, + tabWidth = '100%', +}: SidebarTabProps) => { + // Sidebar details + const { + closedSidebarWidth, + activeIconTextColor, + defaultIconTextColor, + activeTabColor, + defaultTabColor, + } = useSidebarContext() + // Screen routing details + const router = useRouter() + const [currentScreen, setCurrentScreen] = useState('') + const segments: string[] = useSegments() + const tabName = label === 'Sign out' ? '' : label.toLowerCase() + const directory = '/' + tabName + const isActive = currentScreen.includes(tabName) + // Sidebar tab details + const tabHeight = Dimensions.get('window').height * 0.08 + const tabIconTextColor = useActiveColor && isActive ? activeIconTextColor : defaultIconTextColor + const tabButtonColor = useActiveColor && isActive ? activeTabColor : defaultTabColor - // Set current screen name (e.g. "students" for (drawer)/students) - useFocusEffect( - React.useCallback(() => { - if (segments.length > 0) { - // Set screen name to last segment of URL - const screenName = segments[segments.length - 1]; - setCurrentScreen(screenName); - } - }, [segments]) - ); + // Set current screen name (e.g. "students" for (drawer)/students) + useFocusEffect( + React.useCallback(() => { + if (segments.length > 0) { + // Set screen name to last segment of URL + const screenName = segments[segments.length - 1] + setCurrentScreen(screenName) + } + }, [segments]) + ) - return ( - - router.push(directory as RelativePathString)}> - - - - - {label} - - + return ( + + router.push(directory as RelativePathString)} + > + + + + + + {label} + - ); -}; -export default SidebarTab; \ No newline at end of file + + + ) +} +export default SidebarTab diff --git a/thi-app/tailwind.config.js b/thi-app/tailwind.config.js index 9224879..13eef3f 100644 --- a/thi-app/tailwind.config.js +++ b/thi-app/tailwind.config.js @@ -1,13 +1,13 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - darkMode: "class", - content: ["app/**/*.{js,jsx,ts,tsx}", "components/**/*.{js,jsx,ts,tsx}"], - presets: [require("nativewind/preset")], + darkMode: 'class', + content: ['app/**/*.{js,jsx,ts,tsx}', 'components/**/*.{js,jsx,ts,tsx}'], + presets: [require('nativewind/preset')], plugins: [], theme: { extend: { fontFamily: { - jost: ["Jost-VariableFont_wght", "sans-serif"], + jost: ['Jost-VariableFont_wght', 'sans-serif'], }, fontWeight: { thin: 100, @@ -20,4 +20,4 @@ module.exports = { }, }, }, -}; \ No newline at end of file +} From 4632429b58ae16f58074f0a2a739df1d9ea3da0d Mon Sep 17 00:00:00 2001 From: AB-102 <61863411+AB-102@users.noreply.github.com> Date: Tue, 19 Nov 2024 11:38:09 -0600 Subject: [PATCH 09/13] removed unused constants --- thi-app/components/ExternalLink.tsx | 24 ------------------------ thi-app/components/StyledText.tsx | 5 ----- 2 files changed, 29 deletions(-) delete mode 100644 thi-app/components/ExternalLink.tsx delete mode 100644 thi-app/components/StyledText.tsx diff --git a/thi-app/components/ExternalLink.tsx b/thi-app/components/ExternalLink.tsx deleted file mode 100644 index 05b76a5..0000000 --- a/thi-app/components/ExternalLink.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Link } from 'expo-router'; -import * as WebBrowser from 'expo-web-browser'; -import React from 'react'; -import { Platform } from 'react-native'; - -export function ExternalLink(props: React.ComponentProps) { - return ( - { - if (Platform.OS !== 'web') { - // Prevent the default behavior of linking to the default browser on native. - e.preventDefault(); - // Open the link in an in-app browser. - WebBrowser.openBrowserAsync(props.href as string); - } - }} - /> - ); -} diff --git a/thi-app/components/StyledText.tsx b/thi-app/components/StyledText.tsx deleted file mode 100644 index aa3977c..0000000 --- a/thi-app/components/StyledText.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { Text, TextProps } from './Themed'; - -export function MonoText(props: TextProps) { - return ; -} From 1d3a4fa608dca43f911176559d242dffdf2d3166 Mon Sep 17 00:00:00 2001 From: AB-102 <61863411+AB-102@users.noreply.github.com> Date: Tue, 19 Nov 2024 16:08:52 -0600 Subject: [PATCH 10/13] fix swipe gesture crash --- thi-app/app/(drawer)/_layout.tsx | 9 +++++++-- thi-app/index.js | 2 +- thi-app/metro.config.js | 10 +++++++--- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/thi-app/app/(drawer)/_layout.tsx b/thi-app/app/(drawer)/_layout.tsx index b2cd7e2..62e603c 100644 --- a/thi-app/app/(drawer)/_layout.tsx +++ b/thi-app/app/(drawer)/_layout.tsx @@ -1,6 +1,11 @@ import React, { useState } from 'react' import { View, Dimensions, SafeAreaView } from 'react-native' -import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated' +import Animated, { + useSharedValue, + useAnimatedStyle, + withTiming, + runOnJS, +} from 'react-native-reanimated' import { GestureDetector, Gesture } from 'react-native-gesture-handler' import { Slot } from 'expo-router' import Sidebar, { @@ -81,7 +86,7 @@ const DrawerLayout = () => { (event.translationX > 50 && !isSidebarOpen) || (event.translationX < -50 && isSidebarOpen) ) { - toggleSidebar() + runOnJS(toggleSidebar)() } }) diff --git a/thi-app/index.js b/thi-app/index.js index 0671ee3..3407aea 100644 --- a/thi-app/index.js +++ b/thi-app/index.js @@ -1,4 +1,4 @@ -import './gesture-handler'; +import 'react-native-gesture-handler'; import ReactNativeFeatureFlags from "react-native/Libraries/ReactNative/ReactNativeFeatureFlags"; // enable the JS-side of the w3c PointerEvent implementation diff --git a/thi-app/metro.config.js b/thi-app/metro.config.js index 4180728..bd03ba1 100644 --- a/thi-app/metro.config.js +++ b/thi-app/metro.config.js @@ -1,5 +1,7 @@ const path = require("path"); const { getDefaultConfig } = require("expo/metro-config"); +const { withNativeWind } = require("nativewind/metro"); +const { wrapWithReanimatedMetroConfig } = require("react-native-reanimated/metro-config"); // 1. Enable CSS for Expo. const config = getDefaultConfig(__dirname, { @@ -17,8 +19,7 @@ config.resolver.nodeModulesPaths = [ ]; // 2. Enable NativeWind -const { withNativeWind } = require("nativewind/metro"); -module.exports = withNativeWind(config, { +const nativeWindConfig = withNativeWind(config, { // 3. Set `input` to your CSS file with the Tailwind at-rules input: "global.css", // This is optional @@ -27,4 +28,7 @@ module.exports = withNativeWind(config, { features: { transformPercentagePolyfill: true, }, -}); \ No newline at end of file +}); + +// 4. Enable Reanimated +module.exports = wrapWithReanimatedMetroConfig(nativeWindConfig); \ No newline at end of file From 0e69c1b2d1e3b02698900c719068b225bf1c9370 Mon Sep 17 00:00:00 2001 From: AB-102 <61863411+AB-102@users.noreply.github.com> Date: Thu, 21 Nov 2024 15:56:45 -0600 Subject: [PATCH 11/13] Adjust styles to team preference --- thi-app/app/(drawer)/_layout.tsx | 76 ++++++++++----------- thi-app/app/(drawer)/schedule.tsx | 6 +- thi-app/components/Login.tsx | 2 +- thi-app/components/Sidebar.tsx | 108 +++++++++++++++--------------- thi-app/components/SidebarTab.tsx | 84 +++++++++++------------ thi-app/index.js | 7 +- thi-app/metro.config.js | 2 +- thi-app/package.json | 1 + thi-app/tailwind.config.js | 10 +-- 9 files changed, 148 insertions(+), 148 deletions(-) diff --git a/thi-app/app/(drawer)/_layout.tsx b/thi-app/app/(drawer)/_layout.tsx index 62e603c..53028fc 100644 --- a/thi-app/app/(drawer)/_layout.tsx +++ b/thi-app/app/(drawer)/_layout.tsx @@ -1,25 +1,25 @@ -import React, { useState } from 'react' -import { View, Dimensions, SafeAreaView } from 'react-native' +import React, { useState } from "react"; +import { View, Dimensions, SafeAreaView } from "react-native"; import Animated, { useSharedValue, useAnimatedStyle, withTiming, runOnJS, -} from 'react-native-reanimated' -import { GestureDetector, Gesture } from 'react-native-gesture-handler' -import { Slot } from 'expo-router' +} from "react-native-reanimated"; +import { GestureDetector, Gesture } from "react-native-gesture-handler"; +import { Slot } from "expo-router"; import Sidebar, { SidebarContext, useSidebarContext, useTransitionCustomization, -} from '@/components/Sidebar' +} from "@/components/Sidebar"; const DrawerLayout = () => { // Sidebar state, dimensions, and transition settings - const { transitionEasing, transitionDuration } = useTransitionCustomization() - const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) - const [isSidebarOpen, setIsSidebarOpen] = useState(true) - const [isTransitioning, setIsTransitioning] = useState(false) + const { transitionEasing, transitionDuration } = useTransitionCustomization(); + const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + const [isSidebarOpen, setIsSidebarOpen] = useState(true); + const [isTransitioning, setIsTransitioning] = useState(false); const { openSidebarWidth, closedSidebarWidth, @@ -29,66 +29,66 @@ const DrawerLayout = () => { defaultTabColor, buttonColor, buttonSize, - } = useSidebarContext() + } = useSidebarContext(); // Tie animations to initial sidebar state (currently set to open) - const sidebarAnimatedValue = useSharedValue(isSidebarOpen ? 1 : 0) + const sidebarAnimatedValue = useSharedValue(isSidebarOpen ? 1 : 0); const mainScreenWidth = useSharedValue( - Dimensions.get('window').width - (openSidebarWidth + closedSidebarWidth) - ) + Dimensions.get("window").width - (openSidebarWidth + closedSidebarWidth) + ); const toggleSidebar = async () => { if ( (sidebarAnimatedValue.value === 1 && isSidebarOpen) || (sidebarAnimatedValue.value === 0 && !isSidebarOpen) ) { - setIsTransitioning(true) - setIsSidebarOpen(!isSidebarOpen) - transitionSidebar() - transitionMainScreen() + setIsTransitioning(true); + setIsSidebarOpen(!isSidebarOpen); + transitionSidebar(); + transitionMainScreen(); // Allow swipes after complete transition - await delay(transitionDuration) - setIsTransitioning(false) + await delay(transitionDuration); + setIsTransitioning(false); } - } + }; // Set main screen dynamic width const mainScreenAnimatedStyle = useAnimatedStyle(() => { - return { width: mainScreenWidth.value } - }) + return { width: mainScreenWidth.value }; + }); // Transitions sidebar for 400ms duration const transitionSidebar = () => { sidebarAnimatedValue.value = withTiming(isSidebarOpen ? 0 : 1, { duration: transitionDuration, easing: transitionEasing, - }) - } + }); + }; // Transitions main screen for 400ms duration const transitionMainScreen = () => { mainScreenWidth.value = withTiming( isSidebarOpen - ? Dimensions.get('window').width - closedSidebarWidth - : Dimensions.get('window').width - (openSidebarWidth + closedSidebarWidth), + ? Dimensions.get("window").width - closedSidebarWidth + : Dimensions.get("window").width - (openSidebarWidth + closedSidebarWidth), { duration: transitionDuration, easing: transitionEasing, } - ) - } + ); + }; // Horizontal swipe triggers sidebar toggle const swipeGesture = Gesture.Pan().onUpdate((event) => { // Ignore swipes mid-transition - if (isTransitioning) return + if (isTransitioning) return; // Threshold horizontal distance is 50 px to trigger if ( (event.translationX > 50 && !isSidebarOpen) || (event.translationX < -50 && isSidebarOpen) ) { - runOnJS(toggleSidebar)() + runOnJS(toggleSidebar)(); } - }) + }); return ( @@ -107,7 +107,7 @@ const DrawerLayout = () => { }} > - + {/* Sidebar */} @@ -116,9 +116,9 @@ const DrawerLayout = () => { style={[ mainScreenAnimatedStyle, { - position: 'absolute', + position: "absolute", right: 0, - height: Dimensions.get('window').height, + height: Dimensions.get("window").height, zIndex: 0, // Covered by sidebar layer }, ]} @@ -129,6 +129,6 @@ const DrawerLayout = () => { - ) -} -export default DrawerLayout + ); +}; +export default DrawerLayout; diff --git a/thi-app/app/(drawer)/schedule.tsx b/thi-app/app/(drawer)/schedule.tsx index 2ae11c8..3dac1fc 100644 --- a/thi-app/app/(drawer)/schedule.tsx +++ b/thi-app/app/(drawer)/schedule.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import { View, Text } from 'react-native'; +import React from "react"; +import { View, Text } from "react-native"; export default function SchedulePage() { return ( @@ -7,4 +7,4 @@ export default function SchedulePage() { Placeholder schedule page ); -} \ No newline at end of file +} diff --git a/thi-app/components/Login.tsx b/thi-app/components/Login.tsx index 3440710..1ad32c4 100644 --- a/thi-app/components/Login.tsx +++ b/thi-app/components/Login.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { View, Text, Image, TextInput, TouchableOpacity, Dimensions } from 'react-native'; import { useRouter } from 'expo-router'; -import { FontAwesome } from '@expo/vector-icons'; +import { FontAwesome } from '@expo/vector-icons'; const Login = () => { const [username, setUsername] = useState(''); diff --git a/thi-app/components/Sidebar.tsx b/thi-app/components/Sidebar.tsx index 7a81e33..97f201c 100644 --- a/thi-app/components/Sidebar.tsx +++ b/thi-app/components/Sidebar.tsx @@ -1,5 +1,5 @@ -import React, { createContext, useContext } from 'react' -import { View, Image, Dimensions, Pressable, ViewStyle } from 'react-native' +import React, { createContext, useContext } from "react"; +import { View, Image, Dimensions, Pressable, ViewStyle } from "react-native"; import Animated, { SharedValue, useAnimatedStyle, @@ -8,47 +8,47 @@ import Animated, { FadeOutLeft, Easing, AnimatedStyle, -} from 'react-native-reanimated' +} from "react-native-reanimated"; import { Entypo, FontAwesome, FontAwesome6, MaterialIcons, MaterialCommunityIcons, -} from '@expo/vector-icons' -import SidebarTab, { dynamicIconSize } from '@/components/SidebarTab' +} from "@expo/vector-icons"; +import SidebarTab, { dynamicIconSize } from "@/components/SidebarTab"; // Sidebar state, dimensions, and customization export const SidebarContext = createContext({ isSidebarOpen: true, toggleSidebar: () => {}, - openSidebarWidth: Dimensions.get('window').width * 0.18, - closedSidebarWidth: Dimensions.get('window').width * 0.02, - activeIconTextColor: 'white', - defaultIconTextColor: 'black', - activeTabColor: '#10536699', - defaultTabColor: 'transparent', - buttonColor: '#105366', + openSidebarWidth: Dimensions.get("window").width * 0.18, + closedSidebarWidth: Dimensions.get("window").width * 0.02, + activeIconTextColor: "white", + defaultIconTextColor: "black", + activeTabColor: "#10536699", + defaultTabColor: "transparent", + buttonColor: "#105366", buttonSize: 2.3, // in rem (1 rem = 16px) -}) +}); // Customize transition settings const TransitionCustomization = createContext({ transitionEasing: Easing.out(Easing.cubic), transitionDuration: 400, // in ms -}) +}); -export const useSidebarContext = () => useContext(SidebarContext) -export const useTransitionCustomization = () => useContext(TransitionCustomization) +export const useSidebarContext = () => useContext(SidebarContext); +export const useTransitionCustomization = () => useContext(TransitionCustomization); // Open/close sidebar button const SidebarButton = ({ animatedStyle }: { animatedStyle: AnimatedStyle }) => { const { toggleSidebar, openSidebarWidth, closedSidebarWidth, buttonColor, buttonSize } = - useSidebarContext() + useSidebarContext(); return ( - + - ) -} + ); +}; const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { // Sidebar and transition details - const { transitionDuration, transitionEasing } = useTransitionCustomization() - const { isSidebarOpen, openSidebarWidth, closedSidebarWidth } = useSidebarContext() - const enteringAnimation = FadeInLeft.duration(transitionDuration / 1.5).easing(transitionEasing) - const exitingAnimation = FadeOutLeft.duration(transitionDuration).easing(transitionEasing) + const { transitionDuration, transitionEasing } = useTransitionCustomization(); + const { isSidebarOpen, openSidebarWidth, closedSidebarWidth } = useSidebarContext(); + const enteringAnimation = FadeInLeft.duration(transitionDuration / 1.5).easing(transitionEasing); + const exitingAnimation = FadeOutLeft.duration(transitionDuration).easing(transitionEasing); // Sidebar slide interpolation const sidebarStyle = useAnimatedStyle(() => ({ transform: [{ translateX: interpolate(animatedValue.value, [0, 1], [-openSidebarWidth, 0]) }], - })) + })); // Button rotation interpolation (> when closed, < when open) const buttonStyle = useAnimatedStyle(() => ({ transform: [{ rotate: `${interpolate(animatedValue.value, [0, 0.5, 1], [0, -90, -180])}deg` }], - })) + })); return ( @@ -101,18 +101,18 @@ const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { {/* Sidebar contents */} - + {isSidebarOpen && ( {/* App icon */} - + }) => { {/* Navigable screens */} {/* Home */} - + {/* Students */} {/* Schedule */} {/* Games */} {/* Timer */} - + {/* Settings */} - + )} {/* Spacer */} {isSidebarOpen && ( @@ -172,9 +172,9 @@ const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { {/* Sign out */} @@ -184,12 +184,12 @@ const Sidebar = ({ animatedValue }: { animatedValue: SharedValue }) => { {/* Bottom padding */} - ) -} -export default Sidebar + ); +}; +export default Sidebar; diff --git a/thi-app/components/SidebarTab.tsx b/thi-app/components/SidebarTab.tsx index c547424..d637075 100644 --- a/thi-app/components/SidebarTab.tsx +++ b/thi-app/components/SidebarTab.tsx @@ -1,35 +1,35 @@ -import React, { useState } from 'react' -import { useFocusEffect } from '@react-navigation/native' -import { RelativePathString, useRouter, useSegments } from 'expo-router' -import { View, Text, TouchableOpacity, Dimensions, DimensionValue } from 'react-native' -import { Icon } from '@expo/vector-icons/build/createIconSet' -import { useSidebarContext } from '@/components/Sidebar' +import React, { useState } from "react"; +import { useFocusEffect } from "@react-navigation/native"; +import { RelativePathString, useRouter, useSegments } from "expo-router"; +import { View, Text, TouchableOpacity, Dimensions, DimensionValue } from "react-native"; +import { Icon } from "@expo/vector-icons/build/createIconSet"; +import { useSidebarContext } from "@/components/Sidebar"; // Dynamic icon resizing export function dynamicIconSize(): number { - const { closedSidebarWidth } = useSidebarContext() - let size - const height = Dimensions.get('window').height - const outerContainerHeight = closedSidebarWidth + const { closedSidebarWidth } = useSidebarContext(); + let size; + const height = Dimensions.get("window").height; + const outerContainerHeight = closedSidebarWidth; if (height >= 800 && height > outerContainerHeight) { - size = 32 // lg:text-2xl + size = 32; // lg:text-2xl } else if (height >= 600 && height > outerContainerHeight) { - size = 24 // md:text-xl + size = 24; // md:text-xl } else { - size = 16 // default for small phones + size = 16; // default for small phones } - return size + return size; } // Define props for sidebar tab interface SidebarTabProps { - iconSet: Icon - iconName: string - iconSize?: number - label: string - useActiveColor?: boolean - tabWidth?: DimensionValue + iconSet: Icon; + iconName: string; + iconSize?: number; + label: string; + useActiveColor?: boolean; + tabWidth?: DimensionValue; } // Sidebar navigation tab @@ -39,7 +39,7 @@ const SidebarTab = ({ iconSize = dynamicIconSize(), label, useActiveColor = true, - tabWidth = '100%', + tabWidth = "100%", }: SidebarTabProps) => { // Sidebar details const { @@ -48,51 +48,51 @@ const SidebarTab = ({ defaultIconTextColor, activeTabColor, defaultTabColor, - } = useSidebarContext() + } = useSidebarContext(); // Screen routing details - const router = useRouter() - const [currentScreen, setCurrentScreen] = useState('') - const segments: string[] = useSegments() - const tabName = label === 'Sign out' ? '' : label.toLowerCase() - const directory = '/' + tabName - const isActive = currentScreen.includes(tabName) + const router = useRouter(); + const [currentScreen, setCurrentScreen] = useState(""); + const segments: string[] = useSegments(); + const tabName = label === "Sign out" ? "" : label.toLowerCase(); + const directory = "/" + tabName; + const isActive = currentScreen.includes(tabName); // Sidebar tab details - const tabHeight = Dimensions.get('window').height * 0.08 - const tabIconTextColor = useActiveColor && isActive ? activeIconTextColor : defaultIconTextColor - const tabButtonColor = useActiveColor && isActive ? activeTabColor : defaultTabColor + const tabHeight = Dimensions.get("window").height * 0.08; + const tabIconTextColor = useActiveColor && isActive ? activeIconTextColor : defaultIconTextColor; + const tabButtonColor = useActiveColor && isActive ? activeTabColor : defaultTabColor; // Set current screen name (e.g. "students" for (drawer)/students) useFocusEffect( React.useCallback(() => { if (segments.length > 0) { // Set screen name to last segment of URL - const screenName = segments[segments.length - 1] - setCurrentScreen(screenName) + const screenName = segments[segments.length - 1]; + setCurrentScreen(screenName); } }, [segments]) - ) + ); return ( - + router.push(directory as RelativePathString)} > - - + + {label} - ) -} -export default SidebarTab + ); +}; +export default SidebarTab; diff --git a/thi-app/index.js b/thi-app/index.js index 3407aea..3a75b38 100644 --- a/thi-app/index.js +++ b/thi-app/index.js @@ -1,4 +1,4 @@ -import 'react-native-gesture-handler'; +import "react-native-gesture-handler"; import ReactNativeFeatureFlags from "react-native/Libraries/ReactNative/ReactNativeFeatureFlags"; // enable the JS-side of the w3c PointerEvent implementation @@ -6,7 +6,6 @@ ReactNativeFeatureFlags.shouldEmitW3CPointerEvents = () => true; // enable hover events in Pressibility to be backed by the PointerEvent implementation. // shouldEmitW3CPointerEvents should also be true -ReactNativeFeatureFlags.shouldPressibilityUseW3CPointerEventsForHover = () => - true; +ReactNativeFeatureFlags.shouldPressibilityUseW3CPointerEventsForHover = () => true; -import "expo-router/entry"; \ No newline at end of file +import "expo-router/entry"; diff --git a/thi-app/metro.config.js b/thi-app/metro.config.js index bd03ba1..4aecfdc 100644 --- a/thi-app/metro.config.js +++ b/thi-app/metro.config.js @@ -31,4 +31,4 @@ const nativeWindConfig = withNativeWind(config, { }); // 4. Enable Reanimated -module.exports = wrapWithReanimatedMetroConfig(nativeWindConfig); \ No newline at end of file +module.exports = wrapWithReanimatedMetroConfig(nativeWindConfig); diff --git a/thi-app/package.json b/thi-app/package.json index 97c6a63..fb36668 100644 --- a/thi-app/package.json +++ b/thi-app/package.json @@ -42,6 +42,7 @@ "@types/react": "~18.3.12", "jest": "~29.7.0", "jest-expo": "~52.0.1", + "prettier": "3.3.3", "react-test-renderer": "18.2.0", "typescript": "~5.3.3" }, diff --git a/thi-app/tailwind.config.js b/thi-app/tailwind.config.js index 13eef3f..a451f77 100644 --- a/thi-app/tailwind.config.js +++ b/thi-app/tailwind.config.js @@ -1,13 +1,13 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - darkMode: 'class', - content: ['app/**/*.{js,jsx,ts,tsx}', 'components/**/*.{js,jsx,ts,tsx}'], - presets: [require('nativewind/preset')], + darkMode: "class", + content: ["app/**/*.{js,jsx,ts,tsx}", "components/**/*.{js,jsx,ts,tsx}"], + presets: [require("nativewind/preset")], plugins: [], theme: { extend: { fontFamily: { - jost: ['Jost-VariableFont_wght', 'sans-serif'], + jost: ["Jost-VariableFont_wght", "sans-serif"], }, fontWeight: { thin: 100, @@ -20,4 +20,4 @@ module.exports = { }, }, }, -} +}; From 2925da6fd3dd2320087b6d08a2df0d9d8c484e5d Mon Sep 17 00:00:00 2001 From: An Bui <61863411+AB-102@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:02:05 -0600 Subject: [PATCH 12/13] Revert package.json --- thi-app/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/thi-app/package.json b/thi-app/package.json index fb36668..97c6a63 100644 --- a/thi-app/package.json +++ b/thi-app/package.json @@ -42,7 +42,6 @@ "@types/react": "~18.3.12", "jest": "~29.7.0", "jest-expo": "~52.0.1", - "prettier": "3.3.3", "react-test-renderer": "18.2.0", "typescript": "~5.3.3" }, From c367121cd5a72e6061954faa55caa1e1e1735a7a Mon Sep 17 00:00:00 2001 From: An Bui <61863411+AB-102@users.noreply.github.com> Date: Thu, 21 Nov 2024 16:11:34 -0600 Subject: [PATCH 13/13] Revert _layout.tsx --- thi-app/app/_layout.tsx | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/thi-app/app/_layout.tsx b/thi-app/app/_layout.tsx index 30c6bc6..cb88785 100644 --- a/thi-app/app/_layout.tsx +++ b/thi-app/app/_layout.tsx @@ -1,14 +1,14 @@ -import '../global.css' -import { useEffect } from 'react' -import { View } from 'react-native' -import { GestureHandlerRootView } from 'react-native-gesture-handler' -import { useFonts } from 'expo-font' -import { SplashScreen, Stack } from 'expo-router' +import "../global.css"; +import { useEffect } from "react"; +import { View } from "react-native"; +import { GestureHandlerRootView } from "react-native-gesture-handler"; +import { useFonts } from "expo-font"; +import { SplashScreen, Stack } from "expo-router"; export { // Catch any errors thrown by the Layout component. ErrorBoundary, -} from 'expo-router' +} from "expo-router"; // export const unstable_settings = { // // Ensure that reloading on `/modal` keeps a back button present. @@ -16,35 +16,35 @@ export { // }; // Prevent the splash screen from auto-hiding before asset loading is complete. -SplashScreen.preventAutoHideAsync() +SplashScreen.preventAutoHideAsync(); export default function RootLayout() { const [loaded, error] = useFonts({ - Jost: require('../assets/fonts/Jost-VariableFont_wght.ttf'), - }) + 'Jost': require("../assets/fonts/Jost-VariableFont_wght.ttf") + }); useEffect(() => { if (loaded || error) { - SplashScreen.hideAsync() + SplashScreen.hideAsync(); } - }, [loaded, error]) + }, [loaded, error]); if (!loaded && !error) { - return null + return null; } - return + return ; } function RootLayoutNav() { return ( - - + + - - + + - ) + ); }