Skip to content

Commit

Permalink
Merge pull request #16 from rice-apps/sidebar
Browse files Browse the repository at this point in the history
feat: jost font support, schedule tab, more dynamic sidebar resizing and memopt
  • Loading branch information
trihoang0809 authored Nov 23, 2024
2 parents f4c8690 + c367121 commit 3a9e58a
Show file tree
Hide file tree
Showing 14 changed files with 430 additions and 361 deletions.
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

0 comments on commit 3a9e58a

Please sign in to comment.