Calendar improvements

This commit is contained in:
Milan Paunovic
2025-02-02 22:28:40 +01:00
parent 0998dc29d0
commit 5cfec1544a
22 changed files with 5140 additions and 1188 deletions

View File

@ -1,433 +1,356 @@
import React from "react";
import { Drawer } from "expo-router/drawer";
import { useSignOut } from "@/hooks/firebase/useSignOut";
import {
DrawerContentScrollView,
DrawerNavigationOptions,
DrawerNavigationProp,
} from "@react-navigation/drawer";
import {
Button,
ButtonSize,
Text,
TouchableOpacity,
View,
} from "react-native-ui-lib";
import { ImageBackground, Pressable, StyleSheet } from "react-native";
import React, {useCallback} from "react";
import {Drawer} from "expo-router/drawer";
import {DrawerContentScrollView, DrawerContentComponentProps, DrawerNavigationOptions} from "@react-navigation/drawer";
import {ImageBackground, Pressable, StyleSheet} from "react-native";
import {Button, ButtonSize, Text, View} from "react-native-ui-lib";
import * as Device from "expo-device";
import {useSetAtom} from "jotai";
import {Ionicons} from "@expo/vector-icons";
import {DeviceType} from "expo-device";
import {DrawerNavigationProp} from "@react-navigation/drawer";
import {ParamListBase, Theme} from '@react-navigation/native';
import {RouteProp} from "@react-navigation/native";
import {useSignOut} from "@/hooks/firebase/useSignOut";
import {CalendarHeader} from "@/components/pages/calendar/CalendarHeader";
import DrawerButton from "@/components/shared/DrawerButton";
import DrawerIcon from "@/assets/svgs/DrawerIcon";
import NavGroceryIcon from "@/assets/svgs/NavGroceryIcon";
import NavToDosIcon from "@/assets/svgs/NavToDosIcon";
import NavBrainDumpIcon from "@/assets/svgs/NavBrainDumpIcon";
import NavCalendarIcon from "@/assets/svgs/NavCalendarIcon";
import NavSettingsIcon from "@/assets/svgs/NavSettingsIcon";
import ViewSwitch from "@/components/pages/(tablet_pages)/ViewSwitch";
import { useSetAtom } from "jotai";
import {
isFamilyViewAtom,
settingsPageIndex,
toDosPageIndex,
userSettingsView,
} from "@/components/pages/calendar/atoms";
import Ionicons from "@expo/vector-icons/Ionicons";
import * as Device from "expo-device";
import { DeviceType } from "expo-device";
import FeedbackNavIcon from "@/assets/svgs/FeedbackNavIcon";
import DrawerIcon from "@/assets/svgs/DrawerIcon";
import { RouteProp } from "@react-navigation/core";
import RefreshButton from "@/components/shared/RefreshButton";
import { useCalSync } from "@/hooks/useCalSync";
import {useIsFetching} from "@tanstack/react-query";
import { CalendarHeader } from "@/components/pages/calendar/CalendarHeader";
import {
isFamilyViewAtom,
settingsPageIndex,
toDosPageIndex,
userSettingsView,
} from "@/components/pages/calendar/atoms";
import ViewSwitch from "@/components/pages/(tablet_pages)/ViewSwitch";
const isTablet = Device.deviceType === DeviceType.TABLET;
type DrawerParamList = {
index: undefined;
calendar: undefined;
todos: undefined;
index: undefined;
calendar: undefined;
brain_dump: undefined;
settings: undefined;
grocery: undefined;
reminders: undefined;
todos: undefined;
notifications: undefined;
feedback: undefined;
};
type NavigationProp = DrawerNavigationProp<DrawerParamList>;
type DrawerScreenNavigationProp = DrawerNavigationProp<DrawerParamList>;
interface ViewSwitchProps {
navigation: NavigationProp;
interface DrawerButtonConfig {
id: string;
title: string;
color: string;
bgColor: string;
icon: React.FC;
route: keyof DrawerParamList;
}
const DRAWER_BUTTONS: DrawerButtonConfig[] = [
{
id: 'calendar',
title: 'Calendar',
color: 'rgb(7, 184, 199)',
bgColor: 'rgb(231, 248, 250)',
icon: NavCalendarIcon,
route: 'calendar'
},
{
id: 'grocery',
title: 'Groceries',
color: '#50be0c',
bgColor: '#eef9e7',
icon: NavGroceryIcon,
route: 'grocery'
},
{
id: 'feedback',
title: 'Feedback',
color: '#ea156d',
bgColor: '#fdedf4',
icon: FeedbackNavIcon,
route: 'feedback'
},
{
id: 'todos',
title: 'To Dos',
color: '#8005eb',
bgColor: '#f3e6fd',
icon: NavToDosIcon,
route: 'todos'
},
{
id: 'brain_dump',
title: 'Brain Dump',
color: '#e0ca03',
bgColor: '#fffacb',
icon: NavBrainDumpIcon,
route: 'brain_dump'
},
{
id: 'notifications',
title: 'Notifications',
color: '#ffa200',
bgColor: '#ffdda1',
icon: () => <Ionicons name="notifications-outline" size={24} color="#ffa200"/>,
route: 'notifications'
}
];
interface DrawerContentProps {
props: DrawerContentComponentProps;
}
const DrawerContent: React.FC<DrawerContentProps> = ({props}) => {
const {mutateAsync: signOut} = useSignOut();
const setIsFamilyView = useSetAtom(isFamilyViewAtom);
const setPageIndex = useSetAtom(settingsPageIndex);
const setUserView = useSetAtom(userSettingsView);
const setToDosIndex = useSetAtom(toDosPageIndex);
const handleNavigation = useCallback((route: keyof DrawerParamList) => {
props.navigation.navigate(route);
setPageIndex(0);
setToDosIndex(0);
setUserView(true);
setIsFamilyView(false);
}, [props.navigation, setPageIndex, setToDosIndex, setUserView, setIsFamilyView]);
const renderDrawerButtons = () => {
const midPoint = Math.ceil(DRAWER_BUTTONS.length / 2);
const leftButtons = DRAWER_BUTTONS.slice(0, midPoint);
const rightButtons = DRAWER_BUTTONS.slice(midPoint);
return (
<View row paddingH-30>
<View flex-1 paddingR-5>
{leftButtons.map(button => (
<DrawerButton
key={button.id}
title={button.title}
color={button.color}
bgColor={button.bgColor}
pressFunc={() => handleNavigation(button.route)}
icon={<button.icon/>}
/>
))}
</View>
<View flex-1>
{rightButtons.map(button => (
<DrawerButton
key={button.id}
title={button.title}
color={button.color}
bgColor={button.bgColor}
pressFunc={() => handleNavigation(button.route)}
icon={<button.icon/>}
/>
))}
</View>
</View>
);
};
return (
<DrawerContentScrollView {...props}>
<View centerV marginH-30 marginT-20 marginB-20 row>
<ImageBackground
source={require("../../assets/images/splash.png")}
style={styles.logo}
/>
<Text style={styles.title}>Welcome to Cally</Text>
</View>
{renderDrawerButtons()}
<Button
onPress={() => handleNavigation('settings')}
label="Manage Settings"
labelStyle={styles.label}
iconSource={() => (
<View style={styles.settingsIcon}>
<NavSettingsIcon/>
</View>
)}
backgroundColor="white"
color="#464039"
paddingV-30
marginH-30
borderRadius={18.55}
style={{elevation: 0}}
/>
<Button
size={ButtonSize.large}
style={styles.signOutButton}
label="Sign out of Cally"
color="#fd1775"
labelStyle={styles.signOut}
onPress={() => signOut()}
/>
</DrawerContentScrollView>
);
};
interface HeaderRightProps {
routeName: keyof DrawerParamList;
navigation: NavigationProp;
route: RouteProp<DrawerParamList>;
navigation: DrawerScreenNavigationProp;
}
const MemoizedViewSwitch = React.memo<ViewSwitchProps>(({ navigation }) => (
<ViewSwitch navigation={navigation} />
));
const HeaderRight: React.FC<HeaderRightProps> = ({route, navigation}) => {
const showViewSwitch = ["calendar", "todos", "index"].includes(route.name);
const isCalendarPage = ["calendar", "index"].includes(route.name);
const HeaderRight = React.memo<HeaderRightProps>(
({ routeName, navigation }) => {
const showViewSwitch = ["calendar", "todos", "index"].includes(routeName);
if (Device.deviceType !== DeviceType.TABLET || !showViewSwitch) {
return null;
if (!isTablet || !showViewSwitch) {
return isCalendarPage ? <CalendarHeader/> : null;
}
return <MemoizedViewSwitch navigation={navigation} />;
}
);
export default function TabLayout() {
const { mutateAsync: signOut } = useSignOut();
const setIsFamilyView = useSetAtom(isFamilyViewAtom);
const setPageIndex = useSetAtom(settingsPageIndex);
const setUserView = useSetAtom(userSettingsView);
const setToDosIndex = useSetAtom(toDosPageIndex);
const { resyncAllCalendars, isSyncing } = useCalSync();
return (
<View marginR-16 row centerV>
{isTablet && isCalendarPage && (
<View flex-1 center>
<CalendarHeader/>
</View>
)}
<ViewSwitch navigation={navigation}/>
</View>
);
};
const isFetching = useIsFetching({queryKey: ['events']}) > 0;
const isLoading = React.useMemo(() => {
return isSyncing || isFetching;
}, [isSyncing, isFetching]);
const onRefresh = React.useCallback(async () => {
try {
await resyncAllCalendars();
} catch (error) {
console.error("Refresh failed:", error);
}
}, [resyncAllCalendars]);
const screenOptions = ({
navigation,
route,
}: {
navigation: DrawerNavigationProp<DrawerParamList>;
route: RouteProp<DrawerParamList>;
}): DrawerNavigationOptions => ({
const screenOptions: (props: {
route: RouteProp<ParamListBase, string>;
navigation: DrawerNavigationProp<ParamListBase, string>;
theme: Theme;
}) => DrawerNavigationOptions = ({route, navigation}) => ({
lazy: true,
headerShown: true,
headerTitleAlign:
Device.deviceType === DeviceType.TABLET ? "left" : "unaligned",
headerTitle: ({ children }) => {
headerTitleAlign: "left",
headerTitle: ({children}) => {
const isCalendarRoute = ["calendar", "index"].includes(route.name);
if (isCalendarRoute && Device.deviceType !== DeviceType.TABLET) {
return <View centerV><CalendarHeader /></View>;
}
if (isCalendarRoute) return null;
return (
<Text
style={{
fontFamily: "Manrope_600SemiBold",
fontSize: Device.deviceType === DeviceType.TABLET ? 22 : 17,
}}
>
{children}
</Text>
<View flexG centerV paddingL-10>
<Text style={styles.headerTitle}>
{children}
</Text>
</View>
);
},
headerTitleStyle: {
fontFamily: "Manrope_600SemiBold",
fontSize: Device.deviceType === DeviceType.TABLET ? 22 : 17,
},
headerLeft: () => (
<Pressable
onPress={() => {
navigation.toggleDrawer()
}}
hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}
style={({ pressed }) => [
{
marginLeft: 16,
opacity: pressed ? 0.4 : 1
}
]}
>
<DrawerIcon />
</Pressable>
<Pressable
onPress={() => navigation.toggleDrawer()}
hitSlop={{top: 10, bottom: 10, left: 10, right: 10}}
style={({pressed}) => [styles.drawerTrigger, {opacity: pressed ? 0.4 : 1}]}
>
<DrawerIcon/>
</Pressable>
),
headerRight: () => {
const showViewSwitch = ["calendar", "todos", "index"].includes(
route.name
);
const isCalendarPage = ["calendar", "index"].includes(route.name);
headerRight: () => <HeaderRight
route={route as RouteProp<DrawerParamList>}
navigation={navigation as DrawerNavigationProp<DrawerParamList>}
/>,
drawerStyle: styles.drawer,
});
if (Device.deviceType !== DeviceType.TABLET || !showViewSwitch) {
return isCalendarPage ? (
<View marginR-8 >
<RefreshButton onRefresh={onRefresh} isSyncing={isLoading} />
</View>
) : null;
}
return (
<View marginR-16 row centerV>
{Device.deviceType === DeviceType.TABLET && isCalendarPage && <View flex-1 center><CalendarHeader />
</View>}
{isCalendarPage && (
<View marginR-16><RefreshButton onRefresh={onRefresh} isSyncing={isLoading} /></View>
)}
<MemoizedViewSwitch navigation={navigation} />
</View>
);
},
drawerStyle: {
width: Device.deviceType === DeviceType.TABLET ? "30%" : "90%",
backgroundColor: "#f9f8f7",
height: "100%",
},
});
return (
<Drawer
initialRouteName={"index"}
detachInactiveScreens
screenOptions={screenOptions}
drawerContent={(props) => {
return (
<DrawerContentScrollView {...props} style={{}}>
<View centerV marginH-30 marginT-20 marginB-20 row>
<ImageBackground
source={require("../../assets/images/splash.png")}
style={{
backgroundColor: "transparent",
height: 51.43,
aspectRatio: 1,
marginRight: 8,
}}
/>
<Text style={styles.title}>Welcome to Cally</Text>
</View>
<View
style={{
flexDirection: "row",
paddingHorizontal: 30,
}}
>
<View style={{ flex: 1, paddingRight: 5 }}>
<DrawerButton
title={"Calendar"}
color="rgb(7, 184, 199)"
bgColor={"rgb(231, 248, 250)"}
pressFunc={() => {
props.navigation.navigate("calendar");
setPageIndex(0);
setToDosIndex(0);
setUserView(true);
setIsFamilyView(false);
}}
icon={<NavCalendarIcon />}
/>
<DrawerButton
color="#50be0c"
title={"Groceries"}
bgColor={"#eef9e7"}
pressFunc={() => {
props.navigation.navigate("grocery");
setPageIndex(0);
setToDosIndex(0);
setUserView(true);
setIsFamilyView(false);
}}
icon={<NavGroceryIcon />}
/>
<DrawerButton
color="#ea156d"
title={"Feedback"}
bgColor={"#fdedf4"}
pressFunc={() => {
props.navigation.navigate("feedback");
setPageIndex(0);
setToDosIndex(0);
setUserView(true);
setIsFamilyView(false);
}}
icon={<FeedbackNavIcon />}
/>
</View>
<View style={{ flex: 1, paddingRight: 0 }}>
{/*<DrawerButton
color="#fd1775"
title={"My Reminders"}
bgColor={"#ffe8f2"}
pressFunc={() => props.navigation.navigate("reminders")}
icon={
<FontAwesome6
name="clock-rotate-left"
size={28}
color="#fd1775"
/>
}
/>*/}
<DrawerButton
color="#8005eb"
title={"To Dos"}
bgColor={"#f3e6fd"}
pressFunc={() => {
props.navigation.navigate("todos");
setPageIndex(0);
setToDosIndex(0);
setUserView(true);
setIsFamilyView(false);
}}
icon={<NavToDosIcon />}
/>
<DrawerButton
color="#e0ca03"
title={"Brain Dump"}
bgColor={"#fffacb"}
pressFunc={() => {
props.navigation.navigate("brain_dump");
setPageIndex(0);
setToDosIndex(0);
setUserView(true);
setIsFamilyView(false);
}}
icon={<NavBrainDumpIcon />}
/>
<DrawerButton
color="#e0ca03"
title={"Notifications"}
bgColor={"#ffdda1"}
pressFunc={() => {
props.navigation.navigate("notifications");
setPageIndex(0);
setToDosIndex(0);
setUserView(true);
setIsFamilyView(false);
}}
icon={
<Ionicons
name="notifications-outline"
size={24}
color={"#ffa200"}
/>
}
/>
</View>
</View>
<Button
onPress={() => {
props.navigation.navigate("settings");
setPageIndex(0);
setToDosIndex(0);
setUserView(true);
setIsFamilyView(false);
}}
label={"Manage Settings"}
labelStyle={styles.label}
iconSource={() => (
<View
backgroundColor="#ededed"
width={60}
height={60}
style={{ borderRadius: 50 }}
marginR-10
centerV
centerH
>
<NavSettingsIcon />
</View>
)}
backgroundColor="white"
color="#464039"
paddingV-30
marginH-30
borderRadius={18.55}
style={{ elevation: 0 }}
/>
<Button
size={ButtonSize.large}
marginH-10
marginT-12
paddingV-15
style={{
marginTop: 50,
backgroundColor: "transparent",
borderWidth: 1.3,
borderColor: "#fd1775",
}}
label="Sign out of Cally"
color="#fd1775"
labelStyle={styles.signOut}
onPress={() => signOut()}
/>
</DrawerContentScrollView>
);
}}
>
<Drawer.Screen
name="index"
options={{
drawerLabel: "Calendar",
title: "Calendar",
}}
/>
<Drawer.Screen
name="calendar"
options={{
drawerLabel: "Calendar",
title: "Calendar",
drawerItemStyle: { display: "none" },
}}
/>
<Drawer.Screen
name="brain_dump"
options={{
drawerLabel: "Brain Dump",
title: "Brain Dump",
}}
/>
<Drawer.Screen
name="settings"
options={{
drawerLabel: "Settings",
title: "Settings",
}}
/>
<Drawer.Screen
name="grocery"
options={{
drawerLabel: "Groceries",
title: "Groceries",
}}
/>
<Drawer.Screen
name="reminders"
options={{
drawerLabel: "Reminders",
title: "Reminders",
}}
/>
<Drawer.Screen
name="todos"
options={{
drawerLabel: "To-Do",
title:
Device.deviceType === DeviceType.TABLET
? "Family To Dos"
: "To Dos",
}}
/>
<Drawer.Screen
name="notifications"
options={{
drawerLabel: "Notifications",
title: "Notifications",
}}
/>
<Drawer.Screen
name="feedback"
options={{ drawerLabel: "Feedback", title: "Feedback" }}
/>
</Drawer>
);
interface DrawerScreen {
name: keyof DrawerParamList;
title: string;
hideInDrawer?: boolean;
}
const DRAWER_SCREENS: DrawerScreen[] = [
{name: 'index', title: 'Calendar'},
{name: 'calendar', title: 'Calendar', hideInDrawer: true},
{name: 'brain_dump', title: 'Brain Dump'},
{name: 'settings', title: 'Settings'},
{name: 'grocery', title: 'Groceries'},
{name: 'reminders', title: 'Reminders'},
{name: 'todos', title: isTablet ? 'Family To Dos' : 'To Dos'},
{name: 'notifications', title: 'Notifications'},
{name: 'feedback', title: 'Feedback'}
];
const TabLayout: React.FC = () => {
return (
<Drawer
initialRouteName="index"
detachInactiveScreens
screenOptions={screenOptions}
drawerContent={(props) => <DrawerContent props={props}/>}
>
{DRAWER_SCREENS.map(screen => (
<Drawer.Screen
key={screen.name}
name={screen.name}
options={{
drawerLabel: screen.title,
title: screen.title,
...(screen.hideInDrawer && {drawerItemStyle: {display: 'none'}}),
}}
/>
))}
</Drawer>
);
};
const styles = StyleSheet.create({
signOut: { fontFamily: "Poppins_500Medium", fontSize: 15 },
label: { fontFamily: "Poppins_400Medium", fontSize: 15 },
title: {
fontSize: 26.13,
fontFamily: "Manrope_600SemiBold",
color: "#262627",
},
drawer: {
width: isTablet ? "30%" : "90%",
backgroundColor: "#f9f8f7",
height: "100%",
},
drawerTrigger: {
marginLeft: 16,
},
headerTitle: {
fontFamily: "Manrope_600SemiBold",
fontSize: isTablet ? 22 : 17,
},
logo: {
backgroundColor: "transparent",
height: 51.43,
aspectRatio: 1,
marginRight: 8,
},
settingsIcon: {
backgroundColor: "#ededed",
width: 60,
height: 60,
borderRadius: 50,
marginRight: 10,
justifyContent: 'center',
alignItems: 'center',
},
signOutButton: {
marginTop: 50,
marginHorizontal: 10,
paddingVertical: 15,
backgroundColor: "transparent",
borderWidth: 1.3,
borderColor: "#fd1775",
},
signOut: {
fontFamily: "Poppins_500Medium",
fontSize: 15,
},
label: {
fontFamily: "Poppins_400Medium",
fontSize: 15,
},
title: {
fontSize: 26.13,
fontFamily: "Manrope_600SemiBold",
color: "#262627",
},
});
export default TabLayout;

View File

@ -50,15 +50,14 @@ import {Stack} from "expo-router";
import * as SplashScreen from "expo-splash-screen";
import "react-native-reanimated";
import {AuthContextProvider} from "@/contexts/AuthContext";
import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
import {TextProps, ThemeManager, Toast, Typography,} from "react-native-ui-lib";
import {Platform} from 'react-native';
import KeyboardManager from 'react-native-keyboard-manager';
import {enableScreens} from 'react-native-screens';
import {enableScreens, enableFreeze} from 'react-native-screens';
import {PersistQueryClientProvider} from "@/contexts/PersistQueryClientProvider";
import auth from "@react-native-firebase/auth";
enableScreens(true)
enableFreeze(true)
SplashScreen.preventAutoHideAsync();