Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: jost font support, schedule tab, more dynamic sidebar resizing and memopt #16

Merged
merged 13 commits into from
Nov 23, 2024
16 changes: 9 additions & 7 deletions thi-app/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
"resizeMode": "contain",
"backgroundColor": "#ffffff"
},
"assetBundlePatterns": [
"**/*"
],
"assetBundlePatterns": ["**/*"],
"ios": {
"supportsTablet": true,
"userInterfaceStyle": "automatic",
Expand All @@ -36,14 +34,18 @@
},
"plugins": [
"expo-router",
"expo-font",
[
"expo-screen-orientation",
"expo-font",
{
"initialOrientation": "DEFAULT"
"fonts": ["./assets/fonts/Jost-VariableFont_wght.tff"]
}
],
"expo-font"
[
"expo-screen-orientation",
{
"initialOrientation": "LANDSCAPE_LEFT"
}
]
],
"experiments": {
"tsconfigPaths": true,
Expand Down
159 changes: 88 additions & 71 deletions thi-app/app/(drawer)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,117 +1,134 @@
import React, { createContext, useContext, 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,
Easing,
} from 'react-native-reanimated';
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
import { Slot } from 'expo-router';
import Sidebar, { SidebarContext, useSidebarContext } from '../../components/Sidebar';
runOnJS,
} 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";

// Customize transition settings
const TransitionCustomization = createContext({
transitionEasing: Easing.out(Easing.cubic),
transitionDuration: 400, // in ms
// Add more
})

export const useTransitionCustomization = () => useContext(TransitionCustomization);

export default function Layout() {
// Sidebar state, dimensions
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 { 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));
Dimensions.get("window").width - (openSidebarWidth + closedSidebarWidth)
);

// 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
const mainScreenAnimatedStyle = useAnimatedStyle(() => {
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) ?
Dimensions.get('window').width - closedSidebarWidth :
Dimensions.get('window').width - (openSidebarWidth + closedSidebarWidth),
{
duration: transitionDuration,
easing: transitionEasing,
}
);
};

// Transitions sidebar for 400ms duration
const transitionSidebar = () => {
sidebarAnimatedValue.value = withTiming(
sidebarAnimatedValue.value === 1 && isSidebarOpen ? 0 : 1, {
isSidebarOpen
? Dimensions.get("window").width - closedSidebarWidth
: Dimensions.get("window").width - (openSidebarWidth + closedSidebarWidth),
{
duration: transitionDuration,
easing: transitionEasing,
}
);
};

// Horizontal swipes trigger
const swipeGesture = Gesture.Pan()
.onUpdate((event) => {
// 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
if (event.translationX > 50 && !isSidebarOpen) {
// Swipe right opens sidebar
toggleSidebar();
} else if (event.translationX < -50 && isSidebarOpen) {
// Swipe left closes sidebar
toggleSidebar();
// Threshold horizontal distance is 50 px to trigger
if (
(event.translationX > 50 && !isSidebarOpen) ||
(event.translationX < -50 && isSidebarOpen)
) {
runOnJS(toggleSidebar)();
}
});

return (
<SafeAreaView>
<SidebarContext.Provider value={{ isSidebarOpen, toggleSidebar, openSidebarWidth, closedSidebarWidth }}>
<SidebarContext.Provider
value={{
isSidebarOpen,
toggleSidebar,
openSidebarWidth,
closedSidebarWidth,
activeIconTextColor,
defaultIconTextColor,
activeTabColor,
defaultTabColor,
buttonColor,
buttonSize,
}}
>
<GestureDetector gesture={swipeGesture}>
<View className="flex-1 flex-row" collapsable={false}>

{/* Sidebar */}
<Sidebar animatedValue={sidebarAnimatedValue} />

{/* Main screen in ./(drawer)/ */}
<Animated.View style={[
mainScreenAnimatedStyle,
{
position: 'absolute',
right: 0,
height: Dimensions.get('window').height,
zIndex: 0, // Covered by sidebar layer
},
]}>
<Slot />
<Animated.View
style={[
mainScreenAnimatedStyle,
{
position: "absolute",
right: 0,
height: Dimensions.get("window").height,
zIndex: 0, // Covered by sidebar layer
},
]}
>
<Slot />
</Animated.View>

</View>
</GestureDetector>
</SidebarContext.Provider>
</SafeAreaView>
);
}
};
export default DrawerLayout;
10 changes: 10 additions & 0 deletions thi-app/app/(drawer)/schedule.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from "react";
import { View, Text } from "react-native";

export default function SchedulePage() {
return (
<View className="flex-1 items-center justify-center bg-gray-100">
<Text>Placeholder schedule page</Text>
</View>
);
}
21 changes: 7 additions & 14 deletions thi-app/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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;
}

Expand All @@ -54,4 +47,4 @@ function RootLayoutNav() {
</View>
</GestureHandlerRootView>
);
}
}
2 changes: 1 addition & 1 deletion thi-app/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Login from '@/components/login';
import Login from '@/components/Login';
import React from 'react';
import { View } from 'react-native';

Expand Down
File renamed without changes
Binary file removed thi-app/assets/images/students_icon.png
Binary file not shown.
24 changes: 0 additions & 24 deletions thi-app/components/ExternalLink.tsx

This file was deleted.

Loading